[WIN32K:NTUSER] Add a temporary winsta/desktop-connection hack for CSRSS/USERSRV...
[reactos.git] / win32ss / user / winsrv / usersrv / harderror.c
index 19a8db6..6ff336a 100644 (file)
 #include <undocelfapi.h>
 #include <ntstrsafe.h>
 
+#include "resource.h"
+
 #define NDEBUG
 #include <debug.h>
 
-#define IDTRYAGAIN 10
-#define IDCONTINUE 11
 
 /* FUNCTIONS ******************************************************************/
 
+/* Cache for the localized hard-error message box strings */
+LANGID g_CurrentUserLangId = 0;
+UNICODE_STRING g_SuccessU = {0, 0, NULL};
+UNICODE_STRING g_InformationU = {0, 0, NULL};
+UNICODE_STRING g_WarningU = {0, 0, NULL};
+UNICODE_STRING g_ErrorU = {0, 0, NULL};
+UNICODE_STRING g_SystemProcessU = {0, 0, NULL};
+UNICODE_STRING g_OKTerminateU = {0, 0, NULL};
+UNICODE_STRING g_CancelDebugU = {0, 0, NULL};
+
+VOID
+RtlLoadUnicodeString(
+    IN HINSTANCE hInstance OPTIONAL,
+    IN UINT uID,
+    OUT PUNICODE_STRING pUnicodeString,
+    IN PCWSTR pDefaultString)
+{
+    UINT Length;
+
+    /* Try to load the string from the resource */
+    Length = LoadStringW(hInstance, uID, (LPWSTR)&pUnicodeString->Buffer, 0);
+    if (Length == 0)
+    {
+        /* If the resource string was not found, use the fallback default one */
+        RtlInitUnicodeString(pUnicodeString, pDefaultString);
+    }
+    else
+    {
+        /* Set the string length (not NULL-terminated!) */
+        pUnicodeString->MaximumLength = (USHORT)(Length * sizeof(WCHAR));
+        pUnicodeString->Length = pUnicodeString->MaximumLength;
+    }
+}
+
+
+/*
+ * NOTE: _scwprintf() is NOT exported by ntdll.dll,
+ * only _vscwprintf() is, so we need to implement it here.
+ * Code comes from sdk/lib/crt/printf/_scwprintf.c .
+ */
+int
+__cdecl
+_scwprintf(
+    const wchar_t *format,
+    ...)
+{
+    int len;
+    va_list args;
+
+    va_start(args, format);
+    len = _vscwprintf(format, args);
+    va_end(args);
+
+    return len;
+}
+
+
 /* FIXME */
 int
 WINAPI
@@ -38,6 +95,146 @@ MessageBoxTimeoutW(
     DWORD dwTime);
 
 
+static
+VOID
+UserpCaptureStringParameters(
+    OUT PULONG_PTR Parameters,
+    OUT PULONG SizeOfAllUnicodeStrings,
+    IN PHARDERROR_MSG Message,
+    IN HANDLE hProcess OPTIONAL)
+{
+    NTSTATUS Status;
+    ULONG nParam, Size = 0;
+    UNICODE_STRING TempStringU, ParamStringU;
+    ANSI_STRING TempStringA;
+
+    if (SizeOfAllUnicodeStrings)
+        *SizeOfAllUnicodeStrings = 0;
+
+    /* Read all strings from client space */
+    for (nParam = 0; nParam < Message->NumberOfParameters; ++nParam)
+    {
+        Parameters[nParam] = 0;
+
+        /* Check if the current parameter is a unicode string */
+        if (Message->UnicodeStringParameterMask & (1 << nParam))
+        {
+            /* Skip this string if we do not have a client process */
+            if (!hProcess)
+                continue;
+
+            /* Read the UNICODE_STRING from the process memory */
+            Status = NtReadVirtualMemory(hProcess,
+                                         (PVOID)Message->Parameters[nParam],
+                                         &ParamStringU,
+                                         sizeof(ParamStringU),
+                                         NULL);
+            if (!NT_SUCCESS(Status))
+            {
+                /* We failed, skip this string */
+                DPRINT1("NtReadVirtualMemory(Message->Parameters) failed, Status 0x%lx, skipping.\n", Status);
+                continue;
+            }
+
+            /* Allocate a buffer for the string and reserve a NULL terminator */
+            TempStringU.MaximumLength = ParamStringU.Length + sizeof(UNICODE_NULL);
+            TempStringU.Length = ParamStringU.Length;
+            TempStringU.Buffer = RtlAllocateHeap(RtlGetProcessHeap(),
+                                                 HEAP_ZERO_MEMORY,
+                                                 TempStringU.MaximumLength);
+            if (!TempStringU.Buffer)
+            {
+                /* We failed, skip this string */
+                DPRINT1("Cannot allocate memory with size %u, skipping.\n", TempStringU.MaximumLength);
+                continue;
+            }
+
+            /* Read the string buffer from the process memory */
+            Status = NtReadVirtualMemory(hProcess,
+                                         ParamStringU.Buffer,
+                                         TempStringU.Buffer,
+                                         ParamStringU.Length,
+                                         NULL);
+            if (!NT_SUCCESS(Status))
+            {
+                /* We failed, skip this string */
+                DPRINT1("NtReadVirtualMemory(ParamStringU) failed, Status 0x%lx, skipping.\n", Status);
+                RtlFreeHeap(RtlGetProcessHeap(), 0, TempStringU.Buffer);
+                continue;
+            }
+            /* NULL-terminate the string */
+            TempStringU.Buffer[TempStringU.Length / sizeof(WCHAR)] = UNICODE_NULL;
+
+            DPRINT("ParamString = \'%wZ\'\n", &TempStringU);
+
+            if (Message->Status == STATUS_SERVICE_NOTIFICATION)
+            {
+                /* Just keep the allocated NULL-terminated UNICODE string */
+                Parameters[nParam] = (ULONG_PTR)TempStringU.Buffer;
+                Size += TempStringU.Length;
+            }
+            else
+            {
+                /* Allocate a buffer for conversion to ANSI string */
+                TempStringA.MaximumLength = (USHORT)RtlUnicodeStringToAnsiSize(&TempStringU);
+                TempStringA.Buffer = RtlAllocateHeap(RtlGetProcessHeap(),
+                                                     HEAP_ZERO_MEMORY,
+                                                     TempStringA.MaximumLength);
+                if (!TempStringA.Buffer)
+                {
+                    /* We failed, skip this string */
+                    DPRINT1("Cannot allocate memory with size %u, skipping.\n", TempStringA.MaximumLength);
+                    RtlFreeHeap(RtlGetProcessHeap(), 0, TempStringU.Buffer);
+                    continue;
+                }
+
+                /* Convert string to ANSI and free temporary buffer */
+                Status = RtlUnicodeStringToAnsiString(&TempStringA, &TempStringU, FALSE);
+                RtlFreeHeap(RtlGetProcessHeap(), 0, TempStringU.Buffer);
+                if (!NT_SUCCESS(Status))
+                {
+                    /* We failed, skip this string */
+                    DPRINT1("RtlUnicodeStringToAnsiString() failed, Status 0x%lx, skipping.\n", Status);
+                    RtlFreeHeap(RtlGetProcessHeap(), 0, TempStringA.Buffer);
+                    continue;
+                }
+
+                /* Note: RtlUnicodeStringToAnsiString() returns a NULL-terminated string */
+                Parameters[nParam] = (ULONG_PTR)TempStringA.Buffer;
+                Size += TempStringU.Length;
+            }
+        }
+        else
+        {
+            /* It's not a unicode string, just copy the parameter */
+            Parameters[nParam] = Message->Parameters[nParam];
+        }
+    }
+
+    if (SizeOfAllUnicodeStrings)
+        *SizeOfAllUnicodeStrings = Size;
+}
+
+static
+VOID
+UserpFreeStringParameters(
+    IN OUT PULONG_PTR Parameters,
+    IN PHARDERROR_MSG Message)
+{
+    ULONG nParam;
+
+    /* Loop all parameters */
+    for (nParam = 0; nParam < Message->NumberOfParameters; ++nParam)
+    {
+        /* Check if the current parameter is a string */
+        if ((Message->UnicodeStringParameterMask & (1 << nParam)) && (Parameters[nParam] != 0))
+        {
+            /* Free the string buffer */
+            RtlFreeHeap(RtlGetProcessHeap(), 0, (PVOID)Parameters[nParam]);
+        }
+    }
+}
+
 static
 NTSTATUS
 UserpGetClientFileName(
@@ -123,7 +320,7 @@ UserpGetClientFileName(
         return Status;
     }
 
-    ClientFileNameU->Length = wcslen(ClientFileNameU->Buffer) * sizeof(WCHAR);
+    ClientFileNameU->Length = (USHORT)(wcslen(ClientFileNameU->Buffer) * sizeof(WCHAR));
     DPRINT("ClientFileNameU = \'%wZ\'\n", &ClientFileNameU);
 
     return STATUS_SUCCESS;
@@ -131,184 +328,195 @@ UserpGetClientFileName(
 
 static
 VOID
-UserpFreeStringParameters(
-    IN OUT PULONG_PTR Parameters,
-    IN PHARDERROR_MSG HardErrorMessage)
+UserpDuplicateParamStringToUnicodeString(
+    IN OUT PUNICODE_STRING UnicodeString,
+    IN PCWSTR ParamString)
 {
-    ULONG nParam;
+    UNICODE_STRING FormatU, TempStringU;
 
-    /* Loop all parameters */
-    for (nParam = 0; nParam < HardErrorMessage->NumberOfParameters; nParam++)
+    /* Calculate buffer length for the text message */
+    RtlInitUnicodeString(&FormatU, (PWSTR)ParamString);
+    if (UnicodeString->MaximumLength < FormatU.MaximumLength)
     {
-        /* Check if the current parameter is a string */
-        if ((HardErrorMessage->UnicodeStringParameterMask & (1 << nParam)) && (Parameters[nParam] != 0))
+        /* Duplicate the text message in a larger buffer */
+        if (NT_SUCCESS(RtlDuplicateUnicodeString(RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE,
+                                                 &FormatU, &TempStringU)))
         {
-            /* Free the string buffer */
-            RtlFreeHeap(RtlGetProcessHeap(), 0, (PVOID)Parameters[nParam]);
+            *UnicodeString = TempStringU;
+        }
+        else
+        {
+            /* We could not allocate a larger buffer; continue using the smaller original buffer */
+            DPRINT1("Cannot allocate memory for UnicodeString, use original buffer.\n");
+
+            /* Copy the truncated string, NULL-terminate it */
+            FormatU.MaximumLength = UnicodeString->MaximumLength;
+            FormatU.Length = FormatU.MaximumLength - sizeof(UNICODE_NULL);
+            RtlCopyUnicodeString(UnicodeString, &FormatU);
         }
     }
+    else
+    {
+        /* Copy the string, NULL-terminate it */
+        RtlCopyUnicodeString(UnicodeString, &FormatU);
+    }
 }
 
 static
-NTSTATUS
-UserpCaptureStringParameters(
-    OUT PULONG_PTR Parameters,
-    OUT PULONG SizeOfAllUnicodeStrings,
-    IN PHARDERROR_MSG HardErrorMessage,
-    IN HANDLE hProcess OPTIONAL)
+VOID
+UserpFormatMessages(
+    IN OUT PUNICODE_STRING TextStringU,
+    IN OUT PUNICODE_STRING CaptionStringU,
+    OUT PUINT pdwType,
+    OUT PULONG pdwTimeout,
+    IN PHARDERROR_MSG Message)
 {
-    NTSTATUS Status = STATUS_SUCCESS;
-    ULONG nParam, Size = 0;
-    UNICODE_STRING TempStringU, ParamStringU;
-    ANSI_STRING TempStringA;
+    /* Special hardcoded messages */
+    static const PCWSTR pszUnknownHardError =
+        L"Unknown Hard Error 0x%08lx\n"
+        L"Parameters: 0x%p 0x%p 0x%p 0x%p";
+    static const PCWSTR pszExceptionHardError =
+        L"Exception processing message 0x%08lx\n"
+        L"Parameters: 0x%p 0x%p 0x%p 0x%p";
 
-    if (SizeOfAllUnicodeStrings)
-        *SizeOfAllUnicodeStrings = 0;
+    NTSTATUS Status;
+    OBJECT_ATTRIBUTES ObjectAttributes;
+    HANDLE hProcess;
+    ULONG Severity = (ULONG)(Message->Status) >> 30;
+    ULONG SizeOfStrings;
+    ULONG_PTR Parameters[MAXIMUM_HARDERROR_PARAMETERS] = {0};
+    ULONG_PTR CopyParameters[MAXIMUM_HARDERROR_PARAMETERS];
+    UNICODE_STRING WindowTitleU, FileNameU, TempStringU, FormatU, Format2U;
+    ANSI_STRING FormatA, Format2A;
+    HWND hwndOwner;
+    PMESSAGE_RESOURCE_ENTRY MessageResource;
+    PWSTR FormatString, pszBuffer;
+    size_t cszBuffer;
 
-    /* Read all strings from client space */
-    for (nParam = 0; nParam < HardErrorMessage->NumberOfParameters; nParam++)
+    /* Open client process */
+    InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL);
+    Status = NtOpenProcess(&hProcess,
+                           PROCESS_VM_READ | PROCESS_QUERY_INFORMATION,
+                           &ObjectAttributes,
+                           &Message->h.ClientId);
+    if (!NT_SUCCESS(Status))
     {
-        Parameters[nParam] = 0;
-
-        /* Check if the current parameter is a unicode string */
-        if (HardErrorMessage->UnicodeStringParameterMask & (1 << nParam))
-        {
-            /* Skip this string if we do not have a client process */
-            if (!hProcess)
-                continue;
-
-            /* Read the UNICODE_STRING from the process memory */
-            Status = NtReadVirtualMemory(hProcess,
-                                         (PVOID)HardErrorMessage->Parameters[nParam],
-                                         &ParamStringU,
-                                         sizeof(ParamStringU),
-                                         NULL);
-            if (!NT_SUCCESS(Status))
-            {
-                /* We failed, skip this string */
-                DPRINT1("NtReadVirtualMemory(HardErrorMessage->Parameters) failed, Status 0x%lx\n", Status);
-                continue;
-            }
-
-            /* Allocate a buffer for the string */
-            TempStringU.MaximumLength = ParamStringU.Length;
-            TempStringU.Length = ParamStringU.Length;
-            TempStringU.Buffer = RtlAllocateHeap(RtlGetProcessHeap(),
-                                                 HEAP_ZERO_MEMORY,
-                                                 TempStringU.MaximumLength);
-            if (!TempStringU.Buffer)
-            {
-                /* We failed, skip this string */
-                DPRINT1("Cannot allocate memory with size %u\n", TempStringU.MaximumLength);
-                Status = STATUS_NO_MEMORY;
-                continue;
-            }
+        DPRINT1("NtOpenProcess failed with status 0x%08lx, possibly SYSTEM process.\n", Status);
+        hProcess = NULL;
+    }
 
-            /* Read the string buffer from the process memory */
-            Status = NtReadVirtualMemory(hProcess,
-                                         ParamStringU.Buffer,
-                                         TempStringU.Buffer,
-                                         ParamStringU.Length,
-                                         NULL);
-            if (!NT_SUCCESS(Status))
-            {
-                /* We failed, skip this string */
-                DPRINT1("NtReadVirtualMemory(ParamStringU) failed, Status 0x%lx\n", Status);
-                RtlFreeHeap(RtlGetProcessHeap(), 0, TempStringU.Buffer);
-                continue;
-            }
+    /* Capture all string parameters from the process memory */
+    UserpCaptureStringParameters(Parameters, &SizeOfStrings, Message, hProcess);
 
-            DPRINT("ParamString = \'%wZ\'\n", &TempStringU);
+    /* Initialize the output strings */
+    TextStringU->Length = 0;
+    TextStringU->Buffer[0] = UNICODE_NULL;
 
-            /* Allocate a buffer for converted to ANSI string */
-            TempStringA.MaximumLength = RtlUnicodeStringToAnsiSize(&TempStringU);
-            TempStringA.Buffer = RtlAllocateHeap(RtlGetProcessHeap(),
-                                                 HEAP_ZERO_MEMORY,
-                                                 TempStringA.MaximumLength);
-            if (!TempStringA.Buffer)
-            {
-                /* We failed, skip this string */
-                DPRINT1("Cannot allocate memory with size %u\n", TempStringA.MaximumLength);
-                RtlFreeHeap(RtlGetProcessHeap(), 0, TempStringU.Buffer);
-                Status = STATUS_NO_MEMORY;
-                continue;
-            }
+    CaptionStringU->Length = 0;
+    CaptionStringU->Buffer[0] = UNICODE_NULL;
 
-            /* Convert string to ANSI and free temporary buffer */
-            Status = RtlUnicodeStringToAnsiString(&TempStringA, &TempStringU, FALSE);
-            RtlFreeHeap(RtlGetProcessHeap(), 0, TempStringU.Buffer);
-            if (!NT_SUCCESS(Status))
-            {
-                /* We failed, skip this string */
-                RtlFreeHeap(RtlGetProcessHeap(), 0, TempStringA.Buffer);
-                continue;
-            }
+    /*
+     * Check whether it is a service notification, in which case
+     * we format the parameters and take the short route.
+     */
+    if (Message->Status == STATUS_SERVICE_NOTIFICATION)
+    {
+        /* Close the process handle */
+        if (hProcess) NtClose(hProcess);
 
-            /* Note: RtlUnicodeStringToAnsiString returns NULL terminated string */
-            Parameters[nParam] = (ULONG_PTR)TempStringA.Buffer;
-            Size += TempStringU.Length;
-        }
+        /*
+         * Retrieve the message box flags. Note that we filter out
+         * MB_SERVICE_NOTIFICATION to not enter an infinite recursive
+         * loop when we will call MessageBox() later on.
+         */
+        *pdwType = (UINT)Parameters[2] & ~MB_SERVICE_NOTIFICATION;
+
+        /*
+         * Duplicate the UNICODE text message and caption.
+         * If no strings or invalid ones have been provided, keep
+         * the original buffers and reset the string lengths to zero.
+         */
+        if (Message->UnicodeStringParameterMask & 0x1)
+            UserpDuplicateParamStringToUnicodeString(TextStringU, (PCWSTR)Parameters[0]);
+        if (Message->UnicodeStringParameterMask & 0x2)
+            UserpDuplicateParamStringToUnicodeString(CaptionStringU, (PCWSTR)Parameters[1]);
+
+        /* Set the timeout */
+        if (Message->NumberOfParameters >= 4)
+            *pdwTimeout = (ULONG)Parameters[3];
         else
-        {
-            /* It's not a unicode string, just copy the parameter */
-            Parameters[nParam] = HardErrorMessage->Parameters[nParam];
-        }
+            *pdwTimeout = INFINITE;
+
+        goto Quit;
     }
 
-#if 0
-    if (!NT_SUCCESS(Status))
+    /* Set the message box type */
+    *pdwType = 0;
+    switch (Message->ValidResponseOptions)
     {
-        UserpFreeStringParameters(Parameters, HardErrorMessage);
-        return Status;
+        case OptionAbortRetryIgnore:
+            *pdwType = MB_ABORTRETRYIGNORE;
+            break;
+        case OptionOk:
+            *pdwType = MB_OK;
+            break;
+        case OptionOkCancel:
+            *pdwType = MB_OKCANCEL;
+            break;
+        case OptionRetryCancel:
+            *pdwType = MB_RETRYCANCEL;
+            break;
+        case OptionYesNo:
+            *pdwType = MB_YESNO;
+            break;
+        case OptionYesNoCancel:
+            *pdwType = MB_YESNOCANCEL;
+            break;
+        case OptionShutdownSystem:
+            *pdwType = MB_OK;
+            break;
+        case OptionOkNoWait:
+            *pdwType = MB_OK;
+            break;
+        case OptionCancelTryContinue:
+            *pdwType = MB_CANCELTRYCONTINUE;
+            break;
     }
-#endif
 
-    if (SizeOfAllUnicodeStrings)
-        *SizeOfAllUnicodeStrings = Size;
+    /* Set the severity icon */
+    // STATUS_SEVERITY_SUCCESS
+         if (Severity == STATUS_SEVERITY_INFORMATIONAL) *pdwType |= MB_ICONINFORMATION;
+    else if (Severity == STATUS_SEVERITY_WARNING)       *pdwType |= MB_ICONWARNING;
+    else if (Severity == STATUS_SEVERITY_ERROR)         *pdwType |= MB_ICONERROR;
 
-    return Status;
-}
+    *pdwType |= MB_SYSTEMMODAL | MB_SETFOREGROUND;
 
-static
-NTSTATUS
-UserpFormatMessages(
-    OUT PUNICODE_STRING TextStringU,
-    OUT PUNICODE_STRING CaptionStringU,
-    IN  PULONG_PTR Parameters,
-    IN  ULONG SizeOfStrings,
-    IN  PHARDERROR_MSG Message,
-    IN  HANDLE hProcess OPTIONAL)
-{
-    NTSTATUS Status;
-    UNICODE_STRING FileNameU, TempStringU, FormatU, Format2U;
-    ANSI_STRING FormatA, Format2A;
-    PMESSAGE_RESOURCE_ENTRY MessageResource;
-    ULONG_PTR CapturedParameters[MAXIMUM_HARDERROR_PARAMETERS];
-    PWSTR FormatString;
-    ULONG ExceptionCode, Severity;
-    ULONG Size;
+    /* Set the timeout */
+    *pdwTimeout = INFINITE;
 
     /* Copy the Parameters array locally */
-    RtlCopyMemory(&CapturedParameters, Parameters, sizeof(CapturedParameters));
+    RtlCopyMemory(&CopyParameters, Parameters, sizeof(CopyParameters));
 
     /* Get the file name of the client process */
     Status = STATUS_SUCCESS;
     if (hProcess)
         Status = UserpGetClientFileName(&FileNameU, hProcess);
 
+    /* Close the process handle but keep its original value to know where stuff came from */
+    if (hProcess) NtClose(hProcess);
+
     /*
      * Fall back to SYSTEM process if the client process handle
      * was NULL or we failed retrieving a file name.
      */
     if (!hProcess || !NT_SUCCESS(Status) || !FileNameU.Buffer)
     {
-        RtlInitUnicodeString(&FileNameU, L"System Process");
-        hProcess = NULL;
+        hProcess  = NULL;
+        FileNameU = g_SystemProcessU;
     }
 
-    Severity = (ULONG)(Message->Status) >> 30;
-
-    /* Get text string of the error code */
+    /* Retrieve the description of the error code */
+    FormatA.Buffer = NULL;
     Status = RtlFindMessage(GetModuleHandleW(L"ntdll"),
                             (ULONG_PTR)RT_MESSAGETABLE,
                             LANG_NEUTRAL,
@@ -323,86 +531,133 @@ UserpFormatMessages(
         }
         else
         {
-            RtlInitAnsiString(&FormatA, (PCHAR)MessageResource->Text);
-            RtlAnsiStringToUnicodeString(&FormatU, &FormatA, TRUE);
+            RtlInitAnsiString(&FormatA, (PSTR)MessageResource->Text);
+            /* Status = */ RtlAnsiStringToUnicodeString(&FormatU, &FormatA, TRUE);
         }
+        ASSERT(FormatU.Buffer);
     }
     else
     {
-        /* Fall back to hardcoded value */
-        RtlInitUnicodeString(&FormatU, L"Unknown Hard Error");
+        /*
+         * Fall back to unknown hard error format string.
+         * NOTE: The value used here is ReactOS-specific: it allows specifying
+         * the exact hard error status value and the parameters, contrary to
+         * the one on Windows that only says: "Unknown Hard Error".
+         */
+        RtlInitEmptyUnicodeString(&FormatU, NULL, 0);
         FormatA.Buffer = NULL;
     }
 
     FormatString = FormatU.Buffer;
 
-    /* Check whether a caption exists */
-    if (FormatString[0] == L'{')
+    /* Check whether a caption is specified in the format string */
+    if (FormatString && FormatString[0] == L'{')
     {
         /* Set caption start */
         TempStringU.Buffer = ++FormatString;
 
-        /* Get size of the caption */
-        for (Size = 0; *FormatString != UNICODE_NULL && *FormatString != L'}'; Size++)
-            FormatString++;
+        /* Get the caption size and find where the format string really starts */
+        for (TempStringU.Length = 0;
+             *FormatString != UNICODE_NULL && *FormatString != L'}';
+             ++TempStringU.Length)
+        {
+            ++FormatString;
+        }
 
         /* Skip '}', '\r', '\n' */
         FormatString += 3;
 
-        TempStringU.Length = (USHORT)(Size * sizeof(WCHAR));
+        TempStringU.Length *= sizeof(WCHAR);
         TempStringU.MaximumLength = TempStringU.Length;
     }
     else
     {
-        /* FIXME: Use localized strings! */
-
         if (Severity == STATUS_SEVERITY_SUCCESS)
-            RtlInitUnicodeString(&TempStringU, L"Success");
+            TempStringU = g_SuccessU;
         else if (Severity == STATUS_SEVERITY_INFORMATIONAL)
-            RtlInitUnicodeString(&TempStringU, L"System Information");
+            TempStringU = g_InformationU;
         else if (Severity == STATUS_SEVERITY_WARNING)
-            RtlInitUnicodeString(&TempStringU, L"System Warning");
+            TempStringU = g_WarningU;
         else if (Severity == STATUS_SEVERITY_ERROR)
-            RtlInitUnicodeString(&TempStringU, L"System Error");
+            TempStringU = g_ErrorU;
         else
-            RtlInitEmptyUnicodeString(&TempStringU, NULL, 0);
+            ASSERT(FALSE); // Unexpected, since Severity is only <= 3.
     }
 
-    /* Calculate buffer length for the caption */
-    CaptionStringU->MaximumLength = FileNameU.Length + TempStringU.Length +
-                                    4 * sizeof(WCHAR);
-
-    /* Allocate a buffer for the caption */
-    CaptionStringU->Buffer = RtlAllocateHeap(RtlGetProcessHeap(),
-                                             HEAP_ZERO_MEMORY,
-                                             CaptionStringU->MaximumLength);
-    if (!CaptionStringU->Buffer)
+    /* Retrieve the window title of the client, if it has one */
+    RtlInitEmptyUnicodeString(&WindowTitleU, L"", 0);
+    hwndOwner = NULL;
+    EnumThreadWindows(HandleToUlong(Message->h.ClientId.UniqueThread),
+                      FindTopLevelWnd, (LPARAM)&hwndOwner);
+    if (hwndOwner)
     {
-        DPRINT1("Cannot allocate memory for CaptionStringU\n");
-        Status = STATUS_NO_MEMORY;
+        cszBuffer = GetWindowTextLengthW(hwndOwner);
+        if (cszBuffer != 0)
+        {
+            cszBuffer += 3; // 2 characters for ": " and a NULL terminator.
+            cszBuffer *= sizeof(WCHAR);
+            pszBuffer = RtlAllocateHeap(RtlGetProcessHeap(),
+                                        HEAP_ZERO_MEMORY,
+                                        cszBuffer);
+            if (pszBuffer)
+            {
+                RtlInitEmptyUnicodeString(&WindowTitleU, pszBuffer, (USHORT)cszBuffer);
+                cszBuffer = GetWindowTextW(hwndOwner,
+                                           WindowTitleU.Buffer,
+                                           WindowTitleU.MaximumLength / sizeof(WCHAR));
+                WindowTitleU.Length = (USHORT)(cszBuffer * sizeof(WCHAR));
+                RtlAppendUnicodeToString(&WindowTitleU, L": ");
+            }
+        }
     }
 
-    /* Append the file name, seperator and the caption text */
+    /* Calculate buffer length for the caption */
+    cszBuffer = WindowTitleU.Length + FileNameU.Length + TempStringU.Length +
+                3 * sizeof(WCHAR) + sizeof(UNICODE_NULL);
+    if (CaptionStringU->MaximumLength < cszBuffer)
+    {
+        /* Allocate a larger buffer for the caption */
+        pszBuffer = RtlAllocateHeap(RtlGetProcessHeap(),
+                                    HEAP_ZERO_MEMORY,
+                                    cszBuffer);
+        if (!pszBuffer)
+        {
+            /* We could not allocate a larger buffer; continue using the smaller original buffer */
+            DPRINT1("Cannot allocate memory for CaptionStringU, use original buffer.\n");
+        }
+        else
+        {
+            RtlInitEmptyUnicodeString(CaptionStringU, pszBuffer, (USHORT)cszBuffer);
+        }
+    }
     CaptionStringU->Length = 0;
-    RtlAppendUnicodeStringToString(CaptionStringU, &FileNameU);
-    RtlAppendUnicodeToString(CaptionStringU, L" - ");
-    RtlAppendUnicodeStringToString(CaptionStringU, &TempStringU);
-
-    /* Zero terminate the buffer */
-    CaptionStringU->Buffer[CaptionStringU->Length / sizeof(WCHAR)] = UNICODE_NULL;
+    CaptionStringU->Buffer[0] = UNICODE_NULL;
+
+    /* Build the caption */
+    RtlStringCbPrintfW(CaptionStringU->Buffer,
+                       CaptionStringU->MaximumLength,
+                       L"%wZ%wZ - %wZ",
+                       &WindowTitleU, &FileNameU, &TempStringU);
+    CaptionStringU->Length = (USHORT)(wcslen(CaptionStringU->Buffer) * sizeof(WCHAR));
+
+    /* Free the strings if needed */
+    if (WindowTitleU.Buffer && (WindowTitleU.MaximumLength != 0))
+        RtlFreeUnicodeString(&WindowTitleU);
+    if (hProcess)
+        RtlFreeUnicodeString(&FileNameU);
 
-    /* Free the file name buffer */
-    RtlFreeUnicodeString(&FileNameU);
+    Format2A.Buffer = NULL;
 
-    // FIXME: What is 42 == ??
-    Size = 42;
+    /* If we have an unknown hard error, skip the special cases handling */
+    if (!FormatString)
+        goto BuildMessage;
 
     /* Check if this is an exception message */
     if (Message->Status == STATUS_UNHANDLED_EXCEPTION)
     {
-        ExceptionCode = CapturedParameters[0];
+        ULONG ExceptionCode = CopyParameters[0];
 
-        /* Get text string of the exception code */
+        /* Retrieve the description of the exception code */
         Status = RtlFindMessage(GetModuleHandleW(L"ntdll"),
                                 (ULONG_PTR)RT_MESSAGETABLE,
                                 LANG_NEUTRAL,
@@ -417,163 +672,213 @@ UserpFormatMessages(
             }
             else
             {
-                RtlInitAnsiString(&Format2A, (PCHAR)MessageResource->Text);
-                RtlAnsiStringToUnicodeString(&Format2U, &Format2A, TRUE);
+                RtlInitAnsiString(&Format2A, (PSTR)MessageResource->Text);
+                /* Status = */ RtlAnsiStringToUnicodeString(&Format2U, &Format2A, TRUE);
             }
+            ASSERT(Format2U.Buffer);
 
             /* Handle special cases */
             if (ExceptionCode == STATUS_ACCESS_VIOLATION)
             {
+                /* Use a new FormatString */
                 FormatString = Format2U.Buffer;
-                CapturedParameters[0] = CapturedParameters[1];
-                CapturedParameters[1] = CapturedParameters[3];
-                if (CapturedParameters[2])
-                    CapturedParameters[2] = (ULONG_PTR)L"written";
+                CopyParameters[0] = CopyParameters[1];
+                CopyParameters[1] = CopyParameters[3];
+                if (CopyParameters[2])
+                    CopyParameters[2] = (ULONG_PTR)L"written";
                 else
-                    CapturedParameters[2] = (ULONG_PTR)L"read";
+                    CopyParameters[2] = (ULONG_PTR)L"read";
             }
             else if (ExceptionCode == STATUS_IN_PAGE_ERROR)
             {
+                /* Use a new FormatString */
                 FormatString = Format2U.Buffer;
-                CapturedParameters[0] = CapturedParameters[1];
-                CapturedParameters[1] = CapturedParameters[3];
+                CopyParameters[0] = CopyParameters[1];
+                CopyParameters[1] = CopyParameters[3];
             }
             else
             {
-                PWSTR pTmp;
-
                 /* Keep the existing FormatString */
-                CapturedParameters[2] = CapturedParameters[1];
-                CapturedParameters[1] = CapturedParameters[0];
+                CopyParameters[2] = CopyParameters[1];
+                CopyParameters[1] = CopyParameters[0];
 
-                pTmp = Format2U.Buffer;
-                if (!_wcsnicmp(pTmp, L"{EXCEPTION}", 11))
+                pszBuffer = Format2U.Buffer;
+                if (!_wcsnicmp(pszBuffer, L"{EXCEPTION}", 11))
                 {
                     /*
                      * This is a named exception. Skip the mark and
                      * retrieve the exception name that follows it.
                      */
-                    pTmp += 11;
+                    pszBuffer += 11;
 
                     /* Skip '\r', '\n' */
-                    pTmp += 2;
+                    pszBuffer += 2;
 
-                    CapturedParameters[0] = (ULONG_PTR)pTmp;
+                    CopyParameters[0] = (ULONG_PTR)pszBuffer;
                 }
                 else
                 {
                     /* Fall back to hardcoded value */
-                    CapturedParameters[0] = (ULONG_PTR)L"unknown software exception";
+                    CopyParameters[0] = (ULONG_PTR)L"unknown software exception";
                 }
             }
         }
         else
         {
             /* Fall back to hardcoded value, and keep the existing FormatString */
-            CapturedParameters[2] = CapturedParameters[1];
-            CapturedParameters[1] = CapturedParameters[0];
-            CapturedParameters[0] = (ULONG_PTR)L"unknown software exception";
+            CopyParameters[2] = CopyParameters[1];
+            CopyParameters[1] = CopyParameters[0];
+            CopyParameters[0] = (ULONG_PTR)L"unknown software exception";
         }
+    }
 
-        /* FIXME: Use localized strings! */
-        if (Message->ValidResponseOptions == OptionOk ||
-            Message->ValidResponseOptions == OptionOkCancel)
+BuildMessage:
+    /*
+     * Calculate buffer length for the text message. If FormatString
+     * is NULL this means we have an unknown hard error whose format
+     * string is in FormatU.
+     */
+    cszBuffer = 0;
+    /* Wrap in SEH to protect from invalid string parameters */
+    _SEH2_TRY
+    {
+        if (!FormatString)
         {
-            // Tmp = FormatString + wcslen(FormatString);
-            // *++Tmp = L'\n';
-            // *++Tmp = L'\n';
-            Size += 1 + wcslen(L"Click on OK to terminate the program.");
+            /* Fall back to unknown hard error format string, and use the original parameters */
+            cszBuffer = _scwprintf(pszUnknownHardError,
+                                   Message->Status,
+                                   Parameters[0], Parameters[1],
+                                   Parameters[2], Parameters[3]);
+            cszBuffer *= sizeof(WCHAR);
         }
-        if (Message->ValidResponseOptions == OptionOkCancel)
+        else
         {
-            Size += 1 + wcslen(L"Click on CANCEL to debug the program.");
+            cszBuffer = _scwprintf(FormatString,
+                                   CopyParameters[0], CopyParameters[1],
+                                   CopyParameters[2], CopyParameters[3]);
+            cszBuffer *= sizeof(WCHAR);
+
+            /* Add a description for the dialog buttons */
+            if (Message->Status == STATUS_UNHANDLED_EXCEPTION)
+            {
+                if (Message->ValidResponseOptions == OptionOk ||
+                    Message->ValidResponseOptions == OptionOkCancel)
+                {
+                    /* Reserve space for one newline and the OK-terminate-program string */
+                    cszBuffer += sizeof(WCHAR) + g_OKTerminateU.Length;
+                }
+                if (Message->ValidResponseOptions == OptionOkCancel)
+                {
+                    /* Reserve space for one newline and the CANCEL-debug-program string */
+                    cszBuffer += sizeof(WCHAR) + g_CancelDebugU.Length;
+                }
+            }
         }
     }
+    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+    {
+        /* An exception occurred, use a default string with the original parameters */
+        cszBuffer = _scwprintf(pszExceptionHardError,
+                               Message->Status,
+                               Parameters[0], Parameters[1],
+                               Parameters[2], Parameters[3]);
+        cszBuffer *= sizeof(WCHAR);
+    }
+    _SEH2_END;
 
-    /* Calculate length of text buffer */
-    TextStringU->MaximumLength = FormatU.Length + SizeOfStrings +
-                                 (USHORT)(Size * sizeof(WCHAR)) +
-                                 sizeof(UNICODE_NULL);
+    cszBuffer += SizeOfStrings + sizeof(UNICODE_NULL);
 
-    /* Allocate a buffer for the text */
-    TextStringU->Buffer = RtlAllocateHeap(RtlGetProcessHeap(),
-                                          HEAP_ZERO_MEMORY,
-                                          TextStringU->MaximumLength);
-    if (!TextStringU->Buffer)
+    if (TextStringU->MaximumLength < cszBuffer)
     {
-        DPRINT1("Cannot allocate memory for TextStringU\n");
-        Status = STATUS_NO_MEMORY;
+        /* Allocate a larger buffer for the text message */
+        pszBuffer = RtlAllocateHeap(RtlGetProcessHeap(),
+                                    HEAP_ZERO_MEMORY,
+                                    cszBuffer);
+        if (!pszBuffer)
+        {
+            /* We could not allocate a larger buffer; continue using the smaller original buffer */
+            DPRINT1("Cannot allocate memory for TextStringU, use original buffer.\n");
+        }
+        else
+        {
+            RtlInitEmptyUnicodeString(TextStringU, pszBuffer, (USHORT)cszBuffer);
+        }
     }
+    TextStringU->Length = 0;
+    TextStringU->Buffer[0] = UNICODE_NULL;
 
     /* Wrap in SEH to protect from invalid string parameters */
     _SEH2_TRY
     {
         /* Print the string into the buffer */
-        RtlStringCbPrintfW(TextStringU->Buffer,
-                           TextStringU->MaximumLength,
-                           FormatString,
-                           CapturedParameters[0],
-                           CapturedParameters[1],
-                           CapturedParameters[2],
-                           CapturedParameters[3]);
+        pszBuffer = TextStringU->Buffer;
+        cszBuffer = TextStringU->MaximumLength;
 
-        if (Message->Status == STATUS_UNHANDLED_EXCEPTION)
+        if (!FormatString)
         {
-            /* FIXME: Use localized strings! */
-            if (Message->ValidResponseOptions == OptionOk ||
-                Message->ValidResponseOptions == OptionOkCancel)
-            {
-                // Tmp = FormatString + wcslen(FormatString);
-                // *++Tmp = L'\n';
-                // *++Tmp = L'\n';
-                RtlStringCbCatW(TextStringU->Buffer,
-                                TextStringU->MaximumLength,
-                                L"\n");
-                RtlStringCbCatW(TextStringU->Buffer,
-                                TextStringU->MaximumLength,
-                                L"Click on OK to terminate the program.");
-            }
-            if (Message->ValidResponseOptions == OptionOkCancel)
+            /* Fall back to unknown hard error format string, and use the original parameters */
+            RtlStringCbPrintfW(pszBuffer, cszBuffer,
+                               pszUnknownHardError,
+                               Message->Status,
+                               Parameters[0], Parameters[1],
+                               Parameters[2], Parameters[3]);
+        }
+        else
+        {
+            RtlStringCbPrintfExW(pszBuffer, cszBuffer,
+                                 &pszBuffer, &cszBuffer,
+                                 0,
+                                 FormatString,
+                                 CopyParameters[0], CopyParameters[1],
+                                 CopyParameters[2], CopyParameters[3]);
+
+            /* Add a description for the dialog buttons */
+            if (Message->Status == STATUS_UNHANDLED_EXCEPTION)
             {
-                RtlStringCbCatW(TextStringU->Buffer,
-                                TextStringU->MaximumLength,
-                                L"\n");
-                RtlStringCbCatW(TextStringU->Buffer,
-                                TextStringU->MaximumLength,
-                                L"Click on CANCEL to debug the program.");
+                if (Message->ValidResponseOptions == OptionOk ||
+                    Message->ValidResponseOptions == OptionOkCancel)
+                {
+                    RtlStringCbPrintfExW(pszBuffer, cszBuffer,
+                                         &pszBuffer, &cszBuffer,
+                                         0,
+                                         L"\n%wZ",
+                                         &g_OKTerminateU);
+                }
+                if (Message->ValidResponseOptions == OptionOkCancel)
+                {
+                    RtlStringCbPrintfExW(pszBuffer, cszBuffer,
+                                         &pszBuffer, &cszBuffer,
+                                         0,
+                                         L"\n%wZ",
+                                         &g_CancelDebugU);
+                }
             }
         }
-
-        Status = STATUS_SUCCESS;
     }
     _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
     {
-        /* An exception occurred, use a default string */
+        /* An exception occurred, use a default string with the original parameters */
+        DPRINT1("Exception 0x%08lx occurred while building hard-error message, fall back to default message.\n",
+                _SEH2_GetExceptionCode());
+
         RtlStringCbPrintfW(TextStringU->Buffer,
                            TextStringU->MaximumLength,
-                           L"Exception processing message 0x%08lx\n"
-                           L"Parameters: 0x%p 0x%p 0x%p 0x%p",
+                           pszExceptionHardError,
                            Message->Status,
-                           CapturedParameters[0], CapturedParameters[1],
-                           CapturedParameters[2], CapturedParameters[3]);
-
-        /* Set error and free buffers */
-        // Status = _SEH2_GetExceptionCode();
-        // RtlFreeHeap(RtlGetProcessHeap(), 0, TextStringU->Buffer);
-        // RtlFreeHeap(RtlGetProcessHeap(), 0, CaptionStringU->Buffer);
+                           Parameters[0], Parameters[1],
+                           Parameters[2], Parameters[3]);
     }
     _SEH2_END;
 
-    if (NT_SUCCESS(Status))
-    {
-        TextStringU->Length = wcslen(TextStringU->Buffer) * sizeof(WCHAR);
-    }
+    TextStringU->Length = (USHORT)(wcslen(TextStringU->Buffer) * sizeof(WCHAR));
 
-    /* Free converted Unicode strings if the original format strings were Ansi */
+    /* Free the converted UNICODE strings */
     if (Format2A.Buffer) RtlFreeUnicodeString(&Format2U);
     if (FormatA.Buffer) RtlFreeUnicodeString(&FormatU);
 
-    return Status;
+Quit:
+    /* Free the captured parameters */
+    UserpFreeStringParameters(Parameters, Message);
 }
 
 static ULONG
@@ -625,39 +930,44 @@ GetRegInt(
 }
 
 static BOOL
-UserpShowInformationBalloon(PWSTR Text,
-                            PWSTR Caption,
-                            PHARDERROR_MSG Message)
+UserpShowInformationBalloon(
+    IN PUNICODE_STRING TextStringU,
+    IN PUNICODE_STRING CaptionStringU,
+    IN UINT Type,
+    IN PHARDERROR_MSG Message)
 {
     ULONG ShellErrorMode;
-    HWND hwnd;
+    HWND hWndTaskman;
     COPYDATASTRUCT CopyData;
     PBALLOON_HARD_ERROR_DATA pdata;
     DWORD dwSize, cbTextLen, cbTitleLen;
     PWCHAR pText, pCaption;
-    DWORD ret, dwResult;
+    DWORD ret;
+    DWORD_PTR dwResult;
 
     /* Query the shell error mode value */
     ShellErrorMode = GetRegInt(L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Windows",
                                L"ShellErrorMode", 0);
 
-    /* Make the shell display the hard error message in balloon only if necessary */
+    /* Make the shell display the hard error message only if necessary */
     if (ShellErrorMode != 1)
         return FALSE;
 
-    hwnd = GetTaskmanWindow();
-    if (!hwnd)
+    /* Retrieve the shell task window */
+    hWndTaskman = GetTaskmanWindow();
+    if (!hWndTaskman)
     {
         DPRINT1("Failed to find shell task window (last error %lu)\n", GetLastError());
         return FALSE;
     }
 
-    cbTextLen  = ((Text    ? wcslen(Text)    : 0) + 1) * sizeof(WCHAR);
-    cbTitleLen = ((Caption ? wcslen(Caption) : 0) + 1) * sizeof(WCHAR);
+    cbTextLen  = TextStringU->Length + sizeof(UNICODE_NULL);
+    cbTitleLen = CaptionStringU->Length + sizeof(UNICODE_NULL);
 
     dwSize = sizeof(BALLOON_HARD_ERROR_DATA);
     dwSize += cbTextLen + cbTitleLen;
 
+    /* Build the data buffer */
     pdata = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
     if (!pdata)
     {
@@ -667,102 +977,68 @@ UserpShowInformationBalloon(PWSTR Text,
 
     pdata->cbHeaderSize = sizeof(BALLOON_HARD_ERROR_DATA);
     pdata->Status = Message->Status;
+    pdata->dwType = Type;
 
-    if (NT_SUCCESS(Message->Status))
-        pdata->dwType = MB_OK;
-    else if (Message->Status == STATUS_SERVICE_NOTIFICATION)
-        pdata->dwType = Message->Parameters[2];
-    else
-        pdata->dwType = MB_ICONINFORMATION;
-
-    pdata->TitleOffset = pdata->cbHeaderSize;
-    pdata->MessageOffset = pdata->TitleOffset;
-    pdata->MessageOffset += cbTitleLen;
+    pdata->TitleOffset   = pdata->cbHeaderSize;
+    pdata->MessageOffset = pdata->TitleOffset + cbTitleLen;
     pCaption = (PWCHAR)((ULONG_PTR)pdata + pdata->TitleOffset);
     pText = (PWCHAR)((ULONG_PTR)pdata + pdata->MessageOffset);
-    wcscpy(pCaption, Caption);
-    wcscpy(pText, Text);
+    RtlStringCbCopyNW(pCaption, cbTitleLen, CaptionStringU->Buffer, CaptionStringU->Length);
+    RtlStringCbCopyNW(pText, cbTextLen, TextStringU->Buffer, TextStringU->Length);
 
+    /* Send the message */
+
+    /* Retrieve a unique system-wide message to communicate hard error data with the shell */
     CopyData.dwData = RegisterWindowMessageW(L"HardError");
     CopyData.cbData = dwSize;
     CopyData.lpData = pdata;
 
     dwResult = FALSE;
-
-    ret = SendMessageTimeoutW(hwnd, WM_COPYDATA, 0, (LPARAM)&CopyData,
+    ret = SendMessageTimeoutW(hWndTaskman, WM_COPYDATA, 0, (LPARAM)&CopyData,
                               SMTO_NORMAL | SMTO_ABORTIFHUNG, 3000, &dwResult);
 
+    /* Free the buffer */
     RtlFreeHeap(RtlGetProcessHeap(), 0, pdata);
 
     return (ret && dwResult) ? TRUE : FALSE;
 }
 
 static
-ULONG
+HARDERROR_RESPONSE
 UserpMessageBox(
-    IN PCWSTR Text,
-    IN PCWSTR Caption,
-    IN ULONG ValidResponseOptions,
-    IN ULONG Severity,
+    IN PUNICODE_STRING TextStringU,
+    IN PUNICODE_STRING CaptionStringU,
+    IN UINT  Type,
     IN ULONG Timeout)
 {
-    ULONG Type, MessageBoxResponse;
-
-    /* Set the message box type */
-    switch (ValidResponseOptions)
-    {
-        case OptionAbortRetryIgnore:
-            Type = MB_ABORTRETRYIGNORE;
-            break;
-        case OptionOk:
-            Type = MB_OK;
-            break;
-        case OptionOkCancel:
-            Type = MB_OKCANCEL;
-            break;
-        case OptionRetryCancel:
-            Type = MB_RETRYCANCEL;
-            break;
-        case OptionYesNo:
-            Type = MB_YESNO;
-            break;
-        case OptionYesNoCancel:
-            Type = MB_YESNOCANCEL;
-            break;
-        case OptionShutdownSystem:
-            Type = MB_RETRYCANCEL; // FIXME???
-            break;
-        case OptionOkNoWait:
-            /*
-             * At that point showing the balloon failed. Is that correct?
-             */
-            Type = MB_OK; // FIXME!
-            break;
-        case OptionCancelTryContinue:
-            Type = MB_CANCELTRYCONTINUE;
-            break;
+    ULONG MessageBoxResponse;
+    HDESK hDesk, hOldDesk;
 
-        /* Anything else is invalid */
-        default:
-        {
-            DPRINT1("Unknown ValidResponseOptions = %d\n", ValidResponseOptions);
-            return ResponseNotHandled;
-        }
-    }
+    DPRINT("Text = '%S', Caption = '%S', Type = 0x%lx\n",
+           TextStringU->Buffer, CaptionStringU->Buffer, Type);
 
-    /* Set severity */
-    // STATUS_SEVERITY_SUCCESS
-         if (Severity == STATUS_SEVERITY_INFORMATIONAL) Type |= MB_ICONINFORMATION;
-    else if (Severity == STATUS_SEVERITY_WARNING)       Type |= MB_ICONWARNING;
-    else if (Severity == STATUS_SEVERITY_ERROR)         Type |= MB_ICONERROR;
-
-    Type |= MB_SYSTEMMODAL | MB_SETFOREGROUND;
+    // TEMPORARY HACK to fix desktop assignment for harderror message boxes.
+    hDesk = OpenInputDesktop(0, FALSE, GENERIC_WRITE);
+    if (!hDesk)
+        return ResponseNotHandled;
 
-    DPRINT("Text = '%S', Caption = '%S', Severity = %d, Type = 0x%lx\n",
-           Text, Caption, Severity, Type);
+    /* Assign the desktop to this thread */
+    hOldDesk = GetThreadDesktop(GetCurrentThreadId());
+    if (!SetThreadDesktop(hDesk))
+    {
+        CloseDesktop(hDesk);
+        return ResponseNotHandled;
+    }
 
     /* Display a message box */
-    MessageBoxResponse = MessageBoxTimeoutW(NULL, Text, Caption, Type, 0, Timeout);
+    MessageBoxResponse = MessageBoxTimeoutW(NULL,
+                                            TextStringU->Buffer,
+                                            CaptionStringU->Buffer,
+                                            Type, 0, Timeout);
+
+    /* Restore the original desktop */
+    SetThreadDesktop(hOldDesk);
+    CloseDesktop(hDesk);
 
     /* Return response value */
     switch (MessageBoxResponse)
@@ -776,6 +1052,7 @@ UserpMessageBox(
         case IDRETRY:    return ResponseRetry;
         case IDTRYAGAIN: return ResponseTryAgain;
         case IDCONTINUE: return ResponseContinue;
+        default:         return ResponseNotHandled;
     }
 
     return ResponseNotHandled;
@@ -824,13 +1101,12 @@ UserServerHardError(
     IN PCSR_THREAD ThreadData,
     IN PHARDERROR_MSG Message)
 {
-    ULONG_PTR Parameters[MAXIMUM_HARDERROR_PARAMETERS] = {0};
-    OBJECT_ATTRIBUTES ObjectAttributes;
-    UNICODE_STRING TextU, CaptionU;
-    NTSTATUS Status;
-    HANDLE hProcess;
-    ULONG Size;
     ULONG ErrorMode;
+    UINT  dwType = 0;
+    ULONG Timeout = INFINITE;
+    UNICODE_STRING TextU, CaptionU;
+    WCHAR LocalTextBuffer[256];
+    WCHAR LocalCaptionBuffer[256];
 
     ASSERT(ThreadData->Process != NULL);
 
@@ -840,52 +1116,33 @@ UserServerHardError(
     /* Make sure we don't have too many parameters */
     if (Message->NumberOfParameters > MAXIMUM_HARDERROR_PARAMETERS)
     {
-        // FIXME: Windows just fails (STATUS_INVALID_PARAMETER) & returns ResponseNotHandled.
+        // NOTE: Windows just fails (STATUS_INVALID_PARAMETER) & returns ResponseNotHandled.
+        DPRINT1("Invalid NumberOfParameters = %d\n", Message->NumberOfParameters);
         Message->NumberOfParameters = MAXIMUM_HARDERROR_PARAMETERS;
     }
     if (Message->ValidResponseOptions > OptionCancelTryContinue)
     {
-        // STATUS_INVALID_PARAMETER;
-        Message->Response = ResponseNotHandled;
-        return;
+        DPRINT1("Unknown ValidResponseOptions = %d\n", Message->ValidResponseOptions);
+        return; // STATUS_INVALID_PARAMETER;
     }
-    // TODO: More message validation: check NumberOfParameters wrt. Message Status code.
-
-    /* Open client process */
-    InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL);
-    Status = NtOpenProcess(&hProcess,
-                           PROCESS_VM_READ | PROCESS_QUERY_INFORMATION,
-                           &ObjectAttributes,
-                           &Message->h.ClientId);
-    if (!NT_SUCCESS(Status))
+    if (Message->Status == STATUS_SERVICE_NOTIFICATION)
     {
-        DPRINT1("NtOpenProcess failed with status 0x%08lx, possibly SYSTEM process.\n", Status);
-        hProcess = NULL;
+        if (Message->NumberOfParameters < 3)
+        {
+            DPRINT1("Invalid NumberOfParameters = %d for STATUS_SERVICE_NOTIFICATION\n",
+                    Message->NumberOfParameters);
+            return; // STATUS_INVALID_PARAMETER;
+        }
+        // (Message->UnicodeStringParameterMask & 0x3)
     }
 
-    /* Capture all string parameters from the process memory */
-    Status = UserpCaptureStringParameters(Parameters, &Size, Message, hProcess);
-    if (!NT_SUCCESS(Status))
-    {
-        if (hProcess) NtClose(hProcess);
-        return;
-    }
+    /* Re-initialize the hard errors cache */
+    UserInitHardErrorsCache();
 
     /* Format the message caption and text */
-    Status = UserpFormatMessages(&TextU,
-                                 &CaptionU,
-                                 Parameters,
-                                 Size,
-                                 Message,
-                                 hProcess);
-
-    /* Cleanup */
-    UserpFreeStringParameters(Parameters, Message);
-    if (hProcess) NtClose(hProcess);
-
-    /* If we failed, bail out */
-    if (!NT_SUCCESS(Status))
-        return;
+    RtlInitEmptyUnicodeString(&TextU, LocalTextBuffer, sizeof(LocalTextBuffer));
+    RtlInitEmptyUnicodeString(&CaptionU, LocalCaptionBuffer, sizeof(LocalCaptionBuffer));
+    UserpFormatMessages(&TextU, &CaptionU, &dwType, &Timeout, Message);
 
     /* Log the hard error message */
     UserpLogHardError(&TextU, &CaptionU);
@@ -907,8 +1164,9 @@ UserServerHardError(
     {
         /* Display the balloon */
         Message->Response = ResponseOk;
-        if (UserpShowInformationBalloon(TextU.Buffer,
-                                        CaptionU.Buffer,
+        if (UserpShowInformationBalloon(&TextU,
+                                        &CaptionU,
+                                        dwType,
                                         Message))
         {
             Message->Response = ResponseOk;
@@ -917,17 +1175,60 @@ UserServerHardError(
     }
 
     /* Display the message box */
-    Message->Response = UserpMessageBox(TextU.Buffer,
-                                        CaptionU.Buffer,
-                                        Message->ValidResponseOptions,
-                                        (ULONG)(Message->Status) >> 30,
-                                        (ULONG)-1);
+    Message->Response = UserpMessageBox(&TextU,
+                                        &CaptionU,
+                                        dwType,
+                                        Timeout);
 
 Quit:
-    RtlFreeUnicodeString(&TextU);
-    RtlFreeUnicodeString(&CaptionU);
+    /* Free the strings if they have been reallocated */
+    if (TextU.Buffer != LocalTextBuffer)
+        RtlFreeUnicodeString(&TextU);
+    if (CaptionU.Buffer != LocalCaptionBuffer)
+        RtlFreeUnicodeString(&CaptionU);
 
     return;
 }
 
+VOID
+UserInitHardErrorsCache(VOID)
+{
+    NTSTATUS Status;
+    LCID CurrentUserLCID = 0;
+
+    Status = NtQueryDefaultLocale(TRUE, &CurrentUserLCID);
+    if (!NT_SUCCESS(Status) || CurrentUserLCID == 0)
+    {
+        /* Fall back to english locale */
+        DPRINT1("NtQueryDefaultLocale failed with Status = 0x%08lx\n", Status);
+        // LOCALE_SYSTEM_DEFAULT;
+        CurrentUserLCID = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT);
+    }
+    if (g_CurrentUserLangId == LANGIDFROMLCID(CurrentUserLCID))
+    {
+        /* The current lang ID and the hard error strings have already been cached */
+        return;
+    }
+
+    /* Load the strings using the current system locale */
+    RtlLoadUnicodeString(UserServerDllInstance, IDS_SEVERITY_SUCCESS,
+                         &g_SuccessU, L"Success");
+    RtlLoadUnicodeString(UserServerDllInstance, IDS_SEVERITY_INFORMATIONAL,
+                         &g_InformationU, L"System Information");
+    RtlLoadUnicodeString(UserServerDllInstance, IDS_SEVERITY_WARNING,
+                         &g_WarningU, L"System Warning");
+    RtlLoadUnicodeString(UserServerDllInstance, IDS_SEVERITY_ERROR,
+                         &g_ErrorU, L"System Error");
+    // "unknown software exception"
+    RtlLoadUnicodeString(UserServerDllInstance, IDS_SYSTEM_PROCESS,
+                         &g_SystemProcessU, L"System Process");
+    RtlLoadUnicodeString(UserServerDllInstance, IDS_OK_TERMINATE_PROGRAM,
+                         &g_OKTerminateU, L"Click on OK to terminate the program.");
+    RtlLoadUnicodeString(UserServerDllInstance, IDS_CANCEL_DEBUG_PROGRAM,
+                         &g_CancelDebugU, L"Click on CANCEL to debug the program.");
+
+    /* Remember that we cached the hard error strings */
+    g_CurrentUserLangId = LANGIDFROMLCID(CurrentUserLCID);
+}
+
 /* EOF */