unit frmMemoryViewExUnit; {$mode delphi} interface uses {$ifdef darwin} macport, LCLType, LCLIntf, {$endif} {$ifdef windows} windows, {$endif} Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls, ComCtrls, Menus, memdisplay, newkernelhandler, cefuncproc, syncobjs, math, savedscanhandler, foundlisthelper, CustomTypeHandler, symbolhandler, inputboxtopunit, commonTypeDefs, GL, GLext, Types, DPIHelper, betterControls; type TMVCompareMethod=(cmOr, cmXor, cmAnd); type TMemoryDataSource=class(TThread) private cs: TCriticalSection; address: ptruint; buf: pbytearray; bufsize: integer; faddresslistonly: boolean; fcompareagainstsavedscan: boolean; fvartype: TVariableType; fvarsize: integer; comparemethod: TMVCompareMethod; temppagebuf: pbytearray; addresslist: TFoundList; previousvaluelist: TSavedScanHandler; ct: TCustomtype; public procedure lock; procedure unlock; procedure setRegion(address: ptruint; buf: pointer; size: integer); procedure execute; override; procedure fetchmem; procedure setaddresslist(state: boolean; listname: string); procedure setcompare(state: boolean; compareMethod: TMVCompareMethod; listname: string); constructor create(suspended: boolean); end; TMemEntry = record address:PtrUInt; size:integer; end; { TfrmMemoryViewEx } TfrmMemoryViewEx = class(TForm) btnMEMMAP: TButton; cbAddresslist: TComboBox; cbAddresslistOnly: TCheckBox; cbColor: TComboBox; cbCompare: TCheckBox; cbSavedList: TComboBox; cbType: TComboBox; edtAddress: TEdit; edtPitch: TEdit; Label1: TLabel; Label2: TLabel; Label3: TLabel; lblZOOM: TLabel; lblAddress: TLabel; Panel4: TPanel; Panel5: TPanel; Panel6: TPanel; pbMEM: TPaintBox; Panel1: TPanel; Panel2: TPanel; Panel3: TPanel; rbAnd: TRadioButton; rbOr: TRadioButton; rbXor: TRadioButton; sbVERT: TScrollBar; tbPitch: TTrackBar; tbZoom: TTrackBar; procedure btnMEMMAPClick(Sender: TObject); procedure cbAddresslistChange(Sender: TObject); procedure cbAddresslistOnlyChange(Sender: TObject); procedure cbAddresslistDropDown(Sender: TObject); procedure cbCompareChange(Sender: TObject); procedure cbSavedListChange(Sender: TObject); procedure cbColorChange(Sender: TObject); procedure cbTypeChange(Sender: TObject); procedure edtAddressKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure edtPitchChange(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure FormResize(Sender: TObject); procedure FormShow(Sender: TObject); procedure Panel1Click(Sender: TObject); procedure pbMEMPaint(Sender: TObject); procedure sbVERTChange(Sender: TObject); procedure sbVERTKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure tbPitchChange(Sender: TObject); procedure tbZoomChange(Sender: TObject); Procedure mdMouseDown(Sender:Tobject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure mdMouseMove(Sender:Tobject;Shift: TShiftState; X, Y: Integer); procedure mdMouseUp(Sender:Tobject;Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure mdMouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean); procedure mdRecenterDrag; private buf: pbytearray; bufsize: integer; datasource: TMemoryDataSource; showing_help:boolean; mem_map:TList; procedure Panel1DblClick(Sender: TObject); function getCompareMethod: TMVCompareMethod; function ondata(newAddress: ptruint; PreferedMinimumSize: integer; var newbase: pointer; var newsize: integer): boolean; procedure UpdateZoomText(val:single); procedure UpdateAddress(val:PtrUInt); procedure UpdateZoomBar(val:single); procedure UpdateScrollbar(val:PtrUInt); procedure ClearMemMap; procedure MDResize; public md: TMemDisplay; procedure DisplayHelp; end; implementation uses MemoryBrowserFormUnit, MainUnit, ProcessHandlerUnit; {$R *.lfm} { TMemoryDataSource } constructor TMemoryDataSource.create(suspended: boolean); begin cs:=tcriticalsection.create; getmem(temppagebuf, 4096); //so it doesn't need to be allocated/freed each fetchmem call inherited create(suspended); end; procedure TMemoryDataSource.setcompare(state: boolean; compareMethod: TMVCompareMethod; listname: string); begin if state then begin cs.Enter; try if previousvaluelist<>nil then freeandnil(previousvaluelist); previousvaluelist:=TSavedScanHandler.create(mainform.memscan.GetScanFolder, listname); previousvaluelist.AllowNotFound:=true; previousvaluelist.AllowRandomAccess:=true; self.compareMethod:=comparemethod; finally cs.Leave; end; end; fcompareagainstsavedscan:=state; end; procedure TMemoryDataSource.setaddresslist(state: boolean; listname: string); begin if state then begin //Open a "PreviousValue object for the current memscan results. cs.Enter; try if addresslist<>nil then freeandnil(addresslist); addresslist:=TFoundList.create(nil, mainform.memscan, listname); addresslist.Initialize; fvartype:=mainform.memscan.VarType; ct:=mainform.memscan.CustomType; fvarsize:=mainform.memscan.Getbinarysize div 8; finally cs.leave; end; end; faddresslistonly:=state; fetchmem; //update now end; procedure TMemoryDataSource.fetchmem; var x: ptrUint; a,a2: ptruint; s: integer; s2: integer; p: PByteArray; i: qword; j: integer; toread: integer; begin lock; if buf<>nil then //not yet initialized begin a:=address; i:=qword(-1); if faddresslistonly then i:=addresslist.FindClosestAddress(address-fvarsize+1); //all following accesses will be sequential toread:=bufsize; //while a0 do begin s:=minX((address+bufsize)-a, 4096-(a mod 4096)); //the number of bytes left in this page or for this buffer x:=0; if faddresslistonly then begin //check if this page has any addresses. zeromemory(@buf[a-address], s); if int64(i)<>-1 then begin a2:=addresslist.GetAddress(i); //get the first addresses that belong to this page (or has bytes in it) while (is then //cut off if it falls after the region begin s2:=s-(a2-a); end; if s2>0 then begin x:=0; ReadProcessMemory(processhandle, pointer(a2), @buf[a2-address], s2, x); if fcompareagainstsavedscan and (previousvaluelist<>nil) then begin //get the saved scan p:=previousvaluelist.getpointertoaddress(a2, fvartype, ct); if p<>nil then begin case comparemethod of cmor: for j:=0 to x-1 do buf[a2-address+j]:=buf[a2-address+j] or p[j]; cmxor: for j:=0 to x-1 do buf[a2-address+j]:=buf[a2-address+j] xor p[j]; cmand: for j:=0 to x-1 do buf[a2-address+j]:=buf[a2-address+j] and p[j]; end; end; end; inc(i); a2:=addresslist.GetAddress(i); end; end; end; end else begin ReadProcessMemory(processhandle, pointer(a), @buf[a-address], s, x); if x=PreferedMinimumSize; //allow the move if allocated enough memory end; procedure TfrmMemoryViewEx.FormCreate(Sender: TObject); begin //create a datasource thread mem_map:=Tlist.Create; datasource:=TMemoryDataSource.create(true); //possible to add multiple readers in the future md:=TMemDisplay.Create(self); md.onData:=ondata; md.OnMouseDown:=mdMouseDown; md.OnMouseUp:=mdMouseUp; md.OnMouseMove:=mdMouseMove; md.OnMouseWheel:=mdMouseWheel; md.OnDblClick:=Panel1DblClick; md.OnClick:=Panel1Click; md.setPitch(1024); md.zoom:=1; md.Align:=alClient; md.parent:=panel1; bufsize:=panel1.Height*md.pitch; getmem(buf,bufsize); datasource.setRegion(MemoryBrowser.hexview.Address and ptruint(not $FFF), buf, bufsize); md.setPointer(MemoryBrowser.hexview.Address and ptruint(not $FFF), buf, bufsize); UpdateZoomBar(md.zoom); UpdateZoomText(md.zoom); UpdateAddress(md.address); edtPitch.Text:=IntTostr(md.pitch); tbPitch.Position:=md.pitch; datasource.Start; end; procedure TfrmMemoryViewEx.MDResize; var psize,newsize: integer; newp:Pointer; a:PtrUInt; begin if(not Assigned(md))then exit; if(not Assigned(datasource))then exit; psize:=(trunc(md.height / md.zoom)+16)*md.pitch; a:=md.address; if(assigned(md.fOnData) and md.fOnData(a,psize,newp,newsize))then begin md.p:=newp; md.size:=newsize; end; end; procedure TfrmMemoryViewEx.FormShow(Sender: TObject); begin MDResize; AdjustComboboxSize(cbAddresslist, canvas); AdjustComboboxSize(cbSavedList, canvas); end; procedure TfrmMemoryViewEx.FormResize(Sender: TObject); begin MDResize; end; procedure TfrmMemoryViewEx.ClearMemMap; var i:integer; entry:^TMemEntry; begin if(not Assigned(mem_map))then exit; for i:=0 to mem_map.Count-1 do begin entry:=mem_map[i]; Dispose(entry); end; mem_map.Clear; end; procedure TfrmMemoryViewEx.FormDestroy(Sender: TObject); begin if datasource<>nil then begin datasource.Terminate; datasource.WaitFor; freeandnil(datasource); end; ClearMemMap; end; procedure TfrmMemoryViewEx.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if(key=VK_F1)then begin if(not showing_help)then begin showing_help:=true; DisplayHelp; showing_help:=false; end; end else if(key=VK_ESCAPE)then self.close else if(key=VK_F5)then edtAddress.SetFocus else if(key=Ord('g'))then begin if(ssCtrl in Shift)then edtAddress.SetFocus; end; end; procedure TfrmMemoryViewEx.Panel1Click(Sender: TObject); begin sbVERT.SetFocus; end; procedure TfrmMemoryViewEx.pbMEMPaint(Sender: TObject); var i:integer; entry:^TMemEntry; max,a,b:Double; y,y2,tmp,w:integer; lasty,lasty2:integer; begin if(not Assigned(mem_map))then exit; max:=High(UINT); if(processhandler.is64Bit)then max:=High(PtrUint); max:=max / pbMEM.Height; if(max=0)then exit; lasty:=-1; lasty2:=-1; pbMEM.Canvas.Brush.Color:=clDefault; pbMEM.Canvas.Clear; pbMEM.Canvas.Brush.Color:=clRed; w:=pbMEM.Width; for i:=0 to mem_map.Count-1 do begin entry:=mem_map[i]; a:=entry.address; b:=a / max; y:=Trunc(b); b:=(a+entry.size)/max; y2:=trunc(b); if(y=lasty)then begin if(y2=lasty2)then continue; end; tmp:=y2; if(y2=y)then tmp:=y+1; pbMEM.Canvas.FillRect(0,y,w,tmp); lasty:=y; lasty2:=y2; end; end; procedure TfrmMemoryViewEx.sbVERTChange(Sender: TObject); var i:integer; x,y:PtrUInt; begin if(not Assigned(processhandler))then exit; y:=sbVERT.Max; if(y=0)then exit; x:=$FFFFFFFF; if(processhandler.is64Bit)then x:=PtrUInt(-1); x:=x div y; i:=sbVERT.Position; x:=x*i; md.MoveTo(0,0); md.setPointer(x); UpdateAddress(x); end; procedure TfrmMemoryViewEx.sbVERTKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); var i:integer; start_address,end_address,next_address:PtrUInt; entry:^TMemEntry; found:boolean; next:boolean; function is_range_valid(a,b:PtrUInt):boolean; var i:integer; begin result:=false; for i:=0 to mem_map.Count-1 do begin entry:=mem_map[i]; if(entry.address >= a)then begin if(entry.address <= b)then begin result:=true; break; end; end; if((entry.address+entry.size) >= a)then begin if((entry.address+entry.size) <= b)then begin result:=true; break; end; end; if(a >= entry.address)then begin if(b <= (entry.address+entry.size))then begin result:=true; break; end; end; end; end; procedure do_normal_scroll; var delta:PtrUInt; a,b,c:LongWord; begin if(processhandler.is64Bit)then begin delta:=end_address-start_address; end else begin a:=start_address; b:=end_address; c:=b-a; delta:=c; end; case key of VK_RIGHT,VK_DOWN: delta:=delta div 4; VK_LEFT,VK_UP: delta:=-delta div 4; VK_NEXT: delta:=(delta*15) div 16; VK_PRIOR: delta:=-(delta*15) div 16; end; next_address:=start_address+delta; end; begin if(not(key in [VK_NEXT,VK_PRIOR,VK_LEFT,VK_RIGHT,VK_UP,VK_DOWN]))then exit; if(not assigned(mem_map))then exit; found:=false; start_address:=md.getAddressFromScreenPosition(0,0); end_address:=md.getAddressFromScreenPosition(0,md.Height-1); next_address:=start_address; if(is_range_valid(start_address,end_address) or (mem_map.Count=0))then begin do_normal_scroll; found:=true; end else begin next_address:=start_address; next:=false; if(key in [VK_NEXT,VK_RIGHT,VK_DOWN])then next:=true; if(next)then begin for i:=0 to mem_map.Count-1 do begin entry:=mem_map[i]; if(entry.address>end_address)then begin next_address:=entry.address; found:=true; break; end; end; if(not found)then begin if(mem_map.Count>0)then begin entry:=mem_map[0]; next_address:=entry.address; end else do_normal_scroll; found:=true; end; end else begin for i:=mem_map.Count-1 downto 0 do begin entry:=mem_map[i]; if((entry.address+entry.size)0)then begin entry:=mem_map[mem_map.Count-1]; next_address:=entry.address; end else next_address:=0; found:=true; end; end; end; if(found)then begin md.MoveTo(0,0); md.setPointer(next_address); UpdateAddress(next_address); UpdateScrollbar(next_address); key:=0; end; end; procedure TfrmMemoryViewEx.cbAddresslistOnlyChange(Sender: TObject); begin cbAddresslist.enabled:=cbAddresslistOnly.checked; cbCompare.Enabled:=cbAddresslistOnly.checked; cbSavedList.enabled:=cbAddresslistOnly.checked; if datasource<>nil then datasource.setaddresslist(cbAddresslistOnly.checked, 'TMP'); end; procedure TfrmMemoryViewEx.cbAddresslistChange(Sender: TObject); begin if cbAddresslist.ItemIndex=0 then datasource.setaddresslist(cbAddresslistOnly.checked, 'TMP') else datasource.setaddresslist(cbAddresslistOnly.checked, cbAddresslist.text); end; procedure TfrmMemoryViewEx.btnMEMMAPClick(Sender: TObject); var mbi : _MEMORY_BASIC_INFORMATION; address: PtrUInt; mappedfilename: string; kernelmode: boolean=false; entry:^TMemEntry; begin if(not Assigned(md))then exit; if(not Assigned(mem_map))then exit; if DBKLoaded then kernelmode:=ssCtrl in GetKeyShiftState; ClearMemMap; address:=0; mbi.RegionSize:=$1000; while (GetRegionInfo(processhandle,pointer(address),mbi,sizeof(mbi), mappedfilename)<>0) and ((address+mbi.RegionSize)>address) do begin if(mbi.State=MEM_COMMIT)then begin New(entry); entry.address:=address; entry.size:=mbi.RegionSize; mem_map.Add(Pointer(entry)); end; inc(address,mbi.RegionSize); if not kernelmode then begin if processhandler.is64Bit then begin {$IFDEF CPU64} if (address>=QWORD($8000000000000000)) then exit; {$ENDIF} end else if (address>=$80000000) then exit; end; end; pbMEM.Repaint; end; procedure TfrmMemoryViewEx.cbAddresslistDropDown(Sender: TObject); begin TComboBox(sender).Items.Clear; TComboBox(sender).DropDownCount:=mainform.memscan.getsavedresults(TComboBox(sender).Items)+1; TComboBox(sender).Items.Insert(0,'Current scanlist'); end; function TfrmMemoryViewEx.getCompareMethod: TMVCompareMethod; //returns the compare method currently selected begin result:=cmOr; if rbxor.checked then result:=cmxOr else if rbAnd.checked then result:=cmAnd; end; procedure TfrmMemoryViewEx.cbCompareChange(Sender: TObject); begin cbSavedList.enabled:=cbCompare.checked; rbOr.enabled:=cbCompare.checked; rbAnd.enabled:=cbCompare.checked; rbXor.enabled:=cbCompare.checked; datasource.setcompare(cbCompare.checked, getCompareMethod, 'TMP'); end; procedure TfrmMemoryViewEx.cbSavedListChange(Sender: TObject); begin if cbSavedList.ItemIndex=0 then datasource.setcompare(cbCompare.checked, getCompareMethod, 'TMP') else datasource.setcompare(cbCompare.checked, getCompareMethod, cbSavedList.text); end; procedure TfrmMemoryViewEx.cbColorChange(Sender: TObject); begin { Dithered (1 Byte/pixel) RGB (3 Bytes/Pixel) BGR (3 Bytes/Pixel) RGBA (4 Bytes/Pixel) BGRA (4 Bytes/Pixel) } case cbcolor.itemindex of 0: md.setFormat(GL_COLOR_INDEX); 1: md.setFormat(GL_RGB); 2: md.setFormat(GL_BGR); 3: md.setFormat(GL_RGBA); 4: md.setFormat(GL_BGRA); end; end; procedure TfrmMemoryViewEx.cbTypeChange(Sender: TObject); begin { Byte Unsigned Short Short Unsigned Int Int Float 3-3-2 2-3-3 R 5-6-5 5-6-5 R 4-4-4-4 4-4-4-4 R 5-5-5-1 1-5-5-5 R 8-8-8-8 8-8-8-8 R 10-10-10-2 2-10-10-10 R } case cbtype.ItemIndex of 0: md.setType(GL_UNSIGNED_BYTE); 1: md.setType(GL_UNSIGNED_SHORT); 2: md.setType(GL_SHORT); 3: md.setType(GL_UNSIGNED_INT); 4: md.setType(GL_INT); 5: md.setType(GL_FLOAT); 6: md.setType(GL_UNSIGNED_BYTE_3_3_2); 7: md.setType(GL_UNSIGNED_BYTE_2_3_3_REV); 8: md.setType(GL_UNSIGNED_SHORT_5_6_5); 9: md.setType(GL_UNSIGNED_SHORT_5_6_5_REV); 10: md.setType(GL_UNSIGNED_SHORT_4_4_4_4); 11: md.setType(GL_UNSIGNED_SHORT_4_4_4_4_REV); 12: md.setType(GL_UNSIGNED_SHORT_5_5_5_1); 13: md.setType(GL_UNSIGNED_SHORT_1_5_5_5_REV); 14: md.setType(GL_UNSIGNED_INT_8_8_8_8); 15: md.setType(GL_UNSIGNED_INT_8_8_8_8_REV); 16: md.setType(GL_UNSIGNED_INT_10_10_10_2); 17: md.setType(GL_UNSIGNED_INT_2_10_10_10_REV); end; end; procedure TfrmMemoryViewEx.Panel1DblClick(Sender: TObject); var c: tpoint; address: ptruint; begin c:=md.ScreenToClient(mouse.cursorpos); address:=md.getAddressFromScreenPosition(c.x, c.y); MemoryBrowser.hexview.Address:=address; MemoryBrowser.show; end; procedure TfrmMemoryViewEx.edtPitchChange(Sender: TObject); var newpitch: integer; cb:TNotifyEvent; x:integer; begin try newpitch:=strtoint(edtpitch.Caption); md.setPitch(newpitch); edtPitch.Font.Color:=clDefault; if(md.fPitch <> newpitch)then begin cb:=edtPitch.OnChange; edtPitch.OnChange:=nil; edtPitch.Caption:=IntToStr(md.fPitch); edtPitch.OnChange:=cb; end; MDResize; cb:=tbPitch.OnChange; tbPitch.OnChange:=nil; x:=md.fPitch; if(x>tbPitch.Max)then x:=tbPitch.Max; tbPitch.Position:=x; tbPitch.OnChange:=cb; except edtPitch.Font.Color:=clred; end; end; procedure TfrmMemoryViewEx.tbPitchChange(Sender: TObject); var cb:TNotifyEvent; pos:integer; begin cb:=edtPitch.OnChange; edtPitch.OnChange:=nil; pos:=tbPitch.position; if((GetKeyState(VK_CONTROL) and $8000) <> 0)then pos:=pos*10; edtPitch.caption:=inttostr(pos); edtPitch.OnChange:=cb; md.setPitch(pos); MDResize; end; procedure TfrmMemoryViewEx.UpdateZoomText(val:single); begin lblZOOM.Caption:=Format('Zoom:%f',[val]); end; procedure TfrmMemoryViewEx.UpdateZoomBar(val:single); var i:integer; begin if(val<=1)then begin i:=Round(val*256); tbZoom.Position:=i end else begin i:=Round(((val-1)*8)+256); tbZoom.Position:=i; end; end; procedure TfrmMemoryViewEx.tbZoomChange(Sender: TObject); var i:integer; tmp:single; begin i:=tbZoom.Position; if(i>256)then begin tmp:=i-256; tmp:=tmp/8; tmp:=tmp+1; md.zoom:=tmp; end else begin tmp:=i; tmp:=tmp/256.0; md.zoom:=tmp; end; UpdateZoomText(md.zoom); md.render; end; procedure TfrmMemoryViewEx.UpdateScrollbar(val:PtrUInt); var y:double; x:Qword; cb:TNotifyEvent; begin if(not Assigned(processhandler))then exit; x:=$FFFFFFFF; if(processhandler.is64Bit)then x:=qword(-1); y:=val/x; y:=Abs(y*sbVERT.Max); cb:=sbVERT.OnChange; sbVERT.OnChange:=nil; sbVERT.Position:=Round(y); sbVERT.OnChange:=cb; end; procedure TfrmMemoryViewEx.UpdateAddress(val:PtrUInt); var s:string; fmt:string; begin fmt:='%.8X'; if((val and $FFFFFFFF00000000) <> 0)then fmt:='%.16X'; s:=Format(fmt,[val]); edtAddress.Caption:=s; edtAddress.SelLength:=0; end; procedure TfrmMemoryViewEx.edtAddressKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); var val:PtrUInt; begin if(key=VK_RETURN)then begin md.MoveTo(0,0); val:=symhandler.getAddressFromName(edtAddress.Text); md.setPointer(val); UpdateAddress(val); UpdateScrollbar(val); end; end; Procedure TfrmMemoryViewEx.mdMouseDown(Sender:Tobject;Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var a:PtrUInt; begin if button=mbleft then begin md.isDragging:=true; md.DragOrigin.x:=x; md.DragOrigin.y:=y; md.PosOrigin.x:=md.fXpos; md.PosOrigin.y:=md.fYpos; md.dragaddress:=ssCtrl in shift; if(md.dragAddress)then md.addressOrigin:=md.address; a:=md.getAddressFromScreenPosition(0,0); UpdateAddress(a); UpdateScrollbar(a); end; end; procedure TfrmMemoryViewEx.mdMouseMove(Sender:Tobject;Shift: TShiftState; X, Y: Integer); var a: ptruint; newp: pointer; newsize: integer; begin if(md.isDragging)then begin with md do begin if(dragaddress)then begin //move the address by the difference in X position a:=addressOrigin-trunc((PosOrigin.x-(DragOrigin.x-x))/zoom)*fPixelByteSize; if assigned(fOnData) and fOnData(a,size,newp,newsize) then begin address:=a; p:=newp; size:=newsize; LimitCoordinates; //recheck with the new ypos. (in case of size change (end of buf?)) end; render; end else MoveTo(PosOrigin.x-(DragOrigin.x-x), PosOrigin.y+(DragOrigin.y-y)); a:=md.getAddressFromScreenPosition(0,0); UpdateAddress(a); UpdateScrollbar(a); end; end; end; procedure TfrmMemoryViewEx.mdMouseUp(Sender:Tobject;Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if button=mbleft then md.isDragging:=false; //todo: Add a pixel click event handler md.render; end; procedure TfrmMemoryViewEx.mdMouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean); var oldx, oldy: single; tmp:ptruint; val:integer; a:PtrUInt; begin with md do begin if(ssctrl in Shift)then begin oldx:=(-fXpos+(MousePos.x))/fZoom; oldy:=(fYpos+(MousePos.y))/fZoom; if WheelDelta>0 then begin if(fzoom<=32)then begin //zoom in //get the pixel at center of the screen if ssShift in Shift then fZoom:=fZoom * 1.2 else fZoom:=fZoom * 2; fXpos:=trunc(-oldx*fZoom+(MousePos.x)); fypos:=trunc(oldy*fZoom-(MousePos.y)); end; end else if WheelDelta<0 then begin if(fZoom>0.2)then begin //zoom out if ssShift in Shift then fZoom:=fZoom / 1.2 else fZoom:=fZoom / 2; fXpos:=trunc(-oldx*fZoom+(MousePos.x)); fypos:=trunc(oldy*fZoom-(MousePos.y)); end; end; UpdateZoomText(fZoom); UpdateZoomBar(fZoom); end else if(ssshift in Shift)then begin val:=10; if(ssRight in shift)then val:=1; if(WheelDelta<0)then setPitch(fPitch-(val*fPixelByteSize)) else setPitch(fPitch+(val*fPixelByteSize)); edtPitch.Caption:=IntToStr(fPitch); tbPitch.Position:=fPitch; end else begin val:=1; if(ssRight in shift)then begin val:=4; if(ssAlt in shift)then val:=40; end else if(ssAlt in shift)then val:=10; val:=val*(-WheelDelta); fYpos:=fYpos+val; end; LimitCoordinates; setupFont; render; a:=md.getAddressFromScreenPosition(0,0); UpdateAddress(a); UpdateScrollbar(a); end; end; procedure TfrmMemoryViewEx.DisplayHelp; var s,msg:string; begin msg:='ctrl+wheel=zoom'#13#10+ 'shift+wheel=pitch'#13#10+ '(+RMB)'#13#10+ 'wheel=scroll'#13#10+ '(+RMB or +alt)'#13#10+ 'ctrl+click=move address'#13#10; s:=Format(#13#10'xpos=%d ypos=%d'#13#10+ 'pixelsize=%d'#13#10+ 'size=%.8X'#13#10+ 'addr=%.8X'#13#10+ 'width=%d'#13#10+ 'height=%d'#13#10+ 'ptr=%p'#13#10, [md.fXpos,md.fYpos, md.fPixelByteSize, md.size, md.address, md.Width, md.Height, md.p]); msg:=msg+s; MessageBox(self.Handle,PAnsiChar(msg),'MV HELP',MB_OK {$ifdef windows}or MB_SYSTEMMODAL{$endif}); end; procedure TfrmMemoryViewEx.mdRecenterDrag; var p: tpoint; begin if(md.isDragging)then begin p:=self.ScreenToClient(mouse.cursorpos); mdMouseDown(self,mbLeft, [], p.x, p.y); end; end; end.