Beispiel #1
0
INT_PTR CALLBACK EtpGpuPageDlgProc(
    _In_ HWND hwndDlg,
    _In_ UINT uMsg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
    )
{
    LPPROPSHEETPAGE propSheetPage;
    PPH_PROCESS_PROPPAGECONTEXT propPageContext;
    PPH_PROCESS_ITEM processItem;
    PET_GPU_CONTEXT context;

    if (PhPropPageDlgProcHeader(hwndDlg, uMsg, lParam, &propSheetPage, &propPageContext, &processItem))
    {
        context = propPageContext->Context;
    }
    else
    {
        return FALSE;
    }

    switch (uMsg)
    {
    case WM_INITDIALOG:
        {
            ULONG sampleCount;

            sampleCount = PhGetIntegerSetting(L"SampleCount");

            context = PhAllocate(sizeof(ET_GPU_CONTEXT));
            memset(context, 0, sizeof(ET_GPU_CONTEXT));

            context->WindowHandle = hwndDlg;
            context->Block = EtGetProcessBlock(processItem);
            context->Enabled = TRUE;
            context->GpuGroupBox = GetDlgItem(hwndDlg, IDC_GROUPGPU);
            context->MemGroupBox = GetDlgItem(hwndDlg, IDC_GROUPMEM);
            context->SharedGroupBox = GetDlgItem(hwndDlg, IDC_GROUPSHARED);
            propPageContext->Context = context;

            PhInitializeLayoutManager(&context->LayoutManager, hwndDlg);

            PhInitializeGraphState(&context->GpuGraphState);
            PhInitializeGraphState(&context->MemoryGraphState);
            PhInitializeGraphState(&context->MemorySharedGraphState);

            PhInitializeCircularBuffer_FLOAT(&context->GpuHistory, sampleCount);
            PhInitializeCircularBuffer_ULONG(&context->MemoryHistory, sampleCount);
            PhInitializeCircularBuffer_ULONG(&context->MemorySharedHistory, sampleCount);

            GpuPropCreateGraphs(context);
            GpuPropCreatePanel(context);
            GpuPropUpdateInfo(context);
            GpuPropUpdatePanel(context);

            PhRegisterCallback(
                &PhProcessesUpdatedEvent,
                ProcessesUpdatedHandler,
                context,
                &context->ProcessesUpdatedRegistration
                );
        }
        break;
    case WM_DESTROY:
        {
            PhDeleteLayoutManager(&context->LayoutManager);

            PhDeleteGraphState(&context->GpuGraphState);
            PhDeleteGraphState(&context->MemoryGraphState);
            PhDeleteGraphState(&context->MemorySharedGraphState);

            PhDeleteCircularBuffer_FLOAT(&context->GpuHistory);
            PhDeleteCircularBuffer_ULONG(&context->MemoryHistory);
            PhDeleteCircularBuffer_ULONG(&context->MemorySharedHistory);

            if (context->GpuGraphHandle)
                DestroyWindow(context->GpuGraphHandle);
            if (context->MemGraphHandle)
                DestroyWindow(context->MemGraphHandle);
            if (context->SharedGraphHandle)
                DestroyWindow(context->SharedGraphHandle);
            if (context->PanelHandle)
                DestroyWindow(context->PanelHandle);

            PhUnregisterCallback(&PhProcessesUpdatedEvent, &context->ProcessesUpdatedRegistration);
            PhFree(context);

            PhPropPageDlgProcDestroy(hwndDlg);
        }
        break;
    case WM_SHOWWINDOW:
        {
            if (PhBeginPropPageLayout(hwndDlg, propPageContext))
                PhEndPropPageLayout(hwndDlg, propPageContext);
        }
        break;
    case WM_NOTIFY:
        {
            LPNMHDR header = (LPNMHDR)lParam;

            switch (header->code)
            {
            case PSN_SETACTIVE:
                context->Enabled = TRUE;
                break;
            case PSN_KILLACTIVE:
                context->Enabled = FALSE;
                break;
            case GCN_GETDRAWINFO:
                {
                    PPH_GRAPH_GETDRAWINFO getDrawInfo = (PPH_GRAPH_GETDRAWINFO)header;
                    PPH_GRAPH_DRAW_INFO drawInfo = getDrawInfo->DrawInfo;

                    if (header->hwndFrom == context->GpuGraphHandle)
                    {
                        if (PhGetIntegerSetting(L"GraphShowText"))
                        {
                            HDC hdc;

                            PhMoveReference(&context->GpuGraphState.Text, PhFormatString(
                                L"%.2f%%",
                                context->CurrentGpuUsage * 100
                                ));

                            hdc = Graph_GetBufferedContext(context->GpuGraphHandle);
                            SelectObject(hdc, PhApplicationFont);
                            PhSetGraphText(hdc, drawInfo, &context->GpuGraphState.Text->sr,
                                &NormalGraphTextMargin, &NormalGraphTextPadding, PH_ALIGN_TOP | PH_ALIGN_LEFT);
                        }
                        else
                        {
                            drawInfo->Text.Buffer = NULL;
                        }

                        drawInfo->Flags = PH_GRAPH_USE_GRID;
                        PhSiSetColorsGraphDrawInfo(drawInfo, PhGetIntegerSetting(L"ColorCpuKernel"), 0);
                        PhGraphStateGetDrawInfo(&context->GpuGraphState, getDrawInfo, context->GpuHistory.Count);

                        if (!context->GpuGraphState.Valid)
                        {
                            PhCopyCircularBuffer_FLOAT(&context->GpuHistory, context->GpuGraphState.Data1, drawInfo->LineDataCount);
                            context->GpuGraphState.Valid = TRUE;
                        }
                    }
                    else if (header->hwndFrom == context->MemGraphHandle)
                    {
                        if (PhGetIntegerSetting(L"GraphShowText"))
                        {
                            HDC hdc;

                            PhMoveReference(&context->MemoryGraphState.Text, PhFormatString(
                                L"%s",
                                PhaFormatSize(UInt32x32To64(context->CurrentMemUsage, PAGE_SIZE), -1)->Buffer
                                ));

                            hdc = Graph_GetBufferedContext(context->MemGraphHandle);
                            SelectObject(hdc, PhApplicationFont);
                            PhSetGraphText(
                                hdc,
                                drawInfo,
                                &context->MemoryGraphState.Text->sr,
                                &NormalGraphTextMargin,
                                &NormalGraphTextPadding,
                                PH_ALIGN_TOP | PH_ALIGN_LEFT
                                );
                        }
                        else
                        {
                            drawInfo->Text.Buffer = NULL;
                        }

                        drawInfo->Flags = PH_GRAPH_USE_GRID;
                        PhSiSetColorsGraphDrawInfo(drawInfo, PhGetIntegerSetting(L"ColorPhysical"), 0);
                        PhGraphStateGetDrawInfo(
                            &context->MemoryGraphState,
                            getDrawInfo,
                            context->MemoryHistory.Count
                            );

                        if (!context->MemoryGraphState.Valid)
                        {
                            ULONG i = 0;

                            for (i = 0; i < drawInfo->LineDataCount; i++)
                            {
                                context->MemoryGraphState.Data1[i] = (FLOAT)PhGetItemCircularBuffer_ULONG(&context->MemoryHistory, i);
                            }

                            if (EtGpuDedicatedLimit != 0)
                            {
                                PhDivideSinglesBySingle(
                                    context->MemoryGraphState.Data1,
                                    (FLOAT)EtGpuDedicatedLimit / PAGE_SIZE,
                                    drawInfo->LineDataCount
                                    );
                            }

                            context->MemoryGraphState.Valid = TRUE;
                        }
                    }
                    else if (header->hwndFrom == context->SharedGraphHandle)
                    {
                        if (PhGetIntegerSetting(L"GraphShowText"))
                        {
                            HDC hdc;

                            PhMoveReference(&context->MemorySharedGraphState.Text, PhFormatString(
                                L"%s",
                                PhaFormatSize(UInt32x32To64(context->CurrentMemSharedUsage, PAGE_SIZE), -1)->Buffer
                                ));

                            hdc = Graph_GetBufferedContext(context->SharedGraphHandle);
                            SelectObject(hdc, PhApplicationFont);
                            PhSetGraphText(hdc, drawInfo, &context->MemorySharedGraphState.Text->sr,
                                &NormalGraphTextMargin, &NormalGraphTextPadding, PH_ALIGN_TOP | PH_ALIGN_LEFT);
                        }
                        else
                        {
                            drawInfo->Text.Buffer = NULL;
                        }

                        drawInfo->Flags = PH_GRAPH_USE_GRID;
                        PhSiSetColorsGraphDrawInfo(drawInfo, PhGetIntegerSetting(L"ColorPrivate"), 0);
                        PhGraphStateGetDrawInfo(
                            &context->MemorySharedGraphState,
                            getDrawInfo,
                            context->MemorySharedHistory.Count
                            );

                        if (!context->MemorySharedGraphState.Valid)
                        {
                            ULONG i = 0;

                            for (i = 0; i < drawInfo->LineDataCount; i++)
                            {
                                context->MemorySharedGraphState.Data1[i] = (FLOAT)PhGetItemCircularBuffer_ULONG(&context->MemorySharedHistory, i);
                            }

                            if (EtGpuSharedLimit != 0)
                            {
                                PhDivideSinglesBySingle(
                                    context->MemorySharedGraphState.Data1,
                                    (FLOAT)EtGpuSharedLimit / PAGE_SIZE,
                                    drawInfo->LineDataCount
                                    );
                            }

                            context->MemorySharedGraphState.Valid = TRUE;
                        }
                    }
                }
                break;
            case GCN_GETTOOLTIPTEXT:
                {
                    PPH_GRAPH_GETTOOLTIPTEXT getTooltipText = (PPH_GRAPH_GETTOOLTIPTEXT)lParam;

                    if (getTooltipText->Index < getTooltipText->TotalCount)
                    {
                        if (header->hwndFrom == context->GpuGraphHandle)
                        {
                            if (context->GpuGraphState.TooltipIndex != getTooltipText->Index)
                            {
                                FLOAT gpuUsage = PhGetItemCircularBuffer_FLOAT(
                                    &context->GpuHistory,
                                    getTooltipText->Index
                                    );

                                PhMoveReference(&context->GpuGraphState.TooltipText, PhFormatString(
                                    L"%.2f%%",
                                    gpuUsage * 100
                                    ));
                            }

                            getTooltipText->Text = context->GpuGraphState.TooltipText->sr;
                        }
                        else if (header->hwndFrom == context->MemGraphHandle)
                        {
                            if (context->MemoryGraphState.TooltipIndex != getTooltipText->Index)
                            {
                                ULONG gpuMemory = PhGetItemCircularBuffer_ULONG(
                                    &context->MemoryHistory,
                                    getTooltipText->Index
                                    );

                                PhMoveReference(&context->MemoryGraphState.TooltipText,
                                    PhFormatSize(UInt32x32To64(gpuMemory, PAGE_SIZE), -1)
                                    );
                            }

                            getTooltipText->Text = context->MemoryGraphState.TooltipText->sr;
                        }
                        else if (header->hwndFrom == context->SharedGraphHandle)
                        {
                            if (context->MemorySharedGraphState.TooltipIndex != getTooltipText->Index)
                            {
                                ULONG gpuSharedMemory = PhGetItemCircularBuffer_ULONG(
                                    &context->MemorySharedHistory,
                                    getTooltipText->Index
                                    );

                                PhMoveReference(&context->MemorySharedGraphState.TooltipText,
                                    PhFormatSize(UInt32x32To64(gpuSharedMemory, PAGE_SIZE), -1)
                                    );
                            }

                            getTooltipText->Text = context->MemorySharedGraphState.TooltipText->sr;
                        }
                    }
                }
                break;
            }
        }
        break;
    case MSG_UPDATE:
        {
            GpuPropUpdateInfo(context);
            GpuPropUpdateGraphs(context);
            GpuPropUpdatePanel(context);
        }
        break;
    case WM_SIZE:
        {
            GpuPropLayoutGraphs(context);
        }
        break;
    }

    return FALSE;
}
Beispiel #2
0
INT_PTR CALLBACK DotNetAsmPageDlgProc(
    _In_ HWND hwndDlg,
    _In_ UINT uMsg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
    )
{
    LPPROPSHEETPAGE propSheetPage;
    PPH_PROCESS_PROPPAGECONTEXT propPageContext;
    PPH_PROCESS_ITEM processItem;
    PASMPAGE_CONTEXT context;

    if (PhPropPageDlgProcHeader(hwndDlg, uMsg, lParam, &propSheetPage, &propPageContext, &processItem))
    {
        context = propPageContext->Context;
    }
    else
    {
        return FALSE;
    }

    switch (uMsg)
    {
    case WM_INITDIALOG:
        {
            PPH_STRING settings;
            HWND tnHandle;

            context = PhAllocate(sizeof(ASMPAGE_CONTEXT));
            memset(context, 0, sizeof(ASMPAGE_CONTEXT));
            propPageContext->Context = context;
            context->WindowHandle = hwndDlg;
            context->ProcessItem = processItem;

            context->ClrVersions = 0;
            PhGetProcessIsDotNetEx(processItem->ProcessId, NULL, 0, NULL, &context->ClrVersions);

            tnHandle = GetDlgItem(hwndDlg, IDC_LIST);
            context->TnHandle = tnHandle;

            TreeNew_SetCallback(tnHandle, DotNetAsmTreeNewCallback, context);
            TreeNew_SetExtendedFlags(tnHandle, TN_FLAG_ITEM_DRAG_SELECT, TN_FLAG_ITEM_DRAG_SELECT);
            PhSetControlTheme(tnHandle, L"explorer");
            SendMessage(TreeNew_GetTooltips(tnHandle), TTM_SETMAXTIPWIDTH, 0, MAXSHORT);
            PhAddTreeNewColumn(tnHandle, DNATNC_STRUCTURE, TRUE, L"Structure", 240, PH_ALIGN_LEFT, -2, 0);
            PhAddTreeNewColumn(tnHandle, DNATNC_ID, TRUE, L"ID", 50, PH_ALIGN_RIGHT, 0, DT_RIGHT);
            PhAddTreeNewColumn(tnHandle, DNATNC_FLAGS, TRUE, L"Flags", 120, PH_ALIGN_LEFT, 1, 0);
            PhAddTreeNewColumn(tnHandle, DNATNC_PATH, TRUE, L"Path", 600, PH_ALIGN_LEFT, 2, 0); // don't use path ellipsis - the user already has the base file name
            PhAddTreeNewColumn(tnHandle, DNATNC_NATIVEPATH, TRUE, L"Native image path", 600, PH_ALIGN_LEFT, 3, 0);

            settings = PhGetStringSetting(SETTING_NAME_ASM_TREE_LIST_COLUMNS);
            PhCmLoadSettings(tnHandle, &settings->sr);
            PhDereferenceObject(settings);

            PhSwapReference(&context->TnErrorMessage, PhCreateString(L"Loading .NET assemblies..."));
            TreeNew_SetEmptyText(tnHandle, &context->TnErrorMessage->sr, 0);

            if (
                !IsProcessSuspended(processItem->ProcessId) ||
                PhShowMessage(hwndDlg, MB_ICONWARNING | MB_YESNO, L".NET assembly enumeration may not work properly because the process is currently suspended. Do you want to continue?") == IDYES
                )
            {
                CreateDotNetTraceQueryThread(
                    hwndDlg, 
                    context->ClrVersions, 
                    processItem->ProcessId
                    );
            }
            else
            {
                PhSwapReference(&context->TnErrorMessage, 
                    PhCreateString(L"Unable to start the event tracing session because the process is suspended.")
                    );
                TreeNew_SetEmptyText(tnHandle, &context->TnErrorMessage->sr, 0);
                InvalidateRect(tnHandle, NULL, FALSE);
            }
        }
        break;
    case WM_DESTROY:
        {
            PPH_STRING settings;
            ULONG i;

            settings = PhCmSaveSettings(context->TnHandle);
            PhSetStringSetting2(SETTING_NAME_ASM_TREE_LIST_COLUMNS, &settings->sr);
            PhDereferenceObject(settings);

            if (context->NodeList)
            {
                for (i = 0; i < context->NodeList->Count; i++)
                    DestroyNode(context->NodeList->Items[i]);

                PhDereferenceObject(context->NodeList);
            }

            if (context->NodeRootList)
                PhDereferenceObject(context->NodeRootList);

            PhClearReference(&context->TnErrorMessage);
            PhFree(context);

            PhPropPageDlgProcDestroy(hwndDlg);
        }
        break;
    case WM_SHOWWINDOW:
        {
            PPH_LAYOUT_ITEM dialogItem;

            if (dialogItem = PhBeginPropPageLayout(hwndDlg, propPageContext))
            {
                PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_LIST), dialogItem, PH_ANCHOR_ALL);
                PhEndPropPageLayout(hwndDlg, propPageContext);
            }
        }
        break;
    case WM_COMMAND:
        {
            switch (LOWORD(wParam))
            {
            case ID_COPY:
                {
                    PPH_STRING text;

                    text = PhGetTreeNewText(context->TnHandle, 0);
                    PhSetClipboardString(context->TnHandle, &text->sr);
                    PhDereferenceObject(text);
                }
                break;
            }
        }
        break;
    case UPDATE_MSG:
        {
            ULONG result = (ULONG)wParam;
            PASMPAGE_QUERY_CONTEXT queryContext = (PASMPAGE_QUERY_CONTEXT)lParam;

            if (result == 0)
            {
                PhSwapReference(&context->NodeList, queryContext->NodeList);
                PhSwapReference(&context->NodeRootList, queryContext->NodeRootList);
                
                DestroyDotNetTraceQuery(queryContext);

                TreeNew_NodesStructured(context->TnHandle);
            }
            else
            {
                PhSwapReference(&context->TnErrorMessage,
                    PhConcatStrings2(L"Unable to start the event tracing session: ", PhGetStringOrDefault(PhGetWin32Message(result), L"Unknown error"))
                    );
                TreeNew_SetEmptyText(context->TnHandle, &context->TnErrorMessage->sr, 0);
                InvalidateRect(context->TnHandle, NULL, FALSE);
            }
        }
        break;
    }

    return FALSE;
}
Beispiel #3
0
INT_PTR CALLBACK DotNetAsmPageDlgProc(
    __in HWND hwndDlg,
    __in UINT uMsg,
    __in WPARAM wParam,
    __in LPARAM lParam
    )
{
    LPPROPSHEETPAGE propSheetPage;
    PPH_PROCESS_PROPPAGECONTEXT propPageContext;
    PPH_PROCESS_ITEM processItem;
    PASMPAGE_CONTEXT context;

    if (PhPropPageDlgProcHeader(hwndDlg, uMsg, lParam, &propSheetPage, &propPageContext, &processItem))
    {
        context = propPageContext->Context;
    }
    else
    {
        return FALSE;
    }

    switch (uMsg)
    {
    case WM_INITDIALOG:
        {
            ULONG result = 0;
            PPH_STRING settings;
            LARGE_INTEGER timeout;
            HWND tnHandle;

            context = PhAllocate(sizeof(ASMPAGE_CONTEXT));
            memset(context, 0, sizeof(ASMPAGE_CONTEXT));
            propPageContext->Context = context;
            context->WindowHandle = hwndDlg;
            context->ProcessItem = processItem;

            context->ClrVersions = GetProcessDotNetVersions(processItem->ProcessId);

            context->NodeList = PhCreateList(64);
            context->NodeRootList = PhCreateList(2);

            tnHandle = GetDlgItem(hwndDlg, IDC_LIST);
            context->TnHandle = tnHandle;

            TreeNew_SetRedraw(tnHandle, FALSE);

            TreeNew_SetCallback(tnHandle, DotNetAsmTreeNewCallback, context);
            TreeNew_SetExtendedFlags(tnHandle, TN_FLAG_ITEM_DRAG_SELECT, TN_FLAG_ITEM_DRAG_SELECT);
            PhSetControlTheme(tnHandle, L"explorer");
            SendMessage(TreeNew_GetTooltips(tnHandle), TTM_SETMAXTIPWIDTH, 0, MAXSHORT);
            PhAddTreeNewColumn(tnHandle, DNATNC_STRUCTURE, TRUE, L"Structure", 240, PH_ALIGN_LEFT, -2, 0);
            PhAddTreeNewColumn(tnHandle, DNATNC_ID, TRUE, L"ID", 50, PH_ALIGN_RIGHT, 0, DT_RIGHT);
            PhAddTreeNewColumn(tnHandle, DNATNC_FLAGS, TRUE, L"Flags", 120, PH_ALIGN_LEFT, 1, 0);
            PhAddTreeNewColumn(tnHandle, DNATNC_PATH, TRUE, L"Path", 600, PH_ALIGN_LEFT, 2, 0); // don't use path ellipsis - the user already has the base file name
            PhAddTreeNewColumn(tnHandle, DNATNC_NATIVEPATH, TRUE, L"Native Image Path", 600, PH_ALIGN_LEFT, 3, 0);

            settings = PhGetStringSetting(SETTING_NAME_ASM_TREE_LIST_COLUMNS);
            PhCmLoadSettings(tnHandle, &settings->sr);
            PhDereferenceObject(settings);

            SetCursor(LoadCursor(NULL, IDC_WAIT));

            if (context->ClrVersions & CLR_VERSION_1_0)
            {
                AddFakeClrNode(context, L"CLR v1.0.3705"); // what PE displays
            }

            if (context->ClrVersions & CLR_VERSION_1_1)
            {
                AddFakeClrNode(context, L"CLR v1.1.4322");
            }

            timeout.QuadPart = -10 * PH_TIMEOUT_SEC;

            if (context->ClrVersions & CLR_VERSION_2_0)
            {
                context->ClrV2Node = AddFakeClrNode(context, L"CLR v2.0.50727");
                result = UpdateDotNetTraceInfoWithTimeout(context, TRUE, &timeout);
            }

            if (context->ClrVersions & CLR_VERSION_4_ABOVE)
            {
                result = UpdateDotNetTraceInfoWithTimeout(context, FALSE, &timeout);
            }

            TreeNew_NodesStructured(tnHandle);

            TreeNew_SetRedraw(tnHandle, TRUE);
            SetCursor(LoadCursor(NULL, IDC_ARROW));

            if (result != 0)
            {
                ShowWindow(tnHandle, SW_HIDE);
                ShowWindow(GetDlgItem(hwndDlg, IDC_ERROR), SW_SHOW);

                if (result == ERROR_ACCESS_DENIED)
                {
                    SetDlgItemText(hwndDlg, IDC_ERROR, L"Unable to start the event tracing session. Make sure Process Hacker is running with administrative privileges.");
                }
                else
                {
                    SetDlgItemText(hwndDlg, IDC_ERROR,
                        PhaConcatStrings2(L"Unable to start the event tracing session: %s", PhGetStringOrDefault(PhGetWin32Message(result), L"Unknown error"))->Buffer);
                }
            }
        }
        break;
    case WM_DESTROY:
        {
            PPH_STRING settings;
            ULONG i;

            settings = PhCmSaveSettings(context->TnHandle);
            PhSetStringSetting2(SETTING_NAME_ASM_TREE_LIST_COLUMNS, &settings->sr);
            PhDereferenceObject(settings);

            for (i = 0; i < context->NodeList->Count; i++)
                DestroyNode(context->NodeList->Items[i]);

            PhDereferenceObject(context->NodeList);
            PhDereferenceObject(context->NodeRootList);
            PhFree(context);

            PhPropPageDlgProcDestroy(hwndDlg);
        }
        break;
    case WM_SHOWWINDOW:
        {
            PPH_LAYOUT_ITEM dialogItem;

            if (dialogItem = PhBeginPropPageLayout(hwndDlg, propPageContext))
            {
                PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_LIST), dialogItem, PH_ANCHOR_ALL);
                PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_ERROR), dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT | PH_LAYOUT_FORCE_INVALIDATE);
                PhEndPropPageLayout(hwndDlg, propPageContext);
            }
        }
        break;
    case WM_COMMAND:
        {
            switch (LOWORD(wParam))
            {
            case ID_COPY:
                {
                    PPH_STRING text;

                    text = PhGetTreeNewText(context->TnHandle, DNATNC_MAXIMUM);
                    PhSetClipboardStringEx(context->TnHandle, text->Buffer, text->Length);
                    PhDereferenceObject(text);
                }
                break;
            }
        }
        break;
    }

    return FALSE;
}