[NTDLL_WINETEST] Sync with Wine Staging 1.7.55. CORE-10536
[reactos.git] / rostests / winetests / ntdll / rtl.c
index 04254ce..90b9323 100755 (executable)
@@ -25,6 +25,7 @@
 
 #include "ntdll_test.h"
 #include "inaddr.h"
+#include "in6addr.h"
 
 #ifndef __WINE_WINTERNL_H
 
@@ -62,6 +63,9 @@ static inline USHORT __my_ushort_swap(USHORT s)
 
 /* Function ptrs for ntdll calls */
 static HMODULE hntdll = 0;
+static PVOID     (WINAPI *pWinSqmStartSession)(PVOID unknown1, DWORD unknown2, DWORD unknown3);
+static BOOL      (WINAPI *pWinSqmIsOptedIn)(void);
+static NTSTATUS  (WINAPI *pWinSqmEndSession)(PVOID unknown1);
 static SIZE_T    (WINAPI  *pRtlCompareMemory)(LPCVOID,LPCVOID,SIZE_T);
 static SIZE_T    (WINAPI  *pRtlCompareMemoryUlong)(PULONG, SIZE_T, ULONG);
 static NTSTATUS  (WINAPI  *pRtlDeleteTimer)(HANDLE, HANDLE, HANDLE);
@@ -89,9 +93,23 @@ static IMAGE_BASE_RELOCATION *(WINAPI *pLdrProcessRelocationBlock)(void*,UINT,US
 static CHAR *    (WINAPI *pRtlIpv4AddressToStringA)(const IN_ADDR *, LPSTR);
 static NTSTATUS  (WINAPI *pRtlIpv4AddressToStringExA)(const IN_ADDR *, USHORT, LPSTR, PULONG);
 static NTSTATUS  (WINAPI *pRtlIpv4StringToAddressA)(PCSTR, BOOLEAN, PCSTR *, IN_ADDR *);
+static NTSTATUS  (WINAPI *pRtlIpv4StringToAddressExA)(PCSTR, BOOLEAN, IN_ADDR *, PUSHORT);
+static CHAR *    (WINAPI *pRtlIpv6AddressToStringA)(struct in6_addr *, PSTR);
+static NTSTATUS  (WINAPI *pRtlIpv6AddressToStringExA)(struct in6_addr *, ULONG, USHORT, PCHAR, PULONG);
+static NTSTATUS  (WINAPI *pRtlIpv6StringToAddressA)(PCSTR, PCSTR *, struct in6_addr *);
+static NTSTATUS  (WINAPI *pRtlIpv6StringToAddressW)(PCWSTR, PCWSTR *, struct in6_addr *);
+static NTSTATUS  (WINAPI *pRtlIpv6StringToAddressExA)(PCSTR, struct in6_addr *, PULONG, PUSHORT);
+static NTSTATUS  (WINAPI *pRtlIpv6StringToAddressExW)(PCWSTR, struct in6_addr *, PULONG, PUSHORT);
 static NTSTATUS  (WINAPI *pLdrAddRefDll)(ULONG, HMODULE);
 static NTSTATUS  (WINAPI *pLdrLockLoaderLock)(ULONG, ULONG*, ULONG_PTR*);
 static NTSTATUS  (WINAPI *pLdrUnlockLoaderLock)(ULONG, ULONG_PTR);
+static NTSTATUS  (WINAPI *pRtlMultiByteToUnicodeN)(LPWSTR, DWORD, LPDWORD, LPCSTR, DWORD);
+static NTSTATUS  (WINAPI *pRtlGetCompressionWorkSpaceSize)(USHORT, PULONG, PULONG);
+static NTSTATUS  (WINAPI *pRtlDecompressBuffer)(USHORT, PUCHAR, ULONG, const UCHAR*, ULONG, PULONG);
+static NTSTATUS  (WINAPI *pRtlDecompressFragment)(USHORT, PUCHAR, ULONG, const UCHAR*, ULONG, ULONG, PULONG, PVOID);
+static NTSTATUS  (WINAPI *pRtlCompressBuffer)(USHORT, const UCHAR*, ULONG, PUCHAR, ULONG, ULONG, PULONG, PVOID);
+static BOOL      (WINAPI *pRtlIsCriticalSectionLocked)(RTL_CRITICAL_SECTION *);
+static BOOL      (WINAPI *pRtlIsCriticalSectionLockedByThread)(RTL_CRITICAL_SECTION *);
 
 static HMODULE hkernel32 = 0;
 static BOOL      (WINAPI *pIsWow64Process)(HANDLE, PBOOL);
@@ -109,6 +127,9 @@ static void InitFunctionPtrs(void)
     hntdll = LoadLibraryA("ntdll.dll");
     ok(hntdll != 0, "LoadLibrary failed\n");
     if (hntdll) {
+        pWinSqmStartSession = (void *)GetProcAddress(hntdll, "WinSqmStartSession");
+        pWinSqmIsOptedIn = (void *)GetProcAddress(hntdll, "WinSqmIsOptedIn");
+        pWinSqmEndSession = (void *)GetProcAddress(hntdll, "WinSqmEndSession");
        pRtlCompareMemory = (void *)GetProcAddress(hntdll, "RtlCompareMemory");
        pRtlCompareMemoryUlong = (void *)GetProcAddress(hntdll, "RtlCompareMemoryUlong");
         pRtlDeleteTimer = (void *)GetProcAddress(hntdll, "RtlDeleteTimer");
@@ -136,9 +157,23 @@ static void InitFunctionPtrs(void)
         pRtlIpv4AddressToStringA = (void *)GetProcAddress(hntdll, "RtlIpv4AddressToStringA");
         pRtlIpv4AddressToStringExA = (void *)GetProcAddress(hntdll, "RtlIpv4AddressToStringExA");
         pRtlIpv4StringToAddressA = (void *)GetProcAddress(hntdll, "RtlIpv4StringToAddressA");
+        pRtlIpv4StringToAddressExA = (void *)GetProcAddress(hntdll, "RtlIpv4StringToAddressExA");
+        pRtlIpv6AddressToStringA = (void *)GetProcAddress(hntdll, "RtlIpv6AddressToStringA");
+        pRtlIpv6AddressToStringExA = (void *)GetProcAddress(hntdll, "RtlIpv6AddressToStringExA");
+        pRtlIpv6StringToAddressA = (void *)GetProcAddress(hntdll, "RtlIpv6StringToAddressA");
+        pRtlIpv6StringToAddressW = (void *)GetProcAddress(hntdll, "RtlIpv6StringToAddressW");
+        pRtlIpv6StringToAddressExA = (void *)GetProcAddress(hntdll, "RtlIpv6StringToAddressExA");
+        pRtlIpv6StringToAddressExW = (void *)GetProcAddress(hntdll, "RtlIpv6StringToAddressExW");
         pLdrAddRefDll = (void *)GetProcAddress(hntdll, "LdrAddRefDll");
         pLdrLockLoaderLock = (void *)GetProcAddress(hntdll, "LdrLockLoaderLock");
         pLdrUnlockLoaderLock = (void *)GetProcAddress(hntdll, "LdrUnlockLoaderLock");
+        pRtlMultiByteToUnicodeN = (void *)GetProcAddress(hntdll, "RtlMultiByteToUnicodeN");
+        pRtlGetCompressionWorkSpaceSize = (void *)GetProcAddress(hntdll, "RtlGetCompressionWorkSpaceSize");
+        pRtlDecompressBuffer = (void *)GetProcAddress(hntdll, "RtlDecompressBuffer");
+        pRtlDecompressFragment = (void *)GetProcAddress(hntdll, "RtlDecompressFragment");
+        pRtlCompressBuffer = (void *)GetProcAddress(hntdll, "RtlCompressBuffer");
+        pRtlIsCriticalSectionLocked = (void *)GetProcAddress(hntdll, "RtlIsCriticalSectionLocked");
+        pRtlIsCriticalSectionLockedByThread = (void *)GetProcAddress(hntdll, "RtlIsCriticalSectionLockedByThread");
     }
     hkernel32 = LoadLibraryA("kernel32.dll");
     ok(hkernel32 != 0, "LoadLibrary failed\n");
@@ -149,6 +184,48 @@ static void InitFunctionPtrs(void)
     ok(strlen(src) == 15, "Source must be 16 bytes long!\n");
 }
 
+#ifdef __i386__
+const char stdcall3_thunk[] =
+    "\x56"              /* push %esi */
+    "\x89\xE6"          /* mov %esp, %esi */
+    "\xFF\x74\x24\x14"  /* pushl 20(%esp) */
+    "\xFF\x74\x24\x14"  /* pushl 20(%esp) */
+    "\xFF\x74\x24\x14"  /* pushl 20(%esp) */
+    "\xFF\x54\x24\x14"  /* calll 20(%esp) */
+    "\x89\xF0"          /* mov %esi, %eax */
+    "\x29\xE0"          /* sub %esp, %eax */
+    "\x89\xF4"          /* mov %esi, %esp */
+    "\x5E"              /* pop %esi */
+    "\xC2\x10\x00"      /* ret $16 */
+;
+
+static INT (WINAPI *call_stdcall_func3)(PVOID func, PVOID arg0, DWORD arg1, DWORD arg2) = NULL;
+
+static void test_WinSqm(void)
+{
+    INT args;
+
+    if (!pWinSqmStartSession)
+    {
+        win_skip("WinSqmStartSession() is not available\n");
+        return;
+    }
+
+    call_stdcall_func3 = (void*) VirtualAlloc( NULL, sizeof(stdcall3_thunk) - 1, MEM_COMMIT,
+                                               PAGE_EXECUTE_READWRITE );
+    memcpy( call_stdcall_func3, stdcall3_thunk, sizeof(stdcall3_thunk) - 1 );
+
+    args = 3 - call_stdcall_func3( pWinSqmStartSession, NULL, 0, 0 ) / 4;
+    ok(args == 3, "WinSqmStartSession expected to take %d arguments instead of 3\n", args);
+    args = 3 - call_stdcall_func3( pWinSqmIsOptedIn, NULL, 0, 0 ) / 4;
+    ok(args == 0, "WinSqmIsOptedIn expected to take %d arguments instead of 0\n", args);
+    args = 3 - call_stdcall_func3( pWinSqmEndSession, NULL, 0, 0 ) / 4;
+    ok(args == 1, "WinSqmEndSession expected to take %d arguments instead of 1\n", args);
+
+    VirtualFree( call_stdcall_func3, 0, MEM_RELEASE );
+}
+#endif
+
 #define COMP(str1,str2,cmplen,len) size = pRtlCompareMemory(str1, str2, cmplen); \
   ok(size == len, "Expected %ld, got %ld\n", size, (SIZE_T)len)
 
@@ -649,46 +726,11 @@ static void test_RtlUniform(void)
 }
 
 
-static ULONG my_RtlRandom(PULONG seed)
-{
-    static ULONG saved_value[128] =
-    { /*   0 */ 0x4c8bc0aa, 0x4c022957, 0x2232827a, 0x2f1e7626, 0x7f8bdafb, 0x5c37d02a, 0x0ab48f72, 0x2f0c4ffa,
-      /*   8 */ 0x290e1954, 0x6b635f23, 0x5d3885c0, 0x74b49ff8, 0x5155fa54, 0x6214ad3f, 0x111e9c29, 0x242a3a09,
-      /*  16 */ 0x75932ae1, 0x40ac432e, 0x54f7ba7a, 0x585ccbd5, 0x6df5c727, 0x0374dad1, 0x7112b3f1, 0x735fc311,
-      /*  24 */ 0x404331a9, 0x74d97781, 0x64495118, 0x323e04be, 0x5974b425, 0x4862e393, 0x62389c1d, 0x28a68b82,
-      /*  32 */ 0x0f95da37, 0x7a50bbc6, 0x09b0091c, 0x22cdb7b4, 0x4faaed26, 0x66417ccd, 0x189e4bfa, 0x1ce4e8dd,
-      /*  40 */ 0x5274c742, 0x3bdcf4dc, 0x2d94e907, 0x32eac016, 0x26d33ca3, 0x60415a8a, 0x31f57880, 0x68c8aa52,
-      /*  48 */ 0x23eb16da, 0x6204f4a1, 0x373927c1, 0x0d24eb7c, 0x06dd7379, 0x2b3be507, 0x0f9c55b1, 0x2c7925eb,
-      /*  56 */ 0x36d67c9a, 0x42f831d9, 0x5e3961cb, 0x65d637a8, 0x24bb3820, 0x4d08e33d, 0x2188754f, 0x147e409e,
-      /*  64 */ 0x6a9620a0, 0x62e26657, 0x7bd8ce81, 0x11da0abb, 0x5f9e7b50, 0x23e444b6, 0x25920c78, 0x5fc894f0,
-      /*  72 */ 0x5e338cbb, 0x404237fd, 0x1d60f80f, 0x320a1743, 0x76013d2b, 0x070294ee, 0x695e243b, 0x56b177fd,
-      /*  80 */ 0x752492e1, 0x6decd52f, 0x125f5219, 0x139d2e78, 0x1898d11e, 0x2f7ee785, 0x4db405d8, 0x1a028a35,
-      /*  88 */ 0x63f6f323, 0x1f6d0078, 0x307cfd67, 0x3f32a78a, 0x6980796c, 0x462b3d83, 0x34b639f2, 0x53fce379,
-      /*  96 */ 0x74ba50f4, 0x1abc2c4b, 0x5eeaeb8d, 0x335a7a0d, 0x3973dd20, 0x0462d66b, 0x159813ff, 0x1e4643fd,
-      /* 104 */ 0x06bc5c62, 0x3115e3fc, 0x09101613, 0x47af2515, 0x4f11ec54, 0x78b99911, 0x3db8dd44, 0x1ec10b9b,
-      /* 112 */ 0x5b5506ca, 0x773ce092, 0x567be81a, 0x5475b975, 0x7a2cde1a, 0x494536f5, 0x34737bb4, 0x76d9750b,
-      /* 120 */ 0x2a1f6232, 0x2e49644d, 0x7dddcbe7, 0x500cebdb, 0x619dab9e, 0x48c626fe, 0x1cda3193, 0x52dabe9d };
-    ULONG rand;
-    int pos;
-    ULONG result;
-
-    rand = (*seed * 0x7fffffed + 0x7fffffc3) % 0x7fffffff;
-    *seed = (rand * 0x7fffffed + 0x7fffffc3) % 0x7fffffff;
-    pos = *seed & 0x7f;
-    result = saved_value[pos];
-    saved_value[pos] = rand;
-    return(result);
-}
-
-
 static void test_RtlRandom(void)
 {
-    ULONGLONG num;
+    int i, j;
     ULONG seed;
-    ULONG seed_bak;
-    ULONG seed_expected;
-    ULONG result;
-    ULONG result_expected;
+    ULONG res[512];
 
     if (!pRtlRandom)
     {
@@ -696,185 +738,14 @@ static void test_RtlRandom(void)
         return;
     }
 
-/*
- * Unlike RtlUniform, RtlRandom is not documented. We guess that for
- * RtlRandom D.H. Lehmer's 1948 algorithm is used like stated in
- * the documentation of the RtlUniform function. This algorithm is:
- *
- * seed = (seed * const_1 + const_2) % const_3;
- *
- * According to the RtlUniform documentation the random number is
- * distributed over [0..MAXLONG], but in reality it is distributed
- * over [0..MAXLONG-1]. Therefore const_3 might be MAXLONG + 1 or
- * MAXLONG:
- *
- * seed = (seed * const_1 + const_2) % (MAXLONG + 1);
- *
- * or
- *
- * seed = (seed * const_1 + const_2) % MAXLONG;
- *
- * To find out const_2 we just call RtlRandom with seed set to 0:
- */
     seed = 0;
-    result_expected = 0x320a1743;
-    seed_expected =0x44b;
-    result = pRtlRandom(&seed);
-
-/*
- * Windows Vista uses different algorithms, so skip the rest of the tests
- * until that is figured out. Trace output for the failures is about 10.5 MB!
- */
-
-    if (seed == 0x3fc) {
-        skip("Most likely running on Windows Vista which uses a different algorithm\n");
-        return;
+    for (i = 0; i < sizeof(res) / sizeof(res[0]); i++)
+    {
+        res[i] = pRtlRandom(&seed);
+        ok(seed != res[i], "%i: seed is same as res %x\n", i, seed);
+        for (j = 0; j < i; j++)
+            ok(res[i] != res[j], "res[%i] (%x) is same as res[%i] (%x)\n", j, res[j], i, res[i]);
     }
-
-    ok(result == result_expected,
-        "pRtlRandom(&seed (seed == 0)) returns %x, expected %x\n",
-        result, result_expected);
-    ok(seed == seed_expected,
-        "pRtlRandom(&seed (seed == 0)) sets seed to %x, expected %x\n",
-        seed, seed_expected);
-/*
- * Seed is not equal to result as with RtlUniform. To see more we
- * call RtlRandom again with seed set to 0:
- */
-    seed = 0;
-    result_expected = 0x7fffffc3;
-    seed_expected =0x44b;
-    result = pRtlRandom(&seed);
-    ok(result == result_expected,
-        "RtlRandom(&seed (seed == 0)) returns %x, expected %x\n",
-        result, result_expected);
-    ok(seed == seed_expected,
-        "RtlRandom(&seed (seed == 0)) sets seed to %x, expected %x\n",
-        seed, seed_expected);
-/*
- * Seed is set to the same value as before but the result is different.
- * To see more we call RtlRandom again with seed set to 0:
- */
-    seed = 0;
-    result_expected = 0x7fffffc3;
-    seed_expected =0x44b;
-    result = pRtlRandom(&seed);
-    ok(result == result_expected,
-        "RtlRandom(&seed (seed == 0)) returns %x, expected %x\n",
-        result, result_expected);
-    ok(seed == seed_expected,
-        "RtlRandom(&seed (seed == 0)) sets seed to %x, expected %x\n",
-        seed, seed_expected);
-/*
- * Seed is again set to the same value as before. This time we also
- * have the same result as before. Interestingly the value of the
- * result is 0x7fffffc3 which is the same value used in RtlUniform
- * as const_2. If we do
- *
- * seed = 0;
- * result = RtlUniform(&seed);
- *
- * we get the same result (0x7fffffc3) as with
- *
- * seed = 0;
- * RtlRandom(&seed);
- * seed = 0;
- * result = RtlRandom(&seed);
- *
- * And there is another interesting thing. If we do
- *
- * seed = 0;
- * RtlUniform(&seed);
- * RtlUniform(&seed);
- *
- * seed is set to the value 0x44b which ist the same value that
- *
- * seed = 0;
- * RtlRandom(&seed);
- *
- * assigns to seed. Putting these two findings together leads to
- * the conclusion that RtlRandom saves the value in some variable,
- * like in the following algorithm:
- *
- * result = saved_value;
- * saved_value = RtlUniform(&seed);
- * RtlUniform(&seed);
- * return(result);
- *
- * Now we do further tests with seed set to 1:
- */
-    seed = 1;
-    result_expected = 0x7a50bbc6;
-    seed_expected =0x5a1;
-    result = pRtlRandom(&seed);
-    ok(result == result_expected,
-        "RtlRandom(&seed (seed == 1)) returns %x, expected %x\n",
-        result, result_expected);
-    ok(seed == seed_expected,
-        "RtlRandom(&seed (seed == 1)) sets seed to %x, expected %x\n",
-        seed, seed_expected);
-/*
- * If there is just one saved_value the result now would be
- * 0x7fffffc3. From this test we can see that there is more than
- * one saved_value, like with this algorithm:
- *
- * result = saved_value[pos];
- * saved_value[pos] = RtlUniform(&seed);
- * RtlUniform(&seed);
- * return(result);
- *
- * But how is the value of pos determined? The calls to RtlUniform
- * create a sequence of random numbers. Every second random number
- * is put into the saved_value array and is used in some later call
- * of RtlRandom as result. The only reasonable source to determine
- * pos are the random numbers generated by RtlUniform which are not
- * put into the saved_value array. This are the values of seed
- * between the two calls of RtlUniform as in this algorithm:
- *
- * rand = RtlUniform(&seed);
- * RtlUniform(&seed);
- * pos = position(seed);
- * result = saved_value[pos];
- * saved_value[pos] = rand;
- * return(result);
- *
- * What remains to be determined is: The size of the saved_value array,
- * the initial values of the saved_value array and the function
- * position(seed). These tests are not shown here. 
- * The result of these tests is: The size of the saved_value array
- * is 128, the initial values can be seen in the my_RtlRandom
- * function and the position(seed) function is (seed & 0x7f).
- *
- * For a full test of RtlRandom use one of the following loop heads:
- *
- *  for (num = 0; num <= 0xffffffff; num++) {
- *      seed = num;
- *      ...
- *
- *  seed = 0;
- *  for (num = 0; num <= 0xffffffff; num++) {
- *      ...
- */
-    seed = 0;
-    for (num = 0; num <= 100000; num++) {
-        seed_bak = seed;
-       seed_expected = seed;
-        result_expected = my_RtlRandom(&seed_expected);
-       /* The following corrections are necessary because the */
-       /* previous tests changed the saved_value array */
-       if (num == 0) {
-           result_expected = 0x7fffffc3;
-        } else if (num == 81) {
-           result_expected = 0x7fffffb1;
-       } /* if */
-        result = pRtlRandom(&seed);
-        ok(result == result_expected,
-                "test: 0x%x%08x RtlUniform(&seed (seed == %x)) returns %x, expected %x\n",
-                (DWORD)(num >> 32), (DWORD)num, seed_bak, result, result_expected);
-        ok(seed == seed_expected,
-                "test: 0x%x%08x RtlUniform(&seed (seed == %x)) sets seed to %x, expected %x\n",
-                (DWORD)(num >> 32), (DWORD)num, seed_bak, result, seed_expected);
-    } /* for */
 }
 
 
@@ -1320,98 +1191,109 @@ static void test_RtlIpv4AddressToStringEx(void)
         res, size, buffer);
 }
 
+static struct
+{
+    PCSTR address;
+    NTSTATUS res;
+    int terminator_offset;
+    int ip[4];
+    enum { normal_4, strict_diff_4 = 1, ex_fail_4 = 2 } flags;
+    NTSTATUS res_strict;
+    int terminator_offset_strict;
+    int ip_strict[4];
+} ipv4_tests[] =
+{
+    { "",                       STATUS_INVALID_PARAMETER,  0, { -1 } },
+    { " ",                      STATUS_INVALID_PARAMETER,  0, { -1 } },
+    { "1.1.1.1",                STATUS_SUCCESS,            7, {   1,   1,   1,   1 } },
+    { "0.0.0.0",                STATUS_SUCCESS,            7, {   0,   0,   0,   0 } },
+    { "255.255.255.255",        STATUS_SUCCESS,           15, { 255, 255, 255, 255 } },
+    { "255.255.255.255:123",    STATUS_SUCCESS,           15, { 255, 255, 255, 255 } },
+    { "255.255.255.256",        STATUS_INVALID_PARAMETER, 15, { -1 } },
+    { "255.255.255.4294967295", STATUS_INVALID_PARAMETER, 22, { -1 } },
+    { "255.255.255.4294967296", STATUS_INVALID_PARAMETER, 21, { -1 } },
+    { "255.255.255.4294967297", STATUS_INVALID_PARAMETER, 21, { -1 } },
+    { "a",                      STATUS_INVALID_PARAMETER,  0, { -1 } },
+    { "1.1.1.0xaA",             STATUS_SUCCESS,           10, {   1,   1,   1, 170 }, strict_diff_4,
+                                STATUS_INVALID_PARAMETER,  8, { -1 } },
+    { "1.1.1.0XaA",             STATUS_SUCCESS,           10, {   1,   1,   1, 170 }, strict_diff_4,
+                                STATUS_INVALID_PARAMETER,  8, { -1 } },
+    { "1.1.1.0x",               STATUS_INVALID_PARAMETER,  8, { -1 } },
+    { "1.1.1.0xff",             STATUS_SUCCESS,           10, {   1,   1,   1, 255 }, strict_diff_4,
+                                STATUS_INVALID_PARAMETER,  8, { -1 } },
+    { "1.1.1.0x100",            STATUS_INVALID_PARAMETER, 11, { -1 }, strict_diff_4,
+                                STATUS_INVALID_PARAMETER,  8, { -1 } },
+    { "1.1.1.0xffffffff",       STATUS_INVALID_PARAMETER, 16, { -1 }, strict_diff_4,
+                                STATUS_INVALID_PARAMETER,  8, { -1 } },
+    { "1.1.1.0x100000000",      STATUS_INVALID_PARAMETER, 16, { -1, 0, 0, 0 }, strict_diff_4,
+                                STATUS_INVALID_PARAMETER,  8, { -1 } },
+    { "1.1.1.010",              STATUS_SUCCESS,            9, {   1,   1,   1,   8 }, strict_diff_4,
+                                STATUS_INVALID_PARAMETER,  7, { -1 } },
+    { "1.1.1.00",               STATUS_SUCCESS,            8, {   1,   1,   1,   0 }, strict_diff_4,
+                                STATUS_INVALID_PARAMETER,  7, { -1 } },
+    { "1.1.1.007",              STATUS_SUCCESS,            9, {   1,   1,   1,   7 }, strict_diff_4,
+                                STATUS_INVALID_PARAMETER,  7, { -1 } },
+    { "1.1.1.08",               STATUS_INVALID_PARAMETER,  7, { -1 } },
+    { "1.1.1.008",              STATUS_SUCCESS,            8, {   1,   1,   1,   0 }, strict_diff_4 | ex_fail_4,
+                                STATUS_INVALID_PARAMETER,  7, { -1 } },
+    { "1.1.1.0a",               STATUS_SUCCESS,            7, {   1,   1,   1,   0 }, ex_fail_4 },
+    { "1.1.1.0o10",             STATUS_SUCCESS,            7, {   1,   1,   1,   0 }, ex_fail_4 },
+    { "1.1.1.0b10",             STATUS_SUCCESS,            7, {   1,   1,   1,   0 }, ex_fail_4 },
+    { "1.1.1.-2",               STATUS_INVALID_PARAMETER,  6, { -1 } },
+    { "1",                      STATUS_SUCCESS,            1, {   0,   0,   0,   1 }, strict_diff_4,
+                                STATUS_INVALID_PARAMETER,  1, { -1 } },
+    { "-1",                     STATUS_INVALID_PARAMETER,  0, { -1 } },
+    { "203569230",              STATUS_SUCCESS,            9, {  12,  34,  56,  78 }, strict_diff_4,
+                                STATUS_INVALID_PARAMETER,  9, { -1 } },
+    { "1.223756",               STATUS_SUCCESS,            8, {   1,   3, 106,  12 }, strict_diff_4,
+                                STATUS_INVALID_PARAMETER,  8, { -1 } },
+    { "3.4.756",                STATUS_SUCCESS,            7, {   3,   4,   2, 244 }, strict_diff_4,
+                                STATUS_INVALID_PARAMETER,  7, { -1 } },
+    { "3.4.756.1",              STATUS_INVALID_PARAMETER,  9, { -1 } },
+    { "3.4.65536",              STATUS_INVALID_PARAMETER,  9, { -1 } },
+    { "3.4.5.6.7",              STATUS_INVALID_PARAMETER,  7, { -1 } },
+    { "3.4.5.+6",               STATUS_INVALID_PARAMETER,  6, { -1 } },
+    { " 3.4.5.6",               STATUS_INVALID_PARAMETER,  0, { -1 } },
+    { "\t3.4.5.6",              STATUS_INVALID_PARAMETER,  0, { -1 } },
+    { "3.4.5.6 ",               STATUS_SUCCESS,            7, {   3,   4,   5,   6 }, ex_fail_4 },
+    { "3. 4.5.6",               STATUS_INVALID_PARAMETER,  2, { -1 } },
+    { ".",                      STATUS_INVALID_PARAMETER,  1, { -1 } },
+    { "..",                     STATUS_INVALID_PARAMETER,  1, { -1 } },
+    { "1.",                     STATUS_INVALID_PARAMETER,  2, { -1 } },
+    { "1..",                    STATUS_INVALID_PARAMETER,  3, { -1 } },
+    { ".1",                     STATUS_INVALID_PARAMETER,  1, { -1 } },
+    { ".1.",                    STATUS_INVALID_PARAMETER,  1, { -1 } },
+    { ".1.2.3",                 STATUS_INVALID_PARAMETER,  1, { -1 } },
+    { "0.1.2.3",                STATUS_SUCCESS,            7, {   0,   1,   2,   3 } },
+    { "0.1.2.3.",               STATUS_INVALID_PARAMETER,  7, { -1 } },
+    { "[0.1.2.3]",              STATUS_INVALID_PARAMETER,  0, { -1 } },
+    { "::1",                    STATUS_INVALID_PARAMETER,  0, { -1 } },
+    { ":1",                     STATUS_INVALID_PARAMETER,  0, { -1 } },
+};
+const unsigned int ipv4_testcount = sizeof(ipv4_tests) / sizeof(ipv4_tests[0]);
+
+static void init_ip4(IN_ADDR* addr, const int src[4])
+{
+    if (!src || src[0] == -1)
+    {
+        addr->S_un.S_addr = 0xabababab;
+    }
+    else
+    {
+        addr->S_un.S_un_b.s_b1 = src[0];
+        addr->S_un.S_un_b.s_b2 = src[1];
+        addr->S_un.S_un_b.s_b3 = src[2];
+        addr->S_un.S_un_b.s_b4 = src[3];
+    }
+}
+
 static void test_RtlIpv4StringToAddress(void)
 {
     NTSTATUS res;
     IN_ADDR ip, expected_ip;
     PCSTR terminator;
     CHAR dummy;
-    struct
-    {
-        PCSTR address;
-        NTSTATUS res;
-        int terminator_offset;
-        int ip[4];
-        BOOL strict_is_different;
-        NTSTATUS res_strict;
-        int terminator_offset_strict;
-        int ip_strict[4];
-    } tests[] =
-    {
-        { "",                STATUS_INVALID_PARAMETER,  0, { -1 } },
-        { " ",               STATUS_INVALID_PARAMETER,  0, { -1 } },
-        { "1.1.1.1",         STATUS_SUCCESS,            7, {   1,   1,   1,   1 } },
-        { "0.0.0.0",         STATUS_SUCCESS,            7, {   0,   0,   0,   0 } },
-        { "255.255.255.255", STATUS_SUCCESS,           15, { 255, 255, 255, 255 } },
-        { "255.255.255.255:123",
-                             STATUS_SUCCESS,           15, { 255, 255, 255, 255 } },
-        { "255.255.255.256", STATUS_INVALID_PARAMETER, 15, { -1 } },
-        { "255.255.255.4294967295",
-                             STATUS_INVALID_PARAMETER, 22, { -1 } },
-        { "255.255.255.4294967296",
-                             STATUS_INVALID_PARAMETER, 21, { -1 } },
-        { "255.255.255.4294967297",
-                             STATUS_INVALID_PARAMETER, 21, { -1 } },
-        { "a",               STATUS_INVALID_PARAMETER,  0, { -1 } },
-        { "1.1.1.0xaA",      STATUS_SUCCESS,           10, {   1,   1,   1, 170 },
-                       TRUE, STATUS_INVALID_PARAMETER,  8, { -1 } },
-        { "1.1.1.0XaA",      STATUS_SUCCESS,           10, {   1,   1,   1, 170 },
-                       TRUE, STATUS_INVALID_PARAMETER,  8, { -1 } },
-        { "1.1.1.0x",        STATUS_INVALID_PARAMETER,  8, { -1 } },
-        { "1.1.1.0xff",      STATUS_SUCCESS,           10, {   1,   1,   1, 255 },
-                       TRUE, STATUS_INVALID_PARAMETER,  8, { -1 } },
-        { "1.1.1.0x100",     STATUS_INVALID_PARAMETER, 11, { -1 },
-                       TRUE, STATUS_INVALID_PARAMETER,  8, { -1 } },
-        { "1.1.1.0xffffffff",STATUS_INVALID_PARAMETER, 16, { -1 },
-                       TRUE, STATUS_INVALID_PARAMETER,  8, { -1 } },
-        { "1.1.1.0x100000000",
-                             STATUS_INVALID_PARAMETER, 16, { -1, 0, 0, 0 },
-                       TRUE, STATUS_INVALID_PARAMETER,  8, { -1 } },
-        { "1.1.1.010",       STATUS_SUCCESS,            9, {   1,   1,   1,   8 },
-                       TRUE, STATUS_INVALID_PARAMETER,  7, { -1 } },
-        { "1.1.1.00",        STATUS_SUCCESS,            8, {   1,   1,   1,   0 },
-                       TRUE, STATUS_INVALID_PARAMETER,  7, { -1 } },
-        { "1.1.1.007",       STATUS_SUCCESS,            9, {   1,   1,   1,   7 },
-                       TRUE, STATUS_INVALID_PARAMETER,  7, { -1 } },
-        { "1.1.1.08",        STATUS_INVALID_PARAMETER,  7, { -1 } },
-        { "1.1.1.008",       STATUS_SUCCESS,            8, {   1,   1,   1,   0 },
-                       TRUE, STATUS_INVALID_PARAMETER,  7, { -1 } },
-        { "1.1.1.0a",        STATUS_SUCCESS,            7, {   1,   1,   1,   0 } },
-        { "1.1.1.0o10",      STATUS_SUCCESS,            7, {   1,   1,   1,   0 } },
-        { "1.1.1.0b10",      STATUS_SUCCESS,            7, {   1,   1,   1,   0 } },
-        { "1.1.1.-2",        STATUS_INVALID_PARAMETER,  6, { -1 } },
-        { "1",               STATUS_SUCCESS,            1, {   0,   0,   0,   1 },
-                       TRUE, STATUS_INVALID_PARAMETER,  1, { -1 } },
-        { "-1",              STATUS_INVALID_PARAMETER,  0, { -1 } },
-        { "203569230",       STATUS_SUCCESS,            9, {  12,  34,  56,  78 },
-                       TRUE, STATUS_INVALID_PARAMETER,  9, { -1 } },
-        { "1.223756",        STATUS_SUCCESS,            8, {   1,   3, 106,  12 },
-                       TRUE, STATUS_INVALID_PARAMETER,  8, { -1 } },
-        { "3.4.756",         STATUS_SUCCESS,            7, {   3,   4,   2, 244 },
-                       TRUE, STATUS_INVALID_PARAMETER,  7, { -1 } },
-        { "3.4.756.1",       STATUS_INVALID_PARAMETER,  9, { -1 } },
-        { "3.4.65536",       STATUS_INVALID_PARAMETER,  9, { -1 } },
-        { "3.4.5.6.7",       STATUS_INVALID_PARAMETER,  7, { -1 } },
-        { "3.4.5.+6",        STATUS_INVALID_PARAMETER,  6, { -1 } },
-        { " 3.4.5.6",        STATUS_INVALID_PARAMETER,  0, { -1 } },
-        { "\t3.4.5.6",       STATUS_INVALID_PARAMETER,  0, { -1 } },
-        { "3.4.5.6 ",        STATUS_SUCCESS,            7, {   3,   4,   5,   6 } },
-        { "3. 4.5.6",        STATUS_INVALID_PARAMETER,  2, { -1 } },
-        { ".",               STATUS_INVALID_PARAMETER,  1, { -1 } },
-        { "..",              STATUS_INVALID_PARAMETER,  1, { -1 } },
-        { "1.",              STATUS_INVALID_PARAMETER,  2, { -1 } },
-        { "1..",             STATUS_INVALID_PARAMETER,  3, { -1 } },
-        { ".1",              STATUS_INVALID_PARAMETER,  1, { -1 } },
-        { ".1.",             STATUS_INVALID_PARAMETER,  1, { -1 } },
-        { ".1.2.3",          STATUS_INVALID_PARAMETER,  1, { -1 } },
-        { "0.1.2.3",         STATUS_SUCCESS,            7, {   0,   1,   2,   3 } },
-        { "0.1.2.3.",        STATUS_INVALID_PARAMETER,  7, { -1 } },
-        { "[0.1.2.3]",       STATUS_INVALID_PARAMETER,  0, { -1 } },
-        { "::1",             STATUS_INVALID_PARAMETER,  0, { -1 } },
-        { ":1",              STATUS_INVALID_PARAMETER,  0, { -1 } },
-    };
-    const int testcount = sizeof(tests) / sizeof(tests[0]);
-    int i;
+    unsigned int i;
 
     if (!pRtlIpv4StringToAddressA)
     {
@@ -1433,62 +1315,1095 @@ static void test_RtlIpv4StringToAddress(void)
         */
     }
 
-    for (i = 0; i < testcount; i++)
+    for (i = 0; i < ipv4_testcount; i++)
     {
         /* non-strict */
         terminator = &dummy;
         ip.S_un.S_addr = 0xabababab;
-        res = pRtlIpv4StringToAddressA(tests[i].address, FALSE, &terminator, &ip);
-        ok(res == tests[i].res,
+        res = pRtlIpv4StringToAddressA(ipv4_tests[i].address, FALSE, &terminator, &ip);
+        ok(res == ipv4_tests[i].res,
            "[%s] res = 0x%08x, expected 0x%08x\n",
-           tests[i].address, res, tests[i].res);
-        ok(terminator == tests[i].address + tests[i].terminator_offset,
+           ipv4_tests[i].address, res, ipv4_tests[i].res);
+        ok(terminator == ipv4_tests[i].address + ipv4_tests[i].terminator_offset,
            "[%s] terminator = %p, expected %p\n",
-           tests[i].address, terminator, tests[i].address + tests[i].terminator_offset);
-        if (tests[i].ip[0] == -1)
-            expected_ip.S_un.S_addr = 0xabababab;
-        else
-        {
-            expected_ip.S_un.S_un_b.s_b1 = tests[i].ip[0];
-            expected_ip.S_un.S_un_b.s_b2 = tests[i].ip[1];
-            expected_ip.S_un.S_un_b.s_b3 = tests[i].ip[2];
-            expected_ip.S_un.S_un_b.s_b4 = tests[i].ip[3];
-        }
+           ipv4_tests[i].address, terminator, ipv4_tests[i].address + ipv4_tests[i].terminator_offset);
+
+        init_ip4(&expected_ip, ipv4_tests[i].ip);
         ok(ip.S_un.S_addr == expected_ip.S_un.S_addr,
            "[%s] ip = %08x, expected %08x\n",
-           tests[i].address, ip.S_un.S_addr, expected_ip.S_un.S_addr);
+           ipv4_tests[i].address, ip.S_un.S_addr, expected_ip.S_un.S_addr);
 
-        if (!tests[i].strict_is_different)
+        if (!(ipv4_tests[i].flags & strict_diff_4))
         {
-            tests[i].res_strict = tests[i].res;
-            tests[i].terminator_offset_strict = tests[i].terminator_offset;
-            tests[i].ip_strict[0] = tests[i].ip[0];
-            tests[i].ip_strict[1] = tests[i].ip[1];
-            tests[i].ip_strict[2] = tests[i].ip[2];
-            tests[i].ip_strict[3] = tests[i].ip[3];
+            ipv4_tests[i].res_strict = ipv4_tests[i].res;
+            ipv4_tests[i].terminator_offset_strict = ipv4_tests[i].terminator_offset;
+            ipv4_tests[i].ip_strict[0] = ipv4_tests[i].ip[0];
+            ipv4_tests[i].ip_strict[1] = ipv4_tests[i].ip[1];
+            ipv4_tests[i].ip_strict[2] = ipv4_tests[i].ip[2];
+            ipv4_tests[i].ip_strict[3] = ipv4_tests[i].ip[3];
         }
         /* strict */
         terminator = &dummy;
         ip.S_un.S_addr = 0xabababab;
-        res = pRtlIpv4StringToAddressA(tests[i].address, TRUE, &terminator, &ip);
-        ok(res == tests[i].res_strict,
+        res = pRtlIpv4StringToAddressA(ipv4_tests[i].address, TRUE, &terminator, &ip);
+        ok(res == ipv4_tests[i].res_strict,
            "[%s] res = 0x%08x, expected 0x%08x\n",
-           tests[i].address, res, tests[i].res_strict);
-        ok(terminator == tests[i].address + tests[i].terminator_offset_strict,
+           ipv4_tests[i].address, res, ipv4_tests[i].res_strict);
+        ok(terminator == ipv4_tests[i].address + ipv4_tests[i].terminator_offset_strict,
            "[%s] terminator = %p, expected %p\n",
-           tests[i].address, terminator, tests[i].address + tests[i].terminator_offset_strict);
-        if (tests[i].ip_strict[0] == -1)
-            expected_ip.S_un.S_addr = 0xabababab;
+           ipv4_tests[i].address, terminator, ipv4_tests[i].address + ipv4_tests[i].terminator_offset_strict);
+
+        init_ip4(&expected_ip, ipv4_tests[i].ip_strict);
+        ok(ip.S_un.S_addr == expected_ip.S_un.S_addr,
+           "[%s] ip = %08x, expected %08x\n",
+           ipv4_tests[i].address, ip.S_un.S_addr, expected_ip.S_un.S_addr);
+    }
+}
+
+static void test_RtlIpv4StringToAddressEx(void)
+{
+    NTSTATUS res;
+    IN_ADDR ip, expected_ip;
+    USHORT port;
+    static const struct
+    {
+        PCSTR address;
+        NTSTATUS res;
+        int ip[4];
+        USHORT port;
+    } ipv4_ex_tests[] =
+    {
+        { "",               STATUS_INVALID_PARAMETER,   { -1 },         0xdead },
+        { " ",              STATUS_INVALID_PARAMETER,   { -1 },         0xdead },
+        { "1.1.1.1:",       STATUS_INVALID_PARAMETER,   { 1, 1, 1, 1 }, 0xdead },
+        { "1.1.1.1+",       STATUS_INVALID_PARAMETER,   { 1, 1, 1, 1 }, 0xdead },
+        { "1.1.1.1:1",      STATUS_SUCCESS,             { 1, 1, 1, 1 }, 0x100 },
+        { "256.1.1.1:1",    STATUS_INVALID_PARAMETER,   { -1 },         0xdead },
+        { "-1.1.1.1:1",     STATUS_INVALID_PARAMETER,   { -1 },         0xdead },
+        { "0.0.0.0:0",      STATUS_INVALID_PARAMETER,   { 0, 0, 0, 0 }, 0xdead },
+        { "0.0.0.0:1",      STATUS_SUCCESS,             { 0, 0, 0, 0 }, 0x100 },
+        { "1.2.3.4:65535",  STATUS_SUCCESS,             { 1, 2, 3, 4 }, 65535 },
+        { "1.2.3.4:65536",  STATUS_INVALID_PARAMETER,   { 1, 2, 3, 4 }, 0xdead },
+        { "1.2.3.4:0xffff", STATUS_SUCCESS,             { 1, 2, 3, 4 }, 65535 },
+        { "1.2.3.4:0XfFfF", STATUS_SUCCESS,             { 1, 2, 3, 4 }, 65535 },
+        { "1.2.3.4:011064", STATUS_SUCCESS,             { 1, 2, 3, 4 }, 0x3412 },
+        { "1.2.3.4:1234a",  STATUS_INVALID_PARAMETER,   { 1, 2, 3, 4 }, 0xdead },
+        { "1.2.3.4:1234+",  STATUS_INVALID_PARAMETER,   { 1, 2, 3, 4 }, 0xdead },
+        { "1.2.3.4: 1234",  STATUS_INVALID_PARAMETER,   { 1, 2, 3, 4 }, 0xdead },
+        { "1.2.3.4:\t1234", STATUS_INVALID_PARAMETER,   { 1, 2, 3, 4 }, 0xdead },
+    };
+    const unsigned int ipv4_ex_testcount = sizeof(ipv4_ex_tests) / sizeof(ipv4_ex_tests[0]);
+    unsigned int i;
+    BOOLEAN strict;
+
+    if (!pRtlIpv4StringToAddressExA)
+    {
+        skip("RtlIpv4StringToAddressEx not available\n");
+        return;
+    }
+
+    /* do not crash, and do not touch the ip / port. */
+    ip.S_un.S_addr = 0xabababab;
+    port = 0xdead;
+    res = pRtlIpv4StringToAddressExA(NULL, FALSE, &ip, &port);
+    ok(res == STATUS_INVALID_PARAMETER, "[null address] res = 0x%08x, expected 0x%08x\n",
+       res, STATUS_INVALID_PARAMETER);
+    ok(ip.S_un.S_addr == 0xabababab, "RtlIpv4StringToAddressExA should not touch the ip!, ip == %x\n", ip.S_un.S_addr);
+    ok(port == 0xdead, "RtlIpv4StringToAddressExA should not touch the port!, port == %x\n", port);
+
+    port = 0xdead;
+    res = pRtlIpv4StringToAddressExA("1.1.1.1", FALSE, NULL, &port);
+    ok(res == STATUS_INVALID_PARAMETER, "[null ip] res = 0x%08x, expected 0x%08x\n",
+       res, STATUS_INVALID_PARAMETER);
+    ok(port == 0xdead, "RtlIpv4StringToAddressExA should not touch the port!, port == %x\n", port);
+
+    ip.S_un.S_addr = 0xabababab;
+    port = 0xdead;
+    res = pRtlIpv4StringToAddressExA("1.1.1.1", FALSE, &ip, NULL);
+    ok(res == STATUS_INVALID_PARAMETER, "[null port] res = 0x%08x, expected 0x%08x\n",
+       res, STATUS_INVALID_PARAMETER);
+    ok(ip.S_un.S_addr == 0xabababab, "RtlIpv4StringToAddressExA should not touch the ip!, ip == %x\n", ip.S_un.S_addr);
+    ok(port == 0xdead, "RtlIpv4StringToAddressExA should not touch the port!, port == %x\n", port);
+
+    /* first we run the non-ex testcases on the ex function */
+    for (i = 0; i < ipv4_testcount; i++)
+    {
+        NTSTATUS expect_res = (ipv4_tests[i].flags & ex_fail_4) ? STATUS_INVALID_PARAMETER : ipv4_tests[i].res;
+
+        /* non-strict */
+        port = 0xdead;
+        ip.S_un.S_addr = 0xabababab;
+        res = pRtlIpv4StringToAddressExA(ipv4_tests[i].address, FALSE, &ip, &port);
+        ok(res == expect_res, "[%s] res = 0x%08x, expected 0x%08x\n",
+           ipv4_tests[i].address, res, expect_res);
+
+        init_ip4(&expected_ip, ipv4_tests[i].ip);
+        ok(ip.S_un.S_addr == expected_ip.S_un.S_addr, "[%s] ip = %08x, expected %08x\n",
+           ipv4_tests[i].address, ip.S_un.S_addr, expected_ip.S_un.S_addr);
+
+        if (!(ipv4_tests[i].flags & strict_diff_4))
+        {
+            ipv4_tests[i].res_strict = ipv4_tests[i].res;
+            ipv4_tests[i].terminator_offset_strict = ipv4_tests[i].terminator_offset;
+            ipv4_tests[i].ip_strict[0] = ipv4_tests[i].ip[0];
+            ipv4_tests[i].ip_strict[1] = ipv4_tests[i].ip[1];
+            ipv4_tests[i].ip_strict[2] = ipv4_tests[i].ip[2];
+            ipv4_tests[i].ip_strict[3] = ipv4_tests[i].ip[3];
+        }
+        /* strict */
+        expect_res = (ipv4_tests[i].flags & ex_fail_4) ? STATUS_INVALID_PARAMETER : ipv4_tests[i].res_strict;
+        port = 0xdead;
+        ip.S_un.S_addr = 0xabababab;
+        res = pRtlIpv4StringToAddressExA(ipv4_tests[i].address, TRUE, &ip, &port);
+        ok(res == expect_res, "[%s] res = 0x%08x, expected 0x%08x\n",
+           ipv4_tests[i].address, res, expect_res);
+
+        init_ip4(&expected_ip, ipv4_tests[i].ip_strict);
+        ok(ip.S_un.S_addr == expected_ip.S_un.S_addr, "[%s] ip = %08x, expected %08x\n",
+           ipv4_tests[i].address, ip.S_un.S_addr, expected_ip.S_un.S_addr);
+    }
+
+
+    for (i = 0; i < ipv4_ex_testcount; i++)
+    {
+        /* Strict is only relevant for the ip address, so make sure that it does not influence the port */
+        for (strict = 0; strict < 2; strict++)
+        {
+            ip.S_un.S_addr = 0xabababab;
+            port = 0xdead;
+            res = pRtlIpv4StringToAddressExA(ipv4_ex_tests[i].address, strict, &ip, &port);
+            ok(res == ipv4_ex_tests[i].res, "[%s] res = 0x%08x, expected 0x%08x\n",
+               ipv4_ex_tests[i].address, res, ipv4_ex_tests[i].res);
+
+            init_ip4(&expected_ip, ipv4_ex_tests[i].ip);
+            ok(ip.S_un.S_addr == expected_ip.S_un.S_addr, "[%s] ip = %08x, expected %08x\n",
+               ipv4_ex_tests[i].address, ip.S_un.S_addr, expected_ip.S_un.S_addr);
+            ok(port == ipv4_ex_tests[i].port, "[%s] port = %u, expected %u\n",
+               ipv4_ex_tests[i].address, port, ipv4_ex_tests[i].port);
+        }
+    }
+}
+
+/* ipv6 addresses based on the set from https://github.com/beaugunderson/javascript-ipv6/tree/master/test/data */
+static const struct
+{
+    PCSTR address;
+    NTSTATUS res;
+    int terminator_offset;
+    int ip[8];
+    /* win_broken: older versions of windows do not handle this correct
+        ex_fail: Ex function does need the string to be terminated, non-Ex does not.
+        ex_skip: test doesnt make sense for Ex (f.e. it's invalid for non-Ex but valid for Ex) */
+    enum { normal_6, win_broken_6 = 1, ex_fail_6 = 2, ex_skip_6 = 4 } flags;
+} ipv6_tests[] =
+{
+    { "0000:0000:0000:0000:0000:0000:0000:0000",        STATUS_SUCCESS,             39,
+            { 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { "0000:0000:0000:0000:0000:0000:0000:0001",        STATUS_SUCCESS,             39,
+            { 0, 0, 0, 0, 0, 0, 0, 0x100 } },
+    { "0:0:0:0:0:0:0:0",                                STATUS_SUCCESS,             15,
+            { 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { "0:0:0:0:0:0:0:1",                                STATUS_SUCCESS,             15,
+            { 0, 0, 0, 0, 0, 0, 0, 0x100 } },
+    { "0:0:0:0:0:0:0::",                                STATUS_SUCCESS,             13,
+            { 0, 0, 0, 0, 0, 0, 0, 0 }, win_broken_6 },
+    { "0:0:0:0:0:0:13.1.68.3",                          STATUS_SUCCESS,             21,
+            { 0, 0, 0, 0, 0, 0, 0x10d, 0x344 } },
+    { "0:0:0:0:0:0::",                                  STATUS_SUCCESS,             13,
+            { 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { "0:0:0:0:0::",                                    STATUS_SUCCESS,             11,
+            { 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { "0:0:0:0:0:FFFF:129.144.52.38",                   STATUS_SUCCESS,             28,
+            { 0, 0, 0, 0, 0, 0xffff, 0x9081, 0x2634 } },
+    { "0::",                                            STATUS_SUCCESS,             3,
+            { 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { "0:1:2:3:4:5:6:7",                                STATUS_SUCCESS,             15,
+            { 0, 0x100, 0x200, 0x300, 0x400, 0x500, 0x600, 0x700 } },
+    { "1080:0:0:0:8:800:200c:417a",                     STATUS_SUCCESS,             26,
+            { 0x8010, 0, 0, 0, 0x800, 0x8, 0x0c20, 0x7a41 } },
+    { "0:a:b:c:d:e:f::",                                STATUS_SUCCESS,             13,
+            { 0, 0xa00, 0xb00, 0xc00, 0xd00, 0xe00, 0xf00, 0 }, win_broken_6 },
+    { "1111:2222:3333:4444:5555:6666:123.123.123.123",  STATUS_SUCCESS,             45,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7b7b, 0x7b7b } },
+    { "1111:2222:3333:4444:5555:6666:7777:8888",        STATUS_SUCCESS,             39,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x8888 } },
+    { "1111:2222:3333:4444:0x5555:6666:7777:8888",      STATUS_INVALID_PARAMETER,   21,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1111:2222:3333:4444:x555:6666:7777:8888",        STATUS_INVALID_PARAMETER,   20,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1111:2222:3333:4444:0r5555:6666:7777:8888",      STATUS_INVALID_PARAMETER,   21,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1111:2222:3333:4444:r5555:6666:7777:8888",       STATUS_INVALID_PARAMETER,   20,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1111:2222:3333:4444:5555:6666:7777::",           STATUS_SUCCESS,             34,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0 }, win_broken_6 },
+    { "1111:2222:3333:4444:5555:6666::",                STATUS_SUCCESS,             31,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0, 0 } },
+    { "1111:2222:3333:4444:5555:6666::8888",            STATUS_SUCCESS,             35,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0, 0x8888 } },
+    { "1111:2222:3333:4444:5555::",                     STATUS_SUCCESS,             26,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0, 0, 0 } },
+    { "1111:2222:3333:4444:5555::123.123.123.123",      STATUS_SUCCESS,             41,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0, 0x7b7b, 0x7b7b } },
+    { "1111:2222:3333:4444:5555::0x1.123.123.123",      STATUS_SUCCESS,             27,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0, 0, 0x100 }, ex_fail_6 },
+    { "1111:2222:3333:4444:5555::0x88",                 STATUS_SUCCESS,             27,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0, 0, 0x8800 }, ex_fail_6 },
+    { "1111:2222:3333:4444:5555::0X88",                 STATUS_SUCCESS,             27,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0, 0, 0x8800 }, ex_fail_6 },
+    { "1111:2222:3333:4444:5555::0X",                   STATUS_SUCCESS,             27,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0, 0, 0 }, ex_fail_6 },
+    { "1111:2222:3333:4444:5555::0X88:7777",            STATUS_SUCCESS,             27,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0, 0, 0x8800 }, ex_fail_6 },
+    { "1111:2222:3333:4444:5555::0x8888",               STATUS_SUCCESS,             27,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0, 0, 0x8888 }, ex_fail_6 },
+    { "1111:2222:3333:4444:5555::08888",                STATUS_INVALID_PARAMETER,   31,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0xabab, 0xabab, 0xabab } },
+    { "1111:2222:3333:4444:5555::fffff",                STATUS_INVALID_PARAMETER,   31,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0xabab, 0xabab, 0xabab } },
+    { "1111:2222:3333:4444::fffff",                     STATUS_INVALID_PARAMETER,   26,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1111:2222:3333::fffff",                          STATUS_INVALID_PARAMETER,   21,
+            { 0x1111, 0x2222, 0x3333, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1111:2222:3333:4444:5555::7777:8888",            STATUS_SUCCESS,             35,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0, 0x7777, 0x8888 } },
+    { "1111:2222:3333:4444:5555::8888",                 STATUS_SUCCESS,             30,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0, 0, 0x8888 } },
+    { "1111::",                                         STATUS_SUCCESS,             6,
+            { 0x1111, 0, 0, 0, 0, 0, 0, 0 } },
+    { "1111::123.123.123.123",                          STATUS_SUCCESS,             21,
+            { 0x1111, 0, 0, 0, 0, 0, 0x7b7b, 0x7b7b } },
+    { "1111::3333:4444:5555:6666:123.123.123.123",      STATUS_SUCCESS,             41,
+            { 0x1111, 0, 0x3333, 0x4444, 0x5555, 0x6666, 0x7b7b, 0x7b7b } },
+    { "1111::3333:4444:5555:6666:7777:8888",            STATUS_SUCCESS,             35,
+            { 0x1111, 0, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x8888 } },
+    { "1111::4444:5555:6666:123.123.123.123",           STATUS_SUCCESS,             36,
+            { 0x1111, 0, 0, 0x4444, 0x5555, 0x6666, 0x7b7b, 0x7b7b } },
+    { "1111::4444:5555:6666:7777:8888",                 STATUS_SUCCESS,             30,
+            { 0x1111, 0, 0, 0x4444, 0x5555, 0x6666, 0x7777, 0x8888 } },
+    { "1111::5555:6666:123.123.123.123",                STATUS_SUCCESS,             31,
+            { 0x1111, 0, 0, 0, 0x5555, 0x6666, 0x7b7b, 0x7b7b } },
+    { "1111::5555:6666:7777:8888",                      STATUS_SUCCESS,             25,
+            { 0x1111, 0, 0, 0, 0x5555, 0x6666, 0x7777, 0x8888 } },
+    { "1111::6666:123.123.123.123",                     STATUS_SUCCESS,             26,
+            { 0x1111, 0, 0, 0, 0, 0x6666, 0x7b7b, 0x7b7b } },
+    { "1111::6666:7777:8888",                           STATUS_SUCCESS,             20,
+            { 0x1111, 0, 0, 0, 0, 0x6666, 0x7777, 0x8888 } },
+    { "1111::7777:8888",                                STATUS_SUCCESS,             15,
+            { 0x1111, 0, 0, 0, 0, 0, 0x7777, 0x8888 } },
+    { "1111::8888",                                     STATUS_SUCCESS,             10,
+            { 0x1111, 0, 0, 0, 0, 0, 0, 0x8888 } },
+    { "1:2:3:4:5:6:1.2.3.4",                            STATUS_SUCCESS,             19,
+            { 0x100, 0x200, 0x300, 0x400, 0x500, 0x600, 0x201, 0x403 } },
+    { "1:2:3:4:5:6:7:8",                                STATUS_SUCCESS,             15,
+            { 0x100, 0x200, 0x300, 0x400, 0x500, 0x600, 0x700, 0x800 } },
+    { "1:2:3:4:5:6::",                                  STATUS_SUCCESS,             13,
+            { 0x100, 0x200, 0x300, 0x400, 0x500, 0x600, 0, 0 } },
+    { "1:2:3:4:5:6::8",                                 STATUS_SUCCESS,             14,
+            { 0x100, 0x200, 0x300, 0x400, 0x500, 0x600, 0, 0x800 } },
+    { "2001:0000:1234:0000:0000:C1C0:ABCD:0876",        STATUS_SUCCESS,             39,
+            { 0x120, 0, 0x3412, 0, 0, 0xc0c1, 0xcdab, 0x7608 } },
+    { "2001:0000:4136:e378:8000:63bf:3fff:fdd2",        STATUS_SUCCESS,             39,
+            { 0x120, 0, 0x3641, 0x78e3, 0x80, 0xbf63, 0xff3f, 0xd2fd } },
+    { "2001:0db8:0:0:0:0:1428:57ab",                    STATUS_SUCCESS,             27,
+            { 0x120, 0xb80d, 0, 0, 0, 0, 0x2814, 0xab57 } },
+    { "2001:0db8:1234:ffff:ffff:ffff:ffff:ffff",        STATUS_SUCCESS,             39,
+            { 0x120, 0xb80d, 0x3412, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff } },
+    { "2001::CE49:7601:2CAD:DFFF:7C94:FFFE",            STATUS_SUCCESS,             35,
+            { 0x120, 0, 0x49ce, 0x176, 0xad2c, 0xffdf, 0x947c, 0xfeff } },
+    { "2001:db8:85a3::8a2e:370:7334",                   STATUS_SUCCESS,             28,
+            { 0x120, 0xb80d, 0xa385, 0, 0, 0x2e8a, 0x7003, 0x3473 } },
+    { "3ffe:0b00:0000:0000:0001:0000:0000:000a",        STATUS_SUCCESS,             39,
+            { 0xfe3f, 0xb, 0, 0, 0x100, 0, 0, 0xa00 } },
+    { "::",                                             STATUS_SUCCESS,             2,
+            { 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { "::%16",                                          STATUS_SUCCESS,             2,
+            { 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { "::/16",                                          STATUS_SUCCESS,             2,
+            { 0, 0, 0, 0, 0, 0, 0, 0 }, ex_fail_6 },
+    { "::0",                                            STATUS_SUCCESS,             3,
+            { 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { "::0:0",                                          STATUS_SUCCESS,             5,
+            { 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { "::0:0:0",                                        STATUS_SUCCESS,             7,
+            { 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { "::0:0:0:0",                                      STATUS_SUCCESS,             9,
+            { 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { "::0:0:0:0:0",                                    STATUS_SUCCESS,             11,
+            { 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { "::0:0:0:0:0:0",                                  STATUS_SUCCESS,             13,
+            { 0, 0, 0, 0, 0, 0, 0, 0 } },
+    /* this one and the next one are incorrectly parsed by windows,
+        it adds one zero too many in front, cutting off the last digit. */
+    { "::0:0:0:0:0:0:0",                                STATUS_SUCCESS,             13,
+            { 0, 0, 0, 0, 0, 0, 0, 0 }, ex_fail_6 },
+    { "::0:a:b:c:d:e:f",                                STATUS_SUCCESS,             13,
+            { 0, 0, 0, 0xa00, 0xb00, 0xc00, 0xd00, 0xe00 }, ex_fail_6 },
+    { "::123.123.123.123",                              STATUS_SUCCESS,             17,
+            { 0, 0, 0, 0, 0, 0, 0x7b7b, 0x7b7b } },
+    { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",        STATUS_SUCCESS,             39,
+            { 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff } },
+
+    { "':10.0.0.1",                                     STATUS_INVALID_PARAMETER,   0,
+            { -1 } },
+    { "-1",                                             STATUS_INVALID_PARAMETER,   0,
+            { -1 } },
+    { "02001:0000:1234:0000:0000:C1C0:ABCD:0876",       STATUS_INVALID_PARAMETER,   -1,
+            { -1 } },
+    { "2001:00000:1234:0000:0000:C1C0:ABCD:0876",       STATUS_INVALID_PARAMETER,   -1,
+            { 0x120, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "2001:0000:01234:0000:0000:C1C0:ABCD:0876",       STATUS_INVALID_PARAMETER,   -1,
+            { 0x120, 0, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1.2.3.4",                                        STATUS_INVALID_PARAMETER,   7,
+            { 0x201, 0xab03, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1.2.3.4:1111::5555",                             STATUS_INVALID_PARAMETER,   7,
+            { 0x201, 0xab03, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1.2.3.4::5555",                                  STATUS_INVALID_PARAMETER,   7,
+            { 0x201, 0xab03, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "11112222:3333:4444:5555:6666:1.2.3.4",           STATUS_INVALID_PARAMETER,   -1,
+            { -1 } },
+    { "11112222:3333:4444:5555:6666:7777:8888",         STATUS_INVALID_PARAMETER,   -1,
+            { -1 } },
+    { "1111",                                           STATUS_INVALID_PARAMETER,   4,
+            { -1 } },
+    { "1111:22223333:4444:5555:6666:1.2.3.4",           STATUS_INVALID_PARAMETER,   -1,
+            { 0x1111, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1111:22223333:4444:5555:6666:7777:8888",         STATUS_INVALID_PARAMETER,   -1,
+            { 0x1111, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1111:2222:",                                     STATUS_INVALID_PARAMETER,   10,
+            { 0x1111, 0x2222, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1111:2222:1.2.3.4",                              STATUS_INVALID_PARAMETER,   17,
+            { 0x1111, 0x2222, 0x201, 0xab03, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1111:2222:3333",                                 STATUS_INVALID_PARAMETER,   14,
+            { 0x1111, 0x2222, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1111:2222:3333:4444:5555:6666:7777:1.2.3.4",     STATUS_SUCCESS,             36,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x100 }, ex_fail_6 },
+    { "1111:2222:3333:4444:5555:6666:7777:8888:",       STATUS_SUCCESS,             39,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x8888 }, ex_fail_6 },
+    { "1111:2222:3333:4444:5555:6666:7777:8888:1.2.3.4",STATUS_SUCCESS,             39,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x8888 }, ex_fail_6 },
+    { "1111:2222:3333:4444:5555:6666:7777:8888:9999",   STATUS_SUCCESS,             39,
+            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x8888 }, ex_fail_6 },
+    { "1111:2222:::",                                   STATUS_SUCCESS,             11,
+            { 0x1111, 0x2222, 0, 0, 0, 0, 0, 0 }, ex_fail_6 },
+    { "1111::5555:",                                    STATUS_INVALID_PARAMETER,   11,
+            { 0x1111, 0x5555, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1111::3333:4444:5555:6666:7777::",               STATUS_SUCCESS,             30,
+            { 0x1111, 0, 0, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777 }, ex_fail_6 },
+    { "1111:2222:::4444:5555:6666:1.2.3.4",             STATUS_SUCCESS,             11,
+            { 0x1111, 0x2222, 0, 0, 0, 0, 0, 0 }, ex_fail_6 },
+    { "1111::3333::5555:6666:1.2.3.4",                  STATUS_SUCCESS,             10,
+            { 0x1111, 0, 0, 0, 0, 0, 0, 0x3333 }, ex_fail_6 },
+    { "12345::6:7:8",                                   STATUS_INVALID_PARAMETER,   -1,
+            { -1 } },
+    { "1::1.2.256.4",                                   STATUS_INVALID_PARAMETER,   -1,
+            { 0x100, 0x201, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1::1.2.3.256",                                   STATUS_INVALID_PARAMETER,   12,
+            { 0x100, 0x201, 0xab03, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1::1.2.3.300",                                   STATUS_INVALID_PARAMETER,   12,
+            { 0x100, 0x201, 0xab03, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1::1.2::1",                                      STATUS_INVALID_PARAMETER,   6,
+            { 0x100, 0xab01, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1::1.2.3.4::1",                                  STATUS_SUCCESS,             10,
+            { 0x100, 0, 0, 0, 0, 0, 0x201, 0x403 }, ex_fail_6 },
+    { "1::1.",                                          STATUS_INVALID_PARAMETER,   5,
+            { 0x100, 0xab01, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1::1.2",                                         STATUS_INVALID_PARAMETER,   6,
+            { 0x100, 0xab01, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1::1.2.",                                        STATUS_INVALID_PARAMETER,   7,
+            { 0x100, 0x201, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1::1.2.3",                                       STATUS_INVALID_PARAMETER,   8,
+            { 0x100, 0x201, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1::1.2.3.",                                      STATUS_INVALID_PARAMETER,   9,
+            { 0x100, 0x201, 0xab03, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1::1.2.3.4",                                     STATUS_SUCCESS,             10,
+            { 0x100, 0, 0, 0, 0, 0, 0x201, 0x403 } },
+    { "1::1.2.3.900",                                   STATUS_INVALID_PARAMETER,   12,
+            { 0x100, 0x201, 0xab03, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1::1.2.300.4",                                   STATUS_INVALID_PARAMETER,   -1,
+            { 0x100, 0x201, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1::1.256.3.4",                                   STATUS_INVALID_PARAMETER,   -1,
+            { 0x100, 0xab01, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1::256.2.3.4",                                   STATUS_INVALID_PARAMETER,   -1,
+            { 0x100, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "1::2::3",                                        STATUS_SUCCESS,             4,
+            { 0x100, 0, 0, 0, 0, 0, 0, 0x200 }, ex_fail_6 },
+    { "2001:0000:1234: 0000:0000:C1C0:ABCD:0876",       STATUS_INVALID_PARAMETER,   15,
+            { 0x120, 0, 0x3412, 0xabab, 0xabab, 0xabab, 0xabab, 0xabab } },
+    { "2001:0000:1234:0000:0000:C1C0:ABCD:0876  0",     STATUS_SUCCESS,             39,
+            { 0x120, 0, 0x3412, 0, 0, 0xc0c1, 0xcdab, 0x7608 }, ex_fail_6 },
+    { "2001:1:1:1:1:1:255Z255X255Y255",                 STATUS_INVALID_PARAMETER,   18,
+            { 0x120, 0x100, 0x100, 0x100, 0x100, 0x100, 0xabab, 0xabab } },
+    { "2001::FFD3::57ab",                               STATUS_SUCCESS,             10,
+            { 0x120, 0, 0, 0, 0, 0, 0, 0xd3ff }, ex_fail_6 },
+    { ":",                                              STATUS_INVALID_PARAMETER,   0,
+            { -1 } },
+    { ":1111:2222:3333:4444:5555:6666:1.2.3.4",         STATUS_INVALID_PARAMETER,   0,
+            { -1 } },
+    { ":1111:2222:3333:4444:5555:6666:7777:8888",       STATUS_INVALID_PARAMETER,   0,
+            { -1 } },
+    { ":1111::",                                        STATUS_INVALID_PARAMETER,   0,
+            { -1 } },
+    { "::-1",                                           STATUS_SUCCESS,             2,
+            { 0, 0, 0, 0, 0, 0, 0, 0 }, ex_fail_6 },
+    { "::.",                                            STATUS_SUCCESS,             2,
+            { 0, 0, 0, 0, 0, 0, 0, 0 }, ex_fail_6 },
+    { "::..",                                           STATUS_SUCCESS,             2,
+            { 0, 0, 0, 0, 0, 0, 0, 0 }, ex_fail_6 },
+    { "::...",                                          STATUS_SUCCESS,             2,
+            { 0, 0, 0, 0, 0, 0, 0, 0 }, ex_fail_6 },
+    { "XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4",          STATUS_INVALID_PARAMETER,   0,
+            { -1 } },
+    { "[::]",                                           STATUS_INVALID_PARAMETER,   0,
+            { -1 }, ex_skip_6 },
+};
+const unsigned int ipv6_testcount = sizeof(ipv6_tests) / sizeof(ipv6_tests[0]);
+
+static void init_ip6(IN6_ADDR* addr, const int src[8])
+{
+    unsigned int j;
+    if (!src || src[0] == -1)
+    {
+        for (j = 0; j < 8; ++j)
+            addr->s6_words[j] = 0xabab;
+    }
+    else
+    {
+        for (j = 0; j < 8; ++j)
+            addr->s6_words[j] = src[j];
+    }
+}
+
+static void test_RtlIpv6AddressToString(void)
+{
+    CHAR buffer[50];
+    LPCSTR result;
+    IN6_ADDR ip;
+    DWORD_PTR len;
+    struct
+    {
+        PCSTR address;
+        int ip[8];
+    } tests[] =
+    {
+        /* ipv4 addresses & ISATAP addresses */
+        { "::13.1.68.3",                                { 0, 0, 0, 0, 0, 0, 0x10d, 0x344 } },
+        { "::ffff:13.1.68.3",                           { 0, 0, 0, 0, 0, 0xffff, 0x10d, 0x344 } },
+        { "::feff:d01:4403",                            { 0, 0, 0, 0, 0, 0xfffe, 0x10d, 0x344 } },
+        { "::fffe:d01:4403",                            { 0, 0, 0, 0, 0, 0xfeff, 0x10d, 0x344 } },
+        { "::100:d01:4403",                             { 0, 0, 0, 0, 0, 1, 0x10d, 0x344 } },
+        { "::1:d01:4403",                               { 0, 0, 0, 0, 0, 0x100, 0x10d, 0x344 } },
+        { "::ffff:0:4403",                              { 0, 0, 0, 0, 0, 0xffff, 0, 0x344 } },
+        { "::ffff:13.1.0.0",                            { 0, 0, 0, 0, 0, 0xffff, 0x10d, 0 } },
+        { "::ffff:0:0",                                 { 0, 0, 0, 0, 0, 0xffff, 0, 0 } },
+        { "::ffff:0:13.1.68.3",                         { 0, 0, 0, 0, 0xffff, 0, 0x10d, 0x344 } },
+        { "::ffff:ffff:d01:4403",                       { 0, 0, 0, 0, 0xffff, 0xffff, 0x10d, 0x344 } },
+        { "::ffff:0:0:d01:4403",                        { 0, 0, 0, 0xffff, 0, 0, 0x10d, 0x344 } },
+        { "::ffff:255.255.255.255",                     { 0, 0, 0, 0, 0, 0xffff, 0xffff, 0xffff } },
+        { "::ffff:129.144.52.38",                       { 0, 0, 0, 0, 0, 0xffff, 0x9081, 0x2634 } },
+        { "::5efe:129.144.52.38",                       { 0, 0, 0, 0, 0, 0xfe5e, 0x9081, 0x2634 } },
+        { "1111:2222:3333:4444:0:5efe:129.144.52.38",   { 0x1111, 0x2222, 0x3333, 0x4444, 0, 0xfe5e, 0x9081, 0x2634 } },
+        { "1111:2222:3333::5efe:129.144.52.38",         { 0x1111, 0x2222, 0x3333, 0, 0, 0xfe5e, 0x9081, 0x2634 } },
+        { "1111:2222::5efe:129.144.52.38",              { 0x1111, 0x2222, 0, 0, 0, 0xfe5e, 0x9081, 0x2634 } },
+        { "1111::5efe:129.144.52.38",                   { 0x1111, 0, 0, 0, 0, 0xfe5e, 0x9081, 0x2634 } },
+        { "::200:5efe:129.144.52.38",                   { 0, 0, 0, 0, 2, 0xfe5e, 0x9081, 0x2634 } },
+        { "::100:5efe:8190:3426",                       { 0, 0, 0, 0, 1, 0xfe5e, 0x9081, 0x2634 } },
+        /* 'normal' addresses */
+        { "::1",                                        { 0, 0, 0, 0, 0, 0, 0, 0x100 } },
+        { "0:1:2:3:4:5:6:7",                            { 0, 0x100, 0x200, 0x300, 0x400, 0x500, 0x600, 0x700 } },
+        { "1080::8:800:200c:417a",                      { 0x8010, 0, 0, 0, 0x800, 0x8, 0x0c20, 0x7a41 } },
+        { "1111:2222:3333:4444:5555:6666:7b7b:7b7b",    { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7b7b, 0x7b7b } },
+        { "1111:2222:3333:4444:5555:6666:7777:8888",    { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x8888 } },
+        { "1111:2222:3333:4444:5555:6666::",            { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0, 0 } },
+        { "1111:2222:3333:4444:5555:6666:0:8888",       { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0, 0x8888 } },
+        { "1111:2222:3333:4444:5555::",                 { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0, 0, 0 } },
+        { "1111:2222:3333:4444:5555:0:7b7b:7b7b",       { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0, 0x7b7b, 0x7b7b } },
+        { "1111:2222:3333:4444:5555:0:7777:8888",       { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0, 0x7777, 0x8888 } },
+        { "1111:2222:3333:4444:5555::8888",             { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0, 0, 0x8888 } },
+        { "1111::",                                     { 0x1111, 0, 0, 0, 0, 0, 0, 0 } },
+        { "1111::7b7b:7b7b",                            { 0x1111, 0, 0, 0, 0, 0, 0x7b7b, 0x7b7b } },
+        { "1111:0:3333:4444:5555:6666:7b7b:7b7b",       { 0x1111, 0, 0x3333, 0x4444, 0x5555, 0x6666, 0x7b7b, 0x7b7b } },
+        { "1111:0:3333:4444:5555:6666:7777:8888",       { 0x1111, 0, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x8888 } },
+        { "1111::4444:5555:6666:7b7b:7b7b",             { 0x1111, 0, 0, 0x4444, 0x5555, 0x6666, 0x7b7b, 0x7b7b } },
+        { "1111::4444:5555:6666:7777:8888",             { 0x1111, 0, 0, 0x4444, 0x5555, 0x6666, 0x7777, 0x8888 } },
+        { "1111::5555:6666:7b7b:7b7b",                  { 0x1111, 0, 0, 0, 0x5555, 0x6666, 0x7b7b, 0x7b7b } },
+        { "1111::5555:6666:7777:8888",                  { 0x1111, 0, 0, 0, 0x5555, 0x6666, 0x7777, 0x8888 } },
+        { "1111::6666:7b7b:7b7b",                       { 0x1111, 0, 0, 0, 0, 0x6666, 0x7b7b, 0x7b7b } },
+        { "1111::6666:7777:8888",                       { 0x1111, 0, 0, 0, 0, 0x6666, 0x7777, 0x8888 } },
+        { "1111::7777:8888",                            { 0x1111, 0, 0, 0, 0, 0, 0x7777, 0x8888 } },
+        { "1111::8888",                                 { 0x1111, 0, 0, 0, 0, 0, 0, 0x8888 } },
+        { "1:2:3:4:5:6:102:304",                        { 0x100, 0x200, 0x300, 0x400, 0x500, 0x600, 0x201, 0x403 } },
+        { "1:2:3:4:5:6:7:8",                            { 0x100, 0x200, 0x300, 0x400, 0x500, 0x600, 0x700, 0x800 } },
+        { "1:2:3:4:5:6::",                              { 0x100, 0x200, 0x300, 0x400, 0x500, 0x600, 0, 0 } },
+        { "1:2:3:4:5:6:0:8",                            { 0x100, 0x200, 0x300, 0x400, 0x500, 0x600, 0, 0x800 } },
+        { "2001:0:1234::c1c0:abcd:876",                 { 0x120, 0, 0x3412, 0, 0, 0xc0c1, 0xcdab, 0x7608 } },
+        { "2001:0:4136:e378:8000:63bf:3fff:fdd2",       { 0x120, 0, 0x3641, 0x78e3, 0x80, 0xbf63, 0xff3f, 0xd2fd } },
+        { "2001:db8::1428:57ab",                        { 0x120, 0xb80d, 0, 0, 0, 0, 0x2814, 0xab57 } },
+        { "2001:db8:1234:ffff:ffff:ffff:ffff:ffff",     { 0x120, 0xb80d, 0x3412, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff } },
+        { "2001:0:ce49:7601:2cad:dfff:7c94:fffe",       { 0x120, 0, 0x49ce, 0x176, 0xad2c, 0xffdf, 0x947c, 0xfeff } },
+        { "2001:db8:85a3::8a2e:370:7334",               { 0x120, 0xb80d, 0xa385, 0, 0, 0x2e8a, 0x7003, 0x3473 } },
+        { "3ffe:b00::1:0:0:a",                          { 0xfe3f, 0xb, 0, 0, 0x100, 0, 0, 0xa00 } },
+        { "::a:b:c:d:e",                                { 0, 0, 0, 0xa00, 0xb00, 0xc00, 0xd00, 0xe00 } },
+        { "::123.123.123.123",                          { 0, 0, 0, 0, 0, 0, 0x7b7b, 0x7b7b } },
+        { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",    { 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff } },
+        { "1111:2222:3333:4444:5555:6666:7777:1",       { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x100 } },
+        { "1111:2222:3333:4444:5555:6666:7777:8888",    { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x8888 } },
+        { "1111:2222::",                                { 0x1111, 0x2222, 0, 0, 0, 0, 0, 0 } },
+        { "1111::3333:4444:5555:6666:7777",             { 0x1111, 0, 0, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777 } },
+        { "1111:2222::",                                { 0x1111, 0x2222, 0, 0, 0, 0, 0, 0 } },
+        { "1111::3333",                                 { 0x1111, 0, 0, 0, 0, 0, 0, 0x3333 } },
+        { "2001:0:1234::c1c0:abcd:876",                 { 0x120, 0, 0x3412, 0, 0, 0xc0c1, 0xcdab, 0x7608 } },
+        { "2001::ffd3",                                 { 0x120, 0, 0, 0, 0, 0, 0, 0xd3ff } },
+    };
+    const size_t testcount = sizeof(tests) / sizeof(tests[0]);
+    unsigned int i;
+
+    if (!pRtlIpv6AddressToStringA)
+    {
+        skip("RtlIpv6AddressToStringA not available\n");
+        return;
+    }
+
+    memset(buffer, '#', sizeof(buffer));
+    buffer[sizeof(buffer)-1] = 0;
+    memset(&ip, 0, sizeof(ip));
+    result = pRtlIpv6AddressToStringA(&ip, buffer);
+
+    len = strlen(buffer);
+    ok(result == (buffer + len) && !strcmp(buffer, "::"),
+       "got %p with '%s' (expected %p with '::')\n", result, buffer, buffer + len);
+
+    result = pRtlIpv6AddressToStringA(&ip, NULL);
+    ok(result == (LPCSTR)~0 || broken(result == (LPCSTR)len) /* WinXP / Win2k3 */,
+       "got %p, expected %p\n", result, (LPCSTR)~0);
+
+    for (i = 0; i < testcount; i++)
+    {
+        init_ip6(&ip, tests[i].ip);
+        memset(buffer, '#', sizeof(buffer));
+        buffer[sizeof(buffer)-1] = 0;
+
+        result = pRtlIpv6AddressToStringA(&ip, buffer);
+        len = strlen(buffer);
+        ok(result == (buffer + len) && !strcmp(buffer, tests[i].address),
+           "got %p with '%s' (expected %p with '%s')\n", result, buffer, buffer + len, tests[i].address);
+
+        ok(buffer[45] == 0 || broken(buffer[45] != 0) /* WinXP / Win2k3 */,
+           "expected data at buffer[45] to always be NULL\n");
+        ok(buffer[46] == '#', "expected data at buffer[46] not to change\n");
+    }
+}
+
+static void test_RtlIpv6AddressToStringEx(void)
+{
+    CHAR buffer[70];
+    NTSTATUS res;
+    IN6_ADDR ip;
+    ULONG len;
+    struct
+    {
+        PCSTR address;
+        ULONG scopeid;
+        USHORT port;
+        int ip[8];
+    } tests[] =
+    {
+        /* ipv4 addresses & ISATAP addresses */
+        { "::13.1.68.3",                                                0,          0, { 0, 0, 0, 0, 0, 0, 0x10d, 0x344 } },
+        { "::13.1.68.3%1",                                              1,          0, { 0, 0, 0, 0, 0, 0, 0x10d, 0x344 } },
+        { "::13.1.68.3%4294949819",                                     0xffffbbbb, 0, { 0, 0, 0, 0, 0, 0, 0x10d, 0x344 } },
+        { "[::13.1.68.3%4294949819]:65518",                             0xffffbbbb, 0xeeff, { 0, 0, 0, 0, 0, 0, 0x10d, 0x344 } },
+        { "[::13.1.68.3%4294949819]:256",                               0xffffbbbb, 1, { 0, 0, 0, 0, 0, 0, 0x10d, 0x344 } },
+        { "[::13.1.68.3]:256",                                          0,          1, { 0, 0, 0, 0, 0, 0, 0x10d, 0x344 } },
+
+        { "::1:d01:4403",                                               0,          0, { 0, 0, 0, 0, 0, 0x100, 0x10d, 0x344 } },
+        { "::1:d01:4403%1",                                             1,          0, { 0, 0, 0, 0, 0, 0x100, 0x10d, 0x344 } },
+        { "::1:d01:4403%4294949819",                                    0xffffbbbb, 0, { 0, 0, 0, 0, 0, 0x100, 0x10d, 0x344 } },
+        { "[::1:d01:4403%4294949819]:65518",                            0xffffbbbb, 0xeeff, { 0, 0, 0, 0, 0, 0x100, 0x10d, 0x344 } },
+        { "[::1:d01:4403%4294949819]:256",                              0xffffbbbb, 1, { 0, 0, 0, 0, 0, 0x100, 0x10d, 0x344 } },
+        { "[::1:d01:4403]:256",                                         0,          1, { 0, 0, 0, 0, 0, 0x100, 0x10d, 0x344 } },
+
+        { "1111:2222:3333:4444:0:5efe:129.144.52.38",                   0,          0, { 0x1111, 0x2222, 0x3333, 0x4444, 0, 0xfe5e, 0x9081, 0x2634 } },
+        { "1111:2222:3333:4444:0:5efe:129.144.52.38%1",                 1,          0, { 0x1111, 0x2222, 0x3333, 0x4444, 0, 0xfe5e, 0x9081, 0x2634 } },
+        { "1111:2222:3333:4444:0:5efe:129.144.52.38%4294949819",        0xffffbbbb, 0, { 0x1111, 0x2222, 0x3333, 0x4444, 0, 0xfe5e, 0x9081, 0x2634 } },
+        { "[1111:2222:3333:4444:0:5efe:129.144.52.38%4294949819]:65518",0xffffbbbb, 0xeeff, { 0x1111, 0x2222, 0x3333, 0x4444, 0, 0xfe5e, 0x9081, 0x2634 } },
+        { "[1111:2222:3333:4444:0:5efe:129.144.52.38%4294949819]:256",  0xffffbbbb, 1, { 0x1111, 0x2222, 0x3333, 0x4444, 0, 0xfe5e, 0x9081, 0x2634 } },
+        { "[1111:2222:3333:4444:0:5efe:129.144.52.38]:256",             0,          1, { 0x1111, 0x2222, 0x3333, 0x4444, 0, 0xfe5e, 0x9081, 0x2634 } },
+
+        { "::1",                                                        0,          0, { 0, 0, 0, 0, 0, 0, 0, 0x100 } },
+        { "::1%1",                                                      1,          0, { 0, 0, 0, 0, 0, 0, 0, 0x100 } },
+        { "::1%4294949819",                                             0xffffbbbb, 0, { 0, 0, 0, 0, 0, 0, 0, 0x100 } },
+        { "[::1%4294949819]:65518",                                     0xffffbbbb, 0xeeff, { 0, 0, 0, 0, 0, 0, 0, 0x100 } },
+        { "[::1%4294949819]:256",                                       0xffffbbbb, 1, { 0, 0, 0, 0, 0, 0, 0, 0x100 } },
+        { "[::1]:256",                                                  0,          1, { 0, 0, 0, 0, 0, 0, 0, 0x100 } },
+
+        { "1111:2222:3333:4444:5555:6666:7b7b:7b7b",                    0,          0, { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7b7b, 0x7b7b } },
+        { "1111:2222:3333:4444:5555:6666:7b7b:7b7b%1",                  1,          0, { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7b7b, 0x7b7b } },
+        { "1111:2222:3333:4444:5555:6666:7b7b:7b7b%4294949819",         0xffffbbbb, 0, { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7b7b, 0x7b7b } },
+        { "[1111:2222:3333:4444:5555:6666:7b7b:7b7b%4294949819]:65518", 0xffffbbbb, 0xeeff, { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7b7b, 0x7b7b } },
+        { "[1111:2222:3333:4444:5555:6666:7b7b:7b7b%4294949819]:256",   0xffffbbbb, 1, { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7b7b, 0x7b7b } },
+        { "[1111:2222:3333:4444:5555:6666:7b7b:7b7b]:256",              0,          1, { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7b7b, 0x7b7b } },
+
+        { "1111::",                                                     0,          0, { 0x1111, 0, 0, 0, 0, 0, 0, 0 } },
+        { "1111::%1",                                                   1,          0, { 0x1111, 0, 0, 0, 0, 0, 0, 0 } },
+        { "1111::%4294949819",                                          0xffffbbbb, 0, { 0x1111, 0, 0, 0, 0, 0, 0, 0 } },
+        { "[1111::%4294949819]:65518",                                  0xffffbbbb, 0xeeff, { 0x1111, 0, 0, 0, 0, 0, 0, 0 } },
+        { "[1111::%4294949819]:256",                                    0xffffbbbb, 1, { 0x1111, 0, 0, 0, 0, 0, 0, 0 } },
+        { "[1111::]:256",                                               0,          1, { 0x1111, 0, 0, 0, 0, 0, 0, 0 } },
+
+        { "2001::ffd3",                                                 0,          0, { 0x120, 0, 0, 0, 0, 0, 0, 0xd3ff } },
+        { "2001::ffd3%1",                                               1,          0, { 0x120, 0, 0, 0, 0, 0, 0, 0xd3ff } },
+        { "2001::ffd3%4294949819",                                      0xffffbbbb, 0, { 0x120, 0, 0, 0, 0, 0, 0, 0xd3ff } },
+        { "[2001::ffd3%4294949819]:65518",                              0xffffbbbb, 0xeeff, { 0x120, 0, 0, 0, 0, 0, 0, 0xd3ff } },
+        { "[2001::ffd3%4294949819]:256",                                0xffffbbbb, 1, { 0x120, 0, 0, 0, 0, 0, 0, 0xd3ff } },
+        { "[2001::ffd3]:256",                                           0,          1, { 0x120, 0, 0, 0, 0, 0, 0, 0xd3ff } },
+    };
+    const size_t testcount = sizeof(tests) / sizeof(tests[0]);
+    unsigned int i;
+
+    if (!pRtlIpv6AddressToStringExA)
+    {
+        skip("RtlIpv6AddressToStringExA not available\n");
+        return;
+    }
+
+    memset(buffer, '#', sizeof(buffer));
+    buffer[sizeof(buffer)-1] = 0;
+    memset(&ip, 0, sizeof(ip));
+    len = sizeof(buffer);
+    res = pRtlIpv6AddressToStringExA(&ip, 0, 0, buffer, &len);
+
+    ok(res == STATUS_SUCCESS, "[validate] res = 0x%08x, expected STATUS_SUCCESS\n", res);
+    ok(len == 3 && !strcmp(buffer, "::"),
+        "got len %d with '%s' (expected 3 with '::')\n", len, buffer);
+
+    memset(buffer, '#', sizeof(buffer));
+    buffer[sizeof(buffer)-1] = 0;
+
+    len = sizeof(buffer);
+    res = pRtlIpv6AddressToStringExA(NULL, 0, 0, buffer, &len);
+    ok(res == STATUS_INVALID_PARAMETER, "[null ip] res = 0x%08x, expected STATUS_INVALID_PARAMETER\n", res);
+
+    len = sizeof(buffer);
+    res = pRtlIpv6AddressToStringExA(&ip, 0, 0, NULL, &len);
+    ok(res == STATUS_INVALID_PARAMETER, "[null buffer] res = 0x%08x, expected STATUS_INVALID_PARAMETER\n", res);
+
+    res = pRtlIpv6AddressToStringExA(&ip, 0, 0, buffer, NULL);
+    ok(res == STATUS_INVALID_PARAMETER, "[null length] res = 0x%08x, expected STATUS_INVALID_PARAMETER\n", res);
+
+    len = 2;
+    memset(buffer, '#', sizeof(buffer));
+    buffer[sizeof(buffer)-1] = 0;
+    res = pRtlIpv6AddressToStringExA(&ip, 0, 0, buffer, &len);
+    ok(res == STATUS_INVALID_PARAMETER, "[null length] res = 0x%08x, expected STATUS_INVALID_PARAMETER\n", res);
+    ok(buffer[0] == '#', "got first char %c (expected '#')\n", buffer[0]);
+    ok(len == 3, "got len %d (expected len 3)\n", len);
+
+    for (i = 0; i < testcount; i++)
+    {
+        init_ip6(&ip, tests[i].ip);
+        len = sizeof(buffer);
+        memset(buffer, '#', sizeof(buffer));
+        buffer[sizeof(buffer)-1] = 0;
+
+        res = pRtlIpv6AddressToStringExA(&ip, tests[i].scopeid, tests[i].port, buffer, &len);
+
+        ok(res == STATUS_SUCCESS, "[validate] res = 0x%08x, expected STATUS_SUCCESS\n", res);
+        ok(len == (strlen(tests[i].address) + 1) && !strcmp(buffer, tests[i].address),
+           "got len %d with '%s' (expected %d with '%s')\n", len, buffer, strlen(tests[i].address), tests[i].address);
+    }
+}
+
+static void compare_RtlIpv6StringToAddressW(PCSTR name_a, int terminator_offset_a,
+                                            const struct in6_addr *addr_a, NTSTATUS res_a)
+{
+    WCHAR name[512];
+    NTSTATUS res;
+    IN6_ADDR ip;
+    PCWSTR terminator;
+
+    if (!pRtlIpv6StringToAddressW)
+        return;
+
+    pRtlMultiByteToUnicodeN(name, sizeof(name), NULL, name_a, strlen(name_a) + 1);
+
+    init_ip6(&ip, NULL);
+    terminator = (void *)0xdeadbeef;
+    res = pRtlIpv6StringToAddressW(name, &terminator, &ip);
+    ok(res == res_a, "[W:%s] res = 0x%08x, expected 0x%08x\n", name_a, res, res_a);
+
+    if (terminator_offset_a < 0)
+    {
+        ok(terminator == (void *)0xdeadbeef,
+           "[W:%s] terminator = %p, expected it not to change\n",
+           name_a, terminator);
+    }
+    else
+    {
+        ok(terminator == name + terminator_offset_a,
+           "[W:%s] terminator = %p, expected %p\n",
+           name_a, terminator, name + terminator_offset_a);
+    }
+
+    ok(!memcmp(&ip, addr_a, sizeof(ip)),
+       "[W:%s] ip = %x:%x:%x:%x:%x:%x:%x:%x, expected %x:%x:%x:%x:%x:%x:%x:%x\n",
+       name_a,
+       ip.s6_words[0], ip.s6_words[1], ip.s6_words[2], ip.s6_words[3],
+       ip.s6_words[4], ip.s6_words[5], ip.s6_words[6], ip.s6_words[7],
+       addr_a->s6_words[0], addr_a->s6_words[1], addr_a->s6_words[2], addr_a->s6_words[3],
+       addr_a->s6_words[4], addr_a->s6_words[5], addr_a->s6_words[6], addr_a->s6_words[7]);
+}
+
+static void test_RtlIpv6StringToAddress(void)
+{
+    NTSTATUS res;
+    IN6_ADDR ip, expected_ip;
+    PCSTR terminator;
+    unsigned int i;
+
+    if (!pRtlIpv6StringToAddressW)
+    {
+        skip("RtlIpv6StringToAddressW not available\n");
+        /* we can continue, just not test W */
+    }
+
+    if (!pRtlIpv6StringToAddressA)
+    {
+        skip("RtlIpv6StringToAddressA not available\n");
+        return; /* all tests are centered around A, we cannot continue */
+    }
+
+    res = pRtlIpv6StringToAddressA("::", &terminator, &ip);
+    ok(res == STATUS_SUCCESS, "[validate] res = 0x%08x, expected STATUS_SUCCESS\n", res);
+    if (0)
+    {
+        /* any of these crash */
+        res = pRtlIpv6StringToAddressA(NULL, &terminator, &ip);
+        ok(res == STATUS_INVALID_PARAMETER, "[null string] res = 0x%08x, expected STATUS_INVALID_PARAMETER\n", res);
+        res = pRtlIpv6StringToAddressA("::", NULL, &ip);
+        ok(res == STATUS_INVALID_PARAMETER, "[null terminator] res = 0x%08x, expected STATUS_INVALID_PARAMETER\n", res);
+        res = pRtlIpv6StringToAddressA("::", &terminator, NULL);
+        ok(res == STATUS_INVALID_PARAMETER, "[null result] res = 0x%08x, expected STATUS_INVALID_PARAMETER\n", res);
+    }
+
+    /* sanity check */
+    ok(sizeof(ip) == sizeof(USHORT)* 8, "sizeof(ip)\n");
+
+    for (i = 0; i < ipv6_testcount; i++)
+    {
+        init_ip6(&ip, NULL);
+        terminator = (void *)0xdeadbeef;
+        res = pRtlIpv6StringToAddressA(ipv6_tests[i].address, &terminator, &ip);
+        compare_RtlIpv6StringToAddressW(ipv6_tests[i].address, (terminator != (void *)0xdeadbeef) ?
+                                        (terminator - ipv6_tests[i].address) : -1, &ip, res);
+
+        if (ipv6_tests[i].flags & win_broken_6)
+        {
+            ok(res == ipv6_tests[i].res || broken(res == STATUS_INVALID_PARAMETER),
+               "[%s] res = 0x%08x, expected 0x%08x\n",
+               ipv6_tests[i].address, res, ipv6_tests[i].res);
+
+            if (res == STATUS_INVALID_PARAMETER)
+                continue;
+        }
         else
         {
-            expected_ip.S_un.S_un_b.s_b1 = tests[i].ip_strict[0];
-            expected_ip.S_un.S_un_b.s_b2 = tests[i].ip_strict[1];
-            expected_ip.S_un.S_un_b.s_b3 = tests[i].ip_strict[2];
-            expected_ip.S_un.S_un_b.s_b4 = tests[i].ip_strict[3];
+            ok(res == ipv6_tests[i].res,
+               "[%s] res = 0x%08x, expected 0x%08x\n",
+               ipv6_tests[i].address, res, ipv6_tests[i].res);
         }
-        ok(ip.S_un.S_addr == expected_ip.S_un.S_addr,
-           "[%s] ip = %08x, expected %08x\n",
-           tests[i].address, ip.S_un.S_addr, expected_ip.S_un.S_addr);
+
+        if (ipv6_tests[i].terminator_offset < 0)
+        {
+            ok(terminator == (void *)0xdeadbeef,
+               "[%s] terminator = %p, expected it not to change\n",
+               ipv6_tests[i].address, terminator);
+        }
+        else if (ipv6_tests[i].flags & win_broken_6)
+        {
+            PCSTR expected = ipv6_tests[i].address + ipv6_tests[i].terminator_offset;
+            ok(terminator == expected || broken(terminator == expected + 2),
+               "[%s] terminator = %p, expected %p\n",
+               ipv6_tests[i].address, terminator, expected);
+        }
+        else
+        {
+            ok(terminator == ipv6_tests[i].address + ipv6_tests[i].terminator_offset,
+               "[%s] terminator = %p, expected %p\n",
+               ipv6_tests[i].address, terminator, ipv6_tests[i].address + ipv6_tests[i].terminator_offset);
+        }
+
+        init_ip6(&expected_ip, ipv6_tests[i].ip);
+        ok(!memcmp(&ip, &expected_ip, sizeof(ip)),
+           "[%s] ip = %x:%x:%x:%x:%x:%x:%x:%x, expected %x:%x:%x:%x:%x:%x:%x:%x\n",
+           ipv6_tests[i].address,
+           ip.s6_words[0], ip.s6_words[1], ip.s6_words[2], ip.s6_words[3],
+           ip.s6_words[4], ip.s6_words[5], ip.s6_words[6], ip.s6_words[7],
+           expected_ip.s6_words[0], expected_ip.s6_words[1], expected_ip.s6_words[2], expected_ip.s6_words[3],
+           expected_ip.s6_words[4], expected_ip.s6_words[5], expected_ip.s6_words[6], expected_ip.s6_words[7]);
+    }
+}
+
+static void compare_RtlIpv6StringToAddressExW(PCSTR name_a, const struct in6_addr *addr_a, HRESULT res_a, ULONG scope_a, USHORT port_a)
+{
+    WCHAR name[512];
+    NTSTATUS res;
+    IN6_ADDR ip;
+    ULONG scope = 0xbadf00d;
+    USHORT port = 0xbeef;
+
+    if (!pRtlIpv6StringToAddressExW)
+        return;
+
+    pRtlMultiByteToUnicodeN(name, sizeof(name), NULL, name_a, strlen(name_a) + 1);
+
+    init_ip6(&ip, NULL);
+    res = pRtlIpv6StringToAddressExW(name, &ip, &scope, &port);
+
+    ok(res == res_a, "[W:%s] res = 0x%08x, expected 0x%08x\n", name_a, res, res_a);
+    ok(scope == scope_a, "[W:%s] scope = 0x%08x, expected 0x%08x\n", name_a, scope, scope_a);
+    ok(port == port_a, "[W:%s] port = 0x%08x, expected 0x%08x\n", name_a, port, port_a);
+
+    ok(!memcmp(&ip, addr_a, sizeof(ip)),
+       "[W:%s] ip = %x:%x:%x:%x:%x:%x:%x:%x, expected %x:%x:%x:%x:%x:%x:%x:%x\n",
+       name_a,
+       ip.s6_words[0], ip.s6_words[1], ip.s6_words[2], ip.s6_words[3],
+       ip.s6_words[4], ip.s6_words[5], ip.s6_words[6], ip.s6_words[7],
+       addr_a->s6_words[0], addr_a->s6_words[1], addr_a->s6_words[2], addr_a->s6_words[3],
+       addr_a->s6_words[4], addr_a->s6_words[5], addr_a->s6_words[6], addr_a->s6_words[7]);
+}
+
+static void test_RtlIpv6StringToAddressEx(void)
+{
+    NTSTATUS res;
+    IN6_ADDR ip, expected_ip;
+    ULONG scope;
+    USHORT port;
+    static const struct
+    {
+        PCSTR address;
+        NTSTATUS res;
+        ULONG scope;
+        USHORT port;
+        int ip[8];
+    } ipv6_ex_tests[] =
+    {
+        { "[::]",                                           STATUS_SUCCESS,             0,          0,
+            { 0, 0, 0, 0, 0, 0, 0, 0 } },
+        { "[::1]:8080",                                     STATUS_SUCCESS,             0,          0x901f,
+            { 0, 0, 0, 0, 0, 0, 0, 0x100 } },
+        { "[::1]:0x80",                                     STATUS_SUCCESS,             0,          0x8000,
+            { 0, 0, 0, 0, 0, 0, 0, 0x100 } },
+        { "[::1]:0X80",                                     STATUS_SUCCESS,             0,          0x8000,
+            { 0, 0, 0, 0, 0, 0, 0, 0x100 } },
+        { "[::1]:080",                                      STATUS_INVALID_PARAMETER,   0xbadf00d,  0xbeef,
+            { 0, 0, 0, 0, 0, 0, 0, 0x100 } },
+        { "[::1]:800000000080",                             STATUS_INVALID_PARAMETER,   0xbadf00d,  0xbeef,
+            { 0, 0, 0, 0, 0, 0, 0, 0x100 } },
+        { "[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80",   STATUS_SUCCESS,             0,          0x5000,
+            { 0xdcfe, 0x98ba, 0x5476, 0x1032, 0xdcfe, 0x98ba, 0x5476, 0x1032 } },
+        { "[1080:0:0:0:8:800:200C:417A]:1234",              STATUS_SUCCESS,             0,          0xd204,
+            { 0x8010, 0, 0, 0, 0x800, 8, 0xc20, 0x7a41 } },
+        { "[3ffe:2a00:100:7031::1]:8080",                   STATUS_SUCCESS,             0,          0x901f,
+            { 0xfe3f, 0x2a, 1, 0x3170, 0, 0, 0, 0x100 } },
+        { "[ 3ffe:2a00:100:7031::1]:8080",                  STATUS_INVALID_PARAMETER,   0xbadf00d,  0xbeef,
+            { -1 } },
+        { "[3ffe:2a00:100:7031::1 ]:8080",                  STATUS_INVALID_PARAMETER,   0xbadf00d,  0xbeef,
+            { 0xfe3f, 0x2a, 1, 0x3170, 0, 0, 0, 0x100 } },
+        { "[3ffe:2a00:100:7031::1].8080",                   STATUS_INVALID_PARAMETER,   0xbadf00d,  0xbeef,
+            { 0xfe3f, 0x2a, 1, 0x3170, 0, 0, 0, 0x100 } },
+        { "[1080::8:800:200C:417A]:8080",                   STATUS_SUCCESS,             0,          0x901f,
+            { 0x8010, 0, 0, 0, 0x800, 8, 0xc20, 0x7a41 } },
+        { "[1080::8:800:200C:417A]!8080",                   STATUS_INVALID_PARAMETER,   0xbadf00d,  0xbeef,
+            { 0x8010, 0, 0, 0, 0x800, 8, 0xc20, 0x7a41 } },
+        { "[::FFFF:129.144.52.38]:80",                      STATUS_SUCCESS,             0,          0x5000,
+            { 0, 0, 0, 0, 0, 0xffff, 0x9081, 0x2634 } },
+        { "[::FFFF:129.144.52.38]:-80",                     STATUS_INVALID_PARAMETER,   0xbadf00d,  0xbeef,
+            { 0, 0, 0, 0, 0, 0xffff, 0x9081, 0x2634 } },
+        { "[::FFFF:129.144.52.38]:999999999999",            STATUS_INVALID_PARAMETER,   0xbadf00d,  0xbeef,
+            { 0, 0, 0, 0, 0, 0xffff, 0x9081, 0x2634 } },
+        { "[::FFFF:129.144.52.38%-8]:80",                   STATUS_INVALID_PARAMETER,   0xbadf00d,  0xbeef,
+            { 0, 0, 0, 0, 0, 0xffff, 0x9081, 0x2634 } },
+        { "[::FFFF:129.144.52.38]:80",                      STATUS_SUCCESS,             0,          0x5000,
+            { 0, 0, 0, 0, 0, 0xffff, 0x9081, 0x2634 } },
+        { "[12345::6:7:8]:80",                              STATUS_INVALID_PARAMETER,   0xbadf00d,  0xbeef,
+            { -1 } },
+        { "[ff01::8:800:200C:417A%16]:8080",                STATUS_SUCCESS,             16,         0x901f,
+            { 0x1ff, 0, 0, 0, 0x800, 8, 0xc20, 0x7a41 } },
+        { "[ff01::8:800:200C:417A%100]:8080",               STATUS_SUCCESS,             100,        0x901f,
+            { 0x1ff, 0, 0, 0, 0x800, 8, 0xc20, 0x7a41 } },
+        { "[ff01::8:800:200C:417A%1000]:8080",              STATUS_SUCCESS,             1000,       0x901f,
+            { 0x1ff, 0, 0, 0, 0x800, 8, 0xc20, 0x7a41 } },
+        { "[ff01::8:800:200C:417A%10000]:8080",             STATUS_SUCCESS,             10000,      0x901f,
+            { 0x1ff, 0, 0, 0, 0x800, 8, 0xc20, 0x7a41 } },
+        { "[ff01::8:800:200C:417A%1000000]:8080",           STATUS_SUCCESS,             1000000,    0x901f,
+            { 0x1ff, 0, 0, 0, 0x800, 8, 0xc20, 0x7a41 } },
+        { "[ff01::8:800:200C:417A%4294967295]:8080",        STATUS_SUCCESS,             0xffffffff, 0x901f,
+            { 0x1ff, 0, 0, 0, 0x800, 8, 0xc20, 0x7a41 } },
+        { "[ff01::8:800:200C:417A%4294967296]:8080",        STATUS_INVALID_PARAMETER,   0xbadf00d,  0xbeef,
+            { 0x1ff, 0, 0, 0, 0x800, 8, 0xc20, 0x7a41 } },
+        { "[ff01::8:800:200C:417A%-1]:8080",                STATUS_INVALID_PARAMETER,   0xbadf00d,  0xbeef,
+            { 0x1ff, 0, 0, 0, 0x800, 8, 0xc20, 0x7a41 } },
+        { "[ff01::8:800:200C:417A%0]:8080",                 STATUS_SUCCESS,             0,          0x901f,
+            { 0x1ff, 0, 0, 0, 0x800, 8, 0xc20, 0x7a41 } },
+        { "[ff01::8:800:200C:417A%1",                       STATUS_INVALID_PARAMETER,   0xbadf00d,  0xbeef,
+            { 0x1ff, 0, 0, 0, 0x800, 8, 0xc20, 0x7a41 } },
+        { "[ff01::8:800:200C:417A%0x1000]:8080",            STATUS_INVALID_PARAMETER,   0xbadf00d,  0xbeef,
+            { 0x1ff, 0, 0, 0, 0x800, 8, 0xc20, 0x7a41 } },
+        { "[ff01::8:800:200C:417A/16]:8080",                STATUS_INVALID_PARAMETER,   0xbadf00d,  0xbeef,
+            { 0x1ff, 0, 0, 0, 0x800, 8, 0xc20, 0x7a41 } },
+    };
+    const unsigned int ipv6_ex_testcount = sizeof(ipv6_ex_tests) / sizeof(ipv6_ex_tests[0]);
+    const char *simple_ip = "::";
+    unsigned int i;
+
+    if (!pRtlIpv6StringToAddressExW)
+    {
+        skip("RtlIpv6StringToAddressExW not available\n");
+        /* we can continue, just not test W */
+    }
+
+    if (!pRtlIpv6StringToAddressExA)
+    {
+        skip("RtlIpv6StringToAddressExA not available\n");
+        return;
+    }
+
+    res = pRtlIpv6StringToAddressExA(simple_ip, &ip, &scope, &port);
+    ok(res == STATUS_SUCCESS, "[validate] res = 0x%08x, expected STATUS_SUCCESS\n", res);
+
+    init_ip6(&ip, NULL);
+    init_ip6(&expected_ip, NULL);
+    scope = 0xbadf00d;
+    port = 0xbeef;
+    res = pRtlIpv6StringToAddressExA(NULL, &ip, &scope, &port);
+    ok(res == STATUS_INVALID_PARAMETER,
+       "[null string] res = 0x%08x, expected STATUS_INVALID_PARAMETER\n", res);
+    ok(scope == 0xbadf00d, "[null string] scope = 0x%08x, expected 0xbadf00d\n", scope);
+    ok(port == 0xbeef, "[null string] port = 0x%08x, expected 0xbeef\n", port);
+    ok(!memcmp(&ip, &expected_ip, sizeof(ip)),
+       "[null string] ip is changed, expected it not to change\n");
+
+
+    init_ip6(&ip, NULL);
+    scope = 0xbadf00d;
+    port = 0xbeef;
+    res = pRtlIpv6StringToAddressExA(simple_ip, NULL, &scope, &port);
+    ok(res == STATUS_INVALID_PARAMETER,
+       "[null result] res = 0x%08x, expected STATUS_INVALID_PARAMETER\n", res);
+    ok(scope == 0xbadf00d, "[null result] scope = 0x%08x, expected 0xbadf00d\n", scope);
+    ok(port == 0xbeef, "[null result] port = 0x%08x, expected 0xbeef\n", port);
+    ok(!memcmp(&ip, &expected_ip, sizeof(ip)),
+       "[null result] ip is changed, expected it not to change\n");
+
+    init_ip6(&ip, NULL);
+    scope = 0xbadf00d;
+    port = 0xbeef;
+    res = pRtlIpv6StringToAddressExA(simple_ip, &ip, NULL, &port);
+    ok(res == STATUS_INVALID_PARAMETER,
+       "[null scope] res = 0x%08x, expected STATUS_INVALID_PARAMETER\n", res);
+    ok(scope == 0xbadf00d, "[null scope] scope = 0x%08x, expected 0xbadf00d\n", scope);
+    ok(port == 0xbeef, "[null scope] port = 0x%08x, expected 0xbeef\n", port);
+    ok(!memcmp(&ip, &expected_ip, sizeof(ip)),
+       "[null scope] ip is changed, expected it not to change\n");
+
+    init_ip6(&ip, NULL);
+    scope = 0xbadf00d;
+    port = 0xbeef;
+    res = pRtlIpv6StringToAddressExA(simple_ip, &ip, &scope, NULL);
+    ok(res == STATUS_INVALID_PARAMETER,
+       "[null port] res = 0x%08x, expected STATUS_INVALID_PARAMETER\n", res);
+    ok(scope == 0xbadf00d, "[null port] scope = 0x%08x, expected 0xbadf00d\n", scope);
+    ok(port == 0xbeef, "[null port] port = 0x%08x, expected 0xbeef\n", port);
+    ok(!memcmp(&ip, &expected_ip, sizeof(ip)),
+       "[null port] ip is changed, expected it not to change\n");
+
+    /* sanity check */
+    ok(sizeof(ip) == sizeof(USHORT)* 8, "sizeof(ip)\n");
+
+    /* first we run all ip related tests, to make sure someone didnt accidentally reimplement instead of re-use. */
+    for (i = 0; i < ipv6_testcount; i++)
+    {
+        ULONG scope = 0xbadf00d;
+        USHORT port = 0xbeef;
+        NTSTATUS expect_ret = (ipv6_tests[i].flags & ex_fail_6) ? STATUS_INVALID_PARAMETER : ipv6_tests[i].res;
+
+        if (ipv6_tests[i].flags & ex_skip_6)
+            continue;
+
+        init_ip6(&ip, NULL);
+        res = pRtlIpv6StringToAddressExA(ipv6_tests[i].address, &ip, &scope, &port);
+        compare_RtlIpv6StringToAddressExW(ipv6_tests[i].address, &ip, res, scope, port);
+
+        /* make sure nothing was changed if this function fails. */
+        if (res == STATUS_INVALID_PARAMETER)
+        {
+            ok(scope == 0xbadf00d, "[%s] scope = 0x%08x, expected 0xbadf00d\n",
+               ipv6_tests[i].address, scope);
+            ok(port == 0xbeef, "[%s] port = 0x%08x, expected 0xbeef\n",
+               ipv6_tests[i].address, port);
+        }
+        else
+        {
+            ok(scope != 0xbadf00d, "[%s] scope = 0x%08x, not expected 0xbadf00d\n",
+               ipv6_tests[i].address, scope);
+            ok(port != 0xbeef, "[%s] port = 0x%08x, not expected 0xbeef\n",
+               ipv6_tests[i].address, port);
+        }
+
+        if (ipv6_tests[i].flags & win_broken_6)
+        {
+            ok(res == expect_ret || broken(res == STATUS_INVALID_PARAMETER),
+               "[%s] res = 0x%08x, expected 0x%08x\n", ipv6_tests[i].address, res, expect_ret);
+
+            if (res == STATUS_INVALID_PARAMETER)
+                continue;
+        }
+        else
+        {
+            ok(res == expect_ret, "[%s] res = 0x%08x, expected 0x%08x\n",
+               ipv6_tests[i].address, res, expect_ret);
+        }
+
+        /* If ex fails but non-ex does not we cannot check if the part that is converted
+           before it failed was correct, since there is no data for it in the table. */
+        if (res == expect_ret)
+        {
+            init_ip6(&expected_ip, ipv6_tests[i].ip);
+            ok(!memcmp(&ip, &expected_ip, sizeof(ip)),
+               "[%s] ip = %x:%x:%x:%x:%x:%x:%x:%x, expected %x:%x:%x:%x:%x:%x:%x:%x\n",
+               ipv6_tests[i].address,
+               ip.s6_words[0], ip.s6_words[1], ip.s6_words[2], ip.s6_words[3],
+               ip.s6_words[4], ip.s6_words[5], ip.s6_words[6], ip.s6_words[7],
+               expected_ip.s6_words[0], expected_ip.s6_words[1], expected_ip.s6_words[2], expected_ip.s6_words[3],
+               expected_ip.s6_words[4], expected_ip.s6_words[5], expected_ip.s6_words[6], expected_ip.s6_words[7]);
+        }
+    }
+
+    /* now we run scope / port related tests */
+    for (i = 0; i < ipv6_ex_testcount; i++)
+    {
+        scope = 0xbadf00d;
+        port = 0xbeef;
+        init_ip6(&ip, NULL);
+        res = pRtlIpv6StringToAddressExA(ipv6_ex_tests[i].address, &ip, &scope, &port);
+        compare_RtlIpv6StringToAddressExW(ipv6_ex_tests[i].address, &ip, res, scope, port);
+
+        ok(res == ipv6_ex_tests[i].res, "[%s] res = 0x%08x, expected 0x%08x\n",
+           ipv6_ex_tests[i].address, res, ipv6_ex_tests[i].res);
+        ok(scope == ipv6_ex_tests[i].scope, "[%s] scope = 0x%08x, expected 0x%08x\n",
+           ipv6_ex_tests[i].address, scope, ipv6_ex_tests[i].scope);
+        ok(port == ipv6_ex_tests[i].port, "[%s] port = 0x%08x, expected 0x%08x\n",
+           ipv6_ex_tests[i].address, port, ipv6_ex_tests[i].port);
+
+        init_ip6(&expected_ip, ipv6_ex_tests[i].ip);
+        ok(!memcmp(&ip, &expected_ip, sizeof(ip)),
+           "[%s] ip = %x:%x:%x:%x:%x:%x:%x:%x, expected %x:%x:%x:%x:%x:%x:%x:%x\n",
+           ipv6_ex_tests[i].address,
+           ip.s6_words[0], ip.s6_words[1], ip.s6_words[2], ip.s6_words[3],
+           ip.s6_words[4], ip.s6_words[5], ip.s6_words[6], ip.s6_words[7],
+           expected_ip.s6_words[0], expected_ip.s6_words[1], expected_ip.s6_words[2], expected_ip.s6_words[3],
+           expected_ip.s6_words[4], expected_ip.s6_words[5], expected_ip.s6_words[6], expected_ip.s6_words[7]);
     }
 }
 
@@ -1504,14 +2419,6 @@ static void test_LdrAddRefDll(void)
         return;
     }
 
-#ifdef __REACTOS__
-    if (!winetest_interactive)
-    {
-        skip("Skipping LdrAddRefDll tests. See CORE-8102\n");
-        return;
-    }
-#endif
-
     mod = LoadLibraryA("comctl32.dll");
     ok(mod != NULL, "got %p\n", mod);
     ret = FreeLibrary(mod);
@@ -1607,10 +2514,667 @@ static void test_LdrLockLoaderLock(void)
     pLdrUnlockLoaderLock(0, magic);
 }
 
+static void test_RtlCompressBuffer(void)
+{
+    ULONG compress_workspace, decompress_workspace;
+    static const UCHAR test_buffer[] = "WineWineWine";
+    static UCHAR buf1[0x1000], buf2[0x1000];
+    ULONG final_size, buf_size;
+    UCHAR *workspace = NULL;
+    NTSTATUS status;
+
+    if (!pRtlCompressBuffer || !pRtlDecompressBuffer || !pRtlGetCompressionWorkSpaceSize)
+    {
+        win_skip("skipping RtlCompressBuffer tests, required functions not available\n");
+        return;
+    }
+
+    compress_workspace = decompress_workspace = 0xdeadbeef;
+    status = pRtlGetCompressionWorkSpaceSize(COMPRESSION_FORMAT_LZNT1, &compress_workspace,
+                                             &decompress_workspace);
+    ok(status == STATUS_SUCCESS, "got wrong status 0x%08x\n", status);
+    ok(compress_workspace != 0, "got wrong compress_workspace %u\n", compress_workspace);
+    workspace = HeapAlloc(GetProcessHeap(), 0, compress_workspace);
+    ok(workspace != NULL, "HeapAlloc failed %d\n", GetLastError());
+
+    /* test compression format / engine */
+    final_size = 0xdeadbeef;
+    status = pRtlCompressBuffer(COMPRESSION_FORMAT_NONE, test_buffer, sizeof(test_buffer),
+                                buf1, sizeof(buf1) - 1, 4096, &final_size, workspace);
+    ok(status == STATUS_INVALID_PARAMETER, "got wrong status 0x%08x\n", status);
+    ok(final_size == 0xdeadbeef, "got wrong final_size %u\n", final_size);
+
+    final_size = 0xdeadbeef;
+    status = pRtlCompressBuffer(COMPRESSION_FORMAT_DEFAULT, test_buffer, sizeof(test_buffer),
+                                buf1, sizeof(buf1) - 1, 4096, &final_size, workspace);
+    ok(status == STATUS_INVALID_PARAMETER, "got wrong status 0x%08x\n", status);
+    ok(final_size == 0xdeadbeef, "got wrong final_size %u\n", final_size);
+
+    final_size = 0xdeadbeef;
+    status = pRtlCompressBuffer(0xFF, test_buffer, sizeof(test_buffer),
+                                buf1, sizeof(buf1) - 1, 4096, &final_size, workspace);
+    ok(status == STATUS_UNSUPPORTED_COMPRESSION, "got wrong status 0x%08x\n", status);
+    ok(final_size == 0xdeadbeef, "got wrong final_size %u\n", final_size);
+
+    /* test compression */
+    final_size = 0xdeadbeef;
+    memset(buf1, 0x11, sizeof(buf1));
+    status = pRtlCompressBuffer(COMPRESSION_FORMAT_LZNT1, test_buffer, sizeof(test_buffer),
+                                buf1, sizeof(buf1), 4096, &final_size, workspace);
+    ok(status == STATUS_SUCCESS, "got wrong status 0x%08x\n", status);
+    ok((*(WORD *)buf1 & 0x7000) == 0x3000, "no chunk signature found %04x\n", *(WORD *)buf1);
+    todo_wine
+    ok(final_size < sizeof(test_buffer), "got wrong final_size %u\n", final_size);
+
+    /* test decompression */
+    buf_size = final_size;
+    final_size = 0xdeadbeef;
+    memset(buf2, 0x11, sizeof(buf2));
+    status = pRtlDecompressBuffer(COMPRESSION_FORMAT_LZNT1, buf2, sizeof(buf2),
+                                  buf1, buf_size, &final_size);
+    ok(status == STATUS_SUCCESS, "got wrong status 0x%08x\n", status);
+    ok(final_size == sizeof(test_buffer), "got wrong final_size %u\n", final_size);
+    ok(!memcmp(buf2, test_buffer, sizeof(test_buffer)), "got wrong decoded data\n");
+    ok(buf2[sizeof(test_buffer)] == 0x11, "too many bytes written\n");
+
+    /* buffer too small */
+    final_size = 0xdeadbeef;
+    memset(buf1, 0x11, sizeof(buf1));
+    status = pRtlCompressBuffer(COMPRESSION_FORMAT_LZNT1, test_buffer, sizeof(test_buffer),
+                                buf1, 4, 4096, &final_size, workspace);
+    ok(status == STATUS_BUFFER_TOO_SMALL, "got wrong status 0x%08x\n", status);
+
+    HeapFree(GetProcessHeap(), 0, workspace);
+}
+
+static void test_RtlGetCompressionWorkSpaceSize(void)
+{
+    ULONG compress_workspace, decompress_workspace;
+    NTSTATUS status;
+
+    if (!pRtlGetCompressionWorkSpaceSize)
+    {
+        win_skip("RtlGetCompressionWorkSpaceSize is not available\n");
+        return;
+    }
+
+    /* test invalid format / engine */
+    status = pRtlGetCompressionWorkSpaceSize(COMPRESSION_FORMAT_NONE, &compress_workspace,
+                                             &decompress_workspace);
+    ok(status == STATUS_INVALID_PARAMETER, "got wrong status 0x%08x\n", status);
+
+    status = pRtlGetCompressionWorkSpaceSize(COMPRESSION_FORMAT_DEFAULT, &compress_workspace,
+                                             &decompress_workspace);
+    ok(status == STATUS_INVALID_PARAMETER, "got wrong status 0x%08x\n", status);
+
+    status = pRtlGetCompressionWorkSpaceSize(0xFF, &compress_workspace, &decompress_workspace);
+    ok(status == STATUS_UNSUPPORTED_COMPRESSION, "got wrong status 0x%08x\n", status);
+
+    /* test LZNT1 with normal and maximum compression */
+    compress_workspace = decompress_workspace = 0xdeadbeef;
+    status = pRtlGetCompressionWorkSpaceSize(COMPRESSION_FORMAT_LZNT1, &compress_workspace,
+                                             &decompress_workspace);
+    ok(status == STATUS_SUCCESS, "got wrong status 0x%08x\n", status);
+    ok(compress_workspace != 0, "got wrong compress_workspace %u\n", compress_workspace);
+    ok(decompress_workspace == 0x1000, "got wrong decompress_workspace %u\n", decompress_workspace);
+
+    compress_workspace = decompress_workspace = 0xdeadbeef;
+    status = pRtlGetCompressionWorkSpaceSize(COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_MAXIMUM,
+                                             &compress_workspace, &decompress_workspace);
+    ok(status == STATUS_SUCCESS, "got wrong status 0x%08x\n", status);
+    ok(compress_workspace != 0, "got wrong compress_workspace %u\n", compress_workspace);
+    ok(decompress_workspace == 0x1000, "got wrong decompress_workspace %u\n", decompress_workspace);
+}
+
+/* helper for test_RtlDecompressBuffer, checks if a chunk is incomplete */
+static BOOL is_incomplete_chunk(const UCHAR *compressed, ULONG compressed_size, BOOL check_all)
+{
+    ULONG chunk_size;
+
+    if (compressed_size <= sizeof(WORD))
+        return TRUE;
+
+    while (compressed_size >= sizeof(WORD))
+    {
+        chunk_size = (*(WORD *)compressed & 0xFFF) + 1;
+        if (compressed_size < sizeof(WORD) + chunk_size)
+            return TRUE;
+        if (!check_all)
+            break;
+        compressed      += sizeof(WORD) + chunk_size;
+        compressed_size -= sizeof(WORD) + chunk_size;
+    }
+
+    return FALSE;
+}
+
+#define DECOMPRESS_BROKEN_FRAGMENT     1 /* < Win 7 */
+#define DECOMPRESS_BROKEN_TRUNCATED    2 /* broken on all machines */
+
+static void test_RtlDecompressBuffer(void)
+{
+    static const struct
+    {
+        UCHAR compressed[32];
+        ULONG compressed_size;
+        NTSTATUS status;
+        UCHAR uncompressed[32];
+        ULONG uncompressed_size;
+        DWORD broken_flags;
+    }
+    test_lznt[] =
+    {
+        /* 4 byte uncompressed chunk */
+        {
+            {0x03, 0x30, 'W', 'i', 'n', 'e'},
+            6,
+            STATUS_SUCCESS,
+            "Wine",
+            4,
+            DECOMPRESS_BROKEN_FRAGMENT
+        },
+        /* 8 byte uncompressed chunk */
+        {
+            {0x07, 0x30, 'W', 'i', 'n', 'e', 'W', 'i', 'n', 'e'},
+            10,
+            STATUS_SUCCESS,
+            "WineWine",
+            8,
+            DECOMPRESS_BROKEN_FRAGMENT
+        },
+        /* 4 byte compressed chunk */
+        {
+            {0x04, 0xB0, 0x00, 'W', 'i', 'n', 'e'},
+            7,
+            STATUS_SUCCESS,
+            "Wine",
+            4
+        },
+        /* 8 byte compressed chunk */
+        {
+            {0x08, 0xB0, 0x00, 'W', 'i', 'n', 'e', 'W', 'i', 'n', 'e'},
+            11,
+            STATUS_SUCCESS,
+            "WineWine",
+            8
+        },
+        /* compressed chunk using backwards reference */
+        {
+            {0x06, 0xB0, 0x10, 'W', 'i', 'n', 'e', 0x01, 0x30},
+            9,
+            STATUS_SUCCESS,
+            "WineWine",
+            8,
+            DECOMPRESS_BROKEN_TRUNCATED
+        },
+        /* compressed chunk using backwards reference with length > bytes_read */
+        {
+            {0x06, 0xB0, 0x10, 'W', 'i', 'n', 'e', 0x05, 0x30},
+            9,
+            STATUS_SUCCESS,
+            "WineWineWine",
+            12,
+            DECOMPRESS_BROKEN_TRUNCATED
+        },
+        /* same as above, but unused bits != 0 */
+        {
+            {0x06, 0xB0, 0x30, 'W', 'i', 'n', 'e', 0x01, 0x30},
+            9,
+            STATUS_SUCCESS,
+            "WineWine",
+            8,
+            DECOMPRESS_BROKEN_TRUNCATED
+        },
+        /* compressed chunk without backwards reference and unused bits != 0 */
+        {
+            {0x01, 0xB0, 0x02, 'W'},
+            4,
+            STATUS_SUCCESS,
+            "W",
+            1
+        },
+        /* termination sequence after first chunk */
+        {
+            {0x03, 0x30, 'W', 'i', 'n', 'e', 0x00, 0x00, 0x03, 0x30, 'W', 'i', 'n', 'e'},
+            14,
+            STATUS_SUCCESS,
+            "Wine",
+            4,
+            DECOMPRESS_BROKEN_FRAGMENT
+        },
+        /* compressed chunk using backwards reference with 4 bit offset, 12 bit length */
+        {
+            {0x14, 0xB0, 0x00, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+                         0x00, 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+                         0x01, 0x01, 0xF0},
+            23,
+            STATUS_SUCCESS,
+            "ABCDEFGHIJKLMNOPABCD",
+            20,
+            DECOMPRESS_BROKEN_TRUNCATED
+        },
+        /* compressed chunk using backwards reference with 5 bit offset, 11 bit length */
+        {
+            {0x15, 0xB0, 0x00, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+                         0x00, 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+                         0x02, 'A', 0x00, 0x78},
+            24,
+            STATUS_SUCCESS,
+            "ABCDEFGHIJKLMNOPABCD",
+            20,
+            DECOMPRESS_BROKEN_TRUNCATED
+        },
+        /* uncompressed chunk with invalid magic */
+        {
+            {0x03, 0x20, 'W', 'i', 'n', 'e'},
+            6,
+            STATUS_SUCCESS,
+            "Wine",
+            4,
+            DECOMPRESS_BROKEN_FRAGMENT
+        },
+        /* compressed chunk with invalid magic */
+        {
+            {0x04, 0xA0, 0x00, 'W', 'i', 'n', 'e'},
+            7,
+            STATUS_SUCCESS,
+            "Wine",
+            4
+        },
+        /* garbage byte after end of buffer */
+        {
+            {0x00, 0xB0, 0x02, 0x01},
+            4,
+            STATUS_SUCCESS,
+            "",
+            0
+        },
+        /* empty compressed chunk */
+        {
+            {0x00, 0xB0, 0x00},
+            3,
+            STATUS_SUCCESS,
+            "",
+            0
+        },
+        /* empty compressed chunk with unused bits != 0 */
+        {
+            {0x00, 0xB0, 0x01},
+            3,
+            STATUS_SUCCESS,
+            "",
+            0
+        },
+        /* empty input buffer */
+        {
+            {0},
+            0,
+            STATUS_BAD_COMPRESSION_BUFFER,
+        },
+        /* incomplete chunk header */
+        {
+            {0x01},
+            1,
+            STATUS_BAD_COMPRESSION_BUFFER
+        },
+        /* incomplete chunk header */
+        {
+            {0x00, 0x30},
+            2,
+            STATUS_BAD_COMPRESSION_BUFFER
+        },
+        /* compressed chunk with invalid backwards reference */
+        {
+            {0x06, 0xB0, 0x10, 'W', 'i', 'n', 'e', 0x05, 0x40},
+            9,
+            STATUS_BAD_COMPRESSION_BUFFER
+        },
+        /* compressed chunk with incomplete backwards reference */
+        {
+            {0x05, 0xB0, 0x10, 'W', 'i', 'n', 'e', 0x05},
+            8,
+            STATUS_BAD_COMPRESSION_BUFFER
+        },
+        /* incomplete uncompressed chunk */
+        {
+            {0x07, 0x30, 'W', 'i', 'n', 'e'},
+            6,
+            STATUS_BAD_COMPRESSION_BUFFER
+        },
+        /* incomplete compressed chunk */
+        {
+            {0x08, 0xB0, 0x00, 'W', 'i', 'n', 'e'},
+            7,
+            STATUS_BAD_COMPRESSION_BUFFER
+        },
+        /* two compressed chunks, the second one incomplete */
+        {
+            {0x00, 0xB0, 0x02, 0x00, 0xB0},
+            5,
+            STATUS_BAD_COMPRESSION_BUFFER,
+        }
+    };
+
+    static UCHAR buf[0x2000], workspace[0x1000];
+    NTSTATUS status, expected_status;
+    ULONG final_size;
+    int i;
+
+    if (!pRtlDecompressBuffer || !pRtlDecompressFragment)
+    {
+        win_skip("RtlDecompressBuffer or RtlDecompressFragment is not available\n");
+        return;
+    }
+
+    /* test compression format / engine */
+    final_size = 0xdeadbeef;
+    status = pRtlDecompressBuffer(COMPRESSION_FORMAT_NONE, buf, sizeof(buf), test_lznt[0].compressed,
+                                  test_lznt[0].compressed_size, &final_size);
+    ok(status == STATUS_INVALID_PARAMETER, "got wrong status 0x%08x\n", status);
+    ok(final_size == 0xdeadbeef, "got wrong final_size %u\n", final_size);
+
+    final_size = 0xdeadbeef;
+    status = pRtlDecompressBuffer(COMPRESSION_FORMAT_DEFAULT, buf, sizeof(buf), test_lznt[0].compressed,
+                                  test_lznt[0].compressed_size, &final_size);
+    ok(status == STATUS_INVALID_PARAMETER, "got wrong status 0x%08x\n", status);
+    ok(final_size == 0xdeadbeef, "got wrong final_size %u\n", final_size);
+
+    final_size = 0xdeadbeef;
+    status = pRtlDecompressBuffer(0xFF, buf, sizeof(buf), test_lznt[0].compressed,
+                                  test_lznt[0].compressed_size, &final_size);
+    ok(status == STATUS_UNSUPPORTED_COMPRESSION, "got wrong status 0x%08x\n", status);
+    ok(final_size == 0xdeadbeef, "got wrong final_size %u\n", final_size);
+
+    /* regular tests for RtlDecompressBuffer */
+    for (i = 0; i < sizeof(test_lznt) / sizeof(test_lznt[0]); i++)
+    {
+        trace("Running test %d (compressed_size=%u, uncompressed_size=%u, status=0x%08x)\n",
+              i, test_lznt[i].compressed_size, test_lznt[i].uncompressed_size, test_lznt[i].status);
+
+        /* test with very big buffer */
+        final_size = 0xdeadbeef;
+        memset(buf, 0x11, sizeof(buf));
+        status = pRtlDecompressBuffer(COMPRESSION_FORMAT_LZNT1, buf, sizeof(buf), test_lznt[i].compressed,
+                                      test_lznt[i].compressed_size, &final_size);
+        ok(status == test_lznt[i].status || broken(status == STATUS_BAD_COMPRESSION_BUFFER &&
+           (test_lznt[i].broken_flags & DECOMPRESS_BROKEN_FRAGMENT)), "%d: got wrong status 0x%08x\n", i, status);
+        if (!status)
+        {
+            ok(final_size == test_lznt[i].uncompressed_size,
+               "%d: got wrong final_size %u\n", i, final_size);
+            ok(!memcmp(buf, test_lznt[i].uncompressed, test_lznt[i].uncompressed_size),
+               "%d: got wrong decoded data\n", i);
+            ok(buf[test_lznt[i].uncompressed_size] == 0x11,
+               "%d: buf[%u] was modified\n", i, test_lznt[i].uncompressed_size);
+        }
+
+        /* test that modifier for compression engine is ignored */
+        final_size = 0xdeadbeef;
+        memset(buf, 0x11, sizeof(buf));
+        status = pRtlDecompressBuffer(COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_MAXIMUM, buf, sizeof(buf),
+                                      test_lznt[i].compressed, test_lznt[i].compressed_size, &final_size);
+        ok(status == test_lznt[i].status || broken(status == STATUS_BAD_COMPRESSION_BUFFER &&
+           (test_lznt[i].broken_flags & DECOMPRESS_BROKEN_FRAGMENT)), "%d: got wrong status 0x%08x\n", i, status);
+        if (!status)
+        {
+            ok(final_size == test_lznt[i].uncompressed_size,
+               "%d: got wrong final_size %u\n", i, final_size);
+            ok(!memcmp(buf, test_lznt[i].uncompressed, test_lznt[i].uncompressed_size),
+               "%d: got wrong decoded data\n", i);
+            ok(buf[test_lznt[i].uncompressed_size] == 0x11,
+               "%d: buf[%u] was modified\n", i, test_lznt[i].uncompressed_size);
+        }
+
+        /* test with expected output size */
+        if (test_lznt[i].uncompressed_size > 0)
+        {
+            final_size = 0xdeadbeef;
+            memset(buf, 0x11, sizeof(buf));
+            status = pRtlDecompressBuffer(COMPRESSION_FORMAT_LZNT1, buf, test_lznt[i].uncompressed_size,
+                                          test_lznt[i].compressed, test_lznt[i].compressed_size, &final_size);
+            ok(status == test_lznt[i].status, "%d: got wrong status 0x%08x\n", i, status);
+            if (!status)
+            {
+                ok(final_size == test_lznt[i].uncompressed_size,
+                   "%d: got wrong final_size %u\n", i, final_size);
+                ok(!memcmp(buf, test_lznt[i].uncompressed, test_lznt[i].uncompressed_size),
+                   "%d: got wrong decoded data\n", i);
+                ok(buf[test_lznt[i].uncompressed_size] == 0x11,
+                   "%d: buf[%u] was modified\n", i, test_lznt[i].uncompressed_size);
+            }
+        }
+
+        /* test with smaller output size */
+        if (test_lznt[i].uncompressed_size > 1)
+        {
+            final_size = 0xdeadbeef;
+            memset(buf, 0x11, sizeof(buf));
+            status = pRtlDecompressBuffer(COMPRESSION_FORMAT_LZNT1, buf, test_lznt[i].uncompressed_size - 1,
+                                          test_lznt[i].compressed, test_lznt[i].compressed_size, &final_size);
+            if (test_lznt[i].broken_flags & DECOMPRESS_BROKEN_TRUNCATED)
+                todo_wine
+                ok(status == STATUS_BAD_COMPRESSION_BUFFER, "%d: got wrong status 0x%08x\n", i, status);
+            else
+                ok(status == test_lznt[i].status, "%d: got wrong status 0x%08x\n", i, status);
+            if (!status)
+            {
+                ok(final_size == test_lznt[i].uncompressed_size - 1,
+                   "%d: got wrong final_size %u\n", i, final_size);
+                ok(!memcmp(buf, test_lznt[i].uncompressed, test_lznt[i].uncompressed_size - 1),
+                   "%d: got wrong decoded data\n", i);
+                ok(buf[test_lznt[i].uncompressed_size - 1] == 0x11,
+                   "%d: buf[%u] was modified\n", i, test_lznt[i].uncompressed_size - 1);
+            }
+        }
+
+        /* test with zero output size */
+        final_size = 0xdeadbeef;
+        memset(buf, 0x11, sizeof(buf));
+        status = pRtlDecompressBuffer(COMPRESSION_FORMAT_LZNT1, buf, 0, test_lznt[i].compressed,
+                                      test_lznt[i].compressed_size, &final_size);
+        if (is_incomplete_chunk(test_lznt[i].compressed, test_lznt[i].compressed_size, FALSE))
+            ok(status == STATUS_BAD_COMPRESSION_BUFFER, "%d: got wrong status 0x%08x\n", i, status);
+        else
+        {
+            ok(status == STATUS_SUCCESS, "%d: got wrong status 0x%08x\n", i, status);
+            ok(final_size == 0, "%d: got wrong final_size %u\n", i, final_size);
+            ok(buf[0] == 0x11, "%d: buf[0] was modified\n", i);
+        }
+
+        /* test RtlDecompressFragment with offset = 0 */
+        final_size = 0xdeadbeef;
+        memset(buf, 0x11, sizeof(buf));
+        status = pRtlDecompressFragment(COMPRESSION_FORMAT_LZNT1, buf, sizeof(buf), test_lznt[i].compressed,
+                                        test_lznt[i].compressed_size, 0, &final_size, workspace);
+        if (test_lznt[i].broken_flags & DECOMPRESS_BROKEN_FRAGMENT)
+            todo_wine
+            ok(status == STATUS_BAD_COMPRESSION_BUFFER, "%d: got wrong status 0x%08x\n", i, status);
+        else
+            ok(status == test_lznt[i].status, "%d: got wrong status 0x%08x\n", i, status);
+        if (!status)
+        {
+            ok(final_size == test_lznt[i].uncompressed_size,
+               "%d: got wrong final_size %u\n", i, final_size);
+            ok(!memcmp(buf, test_lznt[i].uncompressed, test_lznt[i].uncompressed_size),
+               "%d: got wrong decoded data\n", i);
+            ok(buf[test_lznt[i].uncompressed_size] == 0x11,
+               "%d: buf[%u] was modified\n", i, test_lznt[i].uncompressed_size);
+        }
+
+        /* test RtlDecompressFragment with offset = 1 */
+        final_size = 0xdeadbeef;
+        memset(buf, 0x11, sizeof(buf));
+        status = pRtlDecompressFragment(COMPRESSION_FORMAT_LZNT1, buf, sizeof(buf), test_lznt[i].compressed,
+                                        test_lznt[i].compressed_size, 1, &final_size, workspace);
+        if (test_lznt[i].broken_flags & DECOMPRESS_BROKEN_FRAGMENT)
+            todo_wine
+            ok(status == STATUS_BAD_COMPRESSION_BUFFER, "%d: got wrong status 0x%08x\n", i, status);
+        else
+            ok(status == test_lznt[i].status, "%d: got wrong status 0x%08x\n", i, status);
+        if (!status)
+        {
+            if (test_lznt[i].uncompressed_size == 0)
+            {
+                todo_wine
+                ok(final_size == 4095, "%d: got wrong final_size %u\n", i, final_size);
+                /* Buffer doesn't contain any useful value on Windows */
+                ok(buf[4095] == 0x11, "%d: buf[4095] was modified\n", i);
+            }
+            else
+            {
+                ok(final_size == test_lznt[i].uncompressed_size - 1,
+                   "%d: got wrong final_size %u\n", i, final_size);
+                ok(!memcmp(buf, test_lznt[i].uncompressed + 1, test_lznt[i].uncompressed_size - 1),
+                   "%d: got wrong decoded data\n", i);
+                ok(buf[test_lznt[i].uncompressed_size - 1] == 0x11,
+                   "%d: buf[%u] was modified\n", i, test_lznt[i].uncompressed_size - 1);
+            }
+        }
+
+        /* test RtlDecompressFragment with offset = 4095 */
+        final_size = 0xdeadbeef;
+        memset(buf, 0x11, sizeof(buf));
+        status = pRtlDecompressFragment(COMPRESSION_FORMAT_LZNT1, buf, sizeof(buf), test_lznt[i].compressed,
+                                        test_lznt[i].compressed_size, 4095, &final_size, workspace);
+        if (test_lznt[i].broken_flags & DECOMPRESS_BROKEN_FRAGMENT)
+            todo_wine
+            ok(status == STATUS_BAD_COMPRESSION_BUFFER, "%d: got wrong status 0x%08x\n", i, status);
+        else
+            ok(status == test_lznt[i].status, "%d: got wrong status 0x%08x\n", i, status);
+        if (!status)
+        {
+            todo_wine
+            ok(final_size == 1, "%d: got wrong final_size %u\n", i, final_size);
+            todo_wine
+            ok(buf[0] == 0, "%d: padding is not zero\n", i);
+            ok(buf[1] == 0x11, "%d: buf[1] was modified\n", i);
+        }
+
+        /* test RtlDecompressFragment with offset = 4096 */
+        final_size = 0xdeadbeef;
+        memset(buf, 0x11, sizeof(buf));
+        status = pRtlDecompressFragment(COMPRESSION_FORMAT_LZNT1, buf, sizeof(buf), test_lznt[i].compressed,
+                                        test_lznt[i].compressed_size, 4096, &final_size, workspace);
+        expected_status = is_incomplete_chunk(test_lznt[i].compressed, test_lznt[i].compressed_size, TRUE) ?
+                          test_lznt[i].status : STATUS_SUCCESS;
+        ok(status == expected_status, "%d: got wrong status 0x%08x, expected 0x%08x\n", i, status, expected_status);
+        if (!status)
+        {
+            ok(final_size == 0, "%d: got wrong final_size %u\n", i, final_size);
+            ok(buf[0] == 0x11, "%d: buf[4096] was modified\n", i);
+        }
+    }
+}
+
+#undef DECOMPRESS_BROKEN_FRAGMENT
+#undef DECOMPRESS_BROKEN_TRUNCATED
+
+struct critsect_locked_info
+{
+    CRITICAL_SECTION crit;
+    HANDLE semaphores[2];
+};
+
+static DWORD WINAPI critsect_locked_thread(void *param)
+{
+    struct critsect_locked_info *info = param;
+    DWORD ret;
+
+    ret = pRtlIsCriticalSectionLocked(&info->crit);
+    ok(ret == TRUE, "expected TRUE, got %u\n", ret);
+    ret = pRtlIsCriticalSectionLockedByThread(&info->crit);
+    ok(ret == FALSE, "expected FALSE, got %u\n", ret);
+
+    ReleaseSemaphore(info->semaphores[0], 1, NULL);
+    ret = WaitForSingleObject(info->semaphores[1], 1000);
+    ok(ret == WAIT_OBJECT_0, "expected WAIT_OBJECT_0, got %u\n", ret);
+
+    ret = pRtlIsCriticalSectionLocked(&info->crit);
+    ok(ret == FALSE, "expected FALSE, got %u\n", ret);
+    ret = pRtlIsCriticalSectionLockedByThread(&info->crit);
+    ok(ret == FALSE, "expected FALSE, got %u\n", ret);
+
+    EnterCriticalSection(&info->crit);
+
+    ret = pRtlIsCriticalSectionLocked(&info->crit);
+    ok(ret == TRUE, "expected TRUE, got %u\n", ret);
+    ret = pRtlIsCriticalSectionLockedByThread(&info->crit);
+    ok(ret == TRUE, "expected TRUE, got %u\n", ret);
+
+    ReleaseSemaphore(info->semaphores[0], 1, NULL);
+    ret = WaitForSingleObject(info->semaphores[1], 1000);
+    ok(ret == WAIT_OBJECT_0, "expected WAIT_OBJECT_0, got %u\n", ret);
+
+    LeaveCriticalSection(&info->crit);
+    return 0;
+}
+
+static void test_RtlIsCriticalSectionLocked(void)
+{
+    struct critsect_locked_info info;
+    HANDLE thread;
+    BOOL ret;
+
+    if (!pRtlIsCriticalSectionLocked || !pRtlIsCriticalSectionLockedByThread)
+    {
+        win_skip("skipping RtlIsCriticalSectionLocked tests, required functions not available\n");
+        return;
+    }
+
+    InitializeCriticalSection(&info.crit);
+    info.semaphores[0] = CreateSemaphoreW(NULL, 0, 1, NULL);
+    ok(info.semaphores[0] != NULL, "CreateSemaphore failed with %u\n", GetLastError());
+    info.semaphores[1] = CreateSemaphoreW(NULL, 0, 1, NULL);
+    ok(info.semaphores[1] != NULL, "CreateSemaphore failed with %u\n", GetLastError());
+
+    ret = pRtlIsCriticalSectionLocked(&info.crit);
+    ok(ret == FALSE, "expected FALSE, got %u\n", ret);
+    ret = pRtlIsCriticalSectionLockedByThread(&info.crit);
+    ok(ret == FALSE, "expected FALSE, got %u\n", ret);
+
+    EnterCriticalSection(&info.crit);
+
+    ret = pRtlIsCriticalSectionLocked(&info.crit);
+    ok(ret == TRUE, "expected TRUE, got %u\n", ret);
+    ret = pRtlIsCriticalSectionLockedByThread(&info.crit);
+    ok(ret == TRUE, "expected TRUE, got %u\n", ret);
+
+    thread = CreateThread(NULL, 0, critsect_locked_thread, &info, 0, NULL);
+    ok(thread != NULL, "CreateThread failed with %u\n", GetLastError());
+    ret = WaitForSingleObject(info.semaphores[0], 1000);
+    ok(ret == WAIT_OBJECT_0, "expected WAIT_OBJECT_0, got %u\n", ret);
+
+    LeaveCriticalSection(&info.crit);
+
+    ReleaseSemaphore(info.semaphores[1], 1, NULL);
+    ret = WaitForSingleObject(info.semaphores[0], 1000);
+    ok(ret == WAIT_OBJECT_0, "expected WAIT_OBJECT_0, got %u\n", ret);
+
+    ret = pRtlIsCriticalSectionLocked(&info.crit);
+    ok(ret == TRUE, "expected TRUE, got %u\n", ret);
+    ret = pRtlIsCriticalSectionLockedByThread(&info.crit);
+    ok(ret == FALSE, "expected FALSE, got %u\n", ret);
+
+    ReleaseSemaphore(info.semaphores[1], 1, NULL);
+    ret = WaitForSingleObject(thread, 1000);
+    ok(ret == WAIT_OBJECT_0, "expected WAIT_OBJECT_0, got %u\n", ret);
+
+    CloseHandle(thread);
+    CloseHandle(info.semaphores[0]);
+    CloseHandle(info.semaphores[1]);
+    DeleteCriticalSection(&info.crit);
+}
+
 START_TEST(rtl)
 {
     InitFunctionPtrs();
 
+#ifdef __i386__
+    test_WinSqm();
+#else
+    skip("stdcall-style parameter checks are not supported on this platform.\n");
+#endif
+
     test_RtlCompareMemory();
     test_RtlCompareMemoryUlong();
     test_RtlMoveMemory();
@@ -1631,6 +3195,15 @@ START_TEST(rtl)
     test_RtlIpv4AddressToString();
     test_RtlIpv4AddressToStringEx();
     test_RtlIpv4StringToAddress();
+    test_RtlIpv4StringToAddressEx();
+    test_RtlIpv6AddressToString();
+    test_RtlIpv6AddressToStringEx();
+    test_RtlIpv6StringToAddress();
+    test_RtlIpv6StringToAddressEx();
     test_LdrAddRefDll();
     test_LdrLockLoaderLock();
+    test_RtlCompressBuffer();
+    test_RtlGetCompressionWorkSpaceSize();
+    test_RtlDecompressBuffer();
+    test_RtlIsCriticalSectionLocked();
 }