1866 lines
54 KiB
C
1866 lines
54 KiB
C
/*
|
|
* Process Hacker -
|
|
* hex editor control
|
|
*
|
|
* Copyright (C) 2010-2015 wj32
|
|
*
|
|
* This file is part of Process Hacker.
|
|
*
|
|
* Process Hacker is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Process Hacker is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <ph.h>
|
|
#include <guisup.h>
|
|
#include <hexedit.h>
|
|
#include <hexeditp.h>
|
|
|
|
// Code originally from http://www.codeguru.com/Cpp/controls/editctrl/article.php/c539
|
|
|
|
BOOLEAN PhHexEditInitialization(
|
|
VOID
|
|
)
|
|
{
|
|
WNDCLASSEX c = { sizeof(c) };
|
|
|
|
c.style = CS_GLOBALCLASS;
|
|
c.lpfnWndProc = PhpHexEditWndProc;
|
|
c.cbClsExtra = 0;
|
|
c.cbWndExtra = sizeof(PVOID);
|
|
c.hInstance = PhLibImageBase;
|
|
c.hIcon = NULL;
|
|
c.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
c.hbrBackground = NULL;
|
|
c.lpszMenuName = NULL;
|
|
c.lpszClassName = PH_HEXEDIT_CLASSNAME;
|
|
c.hIconSm = NULL;
|
|
|
|
if (!RegisterClassEx(&c))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
VOID PhpCreateHexEditContext(
|
|
_Out_ PPHP_HEXEDIT_CONTEXT *Context
|
|
)
|
|
{
|
|
PPHP_HEXEDIT_CONTEXT context;
|
|
|
|
context = PhAllocate(sizeof(PHP_HEXEDIT_CONTEXT));
|
|
memset(context, 0, sizeof(PHP_HEXEDIT_CONTEXT)); // important, set NullWidth to 0
|
|
|
|
context->Data = NULL;
|
|
context->Length = 0;
|
|
context->TopIndex = 0;
|
|
context->BytesPerRow = 16;
|
|
context->LinesPerPage = 1;
|
|
|
|
context->ShowHex = TRUE;
|
|
context->ShowAscii = TRUE;
|
|
context->ShowAddress = TRUE;
|
|
context->AddressIsWide = TRUE;
|
|
context->AllowLengthChange = FALSE;
|
|
|
|
context->AddressOffset = 0;
|
|
context->HexOffset = 0;
|
|
context->AsciiOffset = 0;
|
|
|
|
context->Update = TRUE;
|
|
context->NoAddressChange = FALSE;
|
|
context->CurrentMode = EDIT_NONE;
|
|
|
|
context->EditPosition.x = 0;
|
|
context->EditPosition.y = 0;
|
|
context->CurrentAddress = 0;
|
|
context->HalfPage = TRUE;
|
|
|
|
context->SelStart = -1;
|
|
context->SelEnd = -1;
|
|
|
|
*Context = context;
|
|
}
|
|
|
|
VOID PhpFreeHexEditContext(
|
|
_In_ _Post_invalid_ PPHP_HEXEDIT_CONTEXT Context
|
|
)
|
|
{
|
|
if (!Context->UserBuffer && Context->Data) PhFree(Context->Data);
|
|
if (Context->CharBuffer) PhFree(Context->CharBuffer);
|
|
if (Context->Font) DeleteObject(Context->Font);
|
|
PhFree(Context);
|
|
}
|
|
|
|
LRESULT CALLBACK PhpHexEditWndProc(
|
|
_In_ HWND hwnd,
|
|
_In_ UINT uMsg,
|
|
_In_ WPARAM wParam,
|
|
_In_ LPARAM lParam
|
|
)
|
|
{
|
|
PPHP_HEXEDIT_CONTEXT context;
|
|
|
|
context = (PPHP_HEXEDIT_CONTEXT)GetWindowLongPtr(hwnd, 0);
|
|
|
|
if (uMsg == WM_CREATE)
|
|
{
|
|
PhpCreateHexEditContext(&context);
|
|
SetWindowLongPtr(hwnd, 0, (LONG_PTR)context);
|
|
}
|
|
|
|
if (!context)
|
|
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_CREATE:
|
|
{
|
|
context->Font = CreateFont(-(LONG)PhMultiplyDivide(12, PhGlobalDpi, 96), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, L"Courier New");
|
|
}
|
|
break;
|
|
case WM_DESTROY:
|
|
{
|
|
PhpFreeHexEditContext(context);
|
|
}
|
|
break;
|
|
case WM_PAINT:
|
|
{
|
|
PAINTSTRUCT paintStruct;
|
|
HDC hdc;
|
|
|
|
if (hdc = BeginPaint(hwnd, &paintStruct))
|
|
{
|
|
PhpHexEditOnPaint(hwnd, context, &paintStruct, hdc);
|
|
EndPaint(hwnd, &paintStruct);
|
|
}
|
|
}
|
|
break;
|
|
case WM_SIZE:
|
|
{
|
|
PhpHexEditUpdateMetrics(hwnd, context, FALSE, NULL);
|
|
}
|
|
break;
|
|
case WM_SETFOCUS:
|
|
{
|
|
if (context->Data && !PhpHexEditHasSelected(context))
|
|
{
|
|
if (context->EditPosition.x == 0 && context->ShowAddress)
|
|
PhpHexEditCreateAddressCaret(hwnd, context);
|
|
else
|
|
PhpHexEditCreateEditCaret(hwnd, context);
|
|
|
|
SetCaretPos(context->EditPosition.x, context->EditPosition.y);
|
|
ShowCaret(hwnd);
|
|
}
|
|
}
|
|
break;
|
|
case WM_KILLFOCUS:
|
|
{
|
|
DestroyCaret();
|
|
}
|
|
break;
|
|
case WM_VSCROLL:
|
|
{
|
|
SHORT scrollRequest = LOWORD(wParam);
|
|
LONG currentPosition;
|
|
LONG originalTopIndex;
|
|
SCROLLINFO scrollInfo = { sizeof(scrollInfo) };
|
|
|
|
originalTopIndex = context->TopIndex;
|
|
|
|
scrollInfo.fMask = SIF_TRACKPOS;
|
|
GetScrollInfo(hwnd, SB_VERT, &scrollInfo);
|
|
currentPosition = scrollInfo.nTrackPos;
|
|
|
|
if (context->Data)
|
|
{
|
|
LONG mult;
|
|
|
|
mult = context->LinesPerPage * context->BytesPerRow;
|
|
|
|
switch (scrollRequest)
|
|
{
|
|
case SB_LINEDOWN:
|
|
if (context->TopIndex < context->Length - mult)
|
|
{
|
|
context->TopIndex += context->BytesPerRow;
|
|
REDRAW_WINDOW(hwnd);
|
|
}
|
|
break;
|
|
case SB_LINEUP:
|
|
if (context->TopIndex >= context->BytesPerRow)
|
|
{
|
|
context->TopIndex -= context->BytesPerRow;
|
|
REDRAW_WINDOW(hwnd);
|
|
}
|
|
break;
|
|
case SB_PAGEDOWN:
|
|
if (context->TopIndex < context->Length - mult)
|
|
{
|
|
context->TopIndex += mult;
|
|
|
|
if (context->TopIndex > context->Length - mult)
|
|
context->TopIndex = context->Length - mult;
|
|
|
|
REDRAW_WINDOW(hwnd);
|
|
}
|
|
break;
|
|
case SB_PAGEUP:
|
|
if (context->TopIndex > 0)
|
|
{
|
|
context->TopIndex -= mult;
|
|
|
|
if (context->TopIndex < 0)
|
|
context->TopIndex = 0;
|
|
|
|
REDRAW_WINDOW(hwnd);
|
|
}
|
|
break;
|
|
case SB_THUMBTRACK:
|
|
context->TopIndex = currentPosition * context->BytesPerRow;
|
|
REDRAW_WINDOW(hwnd);
|
|
break;
|
|
}
|
|
|
|
SetScrollPos(hwnd, SB_VERT, context->TopIndex / context->BytesPerRow, TRUE);
|
|
|
|
if (!context->NoAddressChange && FALSE) // this behaviour sucks, so just leave it out
|
|
context->CurrentAddress += context->TopIndex - originalTopIndex;
|
|
|
|
PhpHexEditRepositionCaret(hwnd, context, context->CurrentAddress);
|
|
}
|
|
}
|
|
break;
|
|
case WM_MOUSEWHEEL:
|
|
{
|
|
SHORT wheelDelta = GET_WHEEL_DELTA_WPARAM(wParam);
|
|
|
|
if (context->Data)
|
|
{
|
|
ULONG wheelScrollLines;
|
|
|
|
if (!SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheelScrollLines, 0))
|
|
wheelScrollLines = 3;
|
|
|
|
context->TopIndex += context->BytesPerRow * (LONG)wheelScrollLines * -wheelDelta / WHEEL_DELTA;
|
|
|
|
if (context->TopIndex < 0)
|
|
context->TopIndex = 0;
|
|
|
|
if (context->Length >= context->LinesPerPage * context->BytesPerRow)
|
|
{
|
|
if (context->TopIndex > context->Length - context->LinesPerPage * context->BytesPerRow)
|
|
context->TopIndex = context->Length - context->LinesPerPage * context->BytesPerRow;
|
|
}
|
|
|
|
REDRAW_WINDOW(hwnd);
|
|
|
|
SetScrollPos(hwnd, SB_VERT, context->TopIndex / context->BytesPerRow, TRUE);
|
|
|
|
PhpHexEditRepositionCaret(hwnd, context, context->CurrentAddress);
|
|
}
|
|
}
|
|
break;
|
|
case WM_GETDLGCODE:
|
|
if (wParam != VK_ESCAPE)
|
|
return DLGC_WANTALLKEYS;
|
|
break;
|
|
case WM_ERASEBKGND:
|
|
return 1;
|
|
case WM_LBUTTONDOWN:
|
|
{
|
|
ULONG flags = (ULONG)wParam;
|
|
POINT cursorPos;
|
|
|
|
cursorPos.x = (LONG)(SHORT)LOWORD(lParam);
|
|
cursorPos.y = (LONG)(SHORT)HIWORD(lParam);
|
|
|
|
SetFocus(hwnd);
|
|
|
|
if (context->Data)
|
|
{
|
|
POINT point;
|
|
|
|
if (wParam & MK_SHIFT)
|
|
context->SelStart = context->CurrentAddress;
|
|
|
|
PhpHexEditCalculatePosition(hwnd, context, cursorPos.x, cursorPos.y, &point);
|
|
|
|
if (point.x > -1)
|
|
{
|
|
context->EditPosition = point;
|
|
|
|
point.x *= context->NullWidth;
|
|
point.y *= context->LineHeight;
|
|
|
|
if (point.x == 0 && context->ShowAddress)
|
|
PhpHexEditCreateAddressCaret(hwnd, context);
|
|
else
|
|
PhpHexEditCreateEditCaret(hwnd, context);
|
|
|
|
SetCaretPos(point.x, point.y);
|
|
|
|
if (flags & MK_SHIFT)
|
|
{
|
|
context->SelEnd = context->CurrentAddress;
|
|
|
|
if (context->CurrentMode == EDIT_HIGH || context->CurrentMode == EDIT_LOW)
|
|
context->SelEnd++;
|
|
|
|
REDRAW_WINDOW(hwnd);
|
|
}
|
|
}
|
|
|
|
if (!(flags & MK_SHIFT))
|
|
{
|
|
if (DragDetect(hwnd, cursorPos))
|
|
{
|
|
context->SelStart = context->CurrentAddress;
|
|
context->SelEnd = context->SelStart;
|
|
SetCapture(hwnd);
|
|
context->HasCapture = TRUE;
|
|
}
|
|
else
|
|
{
|
|
BOOLEAN selected;
|
|
|
|
selected = context->SelStart != -1;
|
|
context->SelStart = -1;
|
|
context->SelEnd = -1;
|
|
|
|
if (selected)
|
|
REDRAW_WINDOW(hwnd);
|
|
}
|
|
}
|
|
|
|
if (!PhpHexEditHasSelected(context))
|
|
ShowCaret(hwnd);
|
|
}
|
|
}
|
|
break;
|
|
case WM_LBUTTONUP:
|
|
{
|
|
if (context->HasCapture && PhpHexEditHasSelected(context))
|
|
ReleaseCapture();
|
|
|
|
context->HasCapture = FALSE;
|
|
}
|
|
break;
|
|
case WM_MOUSEMOVE:
|
|
{
|
|
ULONG flags = (ULONG)wParam;
|
|
POINT cursorPos;
|
|
|
|
cursorPos.x = (LONG)(SHORT)LOWORD(lParam);
|
|
cursorPos.y = (LONG)(SHORT)HIWORD(lParam);
|
|
|
|
if (
|
|
context->Data &&
|
|
context->HasCapture &&
|
|
context->SelStart != -1
|
|
)
|
|
{
|
|
RECT rect;
|
|
POINT point;
|
|
ULONG oldSelEnd;
|
|
|
|
// User is dragging.
|
|
|
|
GetClientRect(hwnd, &rect);
|
|
|
|
if (!PtInRect(&rect, cursorPos))
|
|
{
|
|
if (cursorPos.y < 0)
|
|
{
|
|
SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
|
|
cursorPos.y = 0;
|
|
}
|
|
else if (cursorPos.y > rect.bottom)
|
|
{
|
|
SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
|
|
cursorPos.y = rect.bottom - 1;
|
|
}
|
|
}
|
|
|
|
oldSelEnd = context->SelEnd;
|
|
PhpHexEditCalculatePosition(hwnd, context, cursorPos.x, cursorPos.y, &point);
|
|
|
|
if (point.x > -1)
|
|
{
|
|
context->SelEnd = context->CurrentAddress;
|
|
|
|
if (context->CurrentMode == EDIT_HIGH || context->CurrentMode == EDIT_LOW)
|
|
context->SelEnd++;
|
|
}
|
|
|
|
if (PhpHexEditHasSelected(context))
|
|
DestroyCaret();
|
|
|
|
if (context->SelEnd != oldSelEnd)
|
|
REDRAW_WINDOW(hwnd);
|
|
}
|
|
}
|
|
break;
|
|
case WM_CHAR:
|
|
{
|
|
ULONG c = (ULONG)wParam;
|
|
|
|
if (!context->Data)
|
|
goto DefaultHandler;
|
|
if (c == '\t')
|
|
goto DefaultHandler;
|
|
|
|
if (GetKeyState(VK_CONTROL) < 0)
|
|
{
|
|
switch (c)
|
|
{
|
|
case 0x3:
|
|
if (PhpHexEditHasSelected(context))
|
|
PhpHexEditCopyEdit(hwnd, context);
|
|
goto DefaultHandler;
|
|
case 0x16:
|
|
PhpHexEditPasteEdit(hwnd, context);
|
|
goto DefaultHandler;
|
|
case 0x18:
|
|
if (PhpHexEditHasSelected(context))
|
|
PhpHexEditCutEdit(hwnd, context);
|
|
goto DefaultHandler;
|
|
case 0x1a:
|
|
PhpHexEditUndoEdit(hwnd, context);
|
|
goto DefaultHandler;
|
|
}
|
|
}
|
|
|
|
// Disallow editing beyond the end of the data.
|
|
if (context->CurrentAddress >= context->Length)
|
|
goto DefaultHandler;
|
|
|
|
if (c == 0x8)
|
|
{
|
|
if (context->CurrentAddress != 0)
|
|
{
|
|
context->CurrentAddress--;
|
|
PhpHexEditSelDelete(hwnd, context, context->CurrentAddress, context->CurrentAddress + 1);
|
|
PhpHexEditRepositionCaret(hwnd, context, context->CurrentAddress);
|
|
REDRAW_WINDOW(hwnd);
|
|
}
|
|
|
|
goto DefaultHandler;
|
|
}
|
|
|
|
PhpHexEditSetSel(hwnd, context, -1, -1);
|
|
|
|
switch (context->CurrentMode)
|
|
{
|
|
case EDIT_NONE:
|
|
goto DefaultHandler;
|
|
case EDIT_HIGH:
|
|
case EDIT_LOW:
|
|
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))
|
|
{
|
|
ULONG b = c - '0';
|
|
|
|
if (b > 9)
|
|
b = 10 + c - 'a';
|
|
|
|
if (context->CurrentMode == EDIT_HIGH)
|
|
{
|
|
context->Data[context->CurrentAddress] =
|
|
(UCHAR)((context->Data[context->CurrentAddress] & 0x0f) | (b << 4));
|
|
}
|
|
else
|
|
{
|
|
context->Data[context->CurrentAddress] =
|
|
(UCHAR)((context->Data[context->CurrentAddress] & 0xf0) | b);
|
|
}
|
|
|
|
PhpHexEditMove(hwnd, context, 1, 0);
|
|
}
|
|
break;
|
|
case EDIT_ASCII:
|
|
context->Data[context->CurrentAddress] = (UCHAR)c;
|
|
PhpHexEditMove(hwnd, context, 1, 0);
|
|
break;
|
|
}
|
|
|
|
REDRAW_WINDOW(hwnd);
|
|
}
|
|
break;
|
|
case WM_KEYDOWN:
|
|
{
|
|
ULONG vk = (ULONG)wParam;
|
|
BOOLEAN shift = GetKeyState(VK_SHIFT) < 0;
|
|
BOOLEAN oldNoAddressChange = context->NoAddressChange;
|
|
BOOLEAN noScrollIntoView = FALSE;
|
|
|
|
context->NoAddressChange = TRUE;
|
|
|
|
switch (vk)
|
|
{
|
|
case VK_DOWN:
|
|
if (context->CurrentMode != EDIT_NONE)
|
|
{
|
|
if (shift)
|
|
{
|
|
if (!PhpHexEditHasSelected(context))
|
|
context->SelStart = context->CurrentAddress;
|
|
|
|
PhpHexEditMove(hwnd, context, 0, 1);
|
|
context->SelEnd = context->CurrentAddress;
|
|
|
|
if (context->CurrentMode == EDIT_HIGH || context->CurrentMode == EDIT_LOW)
|
|
context->SelEnd++;
|
|
|
|
REDRAW_WINDOW(hwnd);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
PhpHexEditSetSel(hwnd, context, -1, -1);
|
|
}
|
|
|
|
PhpHexEditMove(hwnd, context, 0, 1);
|
|
noScrollIntoView = TRUE;
|
|
}
|
|
else
|
|
{
|
|
PhpHexEditMove(hwnd, context, 0, 1);
|
|
}
|
|
break;
|
|
case VK_UP:
|
|
if (context->CurrentMode != EDIT_NONE)
|
|
{
|
|
if (shift)
|
|
{
|
|
if (!PhpHexEditHasSelected(context))
|
|
context->SelStart = context->CurrentAddress;
|
|
|
|
PhpHexEditMove(hwnd, context, 0, -1);
|
|
context->SelEnd = context->CurrentAddress;
|
|
|
|
REDRAW_WINDOW(hwnd);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
PhpHexEditSetSel(hwnd, context, -1, -1);
|
|
}
|
|
|
|
PhpHexEditMove(hwnd, context, 0, -1);
|
|
noScrollIntoView = TRUE;
|
|
}
|
|
else
|
|
{
|
|
PhpHexEditMove(hwnd, context, 0, -1);
|
|
}
|
|
break;
|
|
case VK_LEFT:
|
|
if (context->CurrentMode != EDIT_NONE)
|
|
{
|
|
if (shift)
|
|
{
|
|
if (!PhpHexEditHasSelected(context))
|
|
context->SelStart = context->CurrentAddress;
|
|
|
|
PhpHexEditMove(hwnd, context, -1, 0);
|
|
context->SelEnd = context->CurrentAddress;
|
|
|
|
REDRAW_WINDOW(hwnd);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
PhpHexEditSetSel(hwnd, context, -1, -1);
|
|
}
|
|
|
|
PhpHexEditMove(hwnd, context, -1, 0);
|
|
noScrollIntoView = TRUE;
|
|
}
|
|
break;
|
|
case VK_RIGHT:
|
|
if (context->CurrentMode != EDIT_NONE)
|
|
{
|
|
if (shift)
|
|
{
|
|
if (!PhpHexEditHasSelected(context))
|
|
context->SelStart = context->CurrentAddress;
|
|
|
|
PhpHexEditMove(hwnd, context, 1, 0);
|
|
context->SelEnd = context->CurrentAddress;
|
|
|
|
if (context->CurrentMode == EDIT_HIGH || context->CurrentMode == EDIT_LOW)
|
|
context->SelEnd++;
|
|
|
|
REDRAW_WINDOW(hwnd);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
PhpHexEditSetSel(hwnd, context, -1, -1);
|
|
}
|
|
|
|
PhpHexEditMove(hwnd, context, 1, 0);
|
|
noScrollIntoView = TRUE;
|
|
}
|
|
break;
|
|
case VK_PRIOR:
|
|
if (shift)
|
|
{
|
|
if (!PhpHexEditHasSelected(context))
|
|
context->SelStart = context->CurrentAddress;
|
|
|
|
SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
|
|
PhpHexEditMove(hwnd, context, 0, 0);
|
|
context->SelEnd = context->CurrentAddress;
|
|
|
|
REDRAW_WINDOW(hwnd);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
PhpHexEditSetSel(hwnd, context, -1, -1);
|
|
}
|
|
|
|
SendMessage(hwnd, WM_VSCROLL, SB_PAGEUP, 0);
|
|
PhpHexEditMove(hwnd, context, 0, 0);
|
|
noScrollIntoView = TRUE;
|
|
break;
|
|
case VK_NEXT:
|
|
if (shift)
|
|
{
|
|
if (!PhpHexEditHasSelected(context))
|
|
context->SelStart = context->CurrentAddress;
|
|
|
|
SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
|
|
PhpHexEditMove(hwnd, context, 0, 0);
|
|
context->SelEnd = context->CurrentAddress;
|
|
|
|
REDRAW_WINDOW(hwnd);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
PhpHexEditSetSel(hwnd, context, -1, -1);
|
|
}
|
|
|
|
SendMessage(hwnd, WM_VSCROLL, SB_PAGEDOWN, 0);
|
|
PhpHexEditMove(hwnd, context, 0, 0);
|
|
noScrollIntoView = TRUE;
|
|
break;
|
|
case VK_HOME:
|
|
if (shift)
|
|
{
|
|
if (!PhpHexEditHasSelected(context))
|
|
context->SelStart = context->CurrentAddress;
|
|
|
|
if (GetKeyState(VK_CONTROL) < 0)
|
|
{
|
|
SendMessage(hwnd, WM_VSCROLL, SB_THUMBTRACK, 0);
|
|
}
|
|
else
|
|
{
|
|
// Round down.
|
|
context->CurrentAddress /= context->BytesPerRow;
|
|
context->CurrentAddress *= context->BytesPerRow;
|
|
}
|
|
|
|
PhpHexEditMove(hwnd, context, 0, 0);
|
|
context->SelEnd = context->CurrentAddress;
|
|
|
|
REDRAW_WINDOW(hwnd);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
PhpHexEditSetSel(hwnd, context, -1, -1);
|
|
}
|
|
|
|
if (GetKeyState(VK_CONTROL) < 0)
|
|
{
|
|
SendMessage(hwnd, WM_VSCROLL, SB_THUMBTRACK, 0);
|
|
context->CurrentAddress = 0;
|
|
}
|
|
else
|
|
{
|
|
// Round down.
|
|
context->CurrentAddress /= context->BytesPerRow;
|
|
context->CurrentAddress *= context->BytesPerRow;
|
|
}
|
|
|
|
PhpHexEditMove(hwnd, context, 0, 0);
|
|
noScrollIntoView = TRUE;
|
|
|
|
break;
|
|
case VK_END:
|
|
if (shift)
|
|
{
|
|
if (!PhpHexEditHasSelected(context))
|
|
context->SelStart = context->CurrentAddress;
|
|
|
|
if (GetKeyState(VK_CONTROL) < 0)
|
|
{
|
|
context->CurrentAddress = context->Length - 1;
|
|
SendMessage(hwnd, WM_VSCROLL,
|
|
MAKEWPARAM(SB_THUMBTRACK, ((context->Length + (context->BytesPerRow / 2)) / context->BytesPerRow) - context->LinesPerPage),
|
|
0);
|
|
}
|
|
else
|
|
{
|
|
context->CurrentAddress /= context->BytesPerRow;
|
|
context->CurrentAddress *= context->BytesPerRow;
|
|
context->CurrentAddress += context->BytesPerRow - 1;
|
|
|
|
if (context->CurrentAddress > context->Length)
|
|
context->CurrentAddress = context->Length - 1;
|
|
}
|
|
|
|
PhpHexEditMove(hwnd, context, 0, 0);
|
|
context->SelEnd = context->CurrentAddress;
|
|
|
|
REDRAW_WINDOW(hwnd);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
PhpHexEditSetSel(hwnd, context, -1, -1);
|
|
}
|
|
|
|
if (GetKeyState(VK_CONTROL) < 0)
|
|
{
|
|
context->CurrentAddress = context->Length - 1;
|
|
|
|
if (context->HalfPage)
|
|
{
|
|
SendMessage(hwnd, WM_VSCROLL, 0, 0);
|
|
}
|
|
else
|
|
{
|
|
SendMessage(hwnd, WM_VSCROLL,
|
|
MAKEWPARAM(SB_THUMBTRACK, ((context->Length + (context->BytesPerRow / 2)) / context->BytesPerRow) - context->LinesPerPage),
|
|
0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
context->CurrentAddress /= context->BytesPerRow;
|
|
context->CurrentAddress *= context->BytesPerRow;
|
|
context->CurrentAddress += context->BytesPerRow - 1;
|
|
|
|
if (context->CurrentAddress > context->Length)
|
|
context->CurrentAddress = context->Length - 1;
|
|
}
|
|
|
|
PhpHexEditMove(hwnd, context, 0, 0);
|
|
noScrollIntoView = TRUE;
|
|
|
|
break;
|
|
case VK_INSERT:
|
|
PhpHexEditSelInsert(hwnd, context, context->CurrentAddress,
|
|
max(1, context->SelEnd - context->SelStart));
|
|
REDRAW_WINDOW(hwnd);
|
|
break;
|
|
case VK_DELETE:
|
|
if (PhpHexEditHasSelected(context))
|
|
{
|
|
PhpHexEditClearEdit(hwnd, context);
|
|
}
|
|
else
|
|
{
|
|
PhpHexEditSelDelete(hwnd, context, context->CurrentAddress, context->CurrentAddress + 1);
|
|
REDRAW_WINDOW(hwnd);
|
|
}
|
|
break;
|
|
case '\t':
|
|
switch (context->CurrentMode)
|
|
{
|
|
case EDIT_NONE:
|
|
context->CurrentMode = EDIT_HIGH;
|
|
break;
|
|
case EDIT_HIGH:
|
|
case EDIT_LOW:
|
|
context->CurrentMode = EDIT_ASCII;
|
|
break;
|
|
case EDIT_ASCII:
|
|
context->CurrentMode = EDIT_HIGH;
|
|
break;
|
|
}
|
|
|
|
PhpHexEditMove(hwnd, context, 0, 0);
|
|
|
|
break;
|
|
}
|
|
|
|
// Scroll into view if not in view.
|
|
if (
|
|
!noScrollIntoView &&
|
|
(context->CurrentAddress < context->TopIndex ||
|
|
context->CurrentAddress >= context->TopIndex + context->LinesPerPage * context->BytesPerRow)
|
|
)
|
|
{
|
|
PhpHexEditScrollTo(hwnd, context, context->CurrentAddress);
|
|
}
|
|
|
|
context->NoAddressChange = oldNoAddressChange;
|
|
}
|
|
break;
|
|
case HEM_SETBUFFER:
|
|
{
|
|
PhpHexEditSetBuffer(hwnd, context, (PUCHAR)lParam, (ULONG)wParam);
|
|
}
|
|
return TRUE;
|
|
case HEM_SETDATA:
|
|
{
|
|
PhpHexEditSetData(hwnd, context, (PUCHAR)lParam, (ULONG)wParam);
|
|
}
|
|
return TRUE;
|
|
case HEM_GETBUFFER:
|
|
{
|
|
PULONG length = (PULONG)wParam;
|
|
|
|
if (length)
|
|
*length = context->Length;
|
|
|
|
return (LPARAM)context->Data;
|
|
}
|
|
case HEM_SETSEL:
|
|
{
|
|
LONG selStart = (LONG)wParam;
|
|
LONG selEnd = (LONG)lParam;
|
|
|
|
if (selStart <= 0)
|
|
return FALSE;
|
|
if (selEnd > context->Length)
|
|
return FALSE;
|
|
|
|
PhpHexEditScrollTo(hwnd, context, selStart);
|
|
PhpHexEditSetSel(hwnd, context, selStart, selEnd);
|
|
PhpHexEditRepositionCaret(hwnd, context, selStart);
|
|
REDRAW_WINDOW(hwnd);
|
|
}
|
|
return TRUE;
|
|
case HEM_SETEDITMODE:
|
|
{
|
|
context->CurrentMode = (LONG)wParam;
|
|
REDRAW_WINDOW(hwnd);
|
|
}
|
|
return TRUE;
|
|
case HEM_SETBYTESPERROW:
|
|
{
|
|
LONG bytesPerRow = (LONG)wParam;
|
|
|
|
if (bytesPerRow >= 4)
|
|
{
|
|
context->BytesPerRow = bytesPerRow;
|
|
PhpHexEditUpdateMetrics(hwnd, context, TRUE, NULL);
|
|
PhpHexEditUpdateScrollbars(hwnd, context);
|
|
PhpHexEditScrollTo(hwnd, context, context->CurrentAddress);
|
|
PhpHexEditRepositionCaret(hwnd, context, context->CurrentAddress);
|
|
REDRAW_WINDOW(hwnd);
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
DefaultHandler:
|
|
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
FORCEINLINE VOID PhpPrintHex(
|
|
_In_ HDC hdc,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context,
|
|
_Inout_ PWCHAR Buffer,
|
|
_In_ UCHAR Byte,
|
|
_Inout_ PLONG X,
|
|
_Inout_ PLONG Y,
|
|
_Inout_ PULONG N
|
|
)
|
|
{
|
|
PWCHAR p = Buffer;
|
|
|
|
TO_HEX(p, Byte);
|
|
*p++ = ' ';
|
|
TextOut(hdc, *X, *Y, Buffer, 3);
|
|
*X += Context->NullWidth * 3;
|
|
(*N)++;
|
|
|
|
if (*N == Context->BytesPerRow)
|
|
{
|
|
*N = 0;
|
|
*X = Context->HexOffset;
|
|
*Y += Context->LineHeight;
|
|
}
|
|
}
|
|
|
|
FORCEINLINE VOID PhpPrintAscii(
|
|
_In_ HDC hdc,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context,
|
|
_In_ UCHAR Byte,
|
|
_Inout_ PLONG X,
|
|
_Inout_ PLONG Y,
|
|
_Inout_ PULONG N
|
|
)
|
|
{
|
|
WCHAR c;
|
|
|
|
c = IS_PRINTABLE(Byte) ? Byte : '.';
|
|
TextOut(hdc, *X, *Y, &c, 1);
|
|
*X += Context->NullWidth;
|
|
(*N)++;
|
|
|
|
if (*N == Context->BytesPerRow)
|
|
{
|
|
*N = 0;
|
|
*X = Context->AsciiOffset;
|
|
*Y += Context->LineHeight;
|
|
}
|
|
}
|
|
|
|
FORCEINLINE COLORREF GetLighterHighlightColor(
|
|
VOID
|
|
)
|
|
{
|
|
COLORREF color;
|
|
UCHAR r;
|
|
UCHAR g;
|
|
UCHAR b;
|
|
|
|
color = GetSysColor(COLOR_HIGHLIGHT);
|
|
r = (UCHAR)color;
|
|
g = (UCHAR)(color >> 8);
|
|
b = (UCHAR)(color >> 16);
|
|
|
|
if (r <= 255 - 64)
|
|
r += 64;
|
|
else
|
|
r = 255;
|
|
|
|
if (g <= 255 - 64)
|
|
g += 64;
|
|
else
|
|
g = 255;
|
|
|
|
if (b <= 255 - 64)
|
|
b += 64;
|
|
else
|
|
b = 255;
|
|
|
|
return RGB(r, g, b);
|
|
}
|
|
|
|
VOID PhpHexEditUpdateMetrics(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context,
|
|
_In_ BOOLEAN UpdateLineHeight,
|
|
_In_opt_ HDC hdc
|
|
)
|
|
{
|
|
BOOLEAN freeHdc = FALSE;
|
|
RECT clientRect;
|
|
SIZE size;
|
|
|
|
if (!hdc && UpdateLineHeight)
|
|
{
|
|
hdc = CreateCompatibleDC(hdc);
|
|
SelectObject(hdc, Context->Font);
|
|
freeHdc = TRUE;
|
|
}
|
|
|
|
GetClientRect(hwnd, &clientRect);
|
|
|
|
if (UpdateLineHeight)
|
|
{
|
|
GetCharWidth(hdc, '0', '0', &Context->NullWidth);
|
|
GetTextExtentPoint32(hdc, L"0", 1, &size);
|
|
Context->LineHeight = size.cy;
|
|
}
|
|
|
|
Context->HexOffset = Context->ShowAddress ? (Context->AddressIsWide ? Context->NullWidth * 9 : Context->NullWidth * 5) : 0;
|
|
Context->AsciiOffset = Context->HexOffset + (Context->ShowHex ? (Context->BytesPerRow * 3 * Context->NullWidth) : 0);
|
|
|
|
if (Context->LineHeight != 0)
|
|
{
|
|
Context->LinesPerPage = clientRect.bottom / Context->LineHeight;
|
|
Context->HalfPage = FALSE;
|
|
|
|
if (Context->LinesPerPage * Context->BytesPerRow > Context->Length)
|
|
{
|
|
Context->LinesPerPage = (Context->Length + Context->BytesPerRow / 2) / Context->BytesPerRow;
|
|
|
|
if (Context->Length % Context->BytesPerRow != 0)
|
|
{
|
|
Context->HalfPage = TRUE;
|
|
Context->LinesPerPage++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (freeHdc && hdc)
|
|
DeleteDC(hdc);
|
|
}
|
|
|
|
VOID PhpHexEditOnPaint(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context,
|
|
_In_ PAINTSTRUCT *PaintStruct,
|
|
_In_ HDC hdc
|
|
)
|
|
{
|
|
RECT clientRect;
|
|
HDC bufferDc;
|
|
HBITMAP bufferBitmap;
|
|
HBITMAP oldBufferBitmap;
|
|
LONG height;
|
|
LONG x;
|
|
LONG y;
|
|
LONG i;
|
|
ULONG requiredBufferLength;
|
|
PWCHAR buffer;
|
|
|
|
GetClientRect(hwnd, &clientRect);
|
|
|
|
bufferDc = CreateCompatibleDC(hdc);
|
|
bufferBitmap = CreateCompatibleBitmap(hdc, clientRect.right, clientRect.bottom);
|
|
oldBufferBitmap = SelectObject(bufferDc, bufferBitmap);
|
|
|
|
SetDCBrushColor(bufferDc, GetSysColor(COLOR_WINDOW));
|
|
FillRect(bufferDc, &clientRect, GetStockObject(DC_BRUSH));
|
|
SelectObject(bufferDc, Context->Font);
|
|
SetBoundsRect(bufferDc, &clientRect, DCB_DISABLE);
|
|
|
|
requiredBufferLength = (max(8, Context->BytesPerRow * 3) + 1) * sizeof(WCHAR);
|
|
|
|
if (Context->CharBufferLength < requiredBufferLength)
|
|
{
|
|
if (Context->CharBuffer)
|
|
PhFree(Context->CharBuffer);
|
|
|
|
Context->CharBuffer = PhAllocate(requiredBufferLength);
|
|
Context->CharBufferLength = requiredBufferLength;
|
|
buffer = Context->CharBuffer;
|
|
}
|
|
|
|
buffer = Context->CharBuffer;
|
|
|
|
if (Context->Data)
|
|
{
|
|
// Get character dimensions.
|
|
if (Context->Update)
|
|
{
|
|
PhpHexEditUpdateMetrics(hwnd, Context, TRUE, bufferDc);
|
|
Context->Update = FALSE;
|
|
PhpHexEditUpdateScrollbars(hwnd, Context);
|
|
}
|
|
|
|
height = (clientRect.bottom + Context->LineHeight - 1) / Context->LineHeight * Context->LineHeight; // round up to height
|
|
|
|
if (Context->ShowAddress)
|
|
{
|
|
PH_FORMAT format;
|
|
ULONG w;
|
|
RECT rect;
|
|
|
|
PhInitFormatX(&format, 0);
|
|
format.Type |= FormatPadZeros;
|
|
format.Width = Context->AddressIsWide ? 8 : 4;
|
|
|
|
w = Context->AddressIsWide ? 8 : 4;
|
|
|
|
rect = clientRect;
|
|
rect.left = Context->AddressOffset;
|
|
rect.top = 0;
|
|
|
|
for (i = Context->TopIndex; i < Context->Length && rect.top < height; i += Context->BytesPerRow)
|
|
{
|
|
format.u.Int32 = i;
|
|
PhFormatToBuffer(&format, 1, buffer, requiredBufferLength, NULL);
|
|
DrawText(bufferDc, buffer, w, &rect, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_NOPREFIX | DT_NOCLIP);
|
|
rect.top += Context->LineHeight;
|
|
}
|
|
}
|
|
|
|
if (Context->ShowHex)
|
|
{
|
|
RECT rect;
|
|
LONG n = 0;
|
|
|
|
x = Context->HexOffset;
|
|
y = 0;
|
|
rect = clientRect;
|
|
rect.left = x;
|
|
rect.top = 0;
|
|
|
|
if (Context->SelStart != -1)
|
|
{
|
|
COLORREF highlightColor;
|
|
LONG selStart;
|
|
LONG selEnd;
|
|
|
|
if (Context->CurrentMode == EDIT_HIGH || Context->CurrentMode == EDIT_LOW)
|
|
highlightColor = GetSysColor(COLOR_HIGHLIGHT);
|
|
else
|
|
highlightColor = GetLighterHighlightColor();
|
|
|
|
selStart = Context->SelStart;
|
|
selEnd = Context->SelEnd;
|
|
|
|
if (selStart > selEnd)
|
|
{
|
|
ULONG t;
|
|
|
|
t = selEnd;
|
|
selEnd = selStart;
|
|
selStart = t;
|
|
}
|
|
|
|
if (selStart >= Context->Length)
|
|
selStart = Context->Length - 1;
|
|
if (selEnd > Context->Length)
|
|
selEnd = Context->Length;
|
|
|
|
// Bytes before the selection
|
|
|
|
for (i = Context->TopIndex; i < selStart && y < height; i++)
|
|
{
|
|
PhpPrintHex(bufferDc, Context, buffer, Context->Data[i], &x, &y, &n);
|
|
}
|
|
|
|
// Bytes in the selection
|
|
|
|
SetTextColor(bufferDc, GetSysColor(COLOR_HIGHLIGHTTEXT));
|
|
SetBkColor(bufferDc, highlightColor);
|
|
|
|
for (; i < selEnd && i < Context->Length && y < height; i++)
|
|
{
|
|
PhpPrintHex(bufferDc, Context, buffer, Context->Data[i], &x, &y, &n);
|
|
}
|
|
|
|
// Bytes after the selection
|
|
|
|
SetTextColor(bufferDc, GetSysColor(COLOR_WINDOWTEXT));
|
|
SetBkColor(bufferDc, GetSysColor(COLOR_WINDOW));
|
|
|
|
for (; i < Context->Length && y < height; i++)
|
|
{
|
|
PhpPrintHex(bufferDc, Context, buffer, Context->Data[i], &x, &y, &n);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
i = Context->TopIndex;
|
|
|
|
while (i < Context->Length && rect.top < height)
|
|
{
|
|
PWCHAR p = buffer;
|
|
|
|
for (n = 0; n < Context->BytesPerRow && i < Context->Length; n++)
|
|
{
|
|
TO_HEX(p, Context->Data[i]);
|
|
*p++ = ' ';
|
|
i++;
|
|
}
|
|
|
|
while (n < Context->BytesPerRow)
|
|
{
|
|
p[0] = ' ';
|
|
p[1] = ' ';
|
|
p[2] = ' ';
|
|
p += 3;
|
|
n++;
|
|
}
|
|
|
|
DrawText(bufferDc, buffer, Context->BytesPerRow * 3, &rect, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_NOPREFIX | DT_NOCLIP);
|
|
rect.top += Context->LineHeight;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Context->ShowAscii)
|
|
{
|
|
RECT rect;
|
|
LONG n = 0;
|
|
|
|
x = Context->AsciiOffset;
|
|
y = 0;
|
|
rect = clientRect;
|
|
rect.left = x;
|
|
rect.top = 0;
|
|
|
|
if (Context->SelStart != -1)
|
|
{
|
|
COLORREF highlightColor;
|
|
LONG selStart;
|
|
LONG selEnd;
|
|
|
|
if (Context->CurrentMode == EDIT_ASCII)
|
|
highlightColor = GetSysColor(COLOR_HIGHLIGHT);
|
|
else
|
|
highlightColor = GetLighterHighlightColor();
|
|
|
|
selStart = Context->SelStart;
|
|
selEnd = Context->SelEnd;
|
|
|
|
if (selStart > selEnd)
|
|
{
|
|
LONG t;
|
|
|
|
t = selEnd;
|
|
selEnd = selStart;
|
|
selStart = t;
|
|
}
|
|
|
|
if (selStart >= Context->Length)
|
|
selStart = Context->Length - 1;
|
|
if (selEnd > Context->Length)
|
|
selEnd = Context->Length;
|
|
|
|
// Bytes before the selection
|
|
|
|
for (i = Context->TopIndex; i < selStart && y < height; i++)
|
|
{
|
|
PhpPrintAscii(bufferDc, Context, Context->Data[i], &x, &y, &n);
|
|
}
|
|
|
|
// Bytes in the selection
|
|
|
|
SetTextColor(bufferDc, GetSysColor(COLOR_HIGHLIGHTTEXT));
|
|
SetBkColor(bufferDc, highlightColor);
|
|
|
|
for (; i < selEnd && i < Context->Length && y < height; i++)
|
|
{
|
|
PhpPrintAscii(bufferDc, Context, Context->Data[i], &x, &y, &n);
|
|
}
|
|
|
|
// Bytes after the selection
|
|
|
|
SetTextColor(bufferDc, GetSysColor(COLOR_WINDOWTEXT));
|
|
SetBkColor(bufferDc, GetSysColor(COLOR_WINDOW));
|
|
|
|
for (; i < Context->Length && y < height; i++)
|
|
{
|
|
PhpPrintAscii(bufferDc, Context, Context->Data[i], &x, &y, &n);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
i = Context->TopIndex;
|
|
|
|
while (i < Context->Length && rect.top < height)
|
|
{
|
|
PWCHAR p = buffer;
|
|
|
|
for (n = 0; n < Context->BytesPerRow && i < Context->Length; n++)
|
|
{
|
|
*p++ = IS_PRINTABLE(Context->Data[i]) ? Context->Data[i] : '.'; // 1
|
|
i++;
|
|
}
|
|
|
|
DrawText(bufferDc, buffer, n, &rect, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_NOPREFIX | DT_NOCLIP);
|
|
rect.top += Context->LineHeight;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BitBlt(hdc, 0, 0, clientRect.right, clientRect.bottom, bufferDc, 0, 0, SRCCOPY);
|
|
SelectObject(bufferDc, oldBufferBitmap);
|
|
DeleteObject(bufferBitmap);
|
|
DeleteDC(bufferDc);
|
|
}
|
|
|
|
VOID PhpHexEditUpdateScrollbars(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context
|
|
)
|
|
{
|
|
SCROLLINFO si = { sizeof(si) };
|
|
|
|
si.fMask = SIF_ALL;
|
|
si.nMin = 0;
|
|
si.nMax = (Context->Length / Context->BytesPerRow) - 1;
|
|
si.nPage = Context->LinesPerPage;
|
|
si.nPos = Context->TopIndex / Context->BytesPerRow;
|
|
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
|
|
|
|
if (si.nMax > (LONG)si.nPage)
|
|
EnableScrollBar(hwnd, SB_VERT, ESB_ENABLE_BOTH);
|
|
|
|
// No horizontal scrollbar please.
|
|
/*si.nMin = 0;
|
|
si.nMax = ((Context->ShowAddress ? (Context->AddressIsWide ? 8 : 4) : 0) +
|
|
(Context->ShowHex ? Context->BytesPerRow * 3 : 0) +
|
|
(Context->ShowAscii ? Context->BytesPerRow : 0)) * Context->NullWidth;
|
|
si.nPage = 1;
|
|
si.nPos = 0;
|
|
SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);*/
|
|
}
|
|
|
|
VOID PhpHexEditCreateAddressCaret(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context
|
|
)
|
|
{
|
|
DestroyCaret();
|
|
CreateCaret(hwnd, NULL, Context->NullWidth * (Context->AddressIsWide ? 8 : 4), Context->LineHeight);
|
|
}
|
|
|
|
VOID PhpHexEditCreateEditCaret(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context
|
|
)
|
|
{
|
|
DestroyCaret();
|
|
CreateCaret(hwnd, NULL, Context->NullWidth, Context->LineHeight);
|
|
}
|
|
|
|
VOID PhpHexEditRepositionCaret(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context,
|
|
_In_ LONG Position
|
|
)
|
|
{
|
|
ULONG x;
|
|
ULONG y;
|
|
RECT rect;
|
|
|
|
x = (Position - Context->TopIndex) % Context->BytesPerRow;
|
|
y = (Position - Context->TopIndex) / Context->BytesPerRow;
|
|
|
|
switch (Context->CurrentMode)
|
|
{
|
|
case EDIT_NONE:
|
|
PhpHexEditCreateAddressCaret(hwnd, Context);
|
|
x = 0;
|
|
break;
|
|
case EDIT_HIGH:
|
|
PhpHexEditCreateEditCaret(hwnd, Context);
|
|
x *= Context->NullWidth * 3;
|
|
x += Context->HexOffset;
|
|
break;
|
|
case EDIT_LOW:
|
|
PhpHexEditCreateEditCaret(hwnd, Context);
|
|
x *= Context->NullWidth * 3;
|
|
x += Context->NullWidth;
|
|
x += Context->HexOffset;
|
|
break;
|
|
case EDIT_ASCII:
|
|
PhpHexEditCreateEditCaret(hwnd, Context);
|
|
x *= Context->NullWidth;
|
|
x += Context->AsciiOffset;
|
|
break;
|
|
}
|
|
|
|
Context->EditPosition.x = x;
|
|
Context->EditPosition.y = y * Context->LineHeight;
|
|
|
|
GetClientRect(hwnd, &rect);
|
|
|
|
if (PtInRect(&rect, Context->EditPosition))
|
|
{
|
|
SetCaretPos(Context->EditPosition.x, Context->EditPosition.y);
|
|
ShowCaret(hwnd);
|
|
}
|
|
}
|
|
|
|
VOID PhpHexEditCalculatePosition(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context,
|
|
_In_ LONG X,
|
|
_In_ LONG Y,
|
|
_Out_ POINT *Point
|
|
)
|
|
{
|
|
LONG xp;
|
|
|
|
Y /= Context->LineHeight;
|
|
|
|
if (Y < 0 || Y >= Context->LinesPerPage)
|
|
{
|
|
Point->x = -1;
|
|
Point->y = -1;
|
|
return;
|
|
}
|
|
|
|
if (Y * Context->BytesPerRow >= Context->Length)
|
|
{
|
|
Point->x = -1;
|
|
Point->y = -1;
|
|
return;
|
|
}
|
|
|
|
X += Context->NullWidth;
|
|
X /= Context->NullWidth;
|
|
|
|
if (Context->ShowAddress && X <= (Context->AddressIsWide ? 8 : 4))
|
|
{
|
|
Context->CurrentAddress = Context->TopIndex + Context->BytesPerRow * Y;
|
|
Context->CurrentMode = EDIT_NONE;
|
|
|
|
Point->x = 0;
|
|
Point->y = Y;
|
|
return;
|
|
}
|
|
|
|
xp = Context->HexOffset / Context->NullWidth + Context->BytesPerRow * 3;
|
|
|
|
if (Context->ShowHex && X < xp)
|
|
{
|
|
if (X % 3)
|
|
X--;
|
|
|
|
Context->CurrentAddress = Context->TopIndex +
|
|
Context->BytesPerRow * Y +
|
|
(X - (Context->HexOffset / Context->NullWidth)) / 3;
|
|
Context->CurrentMode = ((X % 3) & 1) ? EDIT_LOW : EDIT_HIGH;
|
|
|
|
Point->x = X;
|
|
Point->y = Y;
|
|
return;
|
|
}
|
|
|
|
X--; // fix selection problem
|
|
|
|
xp = Context->AsciiOffset / Context->NullWidth + Context->BytesPerRow;
|
|
|
|
if (Context->ShowAscii && X * Context->NullWidth >= Context->AsciiOffset && X <= xp)
|
|
{
|
|
Context->CurrentAddress = Context->TopIndex +
|
|
Context->BytesPerRow * Y +
|
|
(X - (Context->AsciiOffset / Context->NullWidth));
|
|
Context->CurrentMode = EDIT_ASCII;
|
|
|
|
Point->x = X;
|
|
Point->y = Y;
|
|
return;
|
|
}
|
|
|
|
Point->x = -1;
|
|
Point->y = -1;
|
|
}
|
|
|
|
VOID PhpHexEditMove(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context,
|
|
_In_ LONG X,
|
|
_In_ LONG Y
|
|
)
|
|
{
|
|
switch (Context->CurrentMode)
|
|
{
|
|
case EDIT_NONE:
|
|
Context->CurrentAddress += Y * Context->BytesPerRow;
|
|
break;
|
|
case EDIT_HIGH:
|
|
if (X != 0)
|
|
Context->CurrentMode = EDIT_LOW;
|
|
if (X == -1)
|
|
Context->CurrentAddress--;
|
|
Context->CurrentAddress += Y * Context->BytesPerRow;
|
|
break;
|
|
case EDIT_LOW:
|
|
if (X != 0)
|
|
Context->CurrentMode = EDIT_HIGH;
|
|
if (X == 1)
|
|
Context->CurrentAddress++;
|
|
Context->CurrentAddress += Y * Context->BytesPerRow;
|
|
break;
|
|
case EDIT_ASCII:
|
|
Context->CurrentAddress += X;
|
|
Context->CurrentAddress += Y * Context->BytesPerRow;
|
|
break;
|
|
}
|
|
|
|
if (Context->CurrentAddress < 0)
|
|
Context->CurrentAddress = 0;
|
|
|
|
if (Context->CurrentAddress >= Context->Length)
|
|
{
|
|
Context->CurrentAddress -= X;
|
|
Context->CurrentAddress -= Y * Context->BytesPerRow;
|
|
}
|
|
|
|
Context->NoAddressChange = TRUE;
|
|
|
|
if (Context->CurrentAddress < Context->TopIndex)
|
|
SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0);
|
|
if (Context->CurrentAddress >= Context->TopIndex + Context->LinesPerPage * Context->BytesPerRow)
|
|
SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
|
|
|
|
Context->NoAddressChange = FALSE;
|
|
PhpHexEditRepositionCaret(hwnd, Context, Context->CurrentAddress);
|
|
}
|
|
|
|
VOID PhpHexEditSetSel(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context,
|
|
_In_ LONG S,
|
|
_In_ LONG E
|
|
)
|
|
{
|
|
DestroyCaret();
|
|
Context->SelStart = S;
|
|
Context->SelEnd = E;
|
|
REDRAW_WINDOW(hwnd);
|
|
|
|
if (S != -1 && E != -1)
|
|
{
|
|
Context->CurrentAddress = S;
|
|
}
|
|
else
|
|
{
|
|
if (Context->EditPosition.x == 0 && Context->ShowAddress)
|
|
PhpHexEditCreateAddressCaret(hwnd, Context);
|
|
else
|
|
PhpHexEditCreateEditCaret(hwnd, Context);
|
|
|
|
SetCaretPos(Context->EditPosition.x, Context->EditPosition.y);
|
|
ShowCaret(hwnd);
|
|
}
|
|
}
|
|
|
|
VOID PhpHexEditScrollTo(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context,
|
|
_In_ LONG Position
|
|
)
|
|
{
|
|
if (Position < Context->TopIndex || Position > Context->TopIndex + Context->LinesPerPage * Context->BytesPerRow)
|
|
{
|
|
Context->TopIndex = Position / Context->BytesPerRow * Context->BytesPerRow; // round down
|
|
Context->TopIndex -= Context->LinesPerPage / 3 * Context->BytesPerRow;
|
|
|
|
if (Context->TopIndex < 0)
|
|
Context->TopIndex = 0;
|
|
|
|
if (Context->Length >= Context->LinesPerPage * Context->BytesPerRow)
|
|
{
|
|
if (Context->TopIndex > Context->Length - Context->LinesPerPage * Context->BytesPerRow)
|
|
Context->TopIndex = Context->Length - Context->LinesPerPage * Context->BytesPerRow;
|
|
}
|
|
|
|
PhpHexEditUpdateScrollbars(hwnd, Context);
|
|
REDRAW_WINDOW(hwnd);
|
|
}
|
|
}
|
|
|
|
VOID PhpHexEditClearEdit(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context
|
|
)
|
|
{
|
|
if (Context->AllowLengthChange)
|
|
{
|
|
Context->CurrentAddress = Context->SelStart;
|
|
PhpHexEditSelDelete(hwnd, Context, Context->SelStart, Context->SelEnd);
|
|
PhpHexEditRepositionCaret(hwnd, Context, Context->CurrentAddress);
|
|
REDRAW_WINDOW(hwnd);
|
|
}
|
|
}
|
|
|
|
VOID PhpHexEditCopyEdit(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context
|
|
)
|
|
{
|
|
if (OpenClipboard(hwnd))
|
|
{
|
|
EmptyClipboard();
|
|
PhpHexEditNormalizeSel(hwnd, Context);
|
|
|
|
if (Context->CurrentMode != EDIT_ASCII)
|
|
{
|
|
ULONG length = Context->SelEnd - Context->SelStart;
|
|
HGLOBAL binaryMemory;
|
|
HGLOBAL hexMemory;
|
|
|
|
binaryMemory = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, length);
|
|
|
|
if (binaryMemory)
|
|
{
|
|
PUCHAR p = GlobalLock(binaryMemory);
|
|
memcpy(p, &Context->Data[Context->SelStart], length);
|
|
GlobalUnlock(binaryMemory);
|
|
|
|
hexMemory = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, (length * 3 + 1) * sizeof(WCHAR));
|
|
|
|
if (hexMemory)
|
|
{
|
|
PWCHAR pw;
|
|
ULONG i;
|
|
|
|
pw = GlobalLock(hexMemory);
|
|
|
|
for (i = 0; i < length; i++)
|
|
{
|
|
TO_HEX(pw, Context->Data[Context->SelStart + i]);
|
|
*pw++ = ' ';
|
|
}
|
|
*pw = 0;
|
|
|
|
GlobalUnlock(hexMemory);
|
|
|
|
SetClipboardData(CF_UNICODETEXT, hexMemory);
|
|
}
|
|
|
|
SetClipboardData(RegisterClipboardFormat(L"BinaryData"), binaryMemory);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ULONG length = Context->SelEnd - Context->SelStart;
|
|
HGLOBAL binaryMemory;
|
|
HGLOBAL asciiMemory;
|
|
|
|
binaryMemory = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, length);
|
|
asciiMemory = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, length + 1);
|
|
|
|
if (binaryMemory)
|
|
{
|
|
PUCHAR p = GlobalLock(binaryMemory);
|
|
memcpy(p, &Context->Data[Context->SelStart], length);
|
|
GlobalUnlock(binaryMemory);
|
|
|
|
if (asciiMemory)
|
|
{
|
|
ULONG i;
|
|
|
|
p = GlobalLock(asciiMemory);
|
|
memcpy(p, &Context->Data[Context->SelStart], length);
|
|
|
|
for (i = 0; i < length; i++)
|
|
{
|
|
if (!IS_PRINTABLE(*p))
|
|
*p = '.';
|
|
p++;
|
|
}
|
|
*p = 0;
|
|
|
|
GlobalUnlock(asciiMemory);
|
|
|
|
SetClipboardData(CF_TEXT, asciiMemory);
|
|
}
|
|
|
|
SetClipboardData(RegisterClipboardFormat(L"BinaryData"), binaryMemory);
|
|
}
|
|
}
|
|
|
|
CloseClipboard();
|
|
}
|
|
}
|
|
|
|
VOID PhpHexEditCutEdit(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context
|
|
)
|
|
{
|
|
if (Context->AllowLengthChange)
|
|
{
|
|
PhpHexEditCopyEdit(hwnd, Context);
|
|
PhpHexEditSelDelete(hwnd, Context, Context->SelStart, Context->SelEnd);
|
|
REDRAW_WINDOW(hwnd);
|
|
}
|
|
}
|
|
|
|
VOID PhpHexEditPasteEdit(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context
|
|
)
|
|
{
|
|
if (OpenClipboard(hwnd))
|
|
{
|
|
HANDLE memory;
|
|
|
|
memory = GetClipboardData(RegisterClipboardFormat(L"BinaryData"));
|
|
|
|
if (!memory)
|
|
memory = GetClipboardData(CF_TEXT);
|
|
|
|
if (memory)
|
|
{
|
|
PUCHAR p = GlobalLock(memory);
|
|
ULONG length = (ULONG)GlobalSize(memory);
|
|
ULONG paste;
|
|
ULONG oldCurrentAddress = Context->CurrentAddress;
|
|
|
|
PhpHexEditNormalizeSel(hwnd, Context);
|
|
|
|
if (Context->AllowLengthChange)
|
|
{
|
|
if (Context->SelStart == -1)
|
|
{
|
|
if (Context->CurrentMode == EDIT_LOW)
|
|
Context->CurrentAddress++;
|
|
|
|
paste = Context->CurrentAddress;
|
|
PhpHexEditSelInsert(hwnd, Context, Context->CurrentAddress, length);
|
|
}
|
|
else
|
|
{
|
|
paste = Context->SelStart;
|
|
PhpHexEditSelDelete(hwnd, Context, Context->SelStart, Context->SelEnd);
|
|
PhpHexEditSelInsert(hwnd, Context, paste, length);
|
|
PhpHexEditSetSel(hwnd, Context, -1, -1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Context->SelStart == -1)
|
|
{
|
|
if (Context->CurrentMode == EDIT_LOW)
|
|
Context->CurrentAddress++;
|
|
|
|
paste = Context->CurrentAddress;
|
|
}
|
|
else
|
|
{
|
|
paste = Context->SelStart;
|
|
}
|
|
|
|
if (length > Context->Length - paste)
|
|
length = Context->Length - paste;
|
|
}
|
|
|
|
memcpy(&Context->Data[paste], p, length);
|
|
GlobalUnlock(memory);
|
|
|
|
Context->CurrentAddress = oldCurrentAddress;
|
|
REDRAW_WINDOW(hwnd);
|
|
}
|
|
|
|
CloseClipboard();
|
|
}
|
|
}
|
|
|
|
VOID PhpHexEditSelectAll(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context
|
|
)
|
|
{
|
|
Context->SelStart = 0;
|
|
Context->SelEnd = Context->Length;
|
|
DestroyCaret();
|
|
REDRAW_WINDOW(hwnd);
|
|
}
|
|
|
|
VOID PhpHexEditUndoEdit(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context
|
|
)
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
VOID PhpHexEditNormalizeSel(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context
|
|
)
|
|
{
|
|
if (Context->SelStart > Context->SelEnd)
|
|
{
|
|
LONG t;
|
|
|
|
t = Context->SelEnd;
|
|
Context->SelEnd = Context->SelStart;
|
|
Context->SelStart = t;
|
|
}
|
|
}
|
|
|
|
VOID PhpHexEditSelDelete(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context,
|
|
_In_ LONG S,
|
|
_In_ LONG E
|
|
)
|
|
{
|
|
if (Context->AllowLengthChange && Context->Length > 0)
|
|
{
|
|
PUCHAR p = PhAllocate(Context->Length - (E - S) + 1);
|
|
|
|
memcpy(p, Context->Data, S);
|
|
|
|
if (S < Context->Length - (E - S))
|
|
memcpy(&p[S], &Context->Data[E], Context->Length - E);
|
|
|
|
PhFree(Context->Data);
|
|
Context->Data = p;
|
|
PhpHexEditSetSel(hwnd, Context, -1, -1);
|
|
Context->Length -= E - S;
|
|
|
|
if (Context->CurrentAddress > Context->Length)
|
|
{
|
|
Context->CurrentAddress = Context->Length;
|
|
PhpHexEditRepositionCaret(hwnd, Context, Context->CurrentAddress);
|
|
}
|
|
|
|
Context->Update = TRUE;
|
|
}
|
|
}
|
|
|
|
VOID PhpHexEditSelInsert(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context,
|
|
_In_ LONG S,
|
|
_In_ LONG L
|
|
)
|
|
{
|
|
if (Context->AllowLengthChange)
|
|
{
|
|
PUCHAR p = PhAllocate(Context->Length + L);
|
|
|
|
memset(p, 0, Context->Length + L);
|
|
memcpy(p, Context->Data, S);
|
|
memcpy(&p[S + L], &Context->Data[S], Context->Length - S);
|
|
|
|
PhFree(Context->Data);
|
|
Context->Data = p;
|
|
PhpHexEditSetSel(hwnd, Context, -1, -1);
|
|
Context->Length += L;
|
|
|
|
Context->Update = TRUE;
|
|
}
|
|
}
|
|
|
|
VOID PhpHexEditSetBuffer(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context,
|
|
_In_ PUCHAR Data,
|
|
_In_ ULONG Length
|
|
)
|
|
{
|
|
Context->Data = Data;
|
|
PhpHexEditSetSel(hwnd, Context, -1, -1);
|
|
Context->Length = Length;
|
|
Context->CurrentAddress = 0;
|
|
Context->EditPosition.x = Context->EditPosition.y = 0;
|
|
Context->CurrentMode = EDIT_HIGH;
|
|
Context->TopIndex = 0;
|
|
Context->Update = TRUE;
|
|
|
|
Context->UserBuffer = TRUE;
|
|
Context->AllowLengthChange = FALSE;
|
|
}
|
|
|
|
VOID PhpHexEditSetData(
|
|
_In_ HWND hwnd,
|
|
_In_ PPHP_HEXEDIT_CONTEXT Context,
|
|
_In_ PUCHAR Data,
|
|
_In_ ULONG Length
|
|
)
|
|
{
|
|
if (Context->Data) PhFree(Context->Data);
|
|
Context->Data = PhAllocate(Length);
|
|
memcpy(Context->Data, Data, Length);
|
|
PhpHexEditSetBuffer(hwnd, Context, Context->Data, Length);
|
|
Context->UserBuffer = FALSE;
|
|
Context->AllowLengthChange = TRUE;
|
|
}
|