From 4cbac040312ef23be03ca42947f06e77f7eccf9c Mon Sep 17 00:00:00 2001 From: Steven Wichers Date: Thu, 8 Aug 2019 23:23:20 -0700 Subject: [PATCH] Initial commit of project into git. --- .gitignore | 73 + README.md | 42 + XDKAssist.dpr | 26 + XDKAssist.dproj | 96 + XDKAssist.res | Bin 0 -> 5708 bytes XDKAssist.todo | 19 + docs/README.md | 11 + docs/What_Hack0r_Complains_About_&_Wants.txt | 35 + docs/xdkassist.ini | 57 + docs/xdkassist.notes | 44 + docs/xdkcmds.txt | 172 + hexcontrol/Delphi-7/MPHexEditor_D7.dpk | 38 + hexcontrol/Delphi-7/MPHexEditor_D7.res | Bin 0 -> 876 bytes hexcontrol/MPDELVER.INC | 100 + hexcontrol/MPHexEditor.RES | Bin 0 -> 1192 bytes hexcontrol/MPHexEditor.chm | Bin 0 -> 126777 bytes hexcontrol/MPHexEditorReg.dcr | Bin 0 -> 3428 bytes hexcontrol/hexeditor.html | 690 ++ hexcontrol/mphexeditor.pas | 8708 ++++++++++++++++++ hexcontrol/mphexeditorex.pas | 3928 ++++++++ hexcontrol/mphexeditorreg.pas | 120 + resources/icons/CHIP.ICO | Bin 0 -> 766 bytes resources/icons/ICQ.ico | Bin 0 -> 23558 bytes resources/icons/IRC.ico | Bin 0 -> 23558 bytes resources/icons/Status_NotAvailable.ico | Bin 0 -> 2862 bytes resources/icons/Status_Play.ico | Bin 0 -> 2862 bytes resources/icons/Status_Stop.ico | Bin 0 -> 2862 bytes resources/icons/Stop.ico | Bin 0 -> 318 bytes resources/icons/black.ico | Bin 0 -> 13502 bytes resources/icons/button_restart4.jpg | Bin 0 -> 861 bytes resources/icons/camera.ico | Bin 0 -> 22486 bytes resources/icons/console-default.ico | Bin 0 -> 8166 bytes resources/icons/console.ico | Bin 0 -> 8166 bytes resources/icons/cpanel.ico | Bin 0 -> 1078 bytes resources/icons/folder-x.ico | Bin 0 -> 4710 bytes resources/icons/folder.ico | Bin 0 -> 10134 bytes resources/icons/green.ico | Bin 0 -> 13502 bytes resources/icons/icon_restart.gif | Bin 0 -> 131 bytes resources/icons/move.ico | Bin 0 -> 766 bytes resources/icons/muldoc.ico | Bin 0 -> 1078 bytes resources/icons/newcsl.ico | Bin 0 -> 10134 bytes resources/icons/nuke.ico | Bin 0 -> 767 bytes resources/icons/play.ico | Bin 0 -> 318 bytes resources/icons/qcdam.ico | Bin 0 -> 766 bytes resources/icons/reboot.ico | Bin 0 -> 10134 bytes resources/icons/replace.ico | Bin 0 -> 766 bytes resources/icons/restart.ico | Bin 0 -> 318 bytes resources/icons/sdk.ico | Bin 0 -> 4710 bytes resources/icons/text.ico | Bin 0 -> 1398 bytes resources/icons/thdtree.ico | Bin 0 -> 766 bytes resources/icons/volume.ico | Bin 0 -> 10134 bytes resources/icons/xbe.ico | Bin 0 -> 8478 bytes resources/icons/xbox.ico | Bin 0 -> 4710 bytes resources/xheader.bmp | Bin 0 -> 7308 bytes resources/xwmark.bmp | Bin 0 -> 26136 bytes src/AppGlobal.pas | 203 + src/Breakpoint.pas | 47 + src/LogStream.pas | 47 + src/Main.dfm | 1724 ++++ src/Main.pas | 1843 ++++ src/Settings.pas | 101 + src/Tool.pas | 15 + src/XBOXManager.pas | 194 + 63 files changed, 18333 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 XDKAssist.dpr create mode 100644 XDKAssist.dproj create mode 100644 XDKAssist.res create mode 100644 XDKAssist.todo create mode 100644 docs/README.md create mode 100644 docs/What_Hack0r_Complains_About_&_Wants.txt create mode 100644 docs/xdkassist.ini create mode 100644 docs/xdkassist.notes create mode 100644 docs/xdkcmds.txt create mode 100644 hexcontrol/Delphi-7/MPHexEditor_D7.dpk create mode 100644 hexcontrol/Delphi-7/MPHexEditor_D7.res create mode 100644 hexcontrol/MPDELVER.INC create mode 100644 hexcontrol/MPHexEditor.RES create mode 100644 hexcontrol/MPHexEditor.chm create mode 100644 hexcontrol/MPHexEditorReg.dcr create mode 100644 hexcontrol/hexeditor.html create mode 100644 hexcontrol/mphexeditor.pas create mode 100644 hexcontrol/mphexeditorex.pas create mode 100644 hexcontrol/mphexeditorreg.pas create mode 100644 resources/icons/CHIP.ICO create mode 100644 resources/icons/ICQ.ico create mode 100644 resources/icons/IRC.ico create mode 100644 resources/icons/Status_NotAvailable.ico create mode 100644 resources/icons/Status_Play.ico create mode 100644 resources/icons/Status_Stop.ico create mode 100644 resources/icons/Stop.ico create mode 100644 resources/icons/black.ico create mode 100644 resources/icons/button_restart4.jpg create mode 100644 resources/icons/camera.ico create mode 100644 resources/icons/console-default.ico create mode 100644 resources/icons/console.ico create mode 100644 resources/icons/cpanel.ico create mode 100644 resources/icons/folder-x.ico create mode 100644 resources/icons/folder.ico create mode 100644 resources/icons/green.ico create mode 100644 resources/icons/icon_restart.gif create mode 100644 resources/icons/move.ico create mode 100644 resources/icons/muldoc.ico create mode 100644 resources/icons/newcsl.ico create mode 100644 resources/icons/nuke.ico create mode 100644 resources/icons/play.ico create mode 100644 resources/icons/qcdam.ico create mode 100644 resources/icons/reboot.ico create mode 100644 resources/icons/replace.ico create mode 100644 resources/icons/restart.ico create mode 100644 resources/icons/sdk.ico create mode 100644 resources/icons/text.ico create mode 100644 resources/icons/thdtree.ico create mode 100644 resources/icons/volume.ico create mode 100644 resources/icons/xbe.ico create mode 100644 resources/icons/xbox.ico create mode 100644 resources/xheader.bmp create mode 100644 resources/xwmark.bmp create mode 100644 src/AppGlobal.pas create mode 100644 src/Breakpoint.pas create mode 100644 src/LogStream.pas create mode 100644 src/Main.dfm create mode 100644 src/Main.pas create mode 100644 src/Settings.pas create mode 100644 src/Tool.pas create mode 100644 src/XBOXManager.pas diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..acb43a0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,73 @@ +.hg/ +*.dproj.local +build/ + +# Uncomment these types if you want even more clean repository. But be careful. +# It can make harm to an existing project source. Read explanations below. +# +# Resource files are binaries containing manifest, project icon and version info. +# They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files. +#*.res +# +# Type library file (binary). In old Delphi versions it should be stored. +# Since Delphi 2009 it is produced from .ridl file and can safely be ignored. +#*.tlb +# +# Diagram Portfolio file. Used by the diagram editor up to Delphi 7. +# Uncomment this if you are not using diagrams or use newer Delphi version. +*.ddp +# +# Visual LiveBindings file. Added in Delphi XE2. +# Uncomment this if you are not using LiveBindings Designer. +*.vlb +# +# Deployment Manager configuration file for your project. Added in Delphi XE2. +# Uncomment this if it is not mobile development and you do not use remote debug feature. +*.deployproj +# +# C++ object files produced when C/C++ Output file generation is configured. +# Uncomment this if you are not using external objects (zlib library for example). +*.obj +# + +# Delphi compiler-generated binaries (safe to delete) +*.exe +*.dll +*.bpl +*.bpi +*.dcp +*.so +*.apk +*.drc +*.map +*.dres +*.rsm +*.tds +*.dcu +*.lib +*.a +*.o +*.ocx + +# Delphi autogenerated files (duplicated info) +*.cfg +*.hpp +*Resource.rc + +# Delphi local files (user-specific info) +*.local +*.identcache +*.projdata +*.tvsconfig +*.dsk + +# Delphi history and backups +__history/ +__recovery/ +*.~* + +# Castalia statistics file (since XE7 Castalia is distributed with Delphi) +*.stat + +# Boss dependency manager vendor folder https://github.com/HashLoad/boss +modules/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3958793 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# XDK Assist: What is XDK Assist? + +XDK Assist is an application designed to allow you to easily communicate with an (original) Xbox in order to debug running applications. This was once the go-to application for creating trainers for modified Xbox systems. + +The original XDK Assist was originally closed source software. It was a product of me being on the original EvolutionX team and creating custom tools for our work. It eventually evolved into something more robust and I released it to the public as-is. A few updates were released, but the project never had an official homepage or source of truth for downloads. + +# Why release the code so late? + +I recently saw that some people are still using XDK Assist today even though it's been almost two decades since the original Xbox was released. Two decades ago I was a much different developer and wasn't so open to open sourcing my programs. Today, however, I'm all for it. I'm hoping release of this code might benefit the few folks out there who still use it. At a minimum it at least helps to preserve what I consider an important part of the Xbox homebrew scene. + +## Not so fast... + +There are, however, some caveats. I was also a much (much) more junior developer two decades ago and did not use proper source control systems. Shoot, two decades ago I wasn't much of a developer at all. In the codebase you'll find a lack of decent commenting, organization, naming, etc. You'll also find lines of functional code commented out with no explanation as to why. + +This release of the code is also compiled from multiple backups. I've attempted to pull in the latest changes from each backup, but it's entirely possible I've missed something or pulled out a vital component. + +Given the above items it's possible this application actually does not work. I have no way to test that this application still works as expected, since it requires an original Xbox a running debug bios. It will compile and run as expected, but there are things I simply cannot review. For example, `TBreakpoint` is commented out in `XBOXManager.pas` and is present in `Breakpoint.pas`. Was that a refactor in progress? Did I finish? Who knows. + +# Requirements + +* Delphi 2007 +* Indy 10.1.1 +* TMPHexEditor (bundled: hexcontrol) +* A modded original Xbox with a debug bios loaded (i.e. EvoX) + +I was able to get this to compile again under Delphi 2007 with Indy 10 that ships with Delphi 2007. It also requires installation of a TMPHexEditor component (bundled with) from Markus Stephany (). You can find a newer version of this component, but it is not tested. You may be able to get it to compile with different versions of Delphi or Indy, but those are untested as well. + +# Shoutouts + +It's a bit juvenile but I still want to shout out to several of the folks I spent a lot of time reversing and developing software with. Some good times! I'm missing several folks but it's hard to remember screennames after so long. + +acidflash +dootdoo +Hack0r +kai`ckul +khuong +jokko +Thatguy2001 +Tsongkie +xbox7887 +[Death] +[sheep] diff --git a/XDKAssist.dpr b/XDKAssist.dpr new file mode 100644 index 0000000..589ee99 --- /dev/null +++ b/XDKAssist.dpr @@ -0,0 +1,26 @@ +program XDKAssist; + +uses + Forms, + Dialogs, + SysUtils, + Main in 'src\Main.pas' {frmMain}, + Breakpoint in 'src\Breakpoint.pas', + LogStream in 'src\LogStream.pas', + Tool in 'src\Tool.pas', + Settings in 'src\Settings.pas', + AppGlobal in 'src\AppGlobal.pas', + XBOXManager in 'src\XBOXManager.pas'; + +{$R *.res} + +begin + Application.Initialize; + Application.Title := 'XDK Assist'; + try + Application.CreateForm(TfrmMain, frmMain); + Application.Run; + except + on E: Exception do ShowMessage(E.Message); + end; +end. diff --git a/XDKAssist.dproj b/XDKAssist.dproj new file mode 100644 index 0000000..4ac5f19 --- /dev/null +++ b/XDKAssist.dproj @@ -0,0 +1,96 @@ + + + + {bafd21de-c786-409f-87d5-f9f2162c20e4} + XDKAssist.dpr + Debug + AnyCPU + DCC32 + build\debug\bin\XDKAssist.exe + true + vcl;rtl;vclx;vclactnband;dbrtl;bdertl;dsnap;teeUI;teedb;tee;adortl;IndyCore;IndySystem;IndyProtocols;xmlrtl;inet;IntrawebDB_90_100;Intraweb_90_100;vclie;inetdbbde;inetdbxpress;soaprtl;VclSmp;MPHexEditor_D7;vcldb + + + 7.0 + False + False + 0 + $(BDS)\Lib\Debug\Indy10;$(BDS)\lib\Debug + $(BDS)\Lib\Debug\Indy10;$(BDS)\lib\Debug + $(BDS)\Lib\Debug\Indy10;$(BDS)\lib\Debug + $(BDS)\Lib\Debug\Indy10;$(BDS)\lib\Debug + RELEASE + build\release\bin + build\release + build\release + build\release + build\release + + + 7.0 + 2 + $(BDS)\Lib\Debug\Indy10;$(BDS)\lib\Debug + $(BDS)\Lib\Debug\Indy10;$(BDS)\lib\Debug + $(BDS)\Lib\Debug\Indy10;$(BDS)\lib\Debug + $(BDS)\Lib\Debug\Indy10;$(BDS)\lib\Debug + DEBUG + build\debug\bin + build\debug + build\debug + build\debug + build\debug + + + Delphi.Personality + + +FalseTrueFalseTrueFalse0017FalseFalseTrueFalseFalse103312520.0.1.71.0.0.0EvoX-TPrivate EvoX-T training toolXDK AssistRushed public release + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + XDKAssist.dprFalse + + + + + MainSource + + + + + +
frmMain
+
+ + + + +
+
\ No newline at end of file diff --git a/XDKAssist.res b/XDKAssist.res new file mode 100644 index 0000000000000000000000000000000000000000..839a791da5d203c23e898bbbc1c71303033cc4a0 GIT binary patch literal 5708 zcmeI0Ux-xK9mjvGQ#v71))!&K!bD1mh1M;{y`u!GF@GR5HWH(ohrqIF_8O^C*)f^9 zu`mMx-M1|iEGIAHLtEO1zUYGwQo2ulsP|c4sxP*rNWqnvy|ZBG>F4*mGdp+3?Y{`k zL;UTWbAJDS-}AfY-np|PB2`8R`GDO(f7>#&Z^dWAY?G7El!i{v!&DiE7!#7K+r(^6 zZ4o}wZs!X#tSm1tv#LQs=o0dUIme~z|3>|#(CjZRF0CytHef@jH)`EhxYP*!CD;)9 zF4PyBmo9~H5H^Ih#U<>GMhL_#g-{EN#2&0Ke$s*sp%waATYWr~{V%9B^ci@Q+Nj?e ztkhN->cfSWijV%Fvr_-Ya59lSTxZk<0?XP9t^T#vNbbLI{l@&vW#v zAP7P4gZzMx-v^V})n|AgEW>Y3ZSDaqoOxTAVZYFQ(6`>w@`m=6sbAxasIuNtHiT}i zeo)u`@X`*}ZY{sGjaEa?fF{b5sq?=iP(KmkgY%aJ2T zhF=B>iq`BzOIdl%4QymX5 zb&1i(FfsZVeT-hiJbI7bBY5;4J&UC1Q}ikN6h(?YAP3}-T4l%VNbX4F2y#3htvr(h zg9C#Dg9C#>0)qpC1A_yD1B1Z?g9C#Dg9C#DgFy#_1A_yD1A_yD3+TY%z~K}J4`fgo zikLYDOAMA6EVgB^#1xMy6q7Uti?sn`3=@MT28*=?95FazaKzwU-*Y>gy%))c5qpgTaHr(+9n5bj5a$hLu?_BRxo|mx3(?M@l`rw2Ffsq~J&u2OG1? zO;8G!6f7wqQVQA;082_>ic^Xr1q&YrdAKGiSW@kWVbRl|R{^GKo|0vqFZTWTRQT zI8tRvZQN`QSMqQlMOo!>hh`L6jfB;%W$wK`XU(Yg*N}Z2?e&CjglkTfy|r4B)S@Ql zP4X**S`^iq<+~dGIDilcj-sSe&Nh$lMA7tK&{c@vq(F!0Uo7mMj=UCF$30*&_SN)v z%MTW&*eCWe58q76+1+vlk7nM8LI#doQn^b}KK%J+xnNYbLhCf=U}qISwn2(vwQc8> zIbmY}#xnk&L>>oUD!u=;;(JWVu#CUQjNS+H_n1SxLvBuO-eIut%(I0V_UU^J-&uTx zF`mB1JbjPhUE?e89<$mjK0te`VP)sKqPMdP?~{Q`&*vsTW9!bD;a6=k<5z>9NqPgv z?#zt$Gc(Vh8eOOP(F_~q4Mc#ya&CtvmwpZbm zeqVa<56!If^}>1(xysO5xfNzW@7TW`j>&mhkW+F-PRgsSr)8eZXL$ENi{F0pnUZ~U ztjbIHd?4p!4*!G>%^}TO_E+R1Y;VhVxnm2XminEz$G>hfc&dye|2 z@I7OGM@{z;na<-=rP?S@)sW5^)*P>D&hmHKVyF0hKdNzp_ywwra!-L;$M+dEB6j0F zVSCkD3m}*yQ*jp}-!`Auv7a=n?)V&g)sCH%_o<)LcgFnQ!edU}Bklv%1^Ed`hLroh zG034i(YnLls5I=NNF>jaS-WzUY#-9I^BWMpEicOt>C=nk*S*#Ui1t}8HpY#2MEfx8 ziJtk_We2s6vA=ohoHA60si|k;J?wA7*V*$tQWdWc+n^ c`p!nDO5|I1BJ(btA?7rm#dGjX=`W>!0z^SDG5`Po literal 0 HcmV?d00001 diff --git a/XDKAssist.todo b/XDKAssist.todo new file mode 100644 index 0000000..a355521 --- /dev/null +++ b/XDKAssist.todo @@ -0,0 +1,19 @@ +{TODO : Display percentage complete on progress bars} +{TODO : For color logging have a rich it up function} +{TODO : Fix disconnection tracking (linked with status checking?)} +{TODO : Command queuing with response checking} +{TODO -cLayout : Better status bar information (program status and what not)} +{DONE -cLayout : Show and hide the main log. Have another log visible by a tabbed window} +{TODO : Possible disassembly view?} +{TODO : have a register compare option} +{TODO : Keep complete breakpoint history with times and registers: list box that when you click it it changes the register information} +{TODO : go through each function and make a list of what you don't like, then go back later and fix all the stuff} +{TODO : Eat messages option will eat common messages like 200- ok and n: execution stopped} +{TODO : Cut out 66% ram usage by reworking buffer stuff. Wait until other changes are in, so you can easily fix any speed issues} +{TODO -cLayout : Redesign log area - show xbox logo faintly behind text, outline the input box, add coloring} +{TODO : Solidify status changes and checking of status} +{TODO : Internal tools editor} +{TODO -cLayout : Redesign register window and breakpoint area} +{TODO -cControls : THistoryBox - contains a 20 string list of previously entered text} +{TODO -cControls : TMPHexEdit - rewrite to allow direct memory access to buffer - or - focus buffer elswhere} +{TODO -cControls : THexBox - accepts only valid hexadecimal input. Features the ability to validate pastes, accept or rject different styles of notation ($,h,0x) and force capitals or zero fills} diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..c7a37b4 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,11 @@ +# What's in this folder? + +This folder contains some documents I had that were related to XDK Assist. I've included them for posterity, but they aren't of much use practically speaking. + +What_Hack0r_Complains_About_&_Wants.txt - At some point I polled Hack0r for a list of things he'd like to see improved in XDK Assist. This is that list. There's actually an unreleased XDK Assist v2 that began to implement some of these requests. + +xdkassist.ini - The INI file that was bundled with a copy of XDK Assist I had. It demonstrates the configuration for the Tools system, ArtMoney offsets, etc. + +xdkassist.notes - Notes from an unknown game I was debugging at some point. + +xdkcmds.txt - Several XDK commands that can be issued over Telnet. \ No newline at end of file diff --git a/docs/What_Hack0r_Complains_About_&_Wants.txt b/docs/What_Hack0r_Complains_About_&_Wants.txt new file mode 100644 index 0000000..f22ed7e --- /dev/null +++ b/docs/What_Hack0r_Complains_About_&_Wants.txt @@ -0,0 +1,35 @@ +What I have problems with: +- Breaking freezes game and I can't continue (most likely my bios) +- Poking (SETMEM) doesn't stick +- After 35-40 dumps, xbox freezes, won't complete dump +- Hex Memory View will go blank if I search for a wrong address or is out of range (have re-dump to see it again) + +What I'd like to see incorporated: +- A search/filter program (kind of like Art Money or Tsearch)... includes different searching/filtering of floats/Integers by 1,2,3,4,6,8,10 bytes; + Unknown values (increased (by), decreased (by), did not change) + Basically, figure out Art Money, then integrate it into XDKA (easier said then done, I know :P but it makes working a lot easier) +- Integrated Hex-Dec, Dec-Hex calculator (like Windows Calculator); Maybe include a base converter (32 bit, Intel) Basically Hexworkshop's base converter +- Maybe a visible buffer range in the Dumping Tag, for example: PC hex ranges: 2000000h-4000000h +- Customized dump button +- Automatic break point copy to Notes +- Change Xbox Logo +- Instead of the black background, maybe include a Evox-T logo in the background (something subtle so it doesn't overpower the text) +- Change logos next to "Dumping", "Notes"...etc to a more 3D look + +If your hardcore (if not void this), then here is my "asking for too much" category: +- Incorporate a picture dumping tool (takes picture of current screen) +- ASM file maker (I'm too lazy to make a new .txt and save it as .asm) +- Timestamp/Title ID displayer (searches through XBE to find it) +- Incorporate Caustic's XBE-EXE converter + +What I want to stay: +- PC-Xbox Xbox-PC hex converter +- GETMEM +- The "HUD" of course (shows what's happening between PC and Xbox)(Connecting, dump...etc) +- All the tabs +- Dumping (obviously) + + +What I never understood: +- What the "Section Flags" does +- Memory View (kind of pointless) diff --git a/docs/xdkassist.ini b/docs/xdkassist.ini new file mode 100644 index 0000000..36bab83 --- /dev/null +++ b/docs/xdkassist.ini @@ -0,0 +1,57 @@ +[Connection] +Host=192.168.1.153 +Port=3000 + +[Dumping] +AutoStop=1 +AutoCopy=1 +Highlight=1 + +[Breakpoints] +Type=Read + +[Window] +Width=657 +Height=611 +LastTab=0 +State=0 + +[Tool0] +Name=bconv32.exe +Class= +Caption=Base Converter +Load=0 + +[Tool1] +Name=C:\Windows\System32\calc.exe +Class= +Caption=Calculator +Load=0 + +[Tool2] +Name=E:\Tools\ArtMoney 7.0.8 Pro\artmoney.exe +Class= +Caption=ArtMoney +Load=0 + +[Tool3] +Name=E:\Tools\tsearch\tsearch.exe +Class= +Caption=TSearch +Load=0 + +[Logging] +Verbose=0 + +[Range] +Enabled=0 +Start=0x005F6920 +End=0x005F6924 +Caption=ArtMoney +Class=TApplication +State=0x00B3F500 + +[Misc] +WarnConClose=1 +[Layout] +ShowMainLog=1 diff --git a/docs/xdkassist.notes b/docs/xdkassist.notes new file mode 100644 index 0000000..edbd6e9 --- /dev/null +++ b/docs/xdkassist.notes @@ -0,0 +1,44 @@ +0x331cee + +life +0x83B22ADC +Breakpoint detected (write,0x83B22ADC,0x16A036). + +mana +83B22AE0 +83B1ED30 +Breakpoint detected (write,0x83B22AE0,0x15BC35). +Breakpoint detected (write,0x83B90100,0x164B48). + +Breakpoint detected (read,0x83B1E5C0,0x149DC9). +Breakpoint detected (read,0x83B1E5C0,0x149E69) +Breakpoint detected (read,0x83B1E5C0,0x164B38). +Breakpoint detected (read,0x83B1E5C0,0x164B48). + +165293- +1652d8 +1652d8 + + + +0016A02E health +0015BC2D cloak decrease +00165300 eb shoot when 0 +00156575 eb unlim items +00164B40 major +00164B82 minor +00146619 8b alarm + + +items + + +alarm +Breakpoint detected (write,0x48F9FC,0x146C34).always +Breakpoint detected (write,0x48F9FC,0x146C3E).always +Breakpoint detected (write,0x48F9FC,0x147611). +Breakpoint detected (write,0x48F9FC,0x14763D). + + +spear +Breakpoint detected (write,0x83B23E04,0x15657F). \ No newline at end of file diff --git a/docs/xdkcmds.txt b/docs/xdkcmds.txt new file mode 100644 index 0000000..6d624d9 --- /dev/null +++ b/docs/xdkcmds.txt @@ -0,0 +1,172 @@ +XDK Telnet Command List by ddh + +Most of this information should be correct. My memory is a little fuzzy about some of the commands I didn't spend much time looking in to. + +Info: + +The game is usually, if not always, thread 28. +More information is available by using the NOTIFYAT command. + +Commands: + +MODULES + Lists current modules + +MODSECTIONS NAME="" + Returns section details about specified module inside of quotes. Use the name from the MODULES command. + +BYE + Disconnect + +REBOOT STOP|WAIT (WARM) (NODEBUG) + Reboots the xbox with the selected reboot style. + +GETMEM ADDR= LENGTH= + Dumps memory in ASCII format. Addr and length can be either hex that is prefixed with 0x, or decimal. + +GETMEM2 ADDR= LENGTH= + Same as GETMEM, only this dumps the memory in binary. It is much faster. + +DEBUGGER CONNECT|DISCONNECT + Informs the XDK that you are a connecting debugger + +ISDEBUGGER + Lets the XDK know that you are a debugger + +GETPID + Returns pid + +THREADS + Returns a thread list + +THREADINFO THREAD= + Returns information about the specified thread. Use the thread number returned by the threads command. + +HALT (THREAD=) + Stops the specified thread, or the default one if no thread is specified + +GO + Tells the xbox to continue running after a STOP. + +CONTINUE THREAD= (EXCEPTION) + Tells the xbox to continue running the specified thread. Used in conjunction with GO. + +WALKMEM + Returns all valid memory sections for the xbox. Use in conjunction with getmem2 for easy mem dumping. + +STOPON (CREATETHREAD|FCE) + Specifies some events that the XDK should stop on. + +ISSTOPPED THREAD= + Checks if the specified thread is stopped or not. + +NOTIFYAT PORT= (DROP) (DEBUG) + Tells the xbox to send more information to you on the specified port. Add the drop when you are done listening (i.e. disconnecting). + +GETEXTCONTEXT THREAD= (CONTROL) (INT) (FP) + Gets context details about the specified thread. This returns binary information. + +BREAK (READ|WRITE|EXECUTE)=0x SIZE= (CLEAR) +BREAK ADDR=0x SIZE= + Sets and clears a breakpoint. You can specify the type of breakpoint, the starting address, and the size of the area you want monitored. Append the CLEAR command to the set string. + +XBEINFO (RUNNING|NAME="") + Returns the timestamp, checksum, and name of the specified XBE. + +DEBUGNAME + Get the name of the running XBOX + +PCLIST + Returns performance information about the XBOX + +QUERYPC NAME=\"%s\" TYPE=0x%08x + Returns specific information about an item spat out by PCLISt + +GPUCOUNT ENABLE|DISABLE + Enable or disable GPU count + +magicboot title="" (DEBUG) + Reboots the system and launches the named xbe + + +ISBREAK ADDR=0x + + + +RESUME THREAD= +SUSPEND THREAD= +SETCONTEXT THREAD= +GETCONTEXT THREAD= (CONTROL|INT|FP) +MODLONG NAME= +BOXID +NONCE +AUTHUSER ADMIN RESP=0q%08x%08x +AUTHUSER NAME=\"%s\" PASSWD=0q%08x%08x +SETUSERPRIV NAME=\"%s\" +GETUSERPRIV NAME=\"%s\" +GETUSERPRIV ME +USER NAME=\"%s\" +USER NAME=\"%s\" REMOVE +USERLIST +ADMINPW NONE +ADMINPW PASSWD=0q%08x%08x +LOCKMODE UNLOCK +LOCKMODE BOXID=0q%08x%08x +SYSTIME +setsystime clockhi=0x%08x clocklo=0x%08x +SENDFILE NAME=\"%s\" LENGTH=0x%x +GETFILE NAME=\"%s\" +GETFILEATTRIBUTES NAME=\"%s\" +SETFILEATTRIBUTES NAME=\"%s\"" " CREATEHI=0x%08x CREATELO=0x%08x CHANGEHI=0x%08x CHANGELO=0x%08x (READONLY=%d HIDDEN=%d) +MKDIR NAME=\"%s\" +RENAME NAME=\"%s\" NEWNAME=\"%s\" +DELETE NAME=\"%s\" (DIR) +DIRLIST NAME=\"%s\" +ALTADDR +DEDICATE HANDLER=%s +DEDICATE GLOBAL +XTLINFO +SUSPEND THREAD=%d +RESUME THREAD=%d +BREAK START +FUNCCALL THREAD=%lu +CAPCONTROL %s +TITLE NAME="" DIR="" CMDLINE="" +DRIVELIST +DRIVEFREESPACE NAME=\"%s\" +screenshot +PSSnap x=%d y=%d flags=%d marker=%d +VSSnap first=%d last=%d flags=%d marker=%d +XBOXIP +mmglobal +STOPON CREATETHREAD|FCE|DEBUGSTR +NOSTOPON CREATETHREAD|FCE|DEBUGSTR +SETCONFIG INDEX=0x%08x VALUE=0x%08x + + + + +SETCONTEXT +THREAD=28 Esp=0 +xd0032adc Ebp=0x +d0032ba0 Eip=0x8 +001c19f EFlags=0 +x202 Eax=0x8004a +c01 Ebx=0x8004ac + +0000: 53 45 54 43 4F 4E 54 45 58 54 20 54 48 52 45 41 SETCONTEXT THREA +0010: 44 3D 32 38 20 45 73 70 3D 30 78 64 30 30 33 32 D=28 Esp=0xd0032 +0020: 63 31 34 20 45 62 70 3D 30 78 31 63 65 30 30 36 c14 Ebp=0x1ce006 +0030: 30 20 45 69 70 3D 30 78 38 30 30 31 63 31 39 66 0 Eip=0x8001c19f +0040: 20 45 46 6C 61 67 73 3D 30 78 32 30 32 20 45 61 EFlags=0x202 Ea +0050: 78 3D 30 78 38 30 30 34 61 63 30 31 20 45 62 78 x=0x8004ac01 Ebx +0060: 3D 30 78 38 30 30 34 61 63 38 63 20 45 63 78 3D =0x8004ac8c Ecx= +0070: 30 78 30 20 45 64 78 3D 30 78 62 30 30 63 62 64 0x0 Edx=0xb00cbd +0080: 63 38 20 45 73 69 3D 30 78 38 30 30 31 62 65 35 c8 Esi=0x8001be5 +0090: 31 20 45 64 69 3D 30 78 62 30 30 32 36 39 37 37 1 Edi=0xb0026977 +00A0: 20 43 72 30 4E 70 78 53 74 61 74 65 3D 30 78 30 Cr0NpxState=0x0 +00B0: 20 65 78 74 3D 32 38 38 0D 0A 54 F1 58 00 C2 00 ext=288..T.X... + +0000: 67 65 74 64 33 64 73 74 61 74 65 20 73 69 7A 65 getd3dstate size +0010: 3D 31 31 38 30 0D 0A 00 00 00 CA 00 00 00 00 00 =1180........... + diff --git a/hexcontrol/Delphi-7/MPHexEditor_D7.dpk b/hexcontrol/Delphi-7/MPHexEditor_D7.dpk new file mode 100644 index 0000000..280abf2 --- /dev/null +++ b/hexcontrol/Delphi-7/MPHexEditor_D7.dpk @@ -0,0 +1,38 @@ +package MPHexEditor_D7; + +{$R *.res} +{$R '..\MPHexEditorReg.dcr'} +{$ALIGN 8} +{$ASSERTIONS ON} +{$BOOLEVAL OFF} +{$DEBUGINFO ON} +{$EXTENDEDSYNTAX ON} +{$IMPORTEDDATA ON} +{$IOCHECKS ON} +{$LOCALSYMBOLS ON} +{$LONGSTRINGS ON} +{$OPENSTRINGS ON} +{$OPTIMIZATION ON} +{$OVERFLOWCHECKS OFF} +{$RANGECHECKS OFF} +{$REFERENCEINFO ON} +{$SAFEDIVIDE OFF} +{$STACKFRAMES OFF} +{$TYPEDADDRESS OFF} +{$VARSTRINGCHECKS ON} +{$WRITEABLECONST OFF} +{$MINENUMSIZE 1} +{$IMAGEBASE $400000} +{$DESCRIPTION 'mirkes.de HexEditor vcl'} +{$IMPLICITBUILD OFF} + +requires + rtl, + designide; + +contains + MPHexEditorReg in '..\MPHexEditorReg.pas', + MPHexEditor in '..\MPHexEditor.pas', + MPHexEditorEx in '..\MPHexEditorEx.pas'; + +end. diff --git a/hexcontrol/Delphi-7/MPHexEditor_D7.res b/hexcontrol/Delphi-7/MPHexEditor_D7.res new file mode 100644 index 0000000000000000000000000000000000000000..b111060100a5f025f8352346a52aa105dda315dc GIT binary patch literal 876 zcmaJ=Jxc>Y5Pj=~+s-6tVP#TUMl38+L=gfOYT+L^n{Cnvf+wW#s%rxNnLu)|%~k%6 zl$I$|i0j+gyL{lskhe2$=FRS8xdnhO;amX29sAg;>k&0dYOR)T)S?pxUE+dJsM75D z-7YQoTL`ZNskJunM9$J{ZiL4pI|=o`jj8rCvcaP5py8wPxGFA0H0{kOJE%e;dBD$_)!iKY@Xrb4<1>ZcrKCx@^u8oUaXty~na!o=Y zMk0{!X2USfhKPn3kwCti4cW0~o3tA+j8P4enbPTVf8#NUL z@9f-WFsx+MR200kbDP1i5@96MDraY8(;t;?;1NwLADnk``lHfS96oah zS@ysKy-Y5SFeF_Mu7qQ_F4MvP@u)9@w8HQ_Y^Mi5<)O1ayXdv`;zh5m7oYg^^Y(kx C5P3lW literal 0 HcmV?d00001 diff --git a/hexcontrol/MPHexEditor.chm b/hexcontrol/MPHexEditor.chm new file mode 100644 index 0000000000000000000000000000000000000000..6a731949eae4086a873862e74e9bcd28383d0b14 GIT binary patch literal 126777 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 Q?+(8^{O|C;!~YHds!VzfV*mgE literal 0 HcmV?d00001 diff --git a/hexcontrol/MPHexEditorReg.dcr b/hexcontrol/MPHexEditorReg.dcr new file mode 100644 index 0000000000000000000000000000000000000000..28cffa9c6dc24af280000f6f21f98d87e1ef8136 GIT binary patch literal 3428 zcmeIzy>28$5Ww-`gxC!)B&-C9%w#6GpaPH}Kp=|%u}K94ya8#w2T4h{BD#mDm}em9 zN4Vs6WD^Yko}OLbdTl@#yJvq>?V9#{^t7f#L=^Kp&^>Fvej@TEnR$L8KgxIVgM1@j z%TM@9z9sX$+?>Y0e7X7f_{ZmTK3#nz+a+x;jz=HH5aA~gBe16{RVDRT$=g5w;3f?Y z4rrp#L`){bR`uAM<7vX+@wHZa0C*CCc;me z!r%xb3QbJ7O;PmC5l9r;cr;a_!4XIl+IVbJqQMbJ6xx^)SE9iYNEF(bu27=E5l9r; zn95Y5!4XIl+L-22qQMbJ6x#9%tSQmp2qX$kg69uW^e1=pEm3F^Do_|4fkdH=*JesI zI0A`68?V%rXmA7)g*IL{7l|-90*OK!OCgnLa0C*CHkL^#(clOq3T;_J(UfR#1QLZN z#q*aa430peZ*43sRieQWNEF&wcBVvwBakSxu_RrI21g)KXk)pC5)F<(qR_@tP9+)~ zfkdH=WnM}&I07ke|DI*fxZr$zJ(gep$j}4Z^1Yem?(S~?SR*f+%eL?N`F5Uh+uP`G z&hi``1Lk9XjAZmUaLTX4*7WUz{Mq^Yy0pPt$HUPt)t&{BC-4b3eZ450B>eH*Y3}?x$&gHhwxa zPIkKPnq>4e{XD&&|DDY1-MfCS!zn*+x0~%|nhu98yZJu=2)8RLOIVYSbZt!r%xb3QdBw@?4`-m<>1riM}=JkU(K@1QLZt9TF%EjzFT& zs6zsU!4XIl8g)pZFgOBPsw$gef(BEMFn^@_*quG#%`kzZ@nk{=a_`@gSI R>&=XCHwN_mdH#tS^$!sN9BBXm literal 0 HcmV?d00001 diff --git a/hexcontrol/hexeditor.html b/hexcontrol/hexeditor.html new file mode 100644 index 0000000..bf31d26 --- /dev/null +++ b/hexcontrol/hexeditor.html @@ -0,0 +1,690 @@ + + + + + + + TMPHexEditor/TMPHexEditorEx readme + + + + + + + + + + +

TMPHexEditor/TMPHexEditorEx components by mirkes.de

+ +

TMPHexEditor is a TCustomGrid descendant to view and + edit binary files in hexadecimal and textual format.

+ +

TMPHexEditorEx is an advanced hex editor, is supports + OLE drag and drop, printing, print preview and more.

+ +
+ top +
+ +

contents:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
version: +   version of the package
target: +   supported borland ides
sources: +   status of the sourcecode
history: +   version history of the package
installation:   how to + install the package
usage: +   how to use the package
contact: +   how to contact the author of the package
license: +   license of the package
+ +
+ top +
+ +

version:

+ +

version 12-29-2004, released december 29, 2004

+ +
+ top +
+ +

target:

+ +

the package has been tested and should work in + Borland Delphi 4, 6 and 7 and C++ Builder 6.

+ +
+ top +
+ +

sources:

+ +

the component sources are included in the package.

+ +
+ top +
+ +

history:

+ +

version 12-29-2004: december 29, 2004

+ +
    +
  • initialized Result to '' in some string functions/methods to avoid non empty Result vars + at function startup due to compiler optimizations (particularly on d4), e.g. printing did not + work correctly under d4
  • +
  • updated some of the sample projects (fixed the broken bcb6 sample, added printing to the + hex viewer and the bcb6 editor sample)
  • +
+ +

version 12-28-2004: december 28, 2004

+ +
    +
  • changed the progress event calling part in Find and FindWithWildcard to avoid a + division by zero error when working on files < 500 bytes
  • +
+ +

version 12-21-2004: december 21, 2004

+ +
    +
  • changed PrepareFindReplaceData method to avoid an exception when + the string parameter is empty
  • +
  • TMPHexEditorEx: support for CF_HTML clipboard format
  • +
+ +

version 11-12-2004: november 12, 2004

+ +
    +
  • changed mouse selection in insert mode, now it's more text editor-like
  • +
  • Undo and Redo disabled when ReadonlyView is True
  • +
  • TMPHexEditorEx: ole drag and drop move operation is now disabled if the editor's + ReadOnlyView property is set to True
  • +
  • some small other modifications
  • +
+ +

version 10-26-2004: october 26, 2004

+ +
    +
  • fixed a typecasting bug in the Undo method (integer overflow)
  • +
  • added some utility functions for unsigned int64 arithmetics (AddU64, TryAddU64, SubtractU64, TrySubtractU64, MultiplyU64, TryMultiplyU64, DivideU64, TryDivideU64, ModuloU64, TryModuloU64)
  • +
+ +

version 08-29-2004: august 29, 2004

+ +
    +
  • added ActiveFieldBackground color property
  • +
  • added print flag pfIncludeRuler in TMPHexEditorEx
  • +
+ +

version 08-14-2004: august 14, 2004

+ +
    +
  • the caret was not set properly when switching from + hex to char pane if no data was in the editor
  • +
  • MaskedChars property added
  • +
  • changed printing in TMPHexEditorEx (color handling, pfSelectionBold meaning)
  • +
+ +

version 06-15-2004: june 15, 2004

+ +
    +
  • added DrawDataPosition and IsDrawDataSelected + properties
  • + +
  • changes in drawing/invalidating to avoid unnecessary + painting
  • + +
  • OnMouseDown is now called also if offset pane or ruler + are clicked
  • + +
  • if BytesPerUnit is changed, the selection is reset if + (SelCount mod BytesPerUnit) <> 0
  • +
+ +

version 06-10-2004: june 10, 2004

+ + + +

version 06-07-2004: june 07, 2004

+ +
    +
  • fixed a crash ("Grid index out of range") when switching + from unicode
  • + +
  • SyncView method modified to be able to synchronize the + view of editors with different data sizes/layouts, also with + offset
  • + +
  • on changing TopRow/LeftCol the caret is + repositionned
  • + +
  • overwritten mouse wheel handling to allow page + scrolling
  • + +
  • manual handling of MaskChar property streaming due to bug + reports ("Invalid Property Value")
  • +
+ +

version 05-30-2004: may 30, 2004

+ +
    +
  • fixed broken CanOpenFile routine (files were always + marked read-only)
  • +
+ +

version 05-27-2004: may 27, 2004

+ +
    +
  • added IsMaxOffset property
  • + +
  • the control gets focused when the mouse is clicked even + when the mouse is over the selection
  • +
+ +

version 05-13-2004: may 13, 2004

+ +
    +
  • OnDrawCell is now also called for the top left cell
  • + +
  • setting UnicodeChars to False now correctly sets + BytesPerUnit to 1
  • +
+ +

version 04-18-2004: april 18, 2004

+ +
    +
  • parameters aBuffer and bBuffer were interchanged in the + CopyMemory call in TranslateBufferFromAnsi
  • + +
  • GetOffsetString can now be called in OnGetOffsetText + without crashing (infinite recursion = stack overflow)
  • +
+ +

version 01-08-2004: january 08, 2004

+ +
    +
  • added some explicit pointer typecasts for {$T+} + compatibility
  • + +
  • removed FindTable and FindTableI properties under BCB + (BCB doesn't like array properties)
  • +
+ +

version 12-16-2003: december 16, 2003

+ +
    +
  • setting the DataSize property is now undoable
  • + +
  • added the public SetDataSizeFillByte property to be able + to control what byte is used to enlarge the data
  • + +
  • now checking NoSizeChange before allowing to set + DataSize
  • + +
  • CreateUndo is no longer a function, but a procedure. now + an exception is raised when no undo record can be + created
  • +
+ +

version 12-10-2003: december 10, 2003

+ +
    +
  • renamed OnLoadSaveProgress to OnProgress
  • + +
  • added property FindProgress
  • + +
  • added custom find methods OnFind, OnWildcardFind
  • + +
  • Find and FindWithWildcard speeded up by using precompiled + character tables
  • + +
  • Find and FindWithWildcard now also fire the OnProgress + event if FindProgress is set to true
  • + +
  • fixed a bug in mouse handling (weird selection or line + offsets when doublecklicking ruler bar/offset panel)
  • + +
  • modified selection code to better support double byte + selection (unicode)
  • +
+ +

version 09-24-2003: september 24, 2003

+ +
    +
  • modified the BCB6 package
  • +
+ +

version 09-09-2003: september 09, 2003

+ +
    +
  • changed some constants, classes and types from MPTH... to + MPH...
  • + +
  • changed MPHCustTransFieldFrom/To to + MPHCustomCharConv
  • + +
  • BytesPerBlock and SeparateBlocksInCharField + propertíes added
  • + +
  • DataSize property is writeable now
  • + +
  • Page down key now also reaches the last row
  • + +
  • added OnGetOffsetText property
  • + +
  • added AddSelectionUndo procedure
  • + +
  • added defines for delphi7, renamed delver.inc to + mpdelver.inc
  • + +
  • added wildcard search (FindWithWildcard)
  • + +
  • added SeekToEOF
  • + +
  • changed keyboard handling, so OnKeyDown should work + better
  • + +
  • added GotoBookmark method to set cursor to a bookmarked + position
  • + +
  • added OnBookmarkChanged property
  • + +
  • support for unsigned int64 radix conversions
  • + +
  • Replace method added
  • +
+ +

version 07-05-2003: july 05, 2003

+ +
    +
  • better handling of odd sized files when BytesPerUnit + <> 1
  • + +
  • added support for pasting clipboard data in fixed + filesize mode in TMPHexEditorEx
  • + +
  • added RegEdit_HexData clipboard support in + TMPHexEditorEx
  • +
+ +

version 05-25-2003-b: may 25, 2003

+ +
    +
  • fixed a bug (moving the cursor beyond eof)
  • +
+ +

version 05-25-2003: may 25, 2003

+ +
    +
  • added some kind of ownerdraw (see OnDrawCell)
  • + +
  • no ':' is printed when offset display is not used
  • + +
  • added hpp generating statements for bcb + compatibility
  • +
+ +

version 05-20-2003: may 20, 2003

+ +
    +
  • renamed, added and changed some methods, classes and + properties
  • + +
  • fixed some bugs in the ruler display (e.g. when + BytesPerRow is changed)
  • + +
  • fixed some bugs when BytesPerUnit <> 1
  • + +
  • added some unicode support (UnicodeChars and + UnicodeBigEndian)
  • + +
  • fixed some half byte (nibble) related bugs
  • +
+ +

version 05-17-2003: may 17, 2003

+ +
    +
  • added DisplayStart and DisplayEnd functions to retrieve + the data bounds currently displayed
  • + +
  • added BytesPerUnit and RulerBytesPerUnit properties to + treat words/dwords/qwords as a unit
  • + +
  • added SyncView procedure and OnSelectionChanged property + to synchronize position and selection with another + editor
  • + +
  • added ShowPositionIfNotFocused property to show the + current position if the editor is not focused
  • + +
  • corrected bottom margin handling when printing
  • + +
  • corrected upper/lowercase hex chars in printing
  • + +
  • the current unit is selected now when doubleclicking + data
  • + +
  • added flags pfCurrentViewOnly (just print the currently + visible data) to PrintOptions.Flags
  • +
+ +

version 10-25-2002: october 25, 2002

+ +
    +
  • corrected the BytesPerColumn default value
  • +
+ +

version 08-18-2002: august 18, 2002

+ +
    +
  • modified painting and selection
  • + +
  • implemented an additional ruler bar at the top
  • + +
  • new properties: ShowRuler, DrawGutter3D
  • +
+ +

version 08-12-2002: august 12, 2002

+ +
    +
  • modified Changed calls to get correct Modified property + in OnChange handler
  • +
+ +

version 08-09-2002: august 09, 2002

+ +
    +
  • included missing include file delver.inc
  • + +
  • added OnChange event
  • +
+ +

version 07-21-2002: july 21, 2002

+ +

too many changes to mention here (completely rewritten, + basic and advanced versions TMPHexEditor and TMPHexEditorEx), + please read the documentation included with this package for + more information

+ +
+ top +
+ +

v 1.16: 02/02/99

+ +
    +
  • added WMGetDlgCode to avoid problems with + shortcut-controls on the form
  • + +
  • changed the property name ReadOnly to ReadOnlyFile ( to + avoid confusion, sorry )
  • + +
  • fixed updating when the font gets changed
  • + +
  • added OnKeyPress-support ( now you can modify the key + before THexEditor will parse it in this event )
  • + +
  • property WantTabs : Boolean ; if true, you can navigate + between char and hex field with the TAB key, if false, you + can navigate between your form's controls with the TAB key, + to change the current field in THexEditor, you have to use + CTRL+T.
  • + +
  • property ReadOnlyView : Boolean ; if true, than the + text/data in THexEditor can't get edited via key presses, + just selection , moving and scrolling are still + available
  • +
+ +

v 1.15: 01/03/99

+ +
    +
  • added option odOctal to TOffsetDisplayStyle to display + line offset in octal system ("8"-based)
  • + +
  • fixed a problem on creating a THexEditor dynamically ( + thanks to John Shailes , JohnShailes _at_ email.msn.com + )
  • + +
  • property AllowInsertMode : Boolean ; if this is set to + true, THexEditor doesn't overwrite but insert values at the + current cursor position ( this cannot be set if NoSizeChange + is True )
  • + +
  • property IsInsertMode : Boolean ; readonly, if it returns + true, the current mode is inserting (see above )
  • + +
  • property AutoCaretMode : Boolean ; if true, the caret + will be set to a block in overwrite mode and to a left line + in insert mode automatically
  • +
+ +

v 1.14: not released

+ +
    +
  • fixed some bugs
  • + +
  • added currently unsupported variable line lengths
  • + +
  • added NoSizeChange property
  • +
+ +

v 1.13: 11/07/98

+ +

added AsText and AsHex property ( and converting functions + for "aa00bb" style hex files ) , MaskWhiteSpaces property to + avoid the '.' if you have a font that can display chars from #0 + to #31; also typing capitals rather than lowercase chars in the + char field is now possible ( most of this stuff has been + suggested from Philippe Chessa , Philippe_Chessa _at_ + compuserve.com, thanks )

+ +

v 1.12: 10/25/98

+ +
    +
  • added Half Byte (Nibble) support (insert/delete, swap + hi/lo nibbles in hex view)
  • + +
  • better performance
  • + +
  • the markers are now available for reading/writing
  • + +
  • added support for some different code types in the char + view : ANSI , 8 Bit ASCII ( OEM / Dos style ) , 7 Bit ASCII , + Macintosh(TM) character set , IBM(TM) EBCDIC cp 038 also + conversion of the file's contents (or a range of them) from + one to another code type is possible (many thanks to + Christophe LE CORFEC, CLC _at_ khalif.com for ebcdic and half + byte suggestions)
  • +
+ +

v 1.1 : 10/04/98

+ +
    +
  • added find,seek, customizable layout (many thanks to John + Hamm, John _at_ TEMPUS.COM )
  • +
+ +

v 1.0 Beta 1 : 08/15/98

+ +
    +
  • first public release
  • +
+ +
+ top +
+ +

installation:

+ +
    +
  • Delphi 7: open, compile and install the package + MPHexEditor_D7.dpk under the vcl\Delphi-7 directory
  • + +
  • Delphi 6: open, compile and install the package + MPHexEditor_D6.dpk under the vcl\Delphi-6 directory
  • + +
  • Delphi 4: open, compile and install the package + MPHexEditor_D4.dpk under the vcl\Delphi-4 directory
  • + +
  • BCB 6: open, compile and install the package + MPHexEditor_BCB6.bpk under the vcl\BCB-6 directory
  • + +
  • others: copy all files under the \vcl subdir in a + directory contained in delphi's search path. add + MPHexEditorReg.pas to one of your library packages (e.g. + "Borland Delphi User Components") and recompile this package. + After successfull recompiling, the two components + TMPHexEditor and TMPHexEditorEx should be available on the + "mirkes.de" tab.
  • +
+ +
+ top +
+ +

usage:

+ +

read the help file included in this package (under the \doc + path).

+ +
+ top +
+ +

contact:

+ +

the author of this package is markus stephany, losheim am + see, saarland, germany.

+ +

mailto:vcl[at]mirkes[dot]de + (change the obvious!)

+ +

http://www.mirkes.de

+ +
+ top +
+ +

license:

+
+Copyright : © markus stephany, all rights reserved
+
+    This source code is freeware. You may use, change, and distribute without
+    charge the source code as you like. This unit can be used freely in any
+    commercial applications. However, it may not be sold as a standalone product
+    and the source code may not be included in a commercial product. This unit
+    is provided as is with no warrent or support. Make sure to read relevant
+    information and documentation from Microsoft before using this unit.
+
+ +
+ top +
+ +

-end-

+ + \ No newline at end of file diff --git a/hexcontrol/mphexeditor.pas b/hexcontrol/mphexeditor.pas new file mode 100644 index 0000000..6f4df94 --- /dev/null +++ b/hexcontrol/mphexeditor.pas @@ -0,0 +1,8708 @@ +(* + + TMPHexEditor v 12-29-2004
+ + @author((C) markus stephany, merkes@mirkes.de, all rights reserved.) + @abstract(TMPHexEditor displays and edits binary files in hexadecimal notation) + @lastmod(12-29-2004) + + credits to :

+ - John Hamm, http://users.snapjax.com/john/

+ + - Christophe Le Corfec for introducing the EBCDIC format and the nice idea about + half byte insert/delete

+ + - Philippe Chessa for his suggestions about AsText, AsHex and better support for + the french keyboard layout

+ + - Daniel Jensen for octal offset display and the INS-key recognition stuff

+ + - Shmuel Zeigerman for introducing more flexible offset display formats

+ + - Vaf, http://carradio.al.ru for reporting missing delver.inc and suggesting OnChange

+ + - Eugene Tarasov for reporting that setting the BytesPerColumn value to 4 at design + time didn't work

+ + - FuseBurner for BytesPerUnit/RulerBytesPerUnit related suggestions

+ + - Motzi for SyncView/ShowPositionIfNotFocused related suggestions

+ + - Martin Hsiao for bcb compatibility and reporting some bugs when moving cursor beyond eof

+ + - Miyu for delphi 7 defines

+ + - Nils Hoyer for bcb testing and his help on creating a BCB6 package

+ + - Skamnitsly S.V for reporting a bug when doubleclicking the ruler bar

+ + - Pete Fraser for reporting problems with array properties under BCB

+ + - Andrew Novikov for bug reports and suggestions

+ + - Al for bug reports

+ + - Dieter Köhler for reporting the delphi vcl related CanFocus bug

+ + - Piotr Likus for reporting a cardinal<->integer related bug in the Undo method

+ + - Marc Girod for bug reports

+ +

history:

+

    +
  • v 12-29-2004: december 29, 2004

    + - initialized Result to '' in some string functions/methods to avoid + non empty Result vars at function startup due to compiler + optimizations (particularly on d4), e.g. printing did not work + correctly under d4
    + - updated some of the sample projects (fixed the broken bcb6 sample, + added printing to the hex viewer and the bcb6 editor sample)

  • + +
  • v 12-28-2004: december 28, 2004

    + - changed the progress event calling part in @link(Find) and + @link(FindWithWildcard) to avoid a division by zero error when working + on files < 500 bytes

  • + +
  • v 12-21-2004: december 21, 2004

    + - changed @link(PrepareFindReplaceData) to avoid an exception when + the string parameter is empty

  • + +
  • v 11-12-2004: november 12, 2004

    + - changed mouse selection in insert mode, now it's more text + editor-like
    + - @link(Undo) and @link(Redo) disabled when @link(ReadonlyView) + is True
    + - some small other modifications
    +

  • + +
  • v 10-26-2004: october 26, 2004

    + - fixed a typecasting bug in the Undo method (integer overflow)
    + - added some utility functions for unsigned int64 arithmetics (@link(AddU64), @link(TryAddU64), + @link(SubtractU64), @link(TrySubtractU64), @link(MultiplyU64), @link(TryMultiplyU64), + @link(DivideU64), @link(TryDivideU64), @link(ModuloU64), @link(TryModuloU64)) +

  • + +
  • v 08-29-2004: august 29, 2004

    + - Added @link(ActiveFieldBackground) color property

  • + +
  • v 08-14-2004: august 14, 2004

    + - the caret was not set properly when switching from + hex to char pane if no data was in the editor
    + - Added @link(MaskedChars) property

  • + +
  • v 06-15-2004: june 15, 2004

    + - Added @link(DrawDataPosition) and @link(IsDrawDataSelected) properties
    + - changes in drawing/invalidating to avoid unnecessary painting
    + - OnMouseDown is now called also if offset pane or ruler are clicked
    + - if @link(BytesPerUnit) is changed, the selection is reset + if (SelCount mod BytesPerUnit) <> 0
    + - if @link(CaretKind) is ckAuto, the caret is a bottom line if + @link(ReadOnlyView) is True

  • + +
  • v 06-10-2004: june 10, 2004

    + - added @link(RulerNumberBase) property
    + - overwritten CanFocus method due to vcl bug (see + + http://info.borland.com/devsupport/delphi/fixes/delphi4/vcl.html, + ref 279

  • + +
  • v 06-07-2004: june 07, 2004

    + - fixed a crash ("Grid index out of range") when switching from + unicode
    + - @link(SyncView) modified to be able to synchronize the view + of editors with different data sizes/layouts, also with offset
    + - on changing TopRow/LeftCol the caret is repositionned
    + - overwritten mouse wheel handling to allow page scrolling
    + - manual handling of MaskChar property streaming due to bug reports + ("Invalid Property Value")

  • + +
  • v 05-30-2004: may 30, 2004

    + - fixed broken CanOpenFile routine (files were always marked read-only)

  • + +
  • v 05-27-2004: may 27, 2004

    + - added @link(IsMaxOffset) property
    + - the control gets focused when the mouse is clicked even when + the mouse is over the selection

  • + +
  • v 05-13-2004: may 13, 2004

    + - @link(OnDrawCell) is now also called for the top left cell
    + - setting @link(UnicodeChars) to False now correctly sets + @link(BytesPerUnit) to 1

  • + +
  • v 04-18-2004: april 18, 2004

    + - parameters aBuffer and bBuffer were interchanged in the + CopyMemory call in @link(TranslateBufferFromAnsi)
    + - @link(GetOffsetString) can now be called in @link(OnGetOffsetText) + without crashing (infinite recursion = stack overflow)

  • + +
  • v 01-08-2004: january 08, 2004

    + - added some explicit pointer typecasts for {$T+} compatibility
    + - removed FindTable and FindTableI properties under BCB (doesn't + compile)

  • + +
  • v 12-16-2003: december 16, 2003

    + - Setting the @link(DataSize) property is now undoable
    + - Added the public @link(SetDataSizeFillByte) property to be able to control + what byte is used to enlarge the data
    + - Now checking @link(NoSizeChange) before allowing to set @link(DataSize)
    + - CreateUndo is no longer a function, but a procedure. Now an + exception is raised when no undo record can be created

  • + +
  • v 12-10-2003: december 10, 2003

    + - Renamed OnLoadSaveProgress to @link(OnProgress)
    + - Added property @link(FindProgress)
    + - Added custom find methods (@link(OnFind), @link(OnWildcardFind)
    + - @link(Find) and @link(FindWithWildcard) speeded up by using + precompiled character tables
    + - @link(Find) and @link(FindWithWildcard) now also fire the @link(OnProgress) event + if @link(FindProgress) is set to true
    + - fixed a bug in mouse handling (weird selection or line offsets when + doublecklicking ruler bar/offset panel)
    + - modified selectioncode to better support double byte selection (unicode)

  • + +
  • v 09-24-2003: september 24, 2003

    + - modified the BCB6 package

  • + +
  • v 09-09-2003: september 09, 2003

    + - changed some constants, classes and types from MPTH... to MPH...
    + - changed MPHCustTransFieldFrom/To to @link(MPHCustomCharConv)
    + - @link(BytesPerBlock) and @link(SeparateBlocksInCharField) properties added
    + - @link(DataSize) property is writeable now
    + - Page down key now also reaches the last row
    + - added @link(OnGetOffsetText) property
    + - added @link(AddSelectionUndo) procedure
    + - added defines for delphi7, renamed delver.inc to mpdelver.inc
    + - added wildcard search (@link(FindWithWildcard))
    + - added @link(SeekToEOF)
    + - changed keyboard handling, so OnKeyDown should work better
    + - added @link(GotoBookmark) method to set cursor to a bookmarked position
    + - added @link(OnBookmarkChanged) property
    + - support for unsigned int64 radix conversions
    + - @link(Replace) method added

  • + +
  • v 07-05-2003: july 05, 2003

    + - better handling of odd sized files when BytesPerUnit <> 1
    + - added support for pasting clipboard data in fixed filesize mode in @link(TMPHexEditorEx)
    + - added RegEdit_HexData clipboard support in @link(TMPHexEditorEx)

  • + +
  • v 05-25-2003-b: may 25, 2003

    + - fixed a bug (moving the cursor beyond eof)

  • + +
  • v 05-25-2003: may 25, 2003

    + - added some kind of ownerdraw (see @link(OnDrawCell))

  • + +
  • v 05-20-2003: may 20, 2003

    + - renamed, added and changed some methods, classes and properties
    + - fixed some bugs in the ruler display (e.g. when BytesPerRow is + changed)
    + - fixed some bugs when BytesPerUnit <> 1
    + - added some unicode support (@link(UnicodeChars) and + @link(UnicodeBigEndian))
    + - fixed some half byte (nibble) related bugs

  • + +
  • v 05-17-2003: may 17, 2003

    + - added @link(DisplayStart) and @link(DisplayEnd) functions to retrieve + the data bounds currently displayed
    + - added @link(BytesPerUnit) and @link(RulerBytesPerUnit) properties to + treat words/dwords/qwords as a unit
    + - added @link(SyncView) procedure and @link(OnSelectionChanged) + property to synchronize position and selection with another + editor
    + - added @link(ShowPositionIfNotFocused) property to show the current + position if the editor is not focused

  • + +
  • v 10-25-2002: october 25, 2002

    + - corrected the BytesPerColumn default value

  • + +
  • v 08-18-2002: august 18, 2002

    + - modified painting and selection
    + - implemented an additional ruler bar at the top
    + - new properties: @link(ShowRuler), @link(DrawGutter3D)

  • + +
  • v 08-12-2002: august 12, 2002

    + - modified Changed calls to get correct Modified property in + OnChange handler

  • + +
  • v 08-09-2002: august 09, 2002

    + - included missing include file delver.inc
    + - added OnChange event

  • + +
  • v 07-21-2002: july 21, 2002

    + too many changes to mention here (completely rewritten, basic and advanced versions + TMPHexEditor and TMPHexEditorEx), plz read the documentation included with this + package for more information
  • +

+ +*) + +unit MPHexEditor; +{$R *.res} +{.$DEFINE TINYHEXER} // don't define this! +{$DEFINE FASTACCESS} // if this is defined, direct access to the stream memory is given + +(* define this if you want to have the old savetostream behaviour + (clear target stream before copying data). + if it is undef'd, do not clear the target stream + (just copy the editor data to the stream) *) +{.$DEFINE OLD_STREAM_OUT} + +{$IFNDEF PASDOC} +{$I MPDELVER.INC} +{$ELSE} + +{$ENDIF} + +interface + +uses + Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, + Grids; + +type + // @exclude + TGridCoord = Grids.TGridCoord; + + // character conversion type + TMPHCharConvType = (cctFromAnsi, cctToAnsi); + // character conversion table + TMPHCharConvTable = array[0..255] of Char; + // character conversion data storage + TMPHCharConv = array[TMPHCharConvType] of TMPHCharConvTable; + +const + // block size in file i/o + MPH_FILEIO_BLOCKSIZE = $F000; + + // this message is posted to the hex editor when it should update the caret position + CM_INTUPDATECARET = CM_BASE + $100; + + // this message is posted when an OnSelectionChange event is to be fired + CM_SELECTIONCHANGED = CM_BASE + $101; + + (* translation tables from/to ms windows ansi (~ MS Latin-1) *) + + // macintosh..ms ansi conversion + MPH_CCONV_MAC: TMPHCharConv = ( + //ansi to mac + (#$00, #$01, #$02, #$03, #$04, #$05, #$06, #$07, #$08, #$09, #$0A, #$0B, + #$0C, #$0D, #$0E, #$0F, + #$10, #$11, #$12, #$13, #$14, #$15, #$16, #$17, #$18, #$19, #$1A, #$1B, + #$1C, #$1D, #$1E, #$1F, + #$20, #$21, #$22, #$23, #$24, #$25, #$26, #$27, #$28, #$29, #$2A, #$2B, + #$2C, #$2D, #$2E, #$2F, + #$30, #$31, #$32, #$33, #$34, #$35, #$36, #$37, #$38, #$39, #$3A, #$3B, + #$3C, #$3D, #$3E, #$3F, + #$40, #$41, #$42, #$43, #$44, #$45, #$46, #$47, #$48, #$49, #$4A, #$4B, + #$4C, #$4D, #$4E, #$4F, + #$50, #$51, #$52, #$53, #$54, #$55, #$56, #$57, #$58, #$59, #$5A, #$5B, + #$5C, #$5D, #$5E, #$5F, + #$60, #$61, #$62, #$63, #$64, #$65, #$66, #$67, #$68, #$69, #$6A, #$6B, + #$6C, #$6D, #$6E, #$6F, + #$70, #$71, #$72, #$73, #$74, #$75, #$76, #$77, #$78, #$79, #$7A, #$7B, + #$7C, #$7D, #$7E, #$7F, + #$C4, #$C5, #$AB, #$C9, #$D1, #$F7, #$DC, #$E1, #$E0, #$E2, #$E4, #$E3, + #$AC, #$B0, #$AA, #$F8, + #$D5, #$CE, #$C3, #$CF, #$D3, #$D4, #$D2, #$DB, #$DA, #$DD, #$F6, #$F5, + #$FA, #$F9, #$FB, #$FC, + #$A0, #$C1, #$A2, #$A3, #$DF, #$B4, #$B6, #$A4, #$C6, #$A9, #$BB, #$C7, + #$C2, #$AD, #$A8, #$FF, + #$A1, #$B1, #$B2, #$B3, #$A5, #$B5, #$A6, #$B7, #$B8, #$B9, #$BC, #$C8, + #$BA, #$BD, #$CA, #$C0, + #$CB, #$E7, #$E5, #$CC, #$80, #$81, #$AE, #$82, #$E9, #$83, #$E6, #$E8, + #$ED, #$EA, #$EB, #$EC, + #$D0, #$84, #$F1, #$EE, #$EF, #$CD, #$85, #$D7, #$AF, #$F4, #$F2, #$F3, + #$86, #$D9, #$DE, #$A7, + #$88, #$87, #$89, #$8B, #$8A, #$8C, #$BE, #$8D, #$8F, #$8E, #$90, #$91, + #$93, #$92, #$94, #$95, + #$F0, #$96, #$98, #$97, #$99, #$9B, #$9A, #$D6, #$BF, #$9D, #$9C, #$9E, + #$9F, #$FD, #$FE, #$D8 + ), + // mac to ansi + (#$00, #$01, #$02, #$03, #$04, #$05, #$06, #$07, #$08, #$09, #$0A, #$0B, + #$0C, #$0D, #$0E, #$0F, + #$10, #$11, #$12, #$13, #$14, #$15, #$16, #$17, #$18, #$19, #$1A, #$1B, + #$1C, #$1D, #$1E, #$1F, + #$20, #$21, #$22, #$23, #$24, #$25, #$26, #$27, #$28, #$29, #$2A, #$2B, + #$2C, #$2D, #$2E, #$2F, + #$30, #$31, #$32, #$33, #$34, #$35, #$36, #$37, #$38, #$39, #$3A, #$3B, + #$3C, #$3D, #$3E, #$3F, + #$40, #$41, #$42, #$43, #$44, #$45, #$46, #$47, #$48, #$49, #$4A, #$4B, + #$4C, #$4D, #$4E, #$4F, + #$50, #$51, #$52, #$53, #$54, #$55, #$56, #$57, #$58, #$59, #$5A, #$5B, + #$5C, #$5D, #$5E, #$5F, + #$60, #$61, #$62, #$63, #$64, #$65, #$66, #$67, #$68, #$69, #$6A, #$6B, + #$6C, #$6D, #$6E, #$6F, + #$70, #$71, #$72, #$73, #$74, #$75, #$76, #$77, #$78, #$79, #$7A, #$7B, + #$7C, #$7D, #$7E, #$7F, + #$C4, #$C5, #$C7, #$C9, #$D1, #$D6, #$DC, #$E1, #$E0, #$E2, #$E4, #$E3, + #$E5, #$E7, #$E9, #$E8, + #$EA, #$EB, #$ED, #$EC, #$EE, #$EF, #$F1, #$F3, #$F2, #$F4, #$F6, #$F5, + #$FA, #$F9, #$FB, #$FC, + #$A0, #$B0, #$A2, #$A3, #$A7, #$B4, #$B6, #$DF, #$AE, #$A9, #$8E, #$82, + #$8C, #$AD, #$C6, #$D8, + #$8D, #$B1, #$B2, #$B3, #$A5, #$B5, #$A6, #$B7, #$B8, #$B9, #$BC, #$AA, + #$BA, #$BD, #$E6, #$F8, + #$BF, #$A1, #$AC, #$92, #$80, #$81, #$A8, #$AB, #$BB, #$83, #$BE, #$C0, + #$C3, #$D5, #$91, #$93, + #$D0, #$84, #$96, #$94, #$95, #$90, #$F7, #$D7, #$FF, #$DD, #$98, #$97, + #$86, #$99, #$DE, #$A4, + #$88, #$87, #$89, #$8B, #$8A, #$C2, #$CA, #$C1, #$CB, #$C8, #$CD, #$CE, + #$CF, #$CC, #$D3, #$D4, + #$F0, #$D2, #$DA, #$DB, #$D9, #$9B, #$9A, #$85, #$8F, #$9D, #$9C, #$9E, + #$9F, #$FD, #$FE, #$AF + ) + ); + + // ebcdic cp38..ms ansi conversion + MPH_CCONV_BCD38: TMPHCharConv = ( + //ansi to bcd (taken from recode 3.5) + (#$00, #$01, #$02, #$03, #$37, #$2D, #$2E, #$2F, #$16, #$05, #$25, #$0B, + #$0C, #$0D, #$0E, #$0F, + #$10, #$11, #$12, #$13, #$3C, #$3D, #$32, #$26, #$18, #$19, #$3F, #$27, + #$1C, #$1D, #$1E, #$1F, + #$40, #$4F, #$7F, #$7B, #$5B, #$6C, #$50, #$7D, #$4D, #$5D, #$5C, #$4E, + #$6B, #$60, #$4B, #$61, + #$F0, #$F1, #$F2, #$F3, #$F4, #$F5, #$F6, #$F7, #$F8, #$F9, #$7A, #$5E, + #$4C, #$7E, #$6E, #$6F, + #$7C, #$C1, #$C2, #$C3, #$C4, #$C5, #$C6, #$C7, #$C8, #$C9, #$D1, #$D2, + #$D3, #$D4, #$D5, #$D6, + #$D7, #$D8, #$D9, #$E2, #$E3, #$E4, #$E5, #$E6, #$E7, #$E8, #$E9, #$4A, + #$E0, #$5A, #$5F, #$6D, + #$79, #$81, #$82, #$83, #$84, #$85, #$86, #$87, #$88, #$89, #$91, #$92, + #$93, #$94, #$95, #$96, + #$97, #$98, #$99, #$A2, #$A3, #$A4, #$A5, #$A6, #$A7, #$A8, #$A9, #$C0, + #$20, #$D0, #$A1, #$07, + #$80, #$22, #$62, #$63, #$64, #$65, #$66, #$67, #$68, #$69, #$8A, #$8B, + #$8C, #$8D, #$8E, #$8F, + #$90, #$77, #$2C, #$0A, #$3B, #$3E, #$1A, #$70, #$71, #$72, #$9A, #$9B, + #$9C, #$9D, #$9E, #$9F, + #$A0, #$15, #$73, #$74, #$75, #$76, #$6A, #$78, #$09, #$3A, #$AA, #$AB, + #$AC, #$AD, #$AE, #$AF, + #$B0, #$B1, #$B2, #$B3, #$B4, #$B5, #$B6, #$B7, #$B8, #$B9, #$BA, #$BB, + #$BC, #$BD, #$BE, #$BF, + #$23, #$41, #$42, #$43, #$44, #$45, #$46, #$47, #$48, #$49, #$CA, #$CB, + #$CC, #$CD, #$CE, #$CF, + #$1B, #$24, #$06, #$14, #$28, #$2B, #$21, #$17, #$51, #$52, #$DA, #$DB, + #$DC, #$DD, #$DE, #$DF, + #$2A, #$E1, #$53, #$54, #$55, #$56, #$57, #$58, #$59, #$29, #$EA, #$EB, + #$EC, #$ED, #$EE, #$EF, + #$30, #$31, #$08, #$33, #$34, #$35, #$36, #$04, #$38, #$39, #$FA, #$FB, + #$FC, #$FD, #$FE, #$FF + ), + // bcd to ansi (taken from recode 3.5) + (#$00, #$01, #$02, #$03, #$F7, #$09, #$D2, #$7F, #$F2, #$A8, #$93, #$0B, + #$0C, #$0D, #$0E, #$0F, + #$10, #$11, #$12, #$13, #$D3, #$A1, #$08, #$D7, #$18, #$19, #$96, #$D0, + #$1C, #$1D, #$1E, #$1F, + #$7C, #$D6, #$81, #$C0, #$D1, #$0A, #$17, #$1B, #$D4, #$E9, #$E0, #$D5, + #$92, #$05, #$06, #$07, + #$F0, #$F1, #$16, #$F3, #$F4, #$F5, #$F6, #$04, #$F8, #$F9, #$A9, #$94, + #$14, #$15, #$95, #$1A, + #$20, #$C1, #$C2, #$C3, #$C4, #$C5, #$C6, #$C7, #$C8, #$C9, #$5B, #$2E, + #$3C, #$28, #$2B, #$21, + #$26, #$D8, #$D9, #$E2, #$E3, #$E4, #$E5, #$E6, #$E7, #$E8, #$5D, #$24, + #$2A, #$29, #$3B, #$5E, + #$2D, #$2F, #$82, #$83, #$84, #$85, #$86, #$87, #$88, #$89, #$A6, #$2C, + #$25, #$5F, #$3E, #$3F, + #$97, #$98, #$99, #$A2, #$A3, #$A4, #$A5, #$91, #$A7, #$60, #$3A, #$23, + #$40, #$27, #$3D, #$22, + #$80, #$61, #$62, #$63, #$64, #$65, #$66, #$67, #$68, #$69, #$8A, #$8B, + #$8C, #$8D, #$8E, #$8F, + #$90, #$6A, #$6B, #$6C, #$6D, #$6E, #$6F, #$70, #$71, #$72, #$9A, #$9B, + #$9C, #$9D, #$9E, #$9F, + #$A0, #$7E, #$73, #$74, #$75, #$76, #$77, #$78, #$79, #$7A, #$AA, #$AB, + #$AC, #$AD, #$AE, #$AF, + #$B0, #$B1, #$B2, #$B3, #$B4, #$B5, #$B6, #$B7, #$B8, #$B9, #$BA, #$BB, + #$BC, #$BD, #$BE, #$BF, + #$7B, #$41, #$42, #$43, #$44, #$45, #$46, #$47, #$48, #$49, #$CA, #$CB, + #$CC, #$CD, #$CE, #$CF, + #$7D, #$4A, #$4B, #$4C, #$4D, #$4E, #$4F, #$50, #$51, #$52, #$DA, #$DB, + #$DC, #$DD, #$DE, #$DF, + #$5C, #$E1, #$53, #$54, #$55, #$56, #$57, #$58, #$59, #$5A, #$EA, #$EB, + #$EC, #$ED, #$EE, #$EF, + #$30, #$31, #$32, #$33, #$34, #$35, #$36, #$37, #$38, #$39, #$FA, #$FB, + #$FC, #$FD, #$FE, #$FF + ) + ); + +type + // custom Exception class + EMPHexEditor = class(Exception); + + (* bookmark record:
+ defined by pressing SHIFT+CTRL+[0..9], goto bookmark by pressing CTRL+[0..9]

+ + - mPosition: file position
+ - mInCharField: cursor in character pane (True) or hex number pane + *) + TMPHBookmark = record + mPosition: integer; + mInCharField: boolean; + end; + + // array of bookmarks, representing keys 0..9 + TMPHBookmarks = array[0..9] of TMPHBookmark; + + (* look of the editor's caret:
+ - ckFull: full block
+ - ckLeft: left line
+ - ckBottom: bottom line
+ - ckAuto: left line if @link(InsertMode), full block if overwrite, + bottom line if ReadOnlyView + *) + TMPHCaretKind = (ckFull, + ckLeft, + ckBottom, + ckAuto + ); + + (* how to show a file's content in the character pane of the editor:
+ - tkAsIs: leave as is (current windows code page)
+ - tkDos8: current dos codepage
+ - tkASCII: 7 bit ascii
+ - tkMac: macintosh charset (translation always from/to ms cp 1252 (ms latin1)!!
+ - tkBCD: ibm ebcdic codepage 38 (translation always from/to ms cp 1252 (ms latin1)!!
+ - tkCustom: custom codepage stored in @link(MPHCustomCharConv) + *) + TMPHTranslationKind = (tkAsIs, + tkDos8, + tkASCII, + tkMac, + tkBCD + + ,tkCustom + + ); + + (* action indicator used in @link(OnProgress) event handler:
+ - pkLoad: loading data
+ - pkSave: saving data
+ - pkFind: finding + *) + TMPHProgressKind = (pkLoad, + pkSave, pkFind + ); + + (* progress event handler, used in @link(OnProgress)

+ + - ProgressType: am i loading or saving? (see @link(TMPHProgressKind))
+ - aName: name of file to be load from/saved to
+ - Percent: current progress (0..100)
+ - Cancel: if set to true, the load/save procedure will abort (no meaning in Find* methods)
+ *) + TMPHProgressEvent = procedure(Sender: TObject; + const ProgressType: TMPHProgressKind; + const aName: TFileName; + const Percent: byte; + var Cancel: boolean) of object; + + (* retrieve the "line number" to display by the application

+ + - Number: the number to convert to text + - OffsetText: the resulting text output + *) + TMPHGetOffsetTextEvent = procedure(Sender: TObject; + const Number: int64; + var OffsetText: string) of object; + + (* handler for custom search routines

+ + - Pattern: the data to find + - PatLength: length of the data to find + - SearchFrom: first search position + - SearchUntil: last search position + - IgnoreCase: case sensitive? + - Wilcard: Wildcard character (only used by FindWithWildcard) + - FoundPos: result, set to -1 if data was not found + *) + TMPHFindEvent = procedure(Sender: TObject; + const Pattern: PChar; const PatLength: integer; + const SearchFrom, SearchUntil: integer; + const IgnoreCase: boolean; + const Wildcard: Char; + var FoundPos: Integer) of object; + + // precompiled converted character table types for faster data search + PMPHFindTable = ^TMPHFindTable; + TMPHFindTable = array[#0..#255] of Char; + + //@exclude + // flags internally used in the undo storage + TMPHUndoFlag = ( + // kind of undo storage + ufKindBytesChanged, + ufKindByteRemoved, + ufKindInsertBuffer, + ufKindReplace, + ufKindAppendBuffer, + ufKindNibbleInsert, + ufKindNibbleDelete, + ufKindConvert, + ufKindSelection, // store a selection + ufKindCombined, + ufKindAllData, // store current data and size for complete undo + // additional information + ufFlagByte1Changed, + ufFlagByte2Changed, + ufFlagModified, + ufFlag2ndByteCol, + ufFlagInCharField, + ufFlagHasSelection, + ufFlagInsertMode, + ufFlagIsUnicode, + ufFlagIsUnicodeBigEndian, + ufFlagHasDescription + ); + + //@exclude + // set of undo flags + TMPHUndoFlags = set of TMPHUndoFlag; + +type + // persistent color storage (contains the colors in hex editors) + TMPHColors = class(TPersistent) + private + FParent: TControl; + FOffset: TColor; + FOddColumn: TColor; + FEvenColumn: TColor; + FCursorFrame: TColor; + FNonFocusCursorFrame: TColor; + FBackground: TColor; + FChangedText: TColor; + FChangedBackground: TColor; + FCurrentOffsetBackground: TColor; + FOffsetBackGround: TColor; + FActiveFieldBackground: TColor; + FCurrentOffset: TColor; + FGrid: TColor; + procedure SetOffsetBackGround(const Value: TColor); + procedure SetCurrentOffset(const Value: TColor); + procedure SetParent(const Value: TControl); + procedure SetGrid(const Value: TColor); + procedure SetBackground(const Value: TColor); + procedure SetChangedBackground(const Value: TColor); + procedure SetChangedText(const Value: TColor); + procedure SetCursorFrame(const Value: TColor); + procedure SetEvenColumn(const Value: TColor); + procedure SetOddColumn(const Value: TColor); + procedure SetOffset(const Value: TColor); + procedure SetActiveFieldBackground(const Value: TColor); + procedure SetCurrentOffsetBackground(const Value: TColor); + procedure SetNonFocusCursorFrame(const Value: TColor); + public + // @exclude(constructor) + constructor Create(Parent: TControl); + // @exclude() + procedure Assign(Source: TPersistent); override; + // parent hex editor control + property Parent: TControl read FParent write SetParent; + published + // background color + property Background: TColor read FBackground write SetBackground; + // background color of modified bytes (in overwrite mode) + property ChangedBackground: TColor read FChangedBackground write + SetChangedBackground; + // foreground color of modified bytes (in overwrite mode) + property ChangedText: TColor read FChangedText write SetChangedText; + // color of the cursor and position frame in the second pane + property CursorFrame: TColor read FCursorFrame write SetCursorFrame; + // foreground color of the line offsets + property Offset: TColor read FOffset write SetOffset; + // foreground color of odd columns + property OddColumn: TColor read FOddColumn write SetOddColumn; + // foreground color of even columns + property EvenColumn: TColor read FEvenColumn write SetEvenColumn; + // background color of the current line in the offset pane (gutter) + property CurrentOffsetBackground: TColor read FCurrentOffsetBackground write + SetCurrentOffsetBackground; + // background color of the offset pane (gutter) + property OffsetBackGround: TColor read FOffsetBackGround write + SetOffsetBackGround; + // foreground color of the current line in the offset pane (gutter) + property CurrentOffset: TColor read FCurrentOffset write SetCurrentOffset; + // pen color of the grid + property Grid: TColor read FGrid write SetGrid; + // color of a cursor frame in a non-focused editor + property NonFocusCursorFrame: TColor read FNonFocusCursorFrame write + SetNonFocusCursorFrame; + // background color of the active field (hex/chars) + property ActiveFieldBackground: TColor read FActiveFieldBackground write SetActiveFieldBackground; + end; + + // @exclude(stream class for internal storage/undo) + TMPHMemoryStream = class(TMemoryStream) + private + procedure CheckBounds(const AMax: Integer); + function PointerAt(const APosition: Integer): Pointer; + protected + public + procedure ReadBufferAt(var Buffer; const APosition, ACount: Integer); + procedure WriteBufferAt(const Buffer; const APosition, ACount: Integer); + procedure Move(const AFromPos, AToPos, ACount: Integer); + procedure TranslateToAnsi(const FromTranslation: TMPHTranslationKind; const + APosition, ACount: integer); + procedure TranslateFromAnsi(const ToTranslation: TMPHTranslationKind; const + APosition, ACount: integer); + function GetAsHex(const APosition, ACount: integer; const SwapNibbles: + Boolean): string; + end; + + //@exclude + // undo storage implementation + TMPHUndoStorage = class; + + //@exclude + // offset format flags + TMPHOffsetFormatFlag = (offCalcWidth, + // calculate minwidth depending on data size (width field = '-') + offCalcRow, + // calculate _BytesPerUnit depending on bytes per row (=real line numbers) + offCalcColumn, // " bytes per column (= column numbers) + offBytesPerUnit // use BytesPerUnit property + ); + + //@exclude + // set of the above flags + TMPHOffsetFormatFlags = set of TMPHOffsetFormatFlag; + + //@exclude + // offset format record + TMPHOffsetFormat = record + Format: string; // format as string + Prefix, + Suffix: string; // splitted format + MinWidth: integer; // min length of value (zero padded on the left) + Flags: // auto calculation flags + TMPHOffsetFormatFlags; + Radix, // radix (base) of display (2..16) + _BytesPerUnit: byte; // length of one unit (1 Byte...BytesPerRow Bytes) + end; + + (* owner draw event type. parameters:

+ - Sender: the hex editor
+ - ACanvas: the editor's canvas
+ - ACol, ARow: the position to be drawn
+ - AWideText: the text to be drawn
+ - ARect: the cell rectangle
+ - ADefaultDraw: if set to True (default), default drawing isperformed after the event handler returns. + if set to false, the event handler must do all cell painting. + *) + TMPHDrawCellEvent = procedure(Sender: TObject; ACanvas: TCanvas; ACol, ARow: + Integer; var AWideText: WideString; ARect: TRect; var ADefaultDraw: Boolean) + of object; + + // protected ancestor of the hex editor components + + TCustomMPHexEditor = class(TCustomGrid) + + private + + FIntLastHexCol: integer; + FFindTable, + FFindTableI: TMPHFindTable; + FIsMaxOffset: boolean; + FFindProgress: boolean; + FBlockSize: Integer; + FSepCharBlocks: boolean; + FOnGetOffsetText: TMPHGetOffsetTextEvent; + FFixedFileSize: boolean; + FCharWidth, + FCharHeight: integer; + FBookmarkImageList: TImageList; + FInsertModeOn: boolean; + FCaretBitmap: TBitmap; + FColors: TMPHColors; + FBytesPerRow: integer; + FOffSetDisplayWidth: integer; + FBytesPerRowDup: integer; + FDataStorage: TMPHMemoryStream; + FSwapNibbles: integer; + FFocusFrame: boolean; + FIsFileReadonly: boolean; + FBytesPerCol: integer; + FPosInCharField, + FLastPosInCharField: boolean; + FFileName: string; + FModifiedBytes: TBits; + FBookmarks: TMPHBookmarks; + FSelStart, + FSelPosition, + FSelEnd: integer; + FSelBeginPosition: integer; + FTranslation: TMPHTranslationKind; + FCaretKind: TMPHCaretKind; + FReplaceUnprintableCharsBy: char; + FAllowInsertMode: boolean; + FWantTabs: boolean; + FReadOnlyView: boolean; + FHideSelection: boolean; + FGraySelOnLostFocus: boolean; + FOnProgress: TMPHProgressEvent; + FMouseDownCol, + FMouseDownRow: integer; + FShowDrag: boolean; + FDropCol, + FDropRow: integer; + FOnInvalidKey, + FOnTopLeftChanged: TNotifyEvent; + FDrawGridLines: boolean; + FDrawGutter3D: boolean; + FGutterWidth: integer; + FOffsetFormat: TMPHOffsetFormat; + FSelectionPossible: boolean; + FBookmarkBitmap: TBitmap; + FCursorList: array of integer; + FHasCustomBMP: boolean; + FStreamFileName: string; + FHasFile: boolean; + FMaxUndo: integer; + FHexChars: array[0..15] of char; + FHexLowerCase: boolean; + FOnChange: TNotifyEvent; + FShowRuler: boolean; + FBytesPerUnit: Integer; + FRulerBytesPerUnit: Integer; + FOnSelectionChanged: TNotifyEvent; + FSelectionChangedCount: Integer; + FShowPositionIfNotFocused: Boolean; + FOffsetHandler: Boolean; + FUsedRulerBytesPerUnit: Integer; + FIsSelecting: boolean; + FMouseUpCanResetSel: boolean; + FUndoStorage: TMPHUndoStorage; + FUnicodeCharacters: Boolean; + FUnicodeBigEndian: Boolean; + FMaskedChars: TSysCharSet; + + FDrawDataPosition: integer; + FOnDrawCell: TMPHDrawCellEvent; + + FOnBookmarkChanged: TNotifyEvent; + property Color; + function IsInsertModePossible: boolean; + function IsFileSizeFixed: boolean; + procedure InternalErase(const KeyWasBackspace: boolean; const UndoDesc: + string = ''); + procedure SetReadOnlyView(const Value: boolean); + procedure SetCaretKind(const Value: TMPHCaretKind); + procedure SetFocusFrame(const Value: boolean); + procedure SetBytesPerColumn(const Value: integer); + procedure SetSwapNibbles(const Value: boolean); + function GetSwapNibbles: boolean; + function GetBytesPerColumn: integer; + procedure SetOffsetDisplayWidth; + procedure SetColors(const Value: TMPHColors); + procedure SetReadOnlyFile(const Value: boolean); + procedure SetTranslation(const Value: TMPHTranslationKind); + procedure SetModified(const Value: boolean); + procedure SetChanged(DataPos: integer; const Value: boolean); + procedure SetFixedFileSize(const Value: boolean); + procedure SetAllowInsertMode(const Value: boolean); + function GetInsertMode: boolean; + procedure SetWantTabs(const Value: boolean); + procedure SetHideSelection(const Value: boolean); + procedure SetGraySelectionIfNotFocused(const Value: boolean); + function CalcColCount: integer; + function GetLastCharCol: integer; + function GetPropColCount: integer; + function GetPropRowCount: integer; + function GetMouseOverSelection: boolean; + function CursorOverSelection(const X, Y: integer): boolean; + function MouseOverFixed(const X, Y: integer): boolean; + procedure AdjustBookmarks(const From, Offset: integer); + procedure IntSetCaretPos(const X, Y, ACol: integer); + procedure TruncMaxPosition(var DataPos: integer); + procedure SetSelection(DataPos, StartPos, EndPos: integer); + function GetCurrentValue: integer; + procedure SetInsertMode(const Value: boolean); + function GetModified: boolean; + //function GetDataPointer: Pointer; + procedure SetBytesPerRow(const Value: integer); + procedure SetMaskChar(const Value: char); + procedure SetAsText(const Value: string); + procedure SetAsHex(const Value: string); + function GetAsText: string; + function GetAsHex: string; + procedure WMTimer(var Msg: TWMTimer); message WM_TIMER; + // show or hide caret depending on row/col in view + procedure CheckSetCaret; + // get the row according to the given buffer position + function GetRow(const DataPos: integer): integer; + // invalid key pressed (in ebcdic) + procedure WrongKey; + // create an inverting caret bitmap + procedure CreateCaretGlyph; + // get start of selection + function GetSelStart: integer; + // get end of selection + function GetSelEnd: integer; + // get selection count + function GetSelCount: integer; + // set selection start + procedure SetSelStart(aValue: integer); + // set selection end + procedure SetSelEnd(aValue: integer); + // position the caret in the given field + procedure SetInCharField(const Value: boolean); + // is the caret in the char field ? + function GetInCharField: boolean; + // insert a buffer (internal) + procedure InternalInsertBuffer(Buffer: PChar; const Size, Position: + integer); + // append some data (int) + procedure InternalAppendBuffer(Buffer: PChar; const Size: integer); + // store the caret properties + procedure InternalGetCurSel(var StartPos, EndPos, ACol, ARow: integer); + // delete data + procedure InternalDelete(StartPos, EndPos, ACol, ARow: integer); + // delete one half byte + function InternalDeleteNibble(const Pos: integer; + const HighNibble: boolean): boolean; + // insert half byte + function InternalInsertNibble(const Pos: integer; const HighNibble: + boolean): boolean; + // used by nibble functions + function CreateShift4BitStream(const StartPos: integer; var FName: + TFileName): TFileStream; + // convert a given amount of data from ansi to something different and vice versa + procedure InternalConvertRange(const aFrom, aTo: integer; const aTransFrom, + aTransTo: TMPHTranslationKind); + // move data in buffer to a different position + procedure MoveFileMem(const aFrom, aTo, aCount: integer); + function GetBookmark(Index: byte): TMPHBookmark; + procedure SetBookmark(Index: byte; const Value: TMPHBookmark); + procedure SetBookmarkVals(const Index: byte; const Position: integer; const + InCharField: boolean); + procedure SetDrawGridLines(const Value: boolean); + procedure SetGutterWidth(const Value: integer); + // images have changed + procedure BookmarkBitmapChanged(Sender: TObject); + procedure SetBookmarkBitmap(const Value: TBitmap); + + function GetVersion: string; + procedure SetVersion(const Value: string); + + // free alloc'd memory of one of the storage streams; + procedure FreeStorage(FreeUndo: boolean = False); + function GetCanUndo: boolean; + function GetCanRedo: boolean; + function GetUndoDescription: string; + function GetOffsetFormat: string; + procedure SetOffsetFormat(const Value: string); + // generate offset format + procedure GenerateOffsetFormat(Value: string); + procedure SetHexLowerCase(const Value: boolean); + procedure SetDrawGutter3D(const Value: boolean); + procedure SetShowRuler(const Value: boolean); + procedure SetBytesPerUnit(const Value: integer); + procedure SetRulerString; + procedure CheckSelectUnit(var AStart, AEnd: Integer); + procedure SetRulerBytesPerUnit(const Value: integer); + procedure SetShowPositionIfNotFocused(const Value: Boolean); + function GetDataAt(Index: integer): Byte; + procedure SetDataAt(Index: integer; const Value: Byte); + procedure SetUnicodeCharacters(const Value: Boolean); + procedure SetUnicodeBigEndian(const Value: Boolean); + function GetPositionAtCursor(const ACol, ARow: integer): integer; + function GetIsCharFieldCol(const ACol: integer): Boolean; +{$IFDEF FASTACCESS} + function GetFastPointer: PByteArray; +{$ENDIF} + procedure SetDataSize(const Value: integer); + procedure SetBlockSize(const Value: Integer); + procedure SetSepCharBlocks(const Value: boolean); + private + + FIsDrawDataSelected: boolean; + + FOnWildcardFind: TMPHFindEvent; + FOnFind: TMPHFindEvent; +{$IFDEF FASTACCESS} + FSetDataSizeFillByte: Byte; +{$ENDIF} + FRulerNumberBase: byte; + procedure SetFindProgress(const Value: boolean); + procedure SetRulerNumberBase(const Value: byte); + procedure SetMaskedChars(const Value: TSysCharSet); + protected + // @exclude() + FRulerString: string; + // @exclude() + FRulerCharString: string; + + // @exclude(used by TMPHexEditorEx for internal drag 'n' drop) + FFixedFileSizeOverride: boolean; + // @exclude(used by TMPHexEditorEx for internal undo changing) + FModified: boolean; + // @exclude(overwrite mouse wheel for zooming) + function DoMouseWheelDown(Shift: TShiftState; MousePos: TPoint): boolean; + override; + // @exclude(overwrite mouse wheel for zooming) + function DoMouseWheelUp(Shift: TShiftState; MousePos: TPoint): boolean; + override; + // @exclude(actually used bytes per unit) + property UsedRulerBytesPerUnit: Integer read FUsedRulerBytesPerUnit; + // @exclude(True: cells are currently to be selected) + property IsSelecting: boolean read FIsSelecting; + // @exclude(True: MouseUp resets selection) + property MouseUpCanResetSel: boolean read FMouseUpCanResetSel write + FMouseUpCanResetSel; + // @exclude(memory stream which contains the undo/redo data) + property UndoStorage: TMPHUndoStorage read FUndoStorage; + // @exclude(stream that contains the data) + property DataStorage: TMPHMemoryStream read FDataStorage; + // @exclude(fire OnSelectionChange) + procedure SelectionChanged; virtual; + // @exclude(set a new selection) + procedure NewSelection(SelFrom, SelTo: integer); + // @exclude(get the current mouse position) + function CheckMouseCoord(var X, Y: integer): TGridCoord; + // @exclude(assure the value is a multiple of FBytesPerUnit) + procedure CheckUnit(var AValue: Integer); + // call changed on every undo creation for OnChange event + procedure Changed; virtual; + // returns the drop file position after a drag'n'drop operation + function DropPosition: integer; + // copy a stream to a second one and fire the OnProgress handler + procedure Stream2Stream(strFrom, strTo: TStream; const Operation: + TMPHProgressKind; const Count: integer = -1); + (* allows descendants to take special action if contents are to be saved + to the file from where the data was load *) + procedure PrepareOverwriteDiskFile; virtual; + // store the current Cursor and set it to crHourGlass (see also @link(OldCursor)) + procedure WaitCursor; + // reset the Cursor to the previous value (see also @link(WaitCursor)) + procedure OldCursor; + // @exclude(override paint) + procedure Paint; override; + // @exclude(view changed) + procedure TopLeftChanged; override; + // adjust cell widths/heigths depending on font, offset format, bytes per row/column... + procedure AdjustMetrics; + // get the size of the contained data + function GetDataSize: integer; + // @exclude(calculate the grid sizes) + procedure CalcSizes; + // @exclude(select one cell) + function SelectCell(ACol, ARow: longint): boolean; override; + // @exclude(get the data position depending on col and row) + function GetPosAtCursor(const aCol, aRow: integer): integer; + // @exclude(vice versa) + function GetCursorAtPos(const aPos: integer; const aChars: boolean): + TGridCoord; + // @exclude(get the column of the other field (hex<->char)) + function GetOtherFieldCol(const aCol: integer): integer; + // @exclude(get the column of the other field (hex<->char)) + function GetOtherFieldColCheck(const aCol: integer): integer; + // @exclude(can the cell be selected ?) + function CheckSelectCell(aCol, aRow: integer): boolean; + // @exclude(char message handler) + procedure WMChar(var Msg: TWMChar); message WM_CHAR; + // @exclude(posted message to update the caret position) + procedure CMINTUPDATECARET(var Msg: TMessage); message CM_INTUPDATECARET; + // @exclude(posted message to fire an OnSelectionChanged event) + procedure CMSelectionChanged(var Msg: TMessage); message + CM_SELECTIONCHANGED; + // @exclude(for shortcuts) + procedure WMGetDlgCode(var Msg: TWMGetDlgCode); message WM_GETDLGCODE; + // @exclude(readjust grid sizes after font has changed) + procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED; + // @exclude(change a byte at the given position) + procedure IntChangeByte(const aOldByte, aNewByte: byte; + aPos, aCol, aRow: integer; const UndoDesc: string = ''); + // @exclude(change two bytes at the given position) + procedure IntChangeWideChar(const aOldChar, aNewChar: WideChar; aPos, aCol, + aRow: integer; const UndoDesc: string = ''); + // @exclude(keydown handler) + procedure KeyDown(var Key: word; Shift: TShiftState); override; + // @exclude(keyup handler) + //procedure KeyUp(var Key: word; Shift: TShiftState); override; + // @exclude(has this byte been modified ?) + function HasChanged(aPos: integer): boolean; + // @exclude(redraw some lines) + procedure RedrawPos(aFrom, aTo: integer); + // @exclude(make a selection) + procedure Select(const aCurCol, aCurRow, aNewCol, aNewRow: integer); + // @exclude(mouse down handler) + procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: + integer); override; + // @exclude(mouse move handler) + procedure MouseMove(Shift: TShiftState; X, Y: integer); override; + // @exclude(mouse up handler) + procedure MouseUP(Button: TMouseButton; Shift: TShiftState; X, Y: integer); + override; + // @exclude(is undo record creation possible?) + function CanCreateUndo(const aKind: TMPHUndoFlag; const aCount, aReplCount: + integer): Boolean; virtual; + // @exclude(add an undo to the undo buffer) + procedure CreateUndo(const aKind: TMPHUndoFlag; const aPos, aCount, + aReplCount: integer; const sDesc: string = ''); + // @exclude(after loading) + procedure Loaded; override; + // @exclude(override CreateWnd) + procedure CreateWnd; override; + // @exclude(wm_setfocus handler) + procedure WMSetFocus(var Msg: TWMSetFocus); message WM_SETFOCUS; + // @exclude(wm_killfocus handler) + procedure WMKillFocus(var Msg: TWMKillFocus); message WM_KILLFOCUS; + // @exclude(wm_vscroll handler) + procedure WMVScroll(var Msg: TWMVScroll); message WM_VSCROLL; + // @exclude(wm_hscroll handler) + procedure WMHScroll(var Msg: TWMHScroll); message WM_HSCROLL; + // @exclude(resize the control) + procedure Resize; override; + // @exclude(store bitmap ? (its set to true, if a custom bitmap has been stored in BookmarkBitmap)) + function HasCustomBookmarkBitmap: boolean; + // number of bytes to show in each row + property BytesPerRow: integer read FBytesPerRow write SetBytesPerRow; + // if set to True, the find* routines also fire OnProgress events (default is False) + property FindProgress: boolean read FFindProgress write SetFindProgress + default False; + // number of bytes to show in each column + property BytesPerColumn: integer read GetBytesPerColumn write + SetBytesPerColumn default 2; + (* translation kind of the data (used to show characters on and to handle key presses in the char pane), + (see also @link(TMPHTranslationKind)) + *) + property Translation: TMPHTranslationKind read FTranslation write + SetTranslation; + (* offset display ("line numbers") format, in the form
+ [r|c|<HEXNUM>%][-|<HEXNUM>!]<HEXNUM>:[Prefix]|[Suffix]
+ (<HEXNUM> means a number in hexadecimal format (without prefix/suffix))

+ - first field (up to the percent sign):
+
    +
  • sets the "bytes per unit field" of the offset display format
  • +
  • if it's set to 1, each row offset displays the data position in bytes
  • +
  • if it's set to 2, each row offset displays the data position in words
  • +
  • if it's set to 4, each row offset displays the data position in dwords
  • +
  • if it's set to "r", each row offset displays the current row number (1st row=0, + see also @link(BytesPerRow))
  • +
  • if it's set to "c", each row offset displays the current column number (1st column=0, + see also @link(BytesPerColumn))
  • +
  • if this field is omitted, bytes per unit is set to the value of the + @link(RulerBytesPerUnit) property
  • +

+ - second field (up to the exclamation mark):
+
    +
  • sets the minimum width of the number part, if the number is shorter, it will be padded + by '0' chars at the left
  • +
  • if this field reads -!, the the minimum width is automatically set to the longest number + that can appear in the editor (the data's size)
  • +
  • if this field is omitted, the minimum width is set to 1
  • +

+ - third field (up to the colon):
+
    +
  • sets the radix (base) of the offset format in hex notation
  • +
  • set this to '10' (without quotes) for hexadecimal offset display, set it to '08' for + octal and to '0a' for decimal offset display
  • +
  • this field cannot be omitted, but the whole format string my be blank to avoid the display of + offset identifiers
  • +

+ - fourth field (up to the pipe ('|') char):
+
    +
  • the prefix that is put in front of the "number" string (e.g. '0x' or '$' to show that numbers are in hex format) +
  • this field may be omitted (but not the pipe char!)
  • +

+ - fifth (and last) field:
+
    +
  • the suffix to put after the "number string" (e.g. 'h' to show hex numbers)
  • +
  • this field may be omitted
+ *) + property OffsetFormat: string read GetOffsetFormat write SetOffsetFormat; + + (* if this handler is assigned, the @link(OffsetFormat) is not used to + create "line numbers", but the application tells the editor how to format the offset text + *) + property OnGetOffsetText: TMPHGetOffsetTextEvent read FOnGetOffsetText write + FOnGetOffsetText; + + (* how many bytes form one block in a row? blocks are separated by a one character wide blank. + -1 means no block separation (see also @link(SeparateBlocksInCharField)) *) + property BytesPerBlock: Integer read FBlockSize write SetBlockSize default + -1; + + (* if @link(BytesPerBlock) is used, this property tells the editor whether it should + separate blocks of bytes in the character pane too or not *) + property SeparateBlocksInCharField: boolean read FSepCharBlocks write + SetSepCharBlocks default True; + + // look of the editor's caret (see @link(TMPHCaretKind)) + property CaretKind: TMPHCaretKind read FCaretKind write SetCaretKind default + ckAuto; + // colors to display (see @link(TMPHColors)) + property Colors: TMPHColors read FColors write SetColors; + (* if FocusFrame is set to True, the current caret position will be displayed in the + second field (hex - characters) as a dotted focus frame, if set to False, it will + be shown as an ordinary rectangle + *) + property FocusFrame: boolean read FFocusFrame write SetFocusFrame; + (* if SwapNibbles is set to True, the hex pane will show all bytes in the order + lower 4 bits-higher 4 bits (i.e. the value 192 dec = C0 hex will be drawn as + 0C). if set to False, hex values will be displayed in usual order. this + setting also affects hex data input and hex-string conversions + *) + property SwapNibbles: boolean read GetSwapNibbles write SetSwapNibbles + default False; + // replace @link(MaskedChars) with the following character in the character pane + property MaskChar: char read FReplaceUnprintableCharsBy write SetMaskChar + stored False; + (* if set to True, the data size is readonly, e.g. no data may be appended, deleted + or inserted, just overwriting is allowed. this also affects @link(InsertMode). + *) + property NoSizeChange: boolean read FFixedFileSize write SetFixedFileSize + default False; + (* if set to False, switching between overwrite and insert mode is not allowed + (see also @link(InsertMode) and @link(NoSizeChange)) + *) + property AllowInsertMode: boolean read FAllowInsertMode write + SetAllowInsertMode default True; + (* if set to True, the Tab key is used to switch the caret between hex and character pane. + if set to False, the Tab key can be used to switch between controls. then the + combination CTRL+T is used to switch the panes + *) + property WantTabs: boolean read FWantTabs write SetWantTabs default True; + // if set to True, the data can not be edited, just cursor movement is allowed ("Hex Viewer" mode) + property ReadOnlyView: boolean read FReadOnlyView write SetReadOnlyView + default False; + // hide the current selection when the hex editor looses focus (see also @link(GraySelectionIfNotFocused)) + property HideSelection: boolean read FHideSelection write SetHideSelection + default False; + (* if set to True and @link(HideSelection) is False, then the current selection will be + grayed when the hex editor looses focus (the values from the @link(Colors) property will + be converted to grayscale colors) + *) + property GraySelectionIfNotFocused: boolean read FGraySelOnLostFocus write + SetGraySelectionIfNotFocused default False; + (* this event is called in @link(LoadFromFile), @link(SaveToFile), @link(Find) and + @link(FindWithWildcard) routines, so a progress indicator may be updated + (see also @link(TMPHProgressEvent), @link(FindProgress)) + *) + property OnProgress: TMPHProgressEvent read FOnProgress write + FOnProgress; + (* this event is fired if an invalid character has been typed (like non-hex characters + in the hex pane) + *) + property OnInvalidKey: TNotifyEvent read FOnInvalidKey write FOnInvalidKey; + // this event is fired if the first visible row or column have been changed (e.g. on scrolling) + property OnTopLeftChanged: TNotifyEvent read FOnTopLeftChanged write + FOnTopLeftChanged; + // returns the current selection in hex format ('00010203...') as string, uses @link(SwapNibbles) + function GetSelectionAsHex: string; + (* replace the current selection by a string containing data in hex format ('00 01 02 03' or similar), + uses @link(SwapNibbles) + *) + procedure SetSelectionAsHex(const s: string); + // returns a string containing the currently selected data + function GetSelectionAsText: string; + // replaces the currently selected data with the string's contents + procedure SetSelectionAsText(const s: string); + // if set to True, a grid is drawn + property DrawGridLines: boolean read FDrawGridLines write SetDrawGridLines; + // width of the offset display gutter, if set to -1, automatically adjust the gutter's width + property GutterWidth: integer read FGutterWidth write SetGutterWidth default + -1; + (* bitmap containing 20 10x10 pixels pictures for bokkmarks (they are displayed in the offset + gutter), the first ten pictures represent the bookmarks 0(10)..9, if they are set in the + hexpane, the last 10 pics are shown if bookmarks are set in the character pane (see also + @link(TMPHBookMark)) + *) + property BookmarkBitmap: TBitmap read FBookmarkBitmap write SetBookmarkBitmap + stored HasCustomBookmarkBitmap; + + // current version of the hex editor component (returns the build data), readonly + property Version: string read GetVersion write SetVersion stored True; + + // maximum memory that is used for undo storage (in bytes, approximately) + property MaxUndo: integer read FMaxUndo write FMaxUndo default 1024 * 1024; + (* insert mode (typed characters are inserted at the current position) or + overwrite mode (typed characters replace values at the current position), see also + @link(AllowInsertMode), @link(NoSizeChange) and @link(ReadOnlyView) + *) + property InsertMode: boolean read GetInsertMode write SetInsertMode default + False; + // if set to True, hex data and hex offsets are displayed in lower case + property HexLowerCase: boolean read FHexLowerCase write SetHexLowerCase + default False; + // this event is called on every data change (load/empty/undo/redo) + property OnChange: TNotifyEvent read FOnChange write FOnChange; + // if set to True, a 3d line is drawn at the right of the offset gutter + property DrawGutter3D: boolean read FDrawGutter3D write SetDrawGutter3D + default True; + // if set to True, a ruler is shown above the first row + property ShowRuler: boolean read FShowRuler write SetShowRuler default + False; + (* number base (i.e. radix) for the ruler display (2-16), tells the component + which number format to use when drawing the ruler + *) + property RulerNumberBase: byte read FRulerNumberBase write SetRulerNumberBase + default 16; + (* setting this property changes the way how mouse/keyboard selection + works:
+ e.g. if set to two, two bytes will be treated as a unit, that means you + cannot select a single byte, only two, four, six... bytes can be selected. + also drag/drop and clipboard pasting is affected (data size + is always a multiple of BytesPerUnit). See also @link(RulerBytesPerUnit) + *) + property BytesPerUnit: integer read FBytesPerUnit write SetBytesPerUnit + default 1; + (* setting this property affects the offset/ruler drawing:
+ e.g. if set to two, two bytes will be treated as a unit, that means the + offset and ruler values will step by one each two bytes. + if this property is set to -1, it will use the value of the + @link(BytesPerUnit) property + *) + property RulerBytesPerUnit: integer read FRulerBytesPerUnit write + SetRulerBytesPerUnit default -1; + // mark the current position even if the editor is not focused + property ShowPositionIfNotFocused: Boolean read FShowPositionIfNotFocused + write SetShowPositionIfNotFocused default False; + (* if set to True, the character pane displays unicode characters + and the @link(BytesPerUnit) property is set to 2. @link(Translation) is + set to tkAsIs. @link(BytesPerRow) and @link(BytesPerColumn) must be a + multiple of two to be able to use the unicode mode. + see also @link(UnicodeBigEndian) + *) + property UnicodeChars: Boolean read FUnicodeCharacters write + SetUnicodeCharacters default False; + (* if set to True, big endian unicode mode is used if @link(UnicodeChars) is + enabled + *) + property UnicodeBigEndian: Boolean read FUnicodeBigEndian write + SetUnicodeBigEndian default False; + // this event is fired when the selection/caret position has changed + property OnSelectionChanged: TNotifyEvent read FOnSelectionChanged write + FOnSelectionChanged; + + // use this event to implement owner drawing. see also @link(TMPHDrawCellEvent) + property OnDrawCell: TMPHDrawCellEvent read FOnDrawCell write FOnDrawCell; + + // fire OnBookmarkChanged + procedure BookmarkChanged; virtual; + + procedure DoSetCellWidth(const Index: integer; Value: integer); + procedure DefineProperties(Filer: TFiler); override; + procedure ReadMaskChar(Reader: TReader); + procedure ReadMaskChar_I(Reader: TReader); + procedure WriteMaskChar_I(Writer: TWriter); + public + { Public-Deklarationen } + //@exclude() + constructor Create(aOwner: TComponent); override; + //@exclude() + destructor Destroy; override; + // these characters are masked in the character pane using @link(MaskChar) + property MaskedChars: TSysCharSet read FMaskedChars write SetMaskedChars; + (* during OnDrawCell event handlers, this property tells the data position currently + being drawn (-1, if offset or ruler are drawn) + *) + property DrawDataPosition: integer read FDrawDataPosition; + + (* during OnDrawCell event handlers, this property tells whether the cell is + to be drawn in selected style (only valid if DrawDataPosition <> -1) + *) + property IsDrawDataSelected: boolean read FIsDrawDataSelected; + + // @exclude(use TMPHexEditor.ReadBuffer!) + function GetMemory(const Index: Integer): char; + (* @exclude(see http://info.borland.com/devsupport/delphi/fixes/delphi4/vcl.html, + ref 279) + *) + + function CanFocus: Boolean; {$IFDEF DELPHi5UP}override;{$ENDIF} + // @exclude(use TMPHexEditor.WriteBuffer!) + procedure SetMemory(const Index: integer; const Value: char); +{$IFDEF FASTACCESS} + property FastPointer: PByteArray read GetFastPointer; +{$ENDIF} + (* this property is valid only in the @link(OnGetOffsetText) event. if True, + the component asks for the string of the highest possible offset, if False, + a row's offset text is queried + *) + property IsMaxOffset: boolean read FIsMaxOffset; + // seek behind the last position if @link(InsertMode) = True, goto last position otherwise + procedure SeekToEOF; + (* synchronize another TCustomMPHexEditor view (top, left, selection), + the optional SyncOffset parameter may be used for a different viewpoint + *) + procedure SyncView(Source: TCustomMPHexEditor; SyncOffset: integer = 0); + // return the offset of the first displayed data + function DisplayStart: integer; + // return the offset of the last displayed data + function DisplayEnd: integer; + // is the given position part of the selection? + function IsSelected(const APosition: integer): boolean; + // calculate a data position from a col/row pair + property PositionAtCursor[const ACol, ARow: integer]: integer read + GetPositionAtCursor; + // is the given col in the hex or the character pane? + property IsCharFieldCol[const ACol: integer]: Boolean read + GetIsCharFieldCol; +{$IFDEF FASTACCESS} + // this byte value is used to fill the data when setting @link(DataSize) + // enlarges the stream + property SetDataSizeFillByte: Byte read FSetDataSizeFillByte write + FSetDataSizeFillByte; +{$ENDIF} + // has data been load from/saved to a file (or is the filename valid) + property HasFile: boolean read FHasFile write FHasFile; + (* each call to UndoBeginUpdate increments an internal counter that prevents using + undo storage and also disables undo functionality (see also @link(UndoEndUpdate)) + *) + function UndoBeginUpdate: integer; virtual; + (* each call to UndoEndUpdate decrements an internal counter that prevents using + undo storage and also disables undo functionality. the return value is the value + of this counter. if the counter is reset to zero, undo creation is permitted again + (see also @link(UndoBeginUpdate)) + *) + function UndoEndUpdate: integer; virtual; + // remove selection state from all data + procedure ResetSelection(const aDraw: boolean); + // see @link(GetSelectionAsHex) and @link(SetSelectionAsHex) + property SelectionAsHex: string read GetSelectionAsHex write + SetSelectionAsHex; + // see @link(GetSelectionAsText) and @link(SetSelectionAsText) + property SelectionAsText: string read GetSelectionAsText write + SetSelectionAsText; +{$IFNDEF BCB} + (* precompiled character comparison table for custom find routines, see also + @link(FindTableI), @link(OnFind), @link(OnWildcardFind), case sensitive, not + public under BCB! + *) + property FindTable: TMPHFindTable read FFindTable ; + (* precompiled character comparison table for custom find routines, see also + @link(FindTable), @link(OnFind), @link(OnWildcardFind), case insensitive, not + public under BCB! + *) + property FindTableI: TMPHFindTable read FFindTableI; +{$ENDIF} + + // implement your custom @link(Find) routine by assigning a method to this handler, + // see also @link(OnWildcardFind) + property OnFind: TMPHFindEvent read FOnFind write FOnFind; + // implement your custom @link(FindWithWildcard) routine by assigning a method + // to this handler, see also @link(OnFind) + property OnWildcardFind: TMPHFindEvent read FOnWildcardFind + write FOnWildcardFind; + (* returns the given position as it would be drawn in the offset gutter, + see also @link(OffsetFormat) + *) + function GetOffsetString(const Position: cardinal): string; virtual; + (* returns the given position as it would be drawn in the offset gutter, exception: + if @link(OffsetFormat) is set to an empty string, returns the hexadecimal representation + of the Position value (see also @link(GetOffsetString)) + *) + function GetAnyOffsetString(const Position: integer): string; virtual; + // returns the height of one row in pixels + function RowHeight: integer; + // free the undo storage (discard all possible undo steps) + procedure ResetUndo; + // set the current position (like TStream.Seek) + function Seek(const aOffset, aOrigin: integer): integer; + (* searches for text or data in the data buffer, returns the find position (-1, if data have not been found):

+ - aBuffer: data to search for
+ - aCount: size of data in aBuffer
+ - aStart: start search at this position
+ - aEnd: searches up to this position
+ - IgnoreCase: if True, lowercase and uppercase characters are treated as if they were equal
+ - SearchText: if True, the current @link(Translation) is taken into account when searching textual data

+ NOTE: call @link(PrepareFindReplaceData) before the first Find call + *) + function Find(aBuffer: PChar; aCount: integer; const aStart, aEnd: integer; + const IgnoreCase: boolean): integer; + (* searches for text or data in the data buffer using a wildcard character + returns the find position (-1, if data have not been found):

+ - aBuffer: data to search for
+ - aCount: size of data in aBuffer
+ - aStart: start search at this position
+ - aEnd: searches up to this position
+ - IgnoreCase: if True, lowercase and uppercase characters are treated as if they were equal
+ - SearchText: if True, the current @link(Translation) is taken into account when searching textual data
+ - Wildcard: this character is a placeholder for any character

+ NOTE: call @link(PrepareFindReplaceData) before the first FindWithWildcard call + *) + function FindWithWildcard(aBuffer: PChar; aCount: integer; const aStart, + aEnd: integer; + const IgnoreCase: boolean; const Wildcard: char): integer; + (* convert a buffer for @link(Find)/@link(FindWithWildcard)/replace operation depending on + unicode mode. sets the string to lower case if IgnoreCase is True. if in unicode mode, + creates a unicode string. + *) + (* + store a selection as undo record, so you can restore the selection start and end by using + @link(Undo). this can be useful e.g. to show position of replaced data + *) + procedure AddSelectionUndo(const AStart, ACount: integer); + function PrepareFindReplaceData(StrData: string; const IgnoreCase, IsText: + boolean): string; + // read data into a buffer + procedure ReadBuffer(var Buffer; const Index, Count: Integer); + // write a buffer to the file data + procedure WriteBuffer(const Buffer; const Index, Count: Integer); virtual; + // delete the currently selected data + procedure DeleteSelection(const UndoDesc: string = ''); + // load the contents of a stream into the data buffer + procedure LoadFromStream(Strm: TStream); + // load the contents of a file into the data buffer + procedure LoadFromFile(const Filename: string); + // save the contents of the data buffer into a stream + procedure SaveToStream(Strm: TStream); + // save the contents of the data buffer to a file + procedure SaveToFile(const Filename: string; const aUnModify: boolean = + True); + // save a range of bytes to a stream + procedure SaveRangeToStream(Strm: TStream; const APosition, ACount: + integer); + // undo the last modification, multiple undos are possible + function Undo: boolean; + // discard the last undo action (only one single redo is possible) + function Redo: boolean; + // empty the data buffer and set the filename (e.g. "Untitled") + procedure CreateEmptyFile(const TempName: string); + (* returns a buffer containing parts of the data buffer's contents. the buffer is allocated + in this routine and must be freed by the caller + *) + function BufferFromFile(const aPos: integer; var aCount: integer): PChar; + // insert some data at the specified position into the data buffer + procedure InsertBuffer(aBuffer: PChar; const aSize, aPos: integer; const + UndoDesc: string = ''; const MoveCursor: Boolean = True); + // append some data at the end of the data buffer + procedure AppendBuffer(aBuffer: PChar; const aSize: integer; const UndoDesc: + string = ''; const MoveCursor: Boolean = True); + // replace the currently selected data with some other data + procedure ReplaceSelection(aBuffer: PChar; aSize: integer; const UndoDesc: + string = ''; const MoveCursor: Boolean = True); + // replace some amount of data + function Replace(aBuffer: PChar; aPosition, aOldCount, aNewCount: integer; + const UndoDesc: + string = ''; const MoveCursor: Boolean = False): integer; + // get the current data position (depending on the cursor/caret) + function GetCursorPos: integer; + // delete 4 bits (=half byte = nibble) from the data buffer (see also @link(InsertNibble)) + function DeleteNibble(const aPos: integer; const HighNibble: boolean; const + UndoDesc: string = ''): boolean; + // insert 4 bits (0000) into the data buffer (see also @link(DeleteNibble)) + function InsertNibble(const aPos: integer; const HighNibble: boolean; const + UndoDesc: string = ''): boolean; + // convert a part of the data buffer's content from one character table to a different one + procedure ConvertRange(const aFrom, aTo: integer; const aTransFrom, + aTransTo: TMPHTranslationKind; const UndoDesc: string = ''); + (* returns the data position of the top left cell and also whether the caret is in the + character pane, see also @link(SetTopLeftPosition) + *) + function GetTopLeftPosition(var oInCharField: boolean): integer; + (* set top left cell to the given data position and also whether the caret is in the + character pane (see also @link(GetTopLeftPosition)) + *) + procedure SetTopLeftPosition(const aPosition: integer; const aInCharField: + boolean); + (* show a drop position marker on the cell at the given mouse cursor position + (see also @link(HideDragCell)) + *) + function ShowDragCell(const X, Y: integer): integer; + // hide the drop position marker (see also @link(ShowDragCell)) + procedure HideDragCell; + // combine two or more changes, so @link(Undo) will discard the at once + procedure CombineUndo(const aCount: integer; const sDesc: string = ''); + (* translate a byte from the current @link(Translation) to the Windows Codepage + (see also @link(TranslateFromAnsiChar)) + *) + function TranslateToAnsiChar(const aByte: byte): char; + (* translate a byte from Windows Codepage to the current @link(Translation) + (see also @link(TranslateToAnsiChar)) + *) + function TranslateFromAnsiChar(const aByte: byte): char; + // retrieve or set the selection start + property SelStart: integer read GetSelStart write SetSelStart; + // retrieve or set the selection end + property SelEnd: integer read GetSelEnd write SetSelEnd; + // retrieve the size of the selected data + property SelCount: integer read GetSelCount; + // is @link(Undo) possible? + property CanUndo: boolean read GetCanUndo; + // is @link(Redo) possible? + property CanRedo: boolean read GetCanRedo; + // is the caret in the character or the hex pane ? + property InCharField: boolean read GetInCharField write SetInCharField; + // description of the next @link(Undo) action + property UndoDescription: string read GetUndoDescription; + // if True, the currently loaded file cannot be overwritten + property ReadOnlyFile: boolean read FIsFileReadonly write SetReadOnlyFile; + // if True, changes have been made to the data buffer content + property Modified: boolean read GetModified write SetModified; + // retrieves or stores the amount of data in the data buffer + // when enlarging the data stream, the @link(SetDataSizeFillByte) property + // tells which value to use to fill the new data + property DataSize: integer read GetDataSize write SetDataSize; + // array to the data buffer's content + property Data[Index: integer]: Byte read GetDataAt write SetDataAt; + // retrieve or set the data as string + property AsText: string read GetAsText write SetAsText; + // retrieve or set the data as hex formatted string (00 01 02 03...) + property AsHex: string read GetAsHex write SetAsHex; + // name of the file that has been loaded into the data buffer + property Filename: string read FFileName; + // retrieve or set bookmarks programmatically (see also @link(TMPHBookmark)) + property Bookmark[Index: byte]: TMPHBookmark read GetBookmark write + SetBookmark; + // has the byte at the given position been modified ? (only in overwrite mode) + property ByteChanged[index: integer]: boolean read HasChanged write + SetChanged; + // retrieves the number of columns (grid columns) + property ColCountRO: integer read GetPropColCount; + // retrieves the number of rows (grid rows) + property RowCountRO: integer read GetPropRowCount; + // returns True if the mouse cursor is positionned over selected data + property MouseOverSelection: boolean read GetMouseOverSelection; + // get the data value at the current caret position, returns -1 if an error occured + property CurrentValue: integer read GetCurrentValue; + // pointer to the whole data buffer's contents + //property DataPointer: Pointer read GetDataPointer; + // select all data + procedure SelectAll; + // retrieves the number of visible columns + property VisibleColCount; + // retrieves the number of visible rows + property VisibleRowCount; + // the control's canvas + property Canvas; + // current column (grid column) + property Col; + // first visible column + property LeftCol; + // current row (grid row) + property Row; + // first visible row (grid row) + property TopRow; + // this event is fired when a bookmark is added/modifed/removed + property OnBookmarkChanged: TNotifyEvent read FOnBookmarkChanged write + FOnBookmarkChanged; + // call this procedure to navigate to a bookmarked position + function GotoBookmark(const Index: integer): boolean; + // call this function if the external offset formatting changed (see @link(OnGetOffsetText)) + procedure UpdateGetOffsetText; + end; + + // published hex editor component + TMPHexEditor = class(TCustomMPHexEditor) + published + // @exclude(inherited) + property Align; + // @exclude(inherited) + property Anchors; + // @exclude(inherited) + property BiDiMode; + // @exclude(inherited) + property BorderStyle; + // @exclude(inherited) + property Constraints; + // @exclude(inherited) + property Ctl3D; + // @exclude(inherited) + property DragCursor; + // @exclude(inherited) + property DragKind; + // @exclude(inherited) + property DragMode; + // @exclude(inherited) + property Enabled; + // @exclude(inherited) + property Font; + // @exclude(inherited) + property ImeMode; + // @exclude(inherited) + property ImeName; + // @exclude(inherited) + property OnClick; + // @exclude(inherited) + property OnDblClick; + // @exclude(inherited) + property OnDragDrop; + // @exclude(inherited) + property OnDragOver; + // @exclude(inherited) + property OnEndDock; + // @exclude(inherited) + property OnEndDrag; + // @exclude(inherited) + property OnEnter; + // @exclude(inherited) + property OnExit; + // @exclude(inherited) + property OnKeyDown; + // @exclude(inherited) + property OnKeyPress; + // @exclude(inherited) + property OnKeyUp; + // @exclude(inherited) + property OnMouseDown; + // @exclude(inherited) + property OnMouseMove; + // @exclude(inherited) + property OnMouseUp; + // @exclude(inherited) + property OnMouseWheel; + // @exclude(inherited) + property OnMouseWheelDown; + // @exclude(inherited) + property OnMouseWheelUp; + // @exclude(inherited) + property OnStartDock; + // @exclude(inherited) + property OnStartDrag; + // @exclude(inherited) + property ParentBiDiMode; + // @exclude(inherited) + property ParentCtl3D; + // @exclude(inherited) + property ParentFont; + // @exclude(inherited) + property ParentShowHint; + // @exclude(inherited) + property PopupMenu; + // @exclude(inherited) + property ScrollBars; + // @exclude(inherited) + property ShowHint; + // @exclude(inherited) + property TabOrder; + // @exclude(inherited) + property TabStop; + // @exclude(inherited) + property Visible; + // see inherited @inherited + property BytesPerRow; + // see inherited @inherited + property BytesPerColumn; + // see inherited @inherited + property Translation; + // see inherited @inherited + property OffsetFormat; + // see inherited @inherited + property CaretKind; + // see inherited @inherited + property Colors; + // see inherited @inherited + property FocusFrame; + // see inherited @inherited + property SwapNibbles; + // see inherited @inherited + property MaskChar; + // see inherited @inherited + property NoSizeChange; + // see inherited @inherited + property AllowInsertMode; + // see inherited @inherited + property DrawGridLines; + // see inherited @inherited + property WantTabs; + // see inherited @inherited + property ReadOnlyView; + // see inherited @inherited + property HideSelection; + // see inherited @inherited + property GraySelectionIfNotFocused; + // see inherited @inherited + property GutterWidth; + // see inherited @inherited + property BookmarkBitmap; + + // see inherited @inherited + property Version; + + // see inherited @inherited + property MaxUndo; + // see inherited @inherited + property InsertMode; + // see inherited @inherited + property HexLowerCase; + // see inherited @inherited + property OnProgress; + // see inherited @inherited + property OnInvalidKey; + // see inherited @inherited + property OnTopLeftChanged; + // see inherited @inherited + property OnChange; + // see inherited @inherited + property DrawGutter3D; + // see inherited @inherited + property ShowRuler; + // see inherited @inherited + property BytesPerUnit; + // see inherited @inherited + property RulerBytesPerUnit; + // see inherited @inherited + property ShowPositionIfNotFocused; + // see inherited @inherited + property OnSelectionChanged; + // see inherited @inherited + property UnicodeChars; + // see inherited @inherited + property UnicodeBigEndian; + + // see inherited @inherited + property OnDrawCell; + + // see inherited @inherited + property OnBookmarkChanged; + // see inherited @inherited + property OnGetOffsetText; + // see inherited @inherited + property BytesPerBlock; + // see inherited @inherited + property SeparateBlocksInCharField; + // see inherited @inherited + property FindProgress; + // see inherited @inherited + property RulerNumberBase; + end; + + // @exclude(undo storage record) + PMPHUndoRec = ^TMPHUndoRec; + // @exclude(undo storage record) + TMPHUndoRec = packed record + DataLen: integer; + Flags: TMPHUndoFlags; + CurPos: integer; + Pos, Count, ReplCount: cardinal; + CurTranslation: TMPHTranslationKind; + CurBPU: Integer; + Buffer: byte; + end; + + // @exclude(implements undo/redo) + TMPHUndoStorage = class(TMemoryStream) + private + FCount, + FUpdateCount: integer; + FEditor: TCustomMPHexEditor; + FDescription: string; + FRedoPointer, + FLastUndo: PMPHUndoRec; + FLastUndoSize: integer; + FLastUndoDesc: string; + procedure SetCount(const Value: integer); + procedure ResetRedo; + procedure CreateRedo(const Rec: TMPHUndoRec); + function GetUndoKind(const Flags: TMPHUndoFlags): TMPHUndoFlag; + procedure AddSelection(const APos, ACount: integer); + function ReadUndoRecord(var aUR: TMPHUndoRec; var SDescription: string): + TMPHUndoFlag; + function GetLastUndoKind: TMPHUndoFlag; + public + constructor Create(AEditor: TCustomMPHexEditor); + destructor Destroy; override; + procedure SetSize(NewSize: longint); override; + procedure CreateUndo(aKind: TMPHUndoFlag; APosition, ACount, AReplaceCount: + integer; const SDescription: string = ''); + function CanUndo: boolean; + function CanRedo: boolean; + function Redo: boolean; + function Undo: boolean; + function BeginUpdate: integer; + function EndUpdate: integer; + procedure Reset(AResetRedo: boolean = True); + procedure RemoveLastUndo; + property Count: integer read FCount write SetCount; + property UpdateCount: integer read FUpdateCount; + property Description: string read FDescription; + property UndoKind: TMPHUndoFlag read GetLastUndoKind; + end; + +resourcestring + + // long descriptive names of character translations + // tkAsIs + MPH_TK_ASIS = 'Windows'; + // tkDos8 + MPH_TK_DOS8 = 'Dos 8 Bit'; + // tkASCII + MPH_TK_ASCII7 = 'ASCII 7 Bit'; + // tkMac + MPH_TK_MAC = 'Macintosh'; + // tkBCD + MPH_TK_BCD38 = 'EBCDIC Codepage 38'; + + // unicode + MPH_UC = 'Unicode Little Endian'; + // unicode be + MPH_UC_BE = 'Unicode Big Endian'; + + // short names (e.g. for status bars) of character translations + // tkAsIs + MPH_TK_ASIS_S = 'WIN'; + // tkDos8 + MPH_TK_DOS8_S = 'DOS'; + // tkASCII + MPH_TK_ASCII7_S = 'ASC'; + // tkMac + MPH_TK_MAC_S = 'MAC'; + // tkBCD + MPH_TK_BCD38_S = 'BCD'; + + // tkCustom + MPH_TK_CUSTOM_S = 'Cust'; + // tkCustom + MPH_TK_CUSTOM = 'Custom Translation'; + + // unicode + MPH_UC_S = 'UCLE'; + // unicode be + MPH_UC_BE_S = 'UCBE'; + +const + // long descriptions of the different translations (e.g. for menues) + MPHTranslationDesc: array[TMPHTranslationKind] of string = (MPH_TK_ASIS, + MPH_TK_DOS8, MPH_TK_ASCII7, MPH_TK_MAC, MPH_TK_BCD38, + MPH_TK_CUSTOM); + + // short descriptions of the different translations (e.g. for status bars) + MPHTranslationDescShort: array[TMPHTranslationKind] of string = + (MPH_TK_ASIS_S, MPH_TK_DOS8_S, MPH_TK_ASCII7_S, MPH_TK_MAC_S, + MPH_TK_BCD38_S, MPH_TK_CUSTOM_S); + + // public utility functions + +(* translate a hexadecimal data representation ("a000 cc45 d3 42"...) to binary data + (see @link(SwapNibbles) for the meaning of the SwapNibbles value) +*) +function ConvertHexToBin(aFrom, aTo: PChar; const aCount: integer; const + SwapNibbles: boolean; var BytesTranslated: integer): PChar; + +(* translate binary data to its hex representation (see @link(ConvertHexToBin)), + (see @link(SwapNibbles) for the meaning of the SwapNibbles value) +*) +function ConvertBinToHex(aFrom, aTo: PChar; const aCount: integer; const + SwapNibbles: boolean): PChar; + +// convert X and Y into a TGridCoord record +function GridCoord(aX, aY: longint): TGridCoord; +// check whether the given key (VK_...) is currently down +function IsKeyDown(aKey: integer): boolean; +// get a unique filename in the temporary directory +function GetTempName: string; + +(* translate an integer to a radix (base) coded string, e.g.
+ - IntToRadix(100,16) converts into a hexadecimal (number) string
+ - IntToRadix(100,2) converts into a string consisting only of 0 and 1
+ - IntToRadix(100,8) means IntToOctal
+
+ hint: Radix must be in the range of 2..16*) +function IntToRadix(Value: integer; Radix: byte): string; +function IntToRadix64(Value: int64; Radix: byte): string; +// translate an integer to a radix coded string and left fill with 0 (see also @link(IntToRadix)) +function IntToRadixLen(Value: integer; Radix, Len: byte): string; +function IntToRadixLen64(Value: int64; Radix, Len: byte): string; +// translate an integer to an octal string (see also @link(IntToRadix)) +function IntToOctal(const Value: integer): string; + +(* translate a radix coded number string into an integer, e.g.
+ - RadixToInt('0f', 16) => 15
+ - RadixToInt('755', 8) => 493 +*) +function RadixToInt(Value: string; Radix: byte): integer; +function RadixToInt64(Value: string; Radix: byte): int64; + +(* 64 bit unsigned integer arithmetics *) + +// division of two unsigned int64 values, may raise an exception on error +function DivideU64(const Dividend, Divisor: int64): int64; +// division of two unsigned int64 values, returns false if an error occurred +function TryDivideU64(const Dividend, Divisor: int64; + var Val: int64): boolean; +// modulo of two unsigned int64 values, may raise an exception on error +function ModuloU64(const Dividend, Divisor: int64): int64; +// modulo of two unsigned int64 values, returns false if an error occurred +function TryModuloU64(const Dividend, Divisor: int64; + var Val: int64): boolean; +// multiplication of two unsigned int64 values, may raise an exception on error +function MultiplyU64(const Multiplier, Multiplicator: int64): int64; +// multiplication of two unsigned int64 values, returns false if an error occurred +function TryMultiplyU64(const Multiplier, Multiplicator: int64; + var Val: int64): boolean; +// addition of two unsigned int64 values, may raise an exception on error +function AddU64(const Addend1, Addend2: int64): int64; +// addition of two unsigned int64 values, returns false if an error occurred +function TryAddU64(const Addend1, Addend2: int64; + var Val: int64): boolean; +// subtraction of two unsigned int64 values, may raise an exception on error +function SubtractU64(const Minuend, Subtrahend: int64): int64; +// subtraction of two unsigned int64 values, returns false if an error occurred +function TrySubtractU64(const Minuend, Subtrahend: int64; + var Val: int64): boolean; + +(* try to find the correct radix (based on prefix/suffix) and return the number, known + prefixes/suffixes are:
+ 0x<number>, 0X<number>, $<number>, <number>h, <number>H: radix 16
+ o<number>, O<number>, 0<number>, <number>o, <number>O: radix 8
+ %<number>, <number>%: radix 2
+ otherwise: radix 10 +*) +function CheckRadixToInt(Value: string): integer; +function CheckRadixToInt64(Value: string): int64; + +// translate an number string built on radix 8 into an integer (see also @link(RadixToInt)) +function OctalToInt(const Value: string): integer; + +// swap lo and high byte of a widechar +procedure SwapWideChar(var WChar: WideChar); + +// @exclude(fade a color to a gray value) +function FadeToGray(aColor: TColor): TColor; + +(* translate data from Ansi to a different character set (see also @link(TMPHTranslationKind))
+ - TType: translate to this character set
+ - aBuffer: pointer to source data
+ - bBuffer: pointer to target data, must be allocated (may equal to aBuffer)
+ - aCount: number of bytes to translate +*) +procedure TranslateBufferFromAnsi(const TType: TMPHTranslationKind; aBuffer, + bBuffer: PChar; const aCount: integer); +// translate data from a different character set to Ansi (see also @link(TranslateBufferFromAnsi)) +procedure TranslateBufferToAnsi(const TType: TMPHTranslationKind; aBuffer, + bBuffer: PChar; const aCount: integer); + +// compatibility +{$IFNDEF DELPHI6UP} +procedure RaiseLastOSError; +{$ENDIF} + +// returns the lower of the two numbers +function Min(a1, a2: integer): integer; +// returns the higer of the two numbers +function Max(a1, a2: integer): integer; + +var + (* translation tables for tkCustom *) + + // this character conversion is used in translations from tkAsIs to tkCustom (see @link(TMPHTranslationKind)) + MPHCustomCharConv: TMPHCharConv; + +const + (* standard offset formats *) + + // standard offset format: hex, auto min width, prefixed by 0x + MPHOffsetHex = '-!10:0x|'; + // standard offset format: decimal + MPHOffsetDec = 'a:|'; + // standard offset format: octal, suffixed by a small "o" + MPHOffsetOct = '0!8:o|'; + +implementation + +uses + Consts, {$IFDEF DELPHI6UP}RTLConsts, {$ENDIF}ImgList, StdCtrls, SysConst; + +const + MPH_VERSION = 'December 29, 2004; © markus stephany, vcl[at]mirkes[dot]de' ; + +resourcestring + + // undo descriptions + UNDO_BYTESCHANGED = 'Change Byte(s)'; + UNDO_REMOVED = 'Remove Data'; + UNDO_INSERT = 'Insert Buffer'; + UNDO_REPLACE = 'Replace'; + UNDO_APPEND = 'Append Buffer'; + UNDO_INSNIBBLE = 'Insert Nibble'; + UNDO_DELNIBBLE = 'Delete Nibble'; + UNDO_CONVERT = 'Convert'; + UNDO_SELECTION = 'Cursor movement'; + UNDO_COMBINED = 'Multiple Modification'; + UNDO_ALLDATA = 'All data saved'; + UNDO_NOUNDO = 'No Undo'; + + // error messages + ERR_FILE_OPEN_FAILED = 'Cannot open %s.'#13#10'(%s.)'; + ERR_FILE_READONLY = 'Cannot save readonly file %s.'; + ERR_INVALID_BOOKMARK = 'Invalid bookmark index'; + ERR_INVALID_SELSTART = 'Invalid selection start'; + ERR_INVALID_SELEND = 'Invalid selection end'; + ERR_INVALID_BYTESPERLINE = 'Invalid bytes per line argument'; + ERR_INVALID_BUFFERFROMFILE = 'Invalid buffer from file argument'; + ERR_INVALID_BYTESPERCOL = 'Invalid bytes per column argument'; + ERR_INVALID_BOOKMARKBMP = 'Invalid bookmark bitmap (must be 10 x 200 px)'; + ERR_CANCELLED = 'Operation Cancelled'; + ERR_MISSING_FORMATCHAR = 'Missing char in offset format: %s'; + ERR_INVALID_FORMATRADIX = + 'Invalid radix in offset format (%xh), allowed: 02h..10h'; + ERR_INVALID_RADIXCHAR = + 'Invalid character %s, cannot convert using radix %xh'; + ERR_INVALID_BPU = 'Invalid bytes per unit value %d, allowed: 1,2,4,8'; + ERR_INVALID_BPU_U = 'BytesPerUnit must be set to 2 in unicode mode'; + ERR_INVALID_RBPU = + 'Invalid ruler bytes per unit value %d, allowed: -1,1,2,4,8'; + ERR_DATA_BOUNDS = 'Data position/length out of data bounds'; + ERR_NO_TRANSLATION_IN_UNICODE_MODE = + 'Translations cannot be used in unicode mode'; + ERR_ODD_FILESIZE_UNICODE = 'Cannot use unicode mode with odd-sized files'; + + ERR_FIXED_FILESIZE = 'Cannot change fixed filesize'; + ERR_NOUNDO = 'Cannot update undo storage'; + + // new, empty file + UNNAMED_FILE = 'Untitled'; + +const + // fixed cols/rows + GRID_FIXED = 2; + + // available undo descriptions + STRS_UNDODESC: array[ufKindBytesChanged..ufKindAllData] of string = + (UNDO_BYTESCHANGED, UNDO_REMOVED, UNDO_INSERT, UNDO_REPLACE, UNDO_APPEND, + UNDO_INSNIBBLE, UNDO_DELNIBBLE, UNDO_CONVERT, UNDO_SELECTION, UNDO_COMBINED, + UNDO_ALLDATA); + + // valid hex characters + HEX_LOWER = '0123456789abcdef'; + HEX_UPPER = '0123456789ABCDEF'; + HEX_ALLCHARS = HEX_LOWER + HEX_UPPER; + +{$IFNDEF DELPHI6UP} + +procedure RaiseLastOSError; +begin + RaiseLastWin32Error; +end; +{$ENDIF} + +// invert the given color + +function Invert(Color: TColor): TColor; +begin + Result := ColorToRGB(Color) xor $00FFFFFF; +end; + +// translate the buffer from ANSI to the given translation mode + +procedure TranslateBufferFromAnsi(const TType: TMPHTranslationKind; aBuffer, + bBuffer: PChar; const aCount: integer); +var + LIntLoop: integer; +begin + case TType of + // changed 04/18/04: bBuffer and aBuffer were interchanged! + tkAsIs: Move(aBuffer^, bBuffer^, aCount); + tkDOS8, + tkASCII: CharToOEMBuff(aBuffer, bBuffer, aCount); + tkMAC: if aCount > 0 then + for LIntLoop := 0 to Pred(aCount) do + bBuffer[LIntLoop] := + MPH_CCONV_MAC[cctFromAnsi][Ord(aBuffer[LIntLoop])]; + tkBCD: if aCount > 0 then + for LIntLoop := 0 to Pred(aCount) do + bBuffer[LIntLoop] := + MPH_CCONV_BCD38[cctFromAnsi][Ord(aBuffer[LIntLoop])]; + + tkCustom: if aCount > 0 then + for LIntLoop := 0 to Pred(aCount) do + bBuffer[LIntLoop] := + MPHCustomCharConv[cctFromAnsi][Ord(aBuffer[LIntLoop])]; + + end; +end; + +// translate the buffer to ANSI from the given translation mode + +procedure TranslateBufferToAnsi(const TType: TMPHTranslationKind; aBuffer, + bBuffer: PChar; const aCount: integer); +var + LIntLoop: integer; +begin + case TType of + tkAsIs: Move(aBuffer^, bBuffer^, aCount); + tkDOS8, + tkASCII: OEMToCharBuff(aBuffer, bBuffer, aCount); + tkMAC: if aCount > 0 then + for LIntLoop := 0 to Pred(aCount) do + bBuffer[LIntLoop] := MPH_CCONV_MAC[cctToAnsi][Ord(aBuffer[LIntLoop])]; + tkBCD: if aCount > 0 then + for LIntLoop := 0 to Pred(aCount) do + bBuffer[LIntLoop] := + MPH_CCONV_BCD38[cctToAnsi][Ord(aBuffer[LIntLoop])]; + + tkCustom: if aCount > 0 then + for LIntLoop := 0 to Pred(aCount) do + bBuffer[LIntLoop] := + MPHCustomCharConv[cctToAnsi][Ord(aBuffer[LIntLoop])]; + + end; +end; + +// ansi to oem + +function OEM2Char(aByte: byte): char; +var + LszBuf: array[0..1] of char; +begin + LszBuf[0] := char(aByte); + LszBuf[1] := #0; + OEMToChar(LSzBuf, LSzBuf); + Result := LSzBuf[0]; +end; + +// oem to ansi + +function Char2OEM(aByte: byte): char; +var + LszBuf: array[0..1] of char; +begin + LszBuf[0] := char(aByte); + LszBuf[1] := #0; + CharToOEM(LSzBuf, LSzBuf); + Result := LSzBuf[0]; +end; + +(* helper functions *) + +// get a temporary file name + +function GetTempName: string; +var + LStrTemp: string; +begin + SetLength(LStrTemp, MAX_PATH + 1); + SetLength(LStrTemp, GetTempPath(MAX_PATH, @LStrTemp[1])); + LStrTemp := Trim(LStrTemp); +{$IFDEF DELPHI6UP} + LstrTemp := IncludeTrailingPathDelimiter(LstrTemp); +{$ELSE} + if LStrTemp[Length(LStrTemp)] <> '\' then + LStrTemp := LStrTemp + '\'; +{$ENDIF} + repeat + Result := LStrTemp + IntToHex(GetTickCount, 8) + '.MPHT'; + until GetFileAttributes(PChar(Result)) = $FFFFFFFF; +end; + +// can the file be opened for reading (possibly read only) ? + +function CanOpenFile(const aName: TFileName; var ReadOnly: boolean): boolean; +var + LHdlFile: THandle; +begin + Result := False; + ReadOnly := True; + if FileExists(aName) then + begin + LHdlFile := FileOpen(aName, fmOpenRead or fmShareDenyNone); + if LHdlFile <> INVALID_HANDLE_VALUE then + begin + FileClose(LHdlFile); + Result := True; + try + LHdlFile := FileOpen(aName, fmOpenReadWrite); + if LHdlFile <> INVALID_HANDLE_VALUE then + begin + FileClose(LHdlFile); + ReadOnly := False; + end; + except + Result := True; + ReadOnly := True; + end; + end; + end; +end; + +// is that key pressed ? + +function IsKeyDown(aKey: integer): boolean; +begin + Result := (GetKeyState(aKey) and (not 1)) <> 0; +end; + +// return the lesser value + +function Min(a1, a2: integer): integer; +begin + if a1 < a2 then + Result := a1 + else + Result := a2; +end; + +// return the bigger value + +function Max(a1, a2: integer): integer; +begin + if a1 > a2 then + Result := a1 + else + Result := a2; +end; + +// cast x,y to grid coord + +function GridCoord(aX, aY: longint): TGridCoord; +begin + Result.x := aX; + Result.y := aY; +end; + +// convert '00 01 02...' to binary data + +function ConvertHexToBin(aFrom, aTo: PChar; const aCount: integer; + const SwapNibbles: boolean; var BytesTranslated: integer): PChar; +var + LBoolHi: boolean; + LIntLoop: integer; + LBytCurrent: byte; + LChrCurrent: char; +begin + Result := aTo; + BytesTranslated := 0; + LBoolHi := True; + LBytCurrent := 0; + for LIntLoop := 0 to Pred(aCount) do + if Pos(aFrom[LIntLoop], HEX_ALLCHARS) <> 0 then + begin + LChrCurrent := UpCase(aFrom[LIntLoop]); + if LBoolHi then + LBytCurrent := ((Pos(LChrCurrent, HEX_UPPER) - 1) * 16) + else + LBytCurrent := LBytCurrent or ((Pos(LChrCurrent, HEX_UPPER) - 1)); + + LBoolHi := not LBoolHi; + if LBoolHi then + begin + if SwapNibbles then + aTo[BytesTranslated] := char(((LBytCurrent and 15) * 16) or + ((LBytCurrent and $F0) shr 4)) + else + aTo[BytesTranslated] := char(LBytCurrent); + + Inc(BytesTranslated); + end; + end; +end; + +// convert binary data to '00 01 02...' + +function ConvertBinToHex(aFrom, aTo: PChar; const aCount: integer; + const SwapNibbles: boolean): PChar; +var + LIntLoop: integer; + LByteCurrent: byte; + LIntLoop2: integer; +begin + Result := aTo; + LIntLoop2 := 0; + for LIntLoop := 0 to Pred(aCount) do + begin + LByteCurrent := Ord(aFrom[LIntLoop]); + if SwapNibbles then + begin + aTo[LIntLoop2] := UpCase(HEX_UPPER[(LByteCurrent and 15) + 1]); + aTo[LIntLoop2 + 1] := UpCase(HEX_UPPER[(LByteCurrent shr 4) + 1]) + end + else + begin + aTo[LIntLoop2 + 1] := UpCase(HEX_UPPER[(LByteCurrent and 15) + 1]); + aTo[LIntLoop2] := UpCase(HEX_UPPER[(LByteCurrent shr 4) + 1]) + end; + + Inc(LIntLoop2, 2); + end; + aTO[LIntLoop2] := #0; +end; + +// translate an integer to a radix coded string + +function IntToRadix(Value: integer; Radix: byte): string; +begin + Result := IntToRadixLen(Value, Radix, 0); +end; + +function IntToRadix64(Value: int64; Radix: byte): string; +begin + Result := IntToRadixLen64(Value, Radix, 0); +end; + +// translate an integer to a radix coded string and left fill with 0 + +function IntToRadixLen(Value: integer; Radix, Len: byte): string; +var + LCrdTemp: cardinal absolute Value; +begin + Result := ''; + repeat + Result := HEX_UPPER[(LCrdTemp mod Radix) + 1] + Result; + LCrdTemp := LCrdTemp div Radix; + until LCrdTemp = 0; + while Length(Result) < Len do + Result := '0' + Result; +end; + +// unsigned 64 bit integer routines (division and modulo) +// this code is derived from assembler code written by +// Norbert Juffa, found on "the assembly gems page" +// (http://www.df.lth.se/~john_e/) + +procedure _UModDiv64; +begin + asm + // divisor > 2^32-1 ? + test ecx, ecx + + // yes, divisor > 32^32-1 + jnz @big_divisor + + // only one division needed ? (ecx = 0) + cmp edx, ebx + + // yes, one division sufficient + jb @one_div + + // save dividend-lo in ecx + mov ecx, eax + + // get dividend-hi + mov eax, edx + + // zero extend it into edx:eax + xor edx, edx + + // quotient-hi in eax + div ebx + + // ecx = quotient-hi, eax =dividend-lo + xchg eax, ecx + +@one_div: + + // eax = quotient-lo + div ebx + + //ebx = remainder-lo + mov ebx, edx + + //edx = quotient-hi(quotient in edx:eax) + mov edx, ecx + + // ecx = remainder-hi (rem. in ecx:ebx) + xor ecx, ecx + jmp @cleanup; + +@big_divisor: + + // save dividend + push edx + push eax + + // divisor now in edi:ebx and ecx:esi + mov esi, ebx + mov edi, ecx + + // shift both divisor and and dividend right by 1 bit + shr edx, 1 + rcr eax, 1 + ror edi, 1 + rcr ebx, 1 + + // ecx = number of remaining shifts + bsr ecx, ecx + + // scale down divisor and dividend such that divisor less than 2^32 (i.e. fits in ebx) + shrd ebx, edi, CL + shrd eax, edx, CL + shr edx, CL + + // restore original divisor (edi:esi) + rol edi, 1 + + // compute quotient + div ebx + + // get dividend lo-word + pop ebx + + // save quotient + mov ecx, eax + + // quotient * divisor hi-word (low only) + imul edi, eax + + // quotient * divisor lo-word + mul esi + + // edx:eax = quotient * divisor + add edx, edi + + // dividend-lo - (quot.*divisor)-lo + sub ebx, eax + + // get quotient + mov eax, ecx + + // restore dividend hi-word + pop ecx + + // subtract divisor * quot. from dividend + sbb ecx, edx + + // 0 if remainder > 0, else FFFFFFFFh + sbb edx, edx + + // nothing to add + and esi, edx + + // back if remainder positive + and edi, edx + + // correct remaider and quotient if necessary + add ebx, esi + adc ecx, edi + add eax, edx + + // clear hi-word of quot (eax<=FFFFFFFFh) + xor edx, edx + +@cleanup: + end; +end; + +{$WARNINGS OFF} + +function UDiv64(I1, I2: Int64): int64; +begin + asm + // save registers + push ebp + push ebx + push esi + push edi + + // load I2 into ebx/ecx + mov ebx, [ebp+$08]; + mov ecx, [ebp+$0c]; + + // load I1 into eax/edx + mov eax, [ebp+$10]; + mov edx, [ebp+$14]; + + call _UModDiv64 + + // store result (division result is in eax:edx) + mov [ebp-$08], eax; + mov [ebp-$04], edx; + + // restore registers + pop edi + pop esi + pop ebx + pop ebp + end; +end; + +function UMod64(I1, I2: Int64): int64; +begin + asm + // save registers + push ebp + push ebx + push esi + push edi + + // load I2 into ebx/ecx + mov ebx, [ebp+$08]; + mov ecx, [ebp+$0c]; + + // load I1 into eax/edx + mov eax, [ebp+$10]; + mov edx, [ebp+$14]; + + call _UModDiv64 + + // store result (division remainder is in ebx:ecx) + mov [ebp-$08], ebx; + mov [ebp-$04], ecx; + + // restore registers + pop edi + pop esi + pop ebx + pop ebp + end; +end; +{$WARNINGS ON} + +(* 64 bit unsigned integer arithmetics *) +function DivideU64(const Dividend, Divisor: int64): int64; +begin + Result := UDiv64(Dividend, Divisor); +end; + +function TryDivideU64(const Dividend, Divisor: int64; + var Val: int64): boolean; +begin + Result := True; + try + Val := UDiv64(Dividend, Divisor); + except + Result := False; + end; +end; + +function ModuloU64(const Dividend, Divisor: int64): int64; +begin + Result := UMod64(Dividend, Divisor); +end; + +function TryModuloU64(const Dividend, Divisor: int64; + var Val: int64): boolean; +begin + Result := True; + try + Val := UMod64(Dividend, Divisor); + except + Result := False; + end; +end; + +// unsigned 64 bit integer routines (multiplication, addition, substraction) +// this code is derived from assembler code found in the online book +// "Art of Assembly Programming" maintained by Randall Hyde +// (http://webster.cs.ucr.edu/) + +function TryMultiplyU64(const Multiplier, Multiplicator: int64; + var Val: int64): boolean; +asm + // save registers + push ebx + push esi + + mov byte ptr result, 1 + + // store val pointer + mov esi, eax + + // multiply lo dword of multiplier * lo dword of multiplicator + mov eax, dword ptr Multiplier + mul dword ptr Multiplicator + + // save lo dword + mov dword [esi], eax + + // save hi dword of partial product + mov ecx, edx + + // multiply lo dword of multiplier * hi dword of multiplicator + mov eax, dword ptr Multiplier + mul dword ptr Multiplicator+4 + + // add to the partial product (including carry) + add eax, ecx + adc edx, 0 + + // save partial product + mov ebx, eax + mov ecx, edx + + // multiply hi dword of multiplier * lo dword of multiplicator + mov eax, dword ptr Multiplier+4 + mul dword ptr Multiplicator + + // add the partial product + add eax, ebx + + // save the partial product + mov dword ptr [esi+4], eax + + // add in the carry flag + adc ecx, edx + + // save carry + pushfd + + // multiply hi dword of multiplier * hi dword of multiplicator + mov eax, dword ptr Multiplier+4 + mul dword ptr Multiplicator+4 + + // load carry + popfd + + // add partial product + carry + adc eax, ecx + adc edx, 0 + + // check overflow + test eax, eax + jnz @over + test edx, edx + jz @finish + +@over: + // overflow + mov byte ptr result, 0 + +@finish: + // restore register + pop esi + pop ebx +end; + +function MultiplyU64(const Multiplier, Multiplicator: int64): int64; +begin + if not TryMultiplyU64(Multiplier, Multiplicator, Result) then + raise EIntOverflow.Create(SIntOverflow); +end; + +function TryAddU64(const Addend1, Addend2: int64; + var Val: int64): boolean; +asm + mov byte ptr result, 1 + + // store val pointer + mov edx, eax + + // add lo dwords + mov eax, dword ptr Addend1 + add eax, dword ptr Addend2 + + // store lo dword + mov dword ptr [edx], eax + + // add hi dwords + carry + mov eax, dword ptr Addend1+4 + adc eax, dword ptr Addend2+4 + + // store hi dword + mov dword ptr [edx+4], eax + + // check carry + jnc @finish + mov byte ptr result, 0 +@finish: +end; + +function AddU64(const Addend1, Addend2: int64): int64; +begin + if not TryAddU64(Addend1, Addend2, Result) then + raise EIntOverflow.Create(SIntOverflow); +end; + +function TrySubtractU64(const Minuend, Subtrahend: int64; + var Val: int64): boolean; +asm + mov byte ptr result, 1 + + // store val pointer + mov edx, eax + + // subtract lo dwords + mov eax, dword ptr Minuend + sub eax, dword ptr Subtrahend + + // store lo dword + mov dword ptr [edx], eax + + // subtract hi dwords - carry + mov eax, dword ptr Minuend+4 + sbb eax, dword ptr Subtrahend+4 + + // store hi dword + mov dword ptr [edx+4], eax + + // check carry + jnc @finish + mov byte ptr result, 0 +@finish: +end; + +function SubtractU64(const Minuend, Subtrahend: int64): int64; +begin + if not TrySubtractU64(Minuend, Subtrahend, Result) then + raise EIntOverflow.Create(SIntOverflow); +end; + +function IntToRadixLen64(Value: int64; Radix, Len: byte): string; +begin + Result := ''; + repeat + Result := HEX_UPPER[UMod64(Value, Radix) + 1] + Result; + Value := UDiv64(Value, Radix); + until Value = 0; + while Length(Result) < Len do + Result := '0' + Result; +end; + +// translate an integer value to an octal string + +function IntToOctal(const Value: integer): string; +begin + Result := IntToRadix(Value, 8); +end; + +// translate a radix coded string into an integer + +function RadixToInt(Value: string; Radix: byte): integer; +var + LCrdTemp: cardinal absolute Result; +begin + LCrdTemp := 0; + Value := UpperCase(Value); + while Value <> '' do + begin + if not (Pos(Value[1], HEX_UPPER) in [1..Radix]) then + raise EMPHexEditor.CreateFmt(ERR_INVALID_RADIXCHAR, [Value[1], Radix]); + LCrdTemp := LCrdTemp * Radix + cardinal(Pos(Value[1], HEX_UPPER) - 1); + Delete(Value, 1, 1); + end; +end; + +function RadixToInt64(Value: string; Radix: byte): int64; +begin + Result := 0; + Value := UpperCase(Value); + while Value <> '' do + begin + if not (Pos(Value[1], HEX_UPPER) in [1..Radix]) then + raise EMPHexEditor.CreateFmt(ERR_INVALID_RADIXCHAR, [Value[1], Radix]); + Result := Result * Radix + cardinal(Pos(Value[1], HEX_UPPER) - 1); + Delete(Value, 1, 1); + end; +end; + +(* try to find the correct radix (based on prefix/suffix) and return the number, known + prefixes/suffixes are:
+ 0x, 0X, $, h, H: radix 16
+ o, O, o, O: radix 8
+ %, %: radix 2
+ otherwise: radix 10 +*) + +function CheckRadixToInt(Value: string): integer; +begin + // hex + if UpperCase(Copy(Value, 1, 2)) = '0X' then + Result := RadixToInt(Copy(Value, 3, MaxInt), 16) + else if Copy(Value, 1, 1) = '$' then + Result := RadixToInt(Copy(Value, 2, MaxInt), 16) + else if UpperCase(Copy(Value, Length(Value), 1)) = 'H' then + Result := RadixToInt(Copy(Value, 1, Length(Value) - 1), 16) + else {// octal} if UpperCase(Copy(Value, Length(Value), 1)) = 'O' then + Result := RadixToInt(Copy(Value, 1, Length(Value) - 1), 8) + else if UpperCase(Copy(Value, 1, 1)) = 'O' then + Result := RadixToInt(Copy(Value, 2, MaxInt), 8) + (* removed, is ambigous else if (Copy(Value, 1, 1) = '0') and (AllCharsIn(['0'..'7'])) then + Result := RadixToInt(Value, 8)*) + else {// binary} if UpperCase(Copy(Value, Length(Value), 1)) = '%' then + Result := RadixToInt(Copy(Value, 1, Length(Value) - 1), 2) + else if UpperCase(Copy(Value, 1, 1)) = '%' then + Result := RadixToInt(Copy(Value, 2, MaxInt), 2) + else + // decimal + Result := StrToInt(Value); +end; + +function CheckRadixToInt64(Value: string): int64; +begin + // hex + if UpperCase(Copy(Value, 1, 2)) = '0X' then + Result := RadixToInt64(Copy(Value, 3, MaxInt), 16) + else if Copy(Value, 1, 1) = '$' then + Result := RadixToInt64(Copy(Value, 2, MaxInt), 16) + else if UpperCase(Copy(Value, Length(Value), 1)) = 'H' then + Result := RadixToInt64(Copy(Value, 1, Length(Value) - 1), 16) + else {// octal} if UpperCase(Copy(Value, Length(Value), 1)) = 'O' then + Result := RadixToInt64(Copy(Value, 1, Length(Value) - 1), 8) + else if UpperCase(Copy(Value, 1, 1)) = 'O' then + Result := RadixToInt64(Copy(Value, 2, MaxInt), 8) + (* removed, is ambigous else if (Copy(Value, 1, 1) = '0') and (AllCharsIn(['0'..'7'])) then + Result := RadixToInt(Value, 8)*) + else {// binary} if UpperCase(Copy(Value, Length(Value), 1)) = '%' then + Result := RadixToInt64(Copy(Value, 1, Length(Value) - 1), 2) + else if UpperCase(Copy(Value, 1, 1)) = '%' then + Result := RadixToInt64(Copy(Value, 2, MaxInt), 2) + else + // decimal + Result := StrToInt64(Value) +end; + +// translate an octal to an integer + +function OctalToInt(const Value: string): integer; +begin + Result := RadixToInt(Value, 8); +end; + +// swap lo and high byte of a widechar + +procedure SwapWideChar(var WChar: WideChar); +var + LWrdChar: word absolute WChar; +begin + LWrdChar := Swap(LWrdChar); +end; + +// fade a color to a gray value + +function FadeToGray(aColor: TColor): TColor; +var + LBytGray: byte; +begin + aColor := ColorToRGB(aColor); + LBytGray := HiByte(GetRValue(aColor) * 74 + GetGValue(aColor) * 146 + + GetBValue(aColor) * 36); + Result := RGB(LBytGray, LBytGray, LBytGray); +end; + +(* TCustomMPHexEditor *) + +constructor TCustomMPHexEditor.Create(aOwner: TComponent); +var + LIntLoop: integer; +begin + inherited Create(aOwner); +{$IFDEF FASTACCESS} + FSetDataSizeFillByte := 0; +{$ENDIF} + FMaskedChars := [#0..#31]; + FRulerNumberBase := 16; + FOffsetHandler := False; + FOnFind := nil; + FOnWildcardFind := nil; + FFindProgress := False; + FBlockSize := -1; + FSepCharBlocks := True; + FUnicodeCharacters := False; + FUnicodeBigEndian := False; + FSelectionChangedCount := 0; + FBytesPerUnit := 1; + FRulerBytesPerUnit := -1; + FUsedRulerBytesPerUnit := 1; + FShowPositionIfNotFocused := False; + FShowRuler := False; + FDrawGutter3D := True; + FHexLowerCase := True; + SetHexLowerCase(False); + DoubleBuffered := True; + FBookmarkBitmap := TBitmap.Create; + FCursorList := nil; + FHasCustomBMP := False; + FStreamFileName := ''; + FHasFile := False; + FMaxUndo := 1024 * 1024; + FPosInCharField := False; + FLastPosInCharField := True; + + FGutterWidth := -1; + GenerateOffsetFormat(MPHOffsetHex); + FSelectionPossible := True; + FBookmarkImageList := TImageList.Create(self); + FBookmarkImageList.DrawingStyle := dsTransparent; + FBookmarkImageList.BkColor := clBlack; + FBookmarkImageList.Width := 10; + FBookmarkImageList.Height := 10; + + Options := [goThumbTracking]; + DesignOptionsBoost := []; + DefaultDrawing := False; + FSaveCellExtents := False; + + FColors := TMPHColors.Create(Self); + FDrawGridLines := False; + + ParentColor := False; + FDataStorage := TMPHMemoryStream.Create; + FUndoStorage := TMPHUndoStorage.Create(self); + + Color := FColors.Background; + + FCharWidth := -1; + FOffSetDisplayWidth := -1; + FBytesPerRow := 16; + FCaretKind := ckAuto; + FFocusFrame := True; + FSwapNibbles := 0; + FFileName := '---'; + + Font.Name := 'Courier New'; + Font.Size := 11; + BorderStyle := bsSingle; + FBytesPerCol := 4; + CTL3D := False; + Cursor := crIBeam; + FModifiedBytes := TBits.Create; + for LIntLoop := Low(FBookmarks) to High(FBookmarks) do + FBookmarks[LIntLoop].mPosition := -1; + SetSelection(-1, -1, -1); + FIsSelecting := False; + ResetUndo; + DefaultColWidth := 0; + DefaultRowHeight := 0; + RowHeights[0] := 0; + RowHeights[1] := 0; + ColCount := CalcColCount; + RowCount := GRID_FIXED + 1; + FTranslation := tkAsIs; + FModified := False; + FIsFileReadonly := True; + FBytesPerRowDup := 2 * FBytesPerRow; + FIntLastHexCol := (GRID_FIXED + FBytesPerRowDup - 1); + FReplaceUnprintableCharsBy := '.'; + FCaretBitmap := TBitmap.Create; + FFixedFileSize := False; + FFixedFileSizeOverride := False; + FAllowInsertMode := True; + FInsertModeOn := False; + FWantTabs := True; + FReadOnlyView := False; + FHideSelection := False; + FGraySelOnLostFocus := False; + FOnProgress := nil; + FShowDrag := False; + FSelBeginPosition := -1; + FBookmarkBitmap.OnChange := BookmarkBitmapChanged; + FBookmarkBitmap.LoadFromResourceName(HINSTANCE, 'BOOKMARKICONS'); + SetRulerString; +{$IFDEF DELPHI7UP} + ControlStyle := ControlStyle + [csNeedsBorderPaint]; +{$ENDIF} +end; + +destructor TCustomMPHexEditor.Destroy; +begin + + FCursorList := nil; + FBookmarkBitmap.OnChange := nil; + FreeStorage; + FreeStorage(True); + FUndoStorage.Free; + FDataStorage.Free; + FModifiedBytes.Free; + FColors.Free; + FCaretBitmap.Free; + FBookmarkImageList.Free; + FBookmarkBitmap.Free; + inherited Destroy; +end; + +procedure TCustomMPHexEditor.AdjustMetrics; +var + LIntLoop: integer; + LIntChWidth: integer; +begin + Canvas.Font.Assign(Font); + FCharWidth := Canvas.TextWidth('w'); + + SetOffsetDisplayWidth; + DoSetCellWidth(1, 6); + + for LIntLoop := 0 to FBytesPerRowDup do + begin + if LIntLoop = Pred(FBytesPerRowDup) then + LIntChWidth := FCharWidth * 2 + else + begin + LIntChWidth := FCharWidth; + if (((LIntLoop + GRID_FIXED) mod FBytesPerCol) = 1) then + Inc(LIntChWidth, FCharWidth); + if (FBlockSize > 1) and (((LIntLoop + GRID_FIXED) mod (FBlockSize * 2)) = + 1) then + Inc(LIntChWidth, FCharWidth); + end; + DoSetCellWidth(LIntLoop + GRID_FIXED, LIntChWidth); + end; + + if FUnicodeCharacters then + LIntLoop := Pred(FBytesPerRow div 2) + else + LIntLoop := Pred(FBytesPerRow); + for LIntLoop := 0 to LIntLoop do + //FBytesPerRowDup + 1 to (FBytesPerRow * 3) - 1 do + begin + if (FUsedRulerBytesPerUnit > 1) and ((LIntLoop mod FUsedRulerBytesPerUnit) + = Pred(FUsedRulerBytesPerUnit)) and (not FUnicodeCharacters) then + LIntChWidth := (FCharWidth * 3 div 2) + 1 + else + LIntChWidth := FCharWidth + 1; + if not FUnicodeCharacters then + begin + if (FBlockSize > 1) and FSepCharBlocks and ((LIntLoop mod FBlockSize) = + Pred(FBlockSize)) then + Inc(LIntChWidth, FCharWidth); + end + else + begin + if (FBlockSize > 1) and FSepCharBlocks and ((LIntLoop mod (FBlockSize div + 2)) = Pred(FBlockSize div 2)) then + Inc(LIntChWidth, FCharWidth); + end; + DoSetCellWidth(LIntLoop + GRID_FIXED + FBytesPerRowDup + 1, LIntChWidth); + end; + + DoSetCellWidth(GetLastCharCol, (FCharWidth * 2) + 1); + + FCharHeight := Canvas.TextHeight('yY') + 2; + DefaultRowHeight := FCharHeight; + RowHeights[1] := 0; + if FShowRuler then + RowHeights[0] := DefaultRowHeight + 3 + else + RowHeights[0] := 0; + CheckSetCaret; +end; + +function TCustomMPHexEditor.GetDataSize: integer; +begin + Result := FDataStorage.Size; +end; + +procedure TCustomMPHexEditor.CreateEmptyFile; +begin + FreeStorage; + if TempName = '' then + FFileName := UNNAMED_FILE + else + FFileName := TempName; + ResetUndo; + ResetSelection(False); + FModifiedBytes.Size := 0; + CalcSizes; + FModified := False; + FIsFileReadonly := True; + FHasFile := False; + MoveColRow(GRID_FIXED, GRID_FIXED, True, True); + Changed; +end; + +procedure TCustomMPHexEditor.SaveToStream(Strm: TStream); +begin + WaitCursor; + try + FDataStorage.Position := 0; + + Stream2Stream(FDataStorage, Strm, pkSave); + finally + Invalidate; + OldCursor; + end; +end; + +procedure TCustomMPHexEditor.SaveRangeToStream(Strm: TStream; const APosition, + ACount: integer); +begin + WaitCursor; + try + FDataStorage.Position := APosition; + Stream2Stream(FDataStorage, Strm, pkSave, ACount); + finally + Invalidate; + OldCursor; + end; +end; + +procedure TCustomMPHexEditor.SaveToFile(const Filename: string; + const aUnModify: boolean = True); +var + LfstFile: TFileStream; +begin + if (FFileName = FileName) then + PrepareOverwriteDiskFile; + + LfstFile := TFileStream.Create(FileName, fmCreate); + try + FStreamFileName := FileName; + SaveToStream(LfstFile); + FHasFile := True; + if aUnModify then + begin + FModifiedBytes.Size := 0; + FModified := False; + FIsFileReadonly := False; + FFileName := Filename; + FDataStorage.Position := 0; + ResetUndo; + end; + finally + FStreamFileName := ''; + LfstFile.Free; + end; +end; + +procedure TCustomMPHexEditor.LoadFromStream(Strm: TStream); +begin + try + FreeStorage; + CalcSizes; + WaitCursor; + try + try + Strm.Position := 0; + FDataStorage.Size := Strm.Size; + FDataStorage.Position := 0; + + Stream2Stream(Strm, FDataStorage, pkLoad); + //FDataStorage.CopyFrom(Strm, Strm.Size - Strm.Position); + + FDataStorage.Position := 0; + finally + with FUndoStorage do + if UpdateCount < 1 then + Reset; + FModifiedBytes.Size := 0; + CalcSizes; + FModified := False; + FIsSelecting := False; + MoveColRow(GRID_FIXED, GRID_FIXED, True, True); + Changed; + end; + finally + OldCursor; + end; + except + FreeStorage; + FreeStorage(True); + FHasFile := False; + raise; + end; +end; + +procedure TCustomMPHexEditor.LoadFromFile(const Filename: string); +var + LfstFile: TFileStream; +begin + if CanOpenFile(FileName, FIsFileReadonly) then + begin + LfstFile := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone); + try + FStreamFileName := FileName; + try + LoadFromStream(LfstFile); + except + FHasFile := False; + raise; + end; + FFileName := FileName; + FHasFile := True; + finally + FStreamFileName := ''; + LfstFile.Free; + end; + end + else + raise EFOpenError.CreateFmt(ERR_FILE_OPEN_FAILED, [FileName, + SysErrorMessage(GetLastError)]); +end; + +procedure TCustomMPHexEditor.CalcSizes; +var + LIntRows: integer; +begin + if FModifiedBytes.Size > DataSize then + FModifiedBytes.Size := DataSize; + + if DataSize < 1 then + begin + RowCount := GRID_FIXED + 1; + ColCount := CalcColCount; + FixedCols := GRID_FIXED; + end + else + begin + LIntRows := (DataSize + (FBytesPerRow - 1)) div FBytesPerRow; + if ((DataSize mod FBytesPerRow) = 0) and InsertMode then + INC(LIntRows); + RowCount := LIntRows + GRID_FIXED; + + ColCount := CalcColCount; + FixedCols := GRID_FIXED; + end; + FixedRows := GRID_FIXED; + AdjustMetrics; +end; + +function TCustomMPHexEditor.TranslateFromAnsiChar(const aByte: byte): char; +begin + case FTranslation of + tkAsIs: Result := char(aByte); + tkDos8, + tkASCII: + begin + if ((FTranslation = tkDos8) or (aByte < 128)) and (aByte > 31) then + Result := Char2Oem(aByte) + else + Result := #0; + end; + tkMac: Result := MPH_CCONV_MAC[cctFromAnsi][aByte]; + tkBCD: Result := MPH_CCONV_BCD38[cctFromAnsi][aByte]; + + tkCustom: Result := MPHCustomCharConv[cctFromAnsi][aByte]; + + else + Result := #0; + end; + if Result in FMaskedChars then + Result := #0; +end; + +function TCustomMPHexEditor.TranslateToAnsiChar(const aByte: byte): char; +begin + case FTranslation of + tkAsIs: Result := char(aByte); + tkDos8, + tkASCII: + begin + Result := Oem2Char(aByte); + if ((FTranslation = tkASCII) and (aByte > 127)) then + Result := FReplaceUnprintableCharsBy; + end; + tkMac: Result := MPH_CCONV_MAC[cctToAnsi][aByte]; + tkBCD: Result := MPH_CCONV_BCD38[cctToAnsi][aByte]; + + tkCustom: Result := MPHCustomCharConv[cctToAnsi][aByte]; + + else + Result := FReplaceUnprintableCharsBy; + end; + + if (FReplaceUnprintableCharsBy <> #0) and (Result in FMaskedChars) then + Result := FReplaceUnprintableCharsBy; +end; + +// get the position of the drag marker + +function TCustomMPHexEditor.DropPosition: integer; +var + LBoolInCharField: boolean; +begin + Result := -1; + LBoolInCharField := FPosInCharField; + try + if FShowDrag then + begin + Result := GetPosAtCursor(FDropCol, FDropRow); + CheckUnit(Result); + end; + finally + FPosInCharField := LBoolInCharField; + end; +end; + +procedure TCustomMPHexEditor.Stream2Stream(strFrom, strTo: TStream; + const Operation: TMPHProgressKind; const Count: integer = -1); +var + LBytProgress, LBytLastProgress: byte; + LIntRemain, LIntRead, LIntCount: integer; + LBoolCancel: boolean; + LStrFile: string; + + LBytBuffer: array[0..MPH_FILEIO_BLOCKSIZE - 1] of byte; +begin + LIntCount := Count; + if LIntCount = -1 then + LIntCount := strFrom.Size - strFrom.Position; + + LIntRemain := LIntCount; + LBoolCancel := False; + LBytLastProgress := 255; + LStrFile := FStreamFileName; + if LStrFile = '' then + LStrFile := FFileName; + + while LIntRemain > 0 do + begin + LBytProgress := Round(((LIntCount - LIntRemain) / LIntCount) * 100); + if (LBytProgress <> LBytLastProgress) or (LIntRemain <= + MPH_FILEIO_BLOCKSIZE) then + begin + if LIntRemain <= MPH_FILEIO_BLOCKSIZE then + LBytLastProgress := 100 + else + LBytLastProgress := LBytProgress; + if Assigned(FOnProgress) then + begin + FOnProgress(self, Operation, LStrFile, LBytLastProgress, + LBoolCancel); + if LBoolCancel then + raise EMPHexEditor.Create(ERR_CANCELLED); + end + end; + + LIntRead := Min(LIntRemain, MPH_FILEIO_BLOCKSIZE); + strFrom.ReadBuffer(LBytBuffer, LIntRead); + strTo.WriteBuffer(LBytBuffer, LIntRead); + Dec(LIntRemain, LIntRead); + end; +end; + +function TCustomMPHexEditor.SelectCell(ACol, ARow: longint): boolean; +var + LIntCurRow: integer; + LRctCellRect: TRect; + LIntOtherFieldCol: integer; + LIntNewPosition, LIntPrevPosition: integer; +begin + LIntCurRow := Row; + if DataSize > 0 then + Result := CheckSelectCell(aCol, aRow) + else + begin + if not ((aCol = GRID_FIXED) or (aCol = Max(GetOtherFieldColCheck(GRID_FIXED) + , GRID_FIXED)) and (aRow = GRID_FIXED)) then + Result := False + else + begin + LRctCellRect := CellRect(aCol, aRow); + if LRctCellRect.Left + LRctCellRect.Bottom = 0 then + IntSetCaretPos(-50, -50, -1) + else + IntSetCaretPos(LRctCellRect.Left, LRctCellRect.Top, aCol); + Result := True; + Exit; + end; + end; + + if Result then + begin + //cursor in anderem feld löschen + if (aCol <> Col) or (aRow <> Row) then + begin + LIntOtherFieldCol := GetOtherFieldColCheck(Col); + LRctCellRect := CellRect(LIntOtherFieldCol, LIntCurRow); + InvalidateRect(Handle, @LRctCellRect, False); + if FShowRuler and (aCol <> Col) then + begin + LRctCellRect := CellRect(LIntOtherFieldCol, 0); + InvalidateRect(Handle, @LRctCellRect, False); + LRctCellRect := CellRect(Col, 0); + InvalidateRect(Handle, @LRctCellRect, False); + end; + + // cursor in anderem feld setzen + LIntOtherFieldCol := GetOtherFieldColCheck(aCol); + LRctCellRect := CellRect(LIntOtherFieldCol, aRow); + InvalidateRect(Handle, @LRctCellRect, False); + if FShowRuler and (aCol <> Col) then + begin + LRctCellRect := CellRect(LIntOtherFieldCol, 0); + InvalidateRect(Handle, @LRctCellRect, False); + LRctCellRect := CellRect(aCol, 0); + InvalidateRect(Handle, @LRctCellRect, False); + end; + + if LIntCurRow <> aRow then + begin + LRctCellRect := CellRect(0, LIntCurRow); + InvalidateRect(Handle, @LRctCellRect, False); + LRctCellRect := CellRect(0, aRow); + InvalidateRect(Handle, @LRctCellRect, False); + end; + end; + + if FIsSelecting then + begin + LIntNewPosition := GetPosAtCursor(aCol, aRow); + LIntPrevPosition := GetPosAtCursor(Col, Row); + if FSelBeginPosition = -1 then + FSelBeginPosition := LIntPrevPosition; + if not InsertMode then + begin + CheckSelectUnit(FSelBeginPosition, LIntNewPosition); + NewSelection(FSelBeginPosition, LIntNewPosition); + end + else + begin + if FSelBeginPosition > LIntNewPosition then + begin + CheckUnit(FSelBeginPosition); + CheckUnit(LIntNewPosition); + if FSelBeginPosition = LIntNewPosition then + begin + ResetSelection(True); + FSelBeginPosition := LIntNewPosition; + FIsSelecting := True; + end + else + begin + NewSelection(FSelBeginPosition - FBytesPerUnit, LIntNewPosition); + end; + end + else if FSelBeginPosition < LIntNewPosition then + begin + CheckUnit(FSelBeginPosition); + CheckUnit(LIntNewPosition); + if FSelBeginPosition = LIntNewPosition then + begin + ResetSelection(True); + FSelBeginPosition := LIntNewPosition; + FIsSelecting := True; + end + else + begin + NewSelection(FSelBeginPosition, LIntNewPosition - FBytesPerUnit); + end; + end + else + begin + ResetSelection(True); + FSelBeginPosition := LIntNewPosition; + FIsSelecting := True; + end + end; + end + else + ResetSelection(True); + + // caret neu setzen + //CheckSetCaret; + LRctCellRect := CellRect(aCol, aRow); + if LRctCellRect.Left + LRctCellRect.Bottom = 0 then + IntSetCaretPos(-50, -50, -1) + else + IntSetCaretPos(LRctCellRect.Left, LRctCellRect.Top, aCol); + SelectionChanged; + end; +end; + +// Obtient la position dans le fichier à partir de la position du curseur + +function TCustomMPHexEditor.GetPosAtCursor(const aCol, aRow: integer): integer; +begin + FPosInCharField := (aCol > (GRID_FIXED + FBytesPerRowDup)); + if FPosInCharField then + begin + Result := aCol - ((GRID_FIXED + 1) + FBytesPerRowDup); + if FUnicodeCharacters then + Result := Result * 2; + end + else + Result := (aCol - GRID_FIXED) div 2; + + Result := Result + ((aRow - GRID_FIXED) * FBytesPerRow); + if Result < 0 then + Result := 0; +end; + +function TCustomMPHexEditor.GetRow(const DataPos: integer): integer; +begin + Result := (DataPos div FBytesPerRow) + GRID_FIXED; +end; + +function TCustomMPHexEditor.GetCursorAtPos(const aPos: integer; + const aChars: boolean): TGridCoord; +var + LIntCol: integer; +begin + if aPos < 0 then + begin + Result.y := GRID_FIXED; + Result.x := GRID_FIXED; + Exit; + end; + + Result.y := GetRow(aPos); + LIntCol := aPos mod FBytesPerRow; + + if aChars then + begin + if FUnicodeCharacters then + Result.x := (LIntCol div 2) + (GRID_FIXED + 1) + FBytesPerRowDup + else + Result.x := LIntCol + (GRID_FIXED + 1 + FBytesPerRowDup) + end + else + Result.x := (LIntCol * 2) + GRID_FIXED; +end; + +function TCustomMPHexEditor.GetOtherFieldCol(const aCol: integer): integer; +var + LIntCol: integer; +begin + FPosInCharField := (aCol > (GRID_FIXED + FBytesPerRowDup)); + if FPosInCharField then + begin + LIntCol := (aCol - (GRID_FIXED + 1 + FBytesPerRowDup)); + if FUnicodeCharacters then + Result := (LIntCol * 4) + GRID_FIXED + else + Result := (LIntCol * 2) + GRID_FIXED; + end + else + begin + if FUnicodeCharacters then + LIntCol := ((aCol - GRID_FIXED) div 4) + else + LIntCol := ((aCol - GRID_FIXED) div 2); + Result := LIntCol + (GRID_FIXED + 1 + FBytesPerRowDup); + end; +end; + +function TCustomMPHexEditor.GetOtherFieldColCheck(const aCol: integer): integer; +var + LIntCol: integer; +begin + if aCol > (GRID_FIXED + FBytesPerRowDup) then + begin + LIntCol := (aCol - (GRID_FIXED + 1 + FBytesPerRowDup)); + if FUnicodeCharacters then + Result := (LIntCol * 4) + GRID_FIXED + else + Result := (LIntCol * 2) + GRID_FIXED; + end + else + begin + if FUnicodeCharacters then + LIntCol := ((aCol - GRID_FIXED) div 4) + else + LIntCol := ((aCol - GRID_FIXED) div 2); + Result := LIntCol + (GRID_FIXED + 1 + FBytesPerRowDup); + end; +end; + +function TCustomMPHexEditor.CheckSelectCell(aCol, aRow: integer): boolean; +var + LgrcEndCoords: TGridCoord; + LIntPos: integer; +begin + Result := inherited SelectCell(aCol, aRow); + + if not FSelectionPossible then + Exit; + + try + FSelectionPossible := False; + + if Result then + begin + // überprüfen, ob linke maustaste oder shift gedrückt, sonst selection zurücksetzen + if not (IsKeyDown(VK_SHIFT) or IsKeyDown(VK_LBUTTON)) then + ResetSelection(True); + + // überprüfen, ob außerhalb der DateiGröße + LIntPos := GetPosAtCursor(aCol, aRow); + if (LIntPos >= DataSize) and not (InsertMode and (LIntPos = DataSize) and + (FPosInCharField or ((aCol mod 2) = 0))) then + begin + if (not InsertMode) then + LgrcEndCoords := GetCursorAtPos(DataSize - 1, InCharField) + else + LgrcEndCoords := GetCursorAtPos(DataSize, InCharField); + + MoveColRow(LgrcEndCoords.x, LgrcEndCoords.y, True, True); + Result := False; + end + else if aCol = (GRID_FIXED + FBytesPerRowDup) then + begin + Result := False; + if IsKeyDown(VK_LBUTTON) then + begin + aCol := aCol - 1; + aCol := Max(GRID_FIXED, aCol); + MoveColRow(aCol, aRow, True, True); + Exit; + end; + end; + end; + + finally + FSelectionPossible := True; + end; +end; + +procedure TCustomMPHexEditor.WMChar(var Msg: TWMChar); +var + LIntPos: integer; + LChrChar: char; + LBytOldData, LBytNewData: byte; + LArrNewData: packed array[0..7] of byte; + LWChrNewData: WideChar absolute LArrNewData; + LgrcPosition: TGridCoord; + LWChrOldData: WideChar; + LWrdKey: Word; +begin + LChrChar := char(Msg.CharCode); + + if Assigned(OnKeyPress) then + OnKeyPress(Self, LChrChar); + + if FReadOnlyView or (LChrChar in FMaskedChars) then + Exit; + + LIntPos := GetPosAtCursor(Col, Row); + if (LIntPos >= DataSize) and not InsertMode then + Exit; + + if not FPosInCharField then + begin + // hex-eingabe, nur 0..9 , a..f erlaubt + if Pos(LChrChar, HEX_ALLCHARS) <> 0 then + begin + LChrChar := UpCase(LChrChar); + + if not InsertMode then + ResetSelection(True); + + LgrcPosition := GetCursorAtPos(LIntPos, FPosInCharField); + // Obtient la valeur du byte dans le fichier (OldByte) + if DataSize > LIntPos then + LBytOldData := Data[LIntPos] + else + LBytOldData := 0; + + if (LgrcPosition.x = (Col - FSwapNibbles)) or (SelCount <> 0) then + LBytNewData := LBytOldData and 15 + ((Pos(LChrChar, HEX_UPPER) - 1) * 16) + else + LBytNewData := (LBytOldData and $F0) + (Pos(LChrChar, HEX_UPPER) - 1); + + FillChar(LArrNewData, sizeof(LArrNewData), #0); + if InsertMode and ((((Col - GRID_FIXED) mod (FBytesPerUnit * 2)) = 0) or + (SelCount > 0)) then + begin + if FSwapNibbles = 0 then + LBytNewData := LBytNewData and $F0 + else + LBytNewData := LBytNewData and $0F; + LArrNewData[0] := LBytNewData; + + if DataSize = 0 then + AppendBuffer(PChar(@LArrNewData), FBytesPerUnit, '', False) + else if SelCount = 0 then + begin + InsertBuffer(PChar(@LArrNewData), FBytesPerUnit, LIntPos, '', False); + end + else + ReplaceSelection(PChar(@LArrNewData), FBytesPerUnit, '', False); + end + else + begin + if LIntPos >= DataSize then + Exit; + IntChangeByte(LBytOldData, LBytNewData, LIntPos, Col, Row); + end; + FIsSelecting := False; + + LWrdKey := VK_RIGHT; + KeyDown(LWrdKey, []); + end + else + WrongKey + end + else + begin + // zeichen-eingabe, alle zeichen erlaubt + LChrChar := TranslateFromAnsiChar(Ord(LChrChar)); + + if (LChrChar in FMaskedChars) then + begin + WrongKey; + Exit; + end; + + if not InsertMode then + ResetSelection(True); + + LgrcPosition := GetCursorAtPos(LIntPos, FPosInCharField); + + FillChar(LArrNewData, sizeof(LArrNewData), #0); + if not FUnicodeCharacters then + LArrNewData[0] := Ord(LChrChar) + else + begin + LWChrNewData := StringToWideChar(LChrChar, @LWChrNewData, 2)^; + if FUnicodeBigEndian then + SwapWideChar(LWChrNewData); + end; + if (DataSize = 0) or (DataSize = LIntPos) then + LBytOldData := 0 + else + LBytOldData := Data[LIntPos]; + if FUnicodeCharacters then + begin + if (DataSize = 0) or (DataSize = LIntPos) or (DataSize = (LIntPos + 1)) + then + LWChrOldData := #0 + else + ReadBuffer(LWChrOldData, LIntPos, 2); + end; + + if InsertMode then + begin + if SelCount > 0 then + ReplaceSelection(PChar(@LArrNewData), FBytesPerUnit, '', False) + else + begin + if LIntPos = DataSize then + AppendBuffer(PChar(@LArrNewData), FBytesPerUnit) + else + begin + if (LIntPos mod FBytesPerUnit) = 0 then + InsertBuffer(PChar(@LArrNewData), FBytesPerUnit, LIntPos, '', False) + else + IntChangeByte(LBytOldData, LArrNewData[0], LIntPos, Col, Row) + end; + FIsSelecting := False; + end; + end + else + begin + if FUnicodeCharacters then + IntChangeWideChar(LWChrOldData, LWChrNewData, LIntPos, Col, Row) + else + IntChangeByte(LBytOldData, Ord(LChrChar), LIntPos, Col, Row); + end; + + LWrdKey := VK_RIGHT; + KeyDown(LWrdKey, []); + end; +end; + +{-------------------------------------------------------------------------------} +// *** procedure TCustomMPHexEditor.IntChangeByte*** +// Change la valeur du byte +// Renseigne la structure Undo +{-------------------------------------------------------------------------------} + +procedure TCustomMPHexEditor.IntChangeByte(const aOldByte, aNewByte: byte; aPos, + aCol, aRow: integer; const UndoDesc: string = ''); +var + LRctBoxRect: TRect; + LIntOtherFieldCol: integer; +begin + if aOldByte = aNewByte then + Exit; + + CreateUndo(ufKindBytesChanged, aPos, 1, 0, UndoDesc); + + // Ecrit dans le fichier + Data[aPos] := aNewByte; + + if not InsertMode then + FModifiedBytes.Bits[aPos] := True; + + aCol := GetCursorAtPos(aPos, False).X; + LIntOtherFieldCol := GetOtherFieldColCheck(aCol); + LRctBoxRect := BoxRect(aCol, aRow, aCol + 1, aRow); + InvalidateRect(Handle, @LRctBoxRect, False); + LRctBoxRect := BoxRect(LIntOtherFieldCol, aRow, LIntOtherFieldCol, aRow); + InvalidateRect(Handle, @LRctBoxRect, False); + Changed; +end; + +procedure TCustomMPHexEditor.IntChangeWideChar(const aOldChar, aNewChar: + WideChar; aPos, aCol, aRow: integer; const UndoDesc: string); +var + LRctBoxRect: TRect; + LIntOtherFieldCol: integer; + LBArrOld: packed array[0..1] of Byte absolute aOldChar; + LBArrNew: packed array[0..1] of Byte absolute aNewChar; +begin + if aOldChar = aNewChar then + Exit; + + CreateUndo(ufKindBytesChanged, aPos, 2, 0, UndoDesc); + + // Ecrit dans le fichier + WriteBuffer(aNewChar, aPos, 2); + + if not InsertMode then + begin + FModifiedBytes.Bits[aPos] := LBArrOld[0] <> LBArrNew[0]; + FModifiedBytes.Bits[aPos + 1] := LBArrOld[1] <> LBArrNew[1]; + end; + + aCol := GetCursorAtPos(aPos, False).X; + LIntOtherFieldCol := GetOtherFieldColCheck(aCol); + LRctBoxRect := BoxRect(aCol, aRow, aCol + 3, aRow); + InvalidateRect(Handle, @LRctBoxRect, False); + LRctBoxRect := BoxRect(LIntOtherFieldCol, aRow, LIntOtherFieldCol, aRow); + InvalidateRect(Handle, @LRctBoxRect, False); + Changed; +end; + +procedure TCustomMPHexEditor.KeyDown(var Key: word; Shift: TShiftState); +var + LIntCol: integer; + LgrcPosition: TGridCoord; + LIntRow: integer; +begin + if Assigned(OnKeyDown) then + OnKeyDown(self, Key, Shift); + + // reset selection if no shift key is pressed (except of SHIFT-Key) + if not ((Shift <> []) or (KEY = VK_SHIFT)) then + if not InsertMode then + ResetSelection(True); + + case Key of + + VK_PRIOR: + begin + if ssCtrl in Shift then + begin + // go to the first visible line + LIntRow := TopRow; + LIntCol := Col; + if LIntRow > -1 then + begin + MoveColRow(LIntCol, LIntRow, True, True); + end; + end + else + begin + // scroll up one page + LIntRow := Max(GRID_FIXED, Row - VisibleRowCount + 1); + TopRow := Max(GRID_FIXED, TopRow - VisibleRowCount + 1); + LIntCol := Col; + if LIntRow > -1 then + begin + MoveColRow(LIntCol, LIntRow, True, True); + end; + end; + end; + + VK_NEXT: + begin + if ssCtrl in Shift then + begin + // go to the Last visible line + LIntRow := Min(RowCount - 1, TopRow + VisibleRowCount - 1); + LIntCol := Col; + if LIntRow > 0 then + begin + MoveColRow(LIntCol, LIntRow, True, True); + end; + end + else + begin + // scroll down one page + LIntRow := Min(RowCount - 1, Row + VisibleRowCount - 1); + TopRow := Min(Max(GRID_FIXED, RowCount - VisibleRowCount), + TopRow + VisibleRowCount - 1); + LIntCol := Col; + if LIntRow > 0 then + begin + MoveColRow(LIntCol, LIntRow, True, True); + end; + end; + end; + + VK_HOME: + begin + InCharField; + if (ssCtrl in Shift) then + begin // strg+pos1 + if not FPosInCharField then + MoveColRow(GRID_FIXED, GRID_FIXED, True, True) + else + MoveColRow(Max(GRID_FIXED, GetOtherFieldCol(GRID_FIXED)), + GRID_FIXED, True, True); + end + else + begin // normaler zeilenstart + if not FPosInCharField then + MoveColRow(GRID_FIXED, Row, True, True) + else + MoveColRow(Max(GRID_FIXED, GetOtherFieldCol(GRID_FIXED)), + Row, True, True); + end; + end; + + VK_END: + begin + InCharField; + if (ssCtrl in Shift) then + begin // strg+end + if (not InsertMode) then + LgrcPosition := GetCursorAtPos(DataSize - 1, FPosInCharField) + else + LgrcPosition := GetCursorAtPos(DataSize, FPosInCharField); + MoveColRow(LgrcPosition.x, LgrcPosition.y, True, True) + end + else + begin // normales zeilenende + if not FPosInCharField then + begin + LIntCol := GetPosAtCursor(GRID_FIXED, Row + 1) - 1; + TruncMaxPosition(LIntCol); + LgrcPosition := GetCursorAtPos(LIntCol, FPosInCharField); + MoveColRow(LgrcPosition.x + 1, LgrcPosition.y, True, True) + end + else + begin + LIntCol := GetPosAtCursor(GRID_FIXED, Row + 1) - 1; + TruncMaxPosition(LIntCol); + LgrcPosition := GetCursorAtPos(LIntCol, True); + MoveColRow(LgrcPosition.x, LgrcPosition.y, True, True); + end + end; + end; + + VK_LEFT, VK_BACK: + if (InsertMode and (not FReadOnlyView)) and (Key = VK_BACK) then + begin + if SelCount > 0 then + DeleteSelection + else + InternalErase(True) + end + else if (not (ssCTRL in Shift)) then + begin + if FIsSelecting or (FUnicodeCharacters and FPosInCharField) then + LIntCol := GetPosAtCursor(Col, Row) - FBytesPerUnit + else + LIntCol := GetPosAtCursor(Col, Row) - 1; + if FPosInCharField then + begin + if LIntCol < 0 then + LIntCol := 0; + LgrcPosition := GetCursorAtPos(LIntCol, FPosInCharField); + MoveColRow(LgrcPosition.x, LgrcPosition.y, True, True); + end + else + begin + if FIsSelecting then + begin + CheckUnit(LIntCol); + LgrcPosition := GetCursorAtPos(LIntCol, FPosInCharField); + MoveColRow(LgrcPosition.x, LgrcPosition.y, True, True); + end + else + begin + LIntCol := LIntCol + 1; + LgrcPosition := GetCursorAtPos(LIntCol, False); + if LgrcPosition.x < Col then + MoveColRow(Col - 1, Row, True, True) + else + begin + LIntCol := LIntCol - 1; + if LIntCol >= 0 then + begin + LgrcPosition := GetCursorAtPos(LIntCol, FPosInCharField); + MoveColRow(LgrcPosition.x + 1, LgrcPosition.y, True, True); + end; + end + end; + end; + end + else + begin + if Key = VK_LEFT then + begin + LIntCol := GRID_FIXED; + MoveColRow(LIntCol, Row, True, True); + end; + end; + + VK_RIGHT: + begin + if (not (ssCTRL in Shift)) then + begin + if FIsSelecting or (FUnicodeCharacters and FPosInCharField) then + LIntCol := GetPosAtCursor(Col, Row) + FBytesPerUnit + else + LIntCol := GetPosAtCursor(Col, Row) + 1; + if FPosInCharField then + begin + TruncMaxPosition(LIntCol); + LgrcPosition := GetCursorAtPos(LIntCol, FPosInCharField); + MoveColRow(LgrcPosition.x, LgrcPosition.y, True, True); + end + else + begin + if FIsSelecting then + begin + CheckUnit(LIntCol); + TruncMaxPosition(LIntCol); + LgrcPosition := GetCursorAtPos(LIntCol, FPosInCharField); + MoveColRow(LgrcPosition.x, LgrcPosition.y, True, True); + end + else + begin + LIntCol := LIntCol - 1; + LgrcPosition := GetCursorAtPos(LIntCol, False); + if (LgrcPosition.x = Col) and not (LIntCol = DataSize) then + MoveColRow(Col + 1, Row, True, True) + else + begin + LIntCol := LIntCol + 1; + if (LIntCol < DataSize) or ((LIntCol = DataSize) and InsertMode) + then + begin + LgrcPosition := GetCursorAtPos(LIntCol, FPosInCharField); + MoveColRow(LgrcPosition.x, LgrcPosition.y, True, True); + end; + end + end; + end; + end + else + begin + LIntCol := GetLastCharCol; + MoveColRow(LIntCol, Row, True, True); + end; + end; + + VK_DOWN: + begin + if (not (ssCTRL in Shift)) then + begin + LIntRow := Row + 1; + + LIntCol := Col; + if LIntRow < RowCount then + begin + MoveColRow(LIntCol, LIntRow, True, True); + end; + end; + end; + + VK_UP: + begin + if (not (ssCTRL in Shift)) then + begin + LIntRow := Row - 1; + LIntCol := Col; + if LIntRow > 1 then + begin + MoveColRow(LIntCol, LIntRow, True, True); + end; + end; + end; + + Word('T'): if (ssCtrl in Shift) then + begin + Col := Max(GRID_FIXED, GetOtherFieldCol(Col)); + end; + + VK_TAB: if ((Shift = []) or (Shift = [ssShift])) then + begin // tab-taste + Col := Max(GRID_FIXED, GetOtherFieldCol(Col)); + end; + + Word('0')..Word('9'): if ssCtrl in Shift then + begin + if ssShift in Shift then + begin + LIntRow := GetPosAtCursor(Col, Row); + SetBookmarkVals(Key - Ord('0'), LIntRow, FPosInCharField); + end + else + begin + GotoBookmark(Key - Ord('0')); + end; + end; + + VK_SHIFT: if (Shift = [ssShift]) or (Shift = [ssShift, ssCtrl]) then + begin // selektion starten + FIsSelecting := True; + end; + + VK_INSERT: + begin + InsertMode := not InsertMode; + end; + + VK_DELETE: if (not FReadOnlyView) then + begin + if (SelCount > 0) and (InsertMode or (Shift = [ssCtrl])) then + DeleteSelection + else if InsertMode or (Shift = [ssCtrl]) then + InternalErase(False) + end; + end; +end; + +function TCustomMPHexEditor.HasChanged(aPos: integer): boolean; +begin + Result := False; + if InsertMode then + Exit; + + if FModifiedBytes.Size > aPos then + Result := FModifiedBytes.Bits[aPos]; +end; + +function TCustomMPHexEditor.IsSelected(const APosition: integer): boolean; +begin + Result := False; + if (FSelPosition <> -1) and (APosition >= FSelStart) and (APosition <= FSelEnd) + then + begin + Result := True + end; +end; + +procedure TCustomMPHexEditor.NewSelection(SelFrom, SelTo: integer); +var + LIntSelStart, LIntSelEnd, LIntSelPos: integer; + LIntOldStart, LIntNewStart, LIntOldEnd, LIntNewEnd: integer; +begin + CheckSelectUnit(SelFrom, SelTo); + LIntSelEnd := FSelEnd; + LIntSelStart := FSelStart; + LIntSelPos := FSelPosition; + + SetSelection(SelFrom, Min(SelFrom, SelTo), Max(SelFrom, SelTo)); + + if (LIntSelPos = -1) then + RedrawPos(Min(FSelStart, FSelEnd), Max(FSelStart, FSelEnd)) + else + begin + // den bereich neu zeichnen, der neu selektiert ist, sowie den, der nicht mehr selektiert ist + // hinzugekommene selektion berechnen + LIntNewStart := Min(SelFrom, SelTo); + LIntOldStart := Min(LIntSelEnd, LIntSelStart); + LIntNewEnd := Max(SelFrom, SelTo); + LIntOldEnd := Max(LIntSelEnd, LIntSelStart); + RedrawPos(Min(LIntNewStart, LIntOldStart), Max(LIntNewStart, LIntOldStart)); + RedrawPos(Min(LIntOldEnd, LIntNewEnd), Max(LIntOldEnd, LIntNewEnd)); + end; + SelectionChanged; +end; + +function TCustomMPHexEditor.GetOffsetFormat: string; +begin + Result := FOffsetFormat.Format; +end; + +procedure TCustomMPHexEditor.SetOffsetFormat(const Value: string); +begin + if Value <> FOffsetFormat.Format then + try + GenerateOffsetFormat(Value); + SetOffsetDisplayWidth; + Invalidate; + except + GenerateOffsetFormat(FOffsetFormat.Format); + raise; + end; +end; + +procedure TCustomMPHexEditor.SetHexLowerCase(const Value: boolean); +begin + if FHexLowerCase <> Value then + begin + FHexLowerCase := Value; + if Value then + Move(HEX_LOWER[1], FHexChars, sizeof(FHexChars)) + else + Move(HEX_UPPER[1], FHexChars, sizeof(FHexChars)); + Invalidate; + end; +end; + +procedure TCustomMPHexEditor.GenerateOffsetFormat(Value: string); +var + LIntTemp: integer; + LStrTemp: string; +begin + with FOffsetFormat do + begin + Flags := []; + LStrTemp := Value; + // aufbau: [r|c|%][-|!]:[Prefix]|[Suffix] + if LStrTemp <> '' then + begin + // bytes per unit + if UpperCase(Copy(LStrTemp, 1, 2)) = 'R%' then + begin + Flags := Flags + [offCalcRow]; + Delete(LStrTemp, 1, 2); + _BytesPerUnit := BytesPerRow; + end + else if UpperCase(Copy(LStrTemp, 1, 2)) = 'C%' then + begin + Flags := Flags + [offCalcColumn]; + Delete(LStrTemp, 1, 2); + _BytesPerUnit := BytesPerColumn; + end + else + begin + LIntTemp := 1; + while (LIntTemp <= Length(LStrTemp)) and + (LStrTemp[LIntTemp] in ['0'..'9', 'A'..'F', 'a'..'f']) do + Inc(LIntTemp); + if Copy(LStrTemp, LIntTemp, 1) = '%' then + begin + // width field + if LIntTemp = 1 then + begin + Flags := Flags + [offBytesPerUnit]; + _BytesPerUnit := FUsedRulerBytesPerUnit; + Delete(LStrTemp, 1, 1) + end + else + begin + _BytesPerUnit := RadixToInt(Copy(LStrTemp, 1, LIntTemp - 1), 16); + // StrToInt('$'+Copy(LStrTemp, 1, LIntTemp-1)); + Delete(LStrTemp, 1, LIntTemp); + end; + end + else + begin + Flags := Flags + [offBytesPerUnit]; + _BytesPerUnit := FUsedRulerBytesPerUnit; + end; + end; + if not (_BytesPerUnit in [1, 2, 4, 8]) then + raise EMPHexEditor.CreateFmt(ERR_INVALID_BPU, [_BytesPerUnit]); + // auto calc width + if Copy(LStrTemp, 1, 2) = '-!' then + begin + Flags := Flags + [offCalcWidth]; + Delete(LStrTemp, 1, 2); + MinWidth := 1; + end + else + begin + // width ? + LIntTemp := 1; + while (LIntTemp <= Length(LStrTemp)) and + (LStrTemp[LIntTemp] in ['0'..'9', 'A'..'F', 'a'..'f']) do + Inc(LIntTemp); + if Copy(LStrTemp, LIntTemp, 1) = '!' then + begin + // width field + if LIntTemp = 1 then + begin + MinWidth := 1; + Delete(LStrTemp, 1, 1) + end + else + begin + MinWidth := RadixToInt(Copy(LStrTemp, 1, LIntTemp - 1), 16); + // StrToInt('$'+Copy(LStrTemp, 1, LIntTemp-1)); + Delete(LStrTemp, 1, LIntTemp); + end; + end + else + MinWidth := 1; + end; + + // radix + LIntTemp := 1; + while (LIntTemp <= Length(LStrTemp)) and (LStrTemp[LIntTemp] in ['0'..'9', + 'A'..'F', 'a'..'f']) do + Inc(LIntTemp); + + if LIntTemp = 1 then + raise EMPHexEditor.CreateFmt(ERR_MISSING_FORMATCHAR, ['number radix']); + + if Copy(LStrTemp, LIntTemp, 1) <> ':' then + raise EMPHexEditor.CreateFmt(ERR_MISSING_FORMATCHAR, [':']); + + Radix := RadixToInt(Copy(LStrTemp, 1, LIntTemp - 1), 16); + if not (Radix in [2..16]) then + raise EMPHexEditor.CreateFmt(ERR_INVALID_FORMATRADIX, [Radix]); + + Delete(LStrTemp, 1, LIntTemp); + + // prefix, suffix + LIntTemp := Pos('|', LStrTemp); + if LIntTemp = 0 then + raise EMPHexEditor.CreateFmt(ERR_MISSING_FORMATCHAR, ['|']); + + Prefix := Copy(LStrTemp, 1, LIntTemp - 1); + Suffix := Copy(LStrTemp, LIntTemp + 1, MaxInt); + end; + Format := Value; + end; +end; + +procedure TCustomMPHexEditor.Select(const aCurCol, aCurRow, aNewCol, aNewRow: + integer); +var + LIntOldStart, + //LIntOldEnd, + LIntNewStart, + LIntNewEnd: integer; +begin + //LIntOldEnd := FSelEnd; + //LIntOldStart := FSelStart; + LIntNewStart := GetPosAtCursor(aNewCol, aNewRow); + if FSelPosition = -1 then + begin + LIntOldStart := LIntNewStart; + //LIntOldEnd := LIntNewStart; + LIntNewEnd := GetPosAtCursor(aCurCol, aCurRow); + NewSelection(LIntNewEnd, LIntOldStart); // abcd + //SetSelection(LIntNewEnd, Min(LIntOldStart, LIntNewEnd), Max(LIntNewEnd, + //LIntOldEnd)); + //RedrawPos(FSelStart, FSelEnd) + end + else + //begin + NewSelection(FSelPosition, LIntNewStart); // abcd + (*// testen, ob neue selection /\ liegt als fSelPO + // wenn ja, dann start = sel, ende = selpo + if LIntNewStart < FSelPosition then + begin + NewSelection(FSelPosition, LIntNewStart);// abcd + //SetSelection(FSelPosition, LIntNewStart, FSelPosition); + //RedrawPos(Min(FSelStart, LIntOldStart), Max(FSelStart, LIntOldStart)); + //RedrawPos(Min(FSelEnd, LIntOldEnd), Max(FSelEnd, LIntOldEnd)); + end + else + begin + NewSelection(FSelPosition, LIntNewStart); //abcd + //SetSelection(FSelPosition, FSelPosition, LIntNewStart); + //RedrawPos(Min(FSelStart, LIntOldStart), Max(FSelStart, LIntOldStart)); + //RedrawPos(Min(FSelEnd, LIntOldEnd), Max(FSelEnd, LIntOldEnd)); + end; +end;*) +end; + +procedure TCustomMPHexEditor.RedrawPos(aFrom, aTo: integer); +var + LRctBox: TRect; +begin + aFrom := GetRow(aFrom); + aTo := GetRow(aTo); + LRctBox := BoxRect(GRID_FIXED, aFrom, GetLastCharCol, aTo); + InvalidateRect(Handle, @LRctBox, False); +end; + +procedure TCustomMPHexEditor.ResetSelection(const aDraw: boolean); +var + LIntOldStart, + LIntOldEnd: integer; +begin + FIsSelecting := False; + LIntOldStart := FSelStart; + LIntOldEnd := FSelEnd; + SetSelection(-1, -1, -1); + FSelBeginPosition := -1; + + if aDraw and ((LIntOldStart > -1) or (LIntOldStart > -1)) then + RedrawPos(LIntOldStart, LIntOldEnd); +end; + +procedure TCustomMPHexEditor.MouseDown(Button: TMouseButton; Shift: TShiftState; + X, Y: integer); +var + LgrcDummy: TGridCoord; + lboolInherited: boolean; +begin + FIsSelecting := False; + FMouseUpCanResetSel := False; + + if Button = mbLeft then + LgrcDummy := CheckMouseCoord(X, Y); + + // do not change selection when clicking ruler or offset panel. + if (not MouseOverSelection) and (not MouseOverFixed(x, y)) then + begin + lBoolInherited := True; + inherited MouseDown(Button, Shift, x, y); + end + else + begin + lboolInherited := False; + // but set focus if possible (05/27/2004) + if not (csDesigning in ComponentState) and + (CanFocus or (GetParentForm(Self) = nil)) then + SetFocus; + end; + + if (GetParentForm(self) <> nil) then + if (GetParentForm(self).ActiveControl = self) then + if GetParentForm(self) <> Screen.ActiveForm then + if HandleAllocated then + Windows.SetFocus(self.Handle); + + if (Button = mbLeft) and (not MouseOverSelection) and + (LgrcDummy.X >= GRID_FIXED) and (LgrcDummy.Y >= GRID_FIXED) then + begin + ResetSelection(True); + if not (ssDouble in Shift) then + FIsSelecting := True; + end; + + if (Button = mbLeft) and MouseOverSelection then + begin + FMouseDownCol := x; + FMouseDownRow := y; + FMouseUpCanResetSel := True; + end; + + if (not lBoolInherited) and (Assigned(OnMouseDown)) and Focused then + OnMouseDown(self, Button, Shift, X, Y); +end; + +procedure TCustomMPHexEditor.InternalGetCurSel(var StartPos, EndPos, ACol, ARow: + integer); +begin + if FSelPosition = -1 then + begin + StartPos := GetPosAtCursor(Col, Row); + EndPos := StartPos + 1; + aCol := Col; + aRow := Row; + end + else + begin + StartPos := FSelStart; + EndPos := FSelEnd + 1; + with GetCursorAtPos(FSelStart, InCharField) do + begin + aCOL := X; + aROW := Y; + end; + end; + + if FModifiedBytes.Size > StartPos then + FModifiedBytes.Size := StartPos; +end; + +function TCustomMPHexEditor.CreateShift4BitStream(const StartPos: integer; var + FName: TFileName): TFileStream; +var + LByt1, + LByt2: byte; + LBytBuffer: array[0..511] of byte; + LIntLoop, + LIntRead: integer; +begin + Result := nil; + if StartPos >= DataSize then + Exit; + + FName := GetTempName; + Result := TFileStream.Create(FName, fmCreate); + Result.Position := 0; + FDataStorage.Position := StartPos; + LByt1 := 0; + while FDataStorage.Position < DataSize do + begin + FillChar(LBytBuffer[0], 512, 0); + LIntRead := FDataStorage.Read(LBytBuffer[0], 512); + for LIntLoop := 0 to Pred(LIntRead) do + begin + LByt2 := LBytBuffer[LIntLoop] and 15; + LBytBuffer[LIntLoop] := (LBytBuffer[LIntLoop] shr 4) or (LByt1 shl 4); + LByt1 := LByt2; + end; + Result.WriteBuffer(LBytBuffer[0], LIntRead); + end; + Result.Position := 0; +end; + +function TCustomMPHexEditor.InternalInsertNibble(const Pos: integer; const + HighNibble: boolean): boolean; +var + LfstNibbleStream: TFileStream; + LStrFName: TFileName; + LIntOldSize: integer; + LByteFirst, + LByteLast: byte; +begin + Result := False; + + if DataSize = 0 then + Exit; + + LIntOldSize := FDataStorage.Size; + + WaitCursor; + try + // nun zuerst alle restlichen bits verschieben + LByteFirst := Data[Pos]; + LByteLast := Data[Pred(DataSize)]; + + LfstNibbleStream := CreateShift4BitStream(Pos, LStrFName); + with LfstNibbleStream do + try + FDataStorage.Position := Pos; + FDataStorage.CopyFrom(LfstNibbleStream, LfstNibbleStream.Size); + finally + Free; + DeleteFile(LStrFName); + end; + + if HighNibble then + LByteFirst := LByteFirst shr 4 + else + LByteFirst := LByteFirst and 240; + Data[Pos] := LByteFirst; + FDataStorage.Size := LIntOldSize + 1; + Data[Pred(DataSize)] := LByteLast shl 4; + Result := True; + finally + OldCursor; + end; +end; + +function TCustomMPHexEditor.InsertNibble(const aPos: integer; const HighNibble: + boolean; const UndoDesc: string = ''): boolean; +const + L_BytAppend: byte = 0; +begin + Result := False; + + if DataSize < 1 then + begin + ResetSelection(False); + AppendBuffer(PChar(@L_BytAppend), 1); + Result := True; + Exit; + end; + + if (aPos >= DataSize) or (aPos < 0) then + Exit; + + CreateUndo(ufKindNibbleInsert, aPos, 0, 0, UndoDesc); + + ResetSelection(False); + Result := InternalInsertNibble(aPos, HighNibble); + + if Result and (FModifiedBytes.Size >= (aPos)) then + FModifiedBytes.Size := aPos; + + CalcSizes; + Changed; +end; + +function TCustomMPHexEditor.InternalDeleteNibble(const Pos: integer; const + HighNibble: boolean): boolean; +var + LfstNibbleStream: TFileStream; + LStrFName: TFileName; + LIntOldSize: integer; + LByt1: byte; +begin + Result := False; + if DataSize = 0 then + Exit; + + LIntOldSize := FDataStorage.Size; + WaitCursor; + try + // nun zuerst alle restlichen bits verschieben + LByt1 := Data[Pos]; + + LfstNibbleStream := CreateShift4BitStream(Pos, LStrFName); + with LfstNibbleStream do + try + FDataStorage.Position := Pos; + Position := 1; + FDataStorage.CopyFrom(LfstNibbleStream, LfstNibbleStream.Size - 1); + finally + Free; + DeleteFile(LStrFName); + end; + + if not HighNibble then + Data[Pos] := (LByt1 and 240) or (Data[Pos] and 15); + + Result := True; + FDataStorage.Size := LIntOldSize; + Data[Pred(DataSize)] := Data[Pred(DataSize)] shl 4; + finally + OldCursor; + end; +end; + +function TCustomMPHexEditor.DeleteNibble(const aPos: integer; const HighNibble: + boolean; const UndoDesc: string = ''): boolean; +begin + Result := False; + + if (aPos >= DataSize) or (aPos < 0) then + Exit; + + CreateUndo(ufKindNibbleDelete, aPos, 0, 0, UndoDesc); + + ResetSelection(False); + Result := InternalDeleteNibble(aPos, HighNibble); + + if Result and (FModifiedBytes.Size >= (aPos)) then + FModifiedBytes.Size := aPos; + + CalcSizes; + Changed; +end; + +procedure TCustomMPHexEditor.InternalConvertRange(const aFrom, aTo: integer; + const aTransFrom, aTransTo: TMPHTranslationKind); +var + LIntSize: integer; +begin + LIntSize := (aTo - aFrom) + 1; + WaitCursor; + try + FDataStorage.TranslateToAnsi(aTransFrom, aFrom, LIntSize); + FDataStorage.TranslateFromAnsi(aTransTo, aFrom, LIntSize); + finally + OldCursor; + end; +end; + +procedure TCustomMPHexEditor.ConvertRange(const aFrom, aTo: integer; const + aTransFrom, aTransTo: TMPHTranslationKind; const UndoDesc: string = ''); +begin + if aFrom > aTo then + Exit; + + if aTransFrom = aTransTo then + Exit; + + if (aTo >= DataSize) or (aFrom < 0) then + Exit; + + CreateUndo(ufKindConvert, aFrom, (aTo - aFrom) + 1, 0, UndoDesc); + + InternalConvertRange(aFrom, aTo, aTransFrom, aTransTo); + + Invalidate; + Changed; +end; + +procedure TCustomMPHexEditor.InternalDelete(StartPos, EndPos, ACol, ARow: + integer); +var + LgrdEndPos: TGridCoord; + LIntNewCol: integer; +begin + if EndPos <= (DataSize - 1) then + MoveFileMem(EndPos, StartPos, DataSize - EndPos); + + FDataStorage.Size := DataSize - (EndPos - StartPos); + EndPos := GetPosAtCursor(aCol, aRow); + + if DataSize < 1 then + begin + LIntNewCol := GRID_FIXED; + if FPosInCharField then + LIntNewCol := Max(GRID_FIXED, GetOtherFieldColCheck(LIntNewCol)); + MoveColRow(LIntNewCol, GRID_FIXED, True, True) + end + else if EndPos >= DataSize then + begin + if InsertMode then + LgrdEndPos := GetCursorAtPos(DataSize, FPosInCharField) + else + LgrdEndPos := GetCursorAtPos(DataSize - 1, FPosInCharField); + MoveColRow(LgrdEndPos.x, LgrdEndPos.y, True, True); + end + else if ACol > -1 then + MoveColRow(aCol, aRow, True, True); + + CalcSizes; + ResetSelection(False); + + Invalidate; +end; + +procedure TCustomMPHexEditor.DeleteSelection(const UndoDesc: string = ''); +var + LIntSelStart, + LIntSelEnd, + LIntCol, + LIntRow: integer; +begin + InternalGetCurSel(LIntSelStart, LIntSelEnd, LIntCol, LIntRow); + CreateUndo(ufKindByteRemoved, LIntSelStart, LIntSelEnd - LIntSelStart, + 0, UndoDesc); + + InternalDelete(LIntSelStart, LIntSelEnd, LIntCol, LIntRow); + Changed; +end; + +procedure TCustomMPHexEditor.CreateUndo(const aKind: TMPHUndoFlag; const aPos, + aCount, aReplCount: integer; const sDesc: string = ''); +begin + + if CanCreateUndo(aKind, aCount, aReplCount) then + begin + if FUndoStorage.UpdateCount = 0 then + FUndoStorage.CreateUndo(aKind, aPos, aCount, aReplCount, sDesc); + FModified := True; + //Changed; + end + else + raise EMPHexEditor.Create(ERR_NOUNDO); +end; + +procedure TCustomMPHexEditor.ResetUndo; +begin + FUndoStorage.Reset; +end; + +function TCustomMPHexEditor.GetCanUndo: boolean; +begin + Result := (not FReadOnlyView) and FUndoStorage.CanUndo; +end; + +function TCustomMPHexEditor.GetCanRedo: boolean; +begin + Result := (not FReadOnlyView) and FUndoStorage.CanRedo; +end; + +function TCustomMPHexEditor.GetUndoDescription: string; +begin + if not (csDestroying in ComponentState) then + begin + with FUndoStorage do + if CanUndo then + Result := Description + else + Result := UNDO_NOUNDO; + end + else + Result := UNDO_NOUNDO; +end; + +function TCustomMPHexEditor.GetSelStart: integer; +begin + if FSelPosition = -1 then + begin + Result := GetPosAtCursor(Col, Row); + end + else + Result := FSelPosition; +end; + +function TCustomMPHexEditor.GetSelEnd: integer; +begin + if FSelPosition = -1 then + Result := GetPosAtCursor(Col, Row) + else + begin + Result := FSelEnd; + if FSelPosition = FSelEnd then + Result := FSelStart; + end; +end; + +procedure TCustomMPHexEditor.SetSelStart(aValue: integer); +begin + if (aValue < 0) or (aValue >= DataSize) then + raise EMPHexEditor.Create(ERR_INVALID_SELSTART) + else + begin + ResetSelection(True); + with GetCursorAtPos(aValue, InCharField) do + MoveColRow(X, Y, True, True); + end; +end; + +procedure TCustomMPHexEditor.SetSelEnd(aValue: integer); +begin + if (aValue < -1) or (aValue >= DataSize) then + raise EMPHexEditor.Create(ERR_INVALID_SELEND) + else + begin + ResetSelection(True); + if aValue > -1 then + begin + with GetCursorAtPos(aValue, InCharField) do + Select(Col, Row, X, Y); + SelectionChanged; + end; + end; +end; + +procedure TCustomMPHexEditor.SetInCharField(const Value: boolean); +begin + if (DataSize < 1) then + Exit; + + if InCharField <> Value then + MoveColRow(GetOtherFieldCol(Col), Row, True, True); +end; + +function TCustomMPHexEditor.GetInCharField: boolean; +begin + Result := False; + if DataSize < 1 then + Exit; + + GetPosAtCursor(Col, Row); + Result := FPosInCharField; +end; + +procedure TCustomMPHexEditor.Loaded; +begin + inherited; + CreateEmptyFile(UNNAMED_FILE); +end; + +procedure TCustomMPHexEditor.CreateWnd; +begin + inherited; + if (csDesigning in ComponentState) or (FFileName = '---') then + CreateEmptyFile(UNNAMED_FILE); +end; + +procedure TCustomMPHexEditor.WMSetFocus(var Msg: TWMSetFocus); +begin + inherited; + CreateCaretGlyph; + CheckSetCaret; + Invalidate; +end; + +procedure TCustomMPHexEditor.WMKillFocus(var Msg: TWMKillFocus); +begin + inherited; + HideCaret(Handle); + DestroyCaret(); + FIsSelecting := False; + Invalidate; +end; + +procedure TCustomMPHexEditor.CMINTUPDATECARET(var Msg: TMessage); +begin + if Msg.WParam = 7 then + begin + CheckSetCaret; + end; +end; + +procedure TCustomMPHexEditor.SetTranslation(const Value: TMPHTranslationKind); +begin + if FTranslation <> Value then + begin + if (Value <> tkAsIs) and FUnicodeCharacters then + raise EMPHexEditor.Create(ERR_NO_TRANSLATION_IN_UNICODE_MODE); + FTranslation := Value; + Invalidate; + end; +end; + +procedure TCustomMPHexEditor.SetModified(const Value: boolean); +begin + FModified := Value; + if not Value then + begin + ResetUndo; + FModifiedBytes.Size := 0; + Invalidate; + end; +end; + +procedure TCustomMPHexEditor.SetBytesPerRow(const Value: integer); +var + LIntPos, + LIntSelPos, + LIntSelStart, + LIntSelEnd: integer; + LBoolInCharField, + LBool2ndCol: boolean; +begin + if ((Value < 1) or (Value > 256)) or + (FUnicodeCharacters and ((Value mod 2) <> 0)) then + raise EMPHexEditor.Create(ERR_INVALID_BYTESPERLINE) + else if FBytesPerRow <> Value then + begin + with FOffsetFormat do + if offCalcRow in Flags then + _BytesPerUnit := Value; + LIntSelPos := FSelPosition; + LIntSelStart := FSelStart; + LIntSelEnd := FSelEnd; + LIntPos := GetPosAtCursor(Col, Row); + LBoolInCharField := FPosInCharField; + LBool2ndCol := GetCursorAtPos(LIntPos, LBoolInCharField).x <> Col; + FBytesPerRow := Value; + FBytesPerRowDup := Value * 2; + FIntLastHexCol := (GRID_FIXED + FBytesPerRowDup - 1); + SetRulerString; + CalcSizes; + if (LIntPos >= DataSize) or (InsertMode and (LIntPos > DataSize)) then + LIntPos := DataSize - 1; + + with GetCursorAtPos(LIntPos, LBoolInCharField) do + begin + if LBool2ndCol then + Inc(x); + + MoveColRow(x, y, True, True); + end; + + SetSelection(LIntSelPos, LIntSelStart, LIntSelEnd); + end; +end; + +procedure TCustomMPHexEditor.InternalAppendBuffer(Buffer: PChar; const Size: + integer); +var + LIntSize: integer; +begin + if DataSize = 0 then + begin + FDataStorage.Position := 0; + FModifiedBytes.Size := 0; + end; + + LIntSize := DataSize; + FDataStorage.Size := LIntSize + Size; + WriteBuffer(Buffer^, LIntSize, Size); + CalcSizes; +end; + +procedure TCustomMPHexEditor.InternalInsertBuffer(Buffer: PChar; const Size, + Position: integer); +var + LIntSize: integer; +begin + if DataSize = 0 then + begin + FDataStorage.Position := 0; + FModifiedBytes.Size := 0; + end; + + LIntSize := DataSize; + FDataStorage.Size := LIntSize + Size; + if Position < LIntSize then + // nur, wenn nicht hinter streamende, dann platz schaffen + MoveFileMem(Position, Position + Size, DataSize - Position - Size); //+ 1); + + WriteBuffer(Buffer^, Position, Size); + CalcSizes; +end; + +procedure TCustomMPHexEditor.InsertBuffer(aBuffer: PChar; const aSize, aPos: + integer; const UndoDesc: string = ''; const MoveCursor: Boolean = True); +begin + CreateUndo(ufKindInsertBuffer, aPos, aSize, 0, UndoDesc); + + InternalInsertBuffer(aBuffer, aSize, aPos); + + if FModifiedBytes.Size >= (aPos) then + FModifiedBytes.Size := aPos; + + if Enabled then + begin + SetSelection(aPos, aPos, aPos + aSize - 1); + if MoveCursor then + begin + with GetCursorAtPos(FSelEnd, InCharField) do + MoveColRow(x, y, True, True); + SetSelection(aPos, aPos, aPos + aSize - 1); + end; + Invalidate; + end; + Changed; +end; + +procedure TCustomMPHexEditor.AppendBuffer(aBuffer: PChar; const aSize: integer; + const UndoDesc: string = ''; const MoveCursor: Boolean = True); +var + LIntSize: integer; +begin + if (not Assigned(aBuffer)) or (aSize = 0) then + Exit; + + CreateUndo(ufKindAppendBuffer, DataSize, aSize, 0, UndoDesc); + + if FModifiedBytes.Size >= (DataSize) then + FModifiedBytes.Size := DataSize; + + LIntSize := DataSize; + InternalAppendBuffer(aBuffer, aSize); + + if MoveCursor then + with GetCursorAtPos(LIntSize, InCharField) do + MoveColRow(x, y, True, True); + SetSelection(LIntSize, LIntSize, LIntSize + aSize - 1); + Invalidate; + Changed; +end; + +procedure TCustomMPHexEditor.ReplaceSelection(aBuffer: PChar; aSize: integer; + const UndoDesc: string = ''; const MoveCursor: Boolean = True); +var + LIntStart, + LIntEnd, + LIntCol, + LIntRow: integer; + LBoolInCharField: boolean; +begin + // auswahl berechnen + LBoolInCharField := GetInCharField; + if FSelPosition = -1 then + InsertBuffer(aBuffer, aSize, SelStart, UndoDesc, MoveCursor) + else + begin + if IsFileSizeFixed then + begin + if aSize > SelCount then + aSize := SelCount + else if SelCount > aSize then + begin + SelStart := Min(SelStart, SelEnd); + SelEnd := SelStart + aSize - 1; + end; + end; + + CreateUndo(ufKindReplace, FSelStart, aSize, SelCount, UndoDesc); + + // zuerst aktuelle auswahl löschen + InternalGetCurSel(LIntStart, LIntEnd, LIntCol, LIntRow); + InternalDelete(LIntStart, LIntEnd, LIntCol, LIntRow); + InternalInsertBuffer(aBuffer, aSize, LIntStart); + if FModifiedBytes.Size >= LIntStart then + FModifiedBytes.Size := Max(0, LIntStart); + + if MoveCursor then + begin + with GetCursorAtPos(LIntStart + aSize - 1, LBoolInCharField) do + MoveColRow(x, y, True, True); + SetSelection(LIntStart + aSize - 1, LIntStart, LIntStart + aSize - 1); + end; + Invalidate; + Changed; + end; +end; + +procedure TCustomMPHexEditor.SetChanged(DataPos: integer; const Value: boolean); +begin + if InsertMode then + FModifiedBytes.Size := 0; + + if not Value then + if FModifiedBytes.Size <= DataPos then + Exit; + + FModifiedBytes[DataPos] := Value; +end; + +procedure TCustomMPHexEditor.MoveFileMem(const aFrom, aTo, aCount: integer); +begin + FDataStorage.Move(aFrom, aTo, aCount); +end; + +function TCustomMPHexEditor.GetCursorPos: integer; +begin + Result := GetPosAtCursor(Col, Row); + if Result < 0 then + Result := 0; + + if Result > Max(0, DataSize - 1) then + Result := Max(0, DataSize - 1) +end; + +function TCustomMPHexEditor.GetSelCount: integer; +begin + if FSelPosition = -1 then + Result := 0 + else + Result := Max(FSelStart, FSelEnd) - Min(FSelStart, FSelEnd) + 1; +end; + +procedure TCustomMPHexEditor.SetReadOnlyFile(const Value: boolean); +begin + if Value and (not FIsFileReadonly) then + begin + FIsFileReadonly := True; + end; +end; + +function TCustomMPHexEditor.BufferFromFile(const aPos: integer; var aCount: + integer): PChar; +begin + if (aPos < 0) or (aPos >= DataSize) then + raise EMPHexEditor.Create(ERR_INVALID_BUFFERFROMFILE) + else + begin + if (aPos + aCount) > DataSize then + aCount := (DataSize - aPos) + 1; + + GetMem(Result, aCount); + try + FDataStorage.ReadBufferAt(Result^, aPos, aCount); + except + try + FreeMem(Result); + except + end; + Result := nil; + aCount := 0; + end; + end; +end; + +procedure TCustomMPHexEditor.WMVScroll(var Msg: TWMVScroll); +begin + inherited; + CheckSetCaret; +end; + +procedure TCustomMPHexEditor.WMHScroll(var Msg: TWMHScroll); +begin + inherited; + CheckSetCaret; +end; + +procedure TCustomMPHexEditor.CreateCaretGlyph; +begin + DestroyCaret(); + FCaretBitmap.Width := FCharWidth; + FCaretBitmap.Height := FCharHeight - 2; + FCaretBitmap.Canvas.Brush.Color := clBlack; + FCaretBitmap.Canvas.FillRect(Rect(0, 0, FCharWidth, FCharHeight - 2)); + FCaretBitmap.Canvas.Brush.Color := clWhite; + case FCaretKind of + ckFull: FCaretBitmap.Canvas.FillRect(Rect(0, 0, FCharWidth, FCharHeight - + 2)); + ckLeft: FCaretBitmap.Canvas.FillRect(Rect(0, 0, 2, FCharHeight - 2)); + ckBottom: FCaretBitmap.Canvas.FillRect(Rect(0, FCharHeight - 4, FCharWidth, + FCharHeight - 2)); + ckAuto: + begin + if FReadOnlyView then + FCaretBitmap.Canvas.FillRect(Rect(0, FCharHeight - 4, FCharWidth, + FCharHeight - 2)) + else + begin + if FInsertModeOn then + FCaretBitmap.Canvas.FillRect(Rect(0, 0, 2, FCharHeight - 2)) + else + FCaretBitmap.Canvas.FillRect(Rect(0, 0, FCharWidth, FCharHeight - + 2)); + end; + end; + end; + CreateCaret(Handle, FCaretBitmap.Handle, 0, 0); + ShowCaret(Handle); +end; + +procedure TCustomMPHexEditor.SetBytesPerColumn(const Value: integer); +begin + if ((Value < 1) or (Value > 256)) or + (FUnicodeCharacters and ((Value mod 2) <> 0)) then + raise EMPHexEditor.Create(ERR_INVALID_BYTESPERCOL) + else if FBytesPerCol <> (Value * 2) then + begin + with FOffsetFormat do + if offCalcColumn in Flags then + _BytesPerUnit := Value; + FBytesPerCol := Value * 2; + AdjustMetrics; + SetRulerString; + Invalidate; + end; +end; + +function TCustomMPHexEditor.GetBytesPerColumn: integer; +begin + Result := FBytesPerCol div 2; +end; + +function TCustomMPHexEditor.PrepareFindReplaceData(StrData: string; const + IgnoreCase, IsText: boolean): string; +var + LWStrTemp: WideString; + LIntLoop: Integer; + lChrTbl: Char; +begin + if Length(StrData) = 0 then + Result := '' + else + begin + if IgnoreCase then + StrData := AnsiLowerCase(StrData); + if IsText and (FTranslation <> tkAsIs) then + begin + UniqueString(StrData); + TranslateBufferFromAnsi(FTranslation, @StrData[1], @StrData[1], + Length(StrData)); + end; + if (not IsText) or (not FUnicodeCharacters) then + Result := StrData + else + begin + // create a unicode string + LWStrTemp := StrData; + if FUnicodeBigEndian then + for LIntLoop := 1 to Length(LWStrTemp) do + SwapWideChar(LWStrTemp[LIntLoop]); + SetLength(Result, Length(LWStrTemp) * 2); + Move(LWStrTemp[1], Result[1], Length(Result)); + end; + + // create compare tables + for LChrTbl := #0 to #255 do + begin + FFindTable[LChrTbl] := LChrTbl; + + FFindTableI[LChrTbl] := LChrTbl; + if FTranslation <> tkAsIs then + TranslateBufferToAnsi(FTranslation, @FFindTableI[LChrTbl], + @FFindTableI[LChrTbl], 1); + CharLowerBuff(@FFindTableI[LChrTbl], 1); + if FTranslation <> tkAsIs then + TranslateBufferFromAnsi(FTranslation, @FFindTableI[LChrTbl], + @FFindTableI[LChrTbl], 1); + end; + end; +end; + +function TCustomMPHexEditor.Find(aBuffer: PChar; aCount: integer; const aStart, + aEnd: integer; const IgnoreCase: boolean): integer; +var + LBoolDummy: Boolean; + LChrCurrent: char; + LIntCurPos, + LIntLoop, + LIntFound, + LIntEnd: integer; + cLoop, + cInc: Cardinal; + LPTblFind: PMPHFindTable; +begin + if Assigned(FOnFind) then + FOnFind(self, aBuffer, aCount, aStart, aEnd, IgnoreCase, #0, Result) + else + begin + Result := -1; + LIntEnd := aEnd; + cLoop := 0; + if LIntEnd >= DataSize then + LIntEnd := DataSize - 1; + + if aCount < 1 then + Exit; + + if aStart + aCount > (LIntEnd + 1) then + Exit; // will never be found, if search-part is smaller than searched data + + if IgnoreCase then + LPTblFind := @FFindTableI + else + LPTblFind := @FFindTable; + + cInc := DataSize div 500; + + WaitCursor; + try + + LIntCurPos := aStart; + LIntLoop := 0; + LIntFound := LIntCurPos + 1; + + repeat + if FFindProgress and Assigned(FOnProgress) then + begin + Inc(cLoop); + // changed in 12-28-2004 to avoid edivbyzero + if (cInc = 0) or ((cLoop mod cInc) = 0) then + FOnProgress(self, pkFind, FFileName, Round((LIntCurpos / DataSize) * + 100), LBoolDummy); + end; + + if LIntCurPos > LIntEnd then + Exit; + + LChrCurrent := LPTblFind^[char(Data[LIntCurPos])]; + + if (LChrCurrent = aBuffer[LIntLoop]) then + begin + if LIntLoop = (aCount - 1) then + begin + Result := LIntCurPos - aCount + 1; + Exit; + end + else + begin + if LIntLoop = 0 then + LIntFound := LIntCurPos + 1; + Inc(LIntCurPos); + Inc(LIntLoop); + end; + end + else + begin + LIntCurPos := LIntFound; + LIntLoop := 0; + LIntFound := LIntCurPos + 1; + end; + until False; + + finally + OldCursor; + end; + end; +end; + +procedure TCustomMPHexEditor.AddSelectionUndo(const AStart, + ACount: integer); +begin + CreateUndo(ufKindSelection, AStart, aCount, 0, ''); +end; + +function TCustomMPHexEditor.FindWithWildcard(aBuffer: PChar; + aCount: integer; const aStart, aEnd: integer; const IgnoreCase: boolean; + const Wildcard: char): integer; +var + LBoolDummy: boolean; + LChrCurrent: char; + LIntCurPos, + LIntLoop, + LIntFound, + LIntEnd: integer; + bFound: boolean; + cLoop, + cInc: cardinal; + LPTblFind: PMPHFindTable; +begin + if Assigned(FOnWildcardFind) then + FOnWildcardFind(self, aBuffer, aCount, aStart, aEnd, IgnoreCase, Wildcard, + Result) + else + begin + Result := -1; + LIntEnd := aEnd; + cLoop := 0; + if LIntEnd >= DataSize then + LIntEnd := DataSize - 1; + + if aCount < 1 then + Exit; + + if aStart + aCount > (LIntEnd + 1) then + Exit; // will never be found, if search-part is smaller than searched data + + if IgnoreCase then + LPTblFind := @FFindTableI + else + LPTblFind := @FFindTable; + + cInc := DataSize div 500; + + WaitCursor; + try + LIntCurPos := aStart; + LIntLoop := 0; + LIntFound := LIntCurPos + 1; + + repeat + if FFindProgress and Assigned(FOnProgress) then + begin + Inc(cLoop); + // changed in 12-28-2004 to avoid edivbyzero + if (cInc = 0) or ((cLoop mod cInc) = 0) then + FOnProgress(self, pkFind, FFileName, Round((LIntCurpos / DataSize) * + 100), LBoolDummy); + end; + + if LIntCurPos > LIntEnd then + Exit; + + bFound := aBuffer[LIntLoop] = WildCard; + if not bFound then + begin + LChrCurrent := LPTblFind^[char(Data[LIntCurPos])]; + bFound := (LChrCurrent = aBuffer[LIntLoop]); + end; + + if bFound then + begin + if LIntLoop = (aCount - 1) then + begin + Result := LIntCurPos - aCount + 1; + Exit; + end + else + begin + if LIntLoop = 0 then + LIntFound := LIntCurPos + 1; + Inc(LIntCurPos); + Inc(LIntLoop); + end; + end + else + begin + LIntCurPos := LIntFound; + LIntLoop := 0; + LIntFound := LIntCurPos + 1; + end; + until False; + + finally + OldCursor; + end; + end; +end; + +procedure TCustomMPHexEditor.SetOffsetDisplayWidth; +var + s: string; +begin + if Assigned(FOnGetOffsetText) and (not FOffsetHandler) then + begin + FOffsetHandler := True; + try + FIsMaxOffset := True; + FOnGetOffsetText(self, (RowCount - 3) * FBytesPerRow, s); + finally + FOffsetHandler := False; + end; + FOffsetDisplayWidth := Length(s) + 1; + end + else + begin + with FOffsetFormat do + if offCalcWidth in Flags then + MinWidth := Length(IntToRadix(((RowCount - 3) * FBytesPerRow) div + _BytesPerUnit, Radix)); + + FOffSetDisplayWidth := Length(GetOffsetString((RowCount - 3) * FBytesPerRow)) + + 1; + end; + if FGutterWidth = -1 then + DoSetCellWidth(0, FOffSetDisplayWidth * FCharWidth + 20 + 1) + else + DoSetCellWidth(0, FGutterWidth); +end; + +function TCustomMPHexEditor.Seek(const aOffset, aOrigin: integer): integer; +var + LIntPos: integer; +begin + Result := -1; + LIntPos := GetCursorPos; + case aOrigin of + soFromBeginning: LIntPos := aOffset; + soFromCurrent: LIntPos := GetCursorPos + aOffset; + soFromEnd: LIntPos := DataSize + aOffset - 1; + end; + + if DataSize < 1 then + Exit; + + LIntPos := Min(Max(0, LIntPos), DataSize - 1); + + SelStart := LIntPos; + Result := LIntPos; +end; + +procedure TCustomMPHexEditor.SetSwapNibbles(const Value: boolean); +begin + if integer(Value) <> FSwapNibbles then + begin + FSwapNibbles := integer(Value); + Invalidate; + end; +end; + +function TCustomMPHexEditor.GetSwapNibbles: boolean; +begin + Result := boolean(FSwapNibbles); +end; + +procedure TCustomMPHexEditor.SetColors(const Value: TMPHColors); +begin + FColors.Assign(Value); +end; + +procedure TCustomMPHexEditor.SetCaretKind(const Value: TMPHCaretKind); +begin + if FCaretKind <> Value then + begin + FCaretKind := Value; + if Focused then + begin + CreateCaretGlyph; + IntSetCaretPos(-50, -50, -1); + Invalidate; + end; + end; +end; + +procedure TCustomMPHexEditor.SetFocusFrame(const Value: boolean); +begin + if FFocusFrame <> Value then + begin + FFocusFrame := Value; + Invalidate; + end; +end; + +procedure TCustomMPHexEditor.SetMaskChar(const Value: char); +begin + if FReplaceUnprintableCharsBy <> Value then + begin + FReplaceUnprintableCharsBy := Value; + Invalidate; + end; +end; + +procedure TCustomMPHexEditor.SetAsText(const Value: string); +var + LpszBuffer: PChar; +begin + if DataSize > 0 then + begin + // alles selektieren + SelStart := 0; + SelEnd := DataSize - 1; + end; + // do translation (thanks to philippe chessa) dec 17 98 + GetMem(LpszBuffer, Length(Value)); + try + Move(Value[1], LpszBuffer^, Length(Value)); + TranslateBufferFromANSI(FTranslation, @Value[1], LpszBuffer, Length(Value)); + ReplaceSelection(LpszBuffer, Length(Value)); + finally + FreeMem(LpszBuffer); + end; +end; + +procedure TCustomMPHexEditor.SetAsHex(const Value: string); +var + LpszBuffer: PChar; + LIntAmount: integer; +begin + if DataSize > 0 then + begin + // alles selektieren + SelStart := 0; + SelEnd := DataSize - 1; + end; + + GetMem(LpszBuffer, Length(Value)); + try + ConvertHexToBin(@Value[1], LpszBuffer, Length(Value), SwapNibbles, + LIntAmount); + ReplaceSelection(LpszBuffer, LIntAmount); + finally + FreeMem(LpszBuffer); + end; +end; + +function TCustomMPHexEditor.GetAsText: string; +begin + if DataSize < 1 then + Result := '' + else + begin + SetLength(Result, DataSize); + ReadBuffer(Result[1], 0, DataSize); + end; +end; + +function TCustomMPHexEditor.GetAsHex: string; +begin + Result := FDataStorage.GetAsHex(0, DataSize, SwapNibbles) +end; + +function TCustomMPHexEditor.GetSelectionAsHex: string; +begin + if (DataSize < 1) or (SelCount < 1) then + Result := '' + else + Result := FDataStorage.GetAsHex(Min(SelStart, SelEnd), SelCount, + SwapNibbles); +end; + +function TCustomMPHexEditor.GetInsertMode: boolean; +begin + Result := FInsertModeOn and IsInsertModePossible; +end; + +procedure TCustomMPHexEditor.SetAllowInsertMode(const Value: boolean); +begin + if not Value then + begin + if FInsertModeOn then + InsertMode := False; + end; + FAllowInsertMode := Value; +end; + +procedure TCustomMPHexEditor.SetFixedFileSize(const Value: boolean); +begin + if Value <> FFixedFileSize then + begin + if Value then + InsertMode := False; + FFixedFileSize := Value; + end; +end; + +procedure TCustomMPHexEditor.InternalErase(const KeyWasBackspace: boolean; const + UndoDesc: string = ''); +var + LIntPos: integer; + LIntSavePos: integer; + LIntCount: integer; +begin + LIntPos := GetCursorPos div FBytesPerUnit * FBytesPerUnit; + LIntCount := FBytesPerUnit; + LIntSavePos := LIntPos; + if KeyWasBackspace then + begin // Delete previous byte(s) + if InsertMode and (SelCount = 0) then + begin + LIntPos := GetPosAtCursor(Col, Row); + if (LIntPos = DataSize) and ((DataSize mod FBytesPerUnit) <> 0) then + LIntCount := 1 + else + begin + LIntPos := LIntPos div FBytesPerUnit * FBytesPerUnit; + LIntCount := FBytesPerUnit; + end; + end; + + if LIntPos = 0 then + Exit; // Can't delete at offset -1 + + CreateUndo(ufKindByteRemoved, LIntPos - LIntCount, LIntCount, + 0, UndoDesc); + + InternalDelete(LIntPos - LIntCount, LIntPos, Col, Row); + if LIntSavePos = LIntPos then + Seek(LIntPos - LIntCount, soFromBeginning) // Move caret + else + begin + if (Col + 1) <= GetLastCharCol then + Col := Col + 1; + end; + Changed; + end + else + begin // Delete next byte + if LIntPos >= DataSize then + Exit; // Cant delete at EOF + while (LIntPos + LIntCount) > DataSize do + Dec(LIntCount); + CreateUndo(ufKindByteRemoved, LIntPos, LIntCount, 0, UndoDesc); + InternalDelete(LIntPos, LIntPos + LIntCount, Col, Row); + Changed; + end; +end; + +procedure TCustomMPHexEditor.WMGetDlgCode(var Msg: TWMGetDlgCode); +begin + inherited; + Msg.Result := Msg.Result or DLGC_WANTARROWS or DLGC_WANTCHARS or + DLGC_WANTALLKEYS; + if FWantTabs then + Msg.Result := Msg.Result or DLGC_WANTTAB + else + Msg.Result := Msg.Result and not DLGC_WANTTAB; +end; + +procedure TCustomMPHexEditor.CMFontChanged(var Message: TMessage); +begin + inherited; + if HandleAllocated then + begin + AdjustMetrics; + if Focused then + begin + CreateCaretGlyph; + end; + end; +end; + +procedure TCustomMPHexEditor.SetWantTabs(const Value: boolean); +begin + FWantTabs := Value; +end; + +procedure TCustomMPHexEditor.SetReadOnlyView(const Value: boolean); +begin + FReadOnlyView := Value; + + if (FCaretKind = ckAuto) and Focused then + CreateCaretGlyph; +end; + +procedure TCustomMPHexEditor.SetHideSelection(const Value: boolean); +begin + if FHideSelection <> Value then + begin + FHideSelection := Value; + if (not Focused) and (GetSelCount > 0) then + Invalidate; + end; +end; + +procedure TCustomMPHexEditor.SetGraySelectionIfNotFocused(const Value: boolean); +begin + if FGraySelOnLostFocus <> Value then + begin + FGraySelOnLostFocus := Value; + if (not Focused) and (GetSelCount > 0) and (not FHideSelection) then + Invalidate; + end; +end; + +function TCustomMPHexEditor.CalcColCount: integer; +begin + if FUnicodeCharacters then + Result := (FBytesPerRow * 2) + (FBytesPerRow div 2) + 1 + GRID_FIXED + else + Result := FBytesPerRow * 3 + 1 + GRID_FIXED; +end; + +function TCustomMPHexEditor.GetLastCharCol: integer; +begin + Result := ColCount - 1; +end; + +function TCustomMPHexEditor.GetTopLeftPosition(var oInCharField: boolean): + integer; +begin + Result := GetPosAtCursor(Max(LeftCol, GRID_FIXED), TopRow); + oInCharField := InCharField; +end; + +procedure TCustomMPHexEditor.SetTopLeftPosition(const aPosition: integer; const + aInCharField: boolean); +begin + with GetCursorAtPos(aPosition, aInCharField) do + begin + TopRow := y; + LeftCol := x; + end; +end; + +function TCustomMPHexEditor.GetPropColCount: integer; +begin + Result := inherited ColCount; +end; + +function TCustomMPHexEditor.GetPropRowCount: integer; +begin + Result := inherited RowCount; +end; + +function TCustomMPHexEditor.ShowDragCell(const X, Y: integer): integer; +var + LRctCell: TRect; + LIntDragPos, + LIntMouseX, + LIntMouseY: integer; +begin + with MouseCoord(X, Y) do + begin + LIntMouseX := X; + LIntMouseY := Y; + if X < GRID_FIXED then + X := GRID_FIXED; + if Y >= RowCount then + Y := RowCount - 1; + if Y < GRID_FIXED then + Y := GRID_FIXED; + LIntDragPos := GetPosAtCursor(X, Y) + end; + + if LIntDragPos < 0 then + LIntDragPos := 0; + if LIntDragPos > DataSize then + LIntDragPos := DataSize; + if IsSelected(LIntDragPos) then + LIntDragPos := Min(SelStart, SelEnd); + CheckUnit(LIntDragPos); + Result := LIntDragPos; + FShowDrag := True; + + if (LIntMouseY <= TopRow) and (LIntMouseY > GRID_FIXED) then + begin + // nach oben scrollen + TopRow := TopRow - 1; + end + else if (LIntMouseY >= (TopRow + VisibleRowCount - 1)) and (LIntMouseY < + Pred(RowCount)) then + begin + // nach unten scrollen + TopRow := TopRow + 1; + end; + + if (LIntMouseX <= LeftCol) and (LIntMouseX > GRID_FIXED) then + begin + // nach links scrollen + LeftCol := LeftCol - 1; + end + else if (LIntMouseX >= (LeftCol + VisibleColCount - 1)) and + (LIntMouseX < GetLastCharCol) then + begin + // nach unten scrollen + LeftCol := LeftCol + 1; + end; + + with GetCursorAtPos(LIntDragPos, FPosInCharField) do + begin + if (x = FDropCol) and (y = FDropRow) then + Exit; + LRctCell := CellRect(FDropCol, FDropRow); + FDropCol := x; + FDropRow := y; + InvalidateRect(Handle, @LRctCell, True); + LRctCell := CellRect(X, Y); + InvalidateRect(Handle, @LRctCell, True); + end; +end; + +procedure TCustomMPHexEditor.HideDragCell; +begin + FShowDrag := False; + Invalidate; +end; + +procedure TCustomMPHexEditor.CombineUndo(const aCount: integer; const sDesc: + string = ''); +begin + CreateUndo(ufKindCombined, 0, aCount, 0, sDesc); +end; + +function TCustomMPHexEditor.GetMouseOverSelection: boolean; +var + LPntMouse: TPoint; +begin + Windows.GetCursorPos(LPntMouse); + LPntMouse := ScreenToClient(LPntMouse); + Result := CursorOverSelection(LPntMouse.x, LPntMouse.y); +end; + +function TCustomMPHexEditor.CursorOverSelection(const X, Y: integer): boolean; +var + LIntPos: integer; + LBoolInCharField: boolean; +begin + Result := False; + if SelCount * DataSize = 0 then + Exit; + + LBoolInCharField := FPosInCharField; + with MouseCoord(x, y) do + begin + if (x < GRID_FIXED) or (y < GRID_FIXED) then + Exit; + + LIntPos := GetPosAtCursor(X, Y); + FPosInCharField:=(LBoolInCharField); + if (LIntPos < 0) or (LIntPos >= DataSize) then + Exit; + end; + + Result := IsSelected(LIntPos); +end; + +function TCustomMPHexEditor.MouseOverFixed(const X, Y: integer): boolean; +begin + with MouseCoord(x, y) do + Result := (x < GRID_FIXED) or (y < GRID_FIXED); +end; + +procedure TCustomMPHexEditor.MouseMove(Shift: TShiftState; X, Y: integer); +var + LgrcCoords: TGridCoord; +begin + if Shift = [ssLeft] then + LgrcCoords := CheckMouseCoord(X, Y); + + inherited MouseMove(Shift, x, y); + + if FMouseUpCanResetSel then + begin + FMouseUpCanResetSel := (LgrcCoords.x = FMouseDownCol) and + (LgrcCoords.y = FMouseDownRow); + end; + + if (Shift = []) and (CursorOverSelection(X, Y) or MouseOverFixed(X, Y)) then + Cursor := crArrow + else + Cursor := crIBeam; +end; + +procedure TCustomMPHexEditor.WMTimer(var Msg: TWMTimer); +var + LPtMouse: TPoint; + LgrcCoord: TGridCoord; +begin + if FGridState <> gsSelecting then + Exit; + Windows.GetCursorPos(LPtMouse); + LPtMouse := ScreenToClient(LPtMouse); + LgrcCoord := CheckMouseCoord(LPtMouse.X, LPtMouse.Y); + if (LGrcCoord.X <> -1) and (LGrcCoord.Y <> -1) then + inherited; +end; + +function TCustomMPHexEditor.CheckMouseCoord(var X, Y: integer): TGridCoord; +var + LRctCell: TRect; +begin + Result := MouseCoord(X, Y); + if FInsertModeOn then + begin + // use the following cell if the mouse is over the second half of the cell + LRctCell := CellRect(Result.X, Result.Y); + if (LRctCell.Left + (FCharWidth div 2)) <= X then + begin + if not (Result.X in [GetLastCharCol, FBytesPerRowDup + GRID_FIXED - 1]) then + begin + X := LRctCell.Right+1; + Inc(Result.X); + LRctCell := CellRect(Result.X, Result.Y); + end; + end; + if (Result.X = GetLastCharCol) then + begin + if (X - LRctCell.Left) > (FCharWidth div 2) then + begin + Y := Y + RowHeight; + Result.Y := Result.Y + 1; + Result.X := FBytesPerRowDup + 1 + GRID_FIXED; + X := CellRect(Result.X, Result.Y - 1).Left; + //Dec(X, FCharWidth * FBytesPerRow); + end; + end + else if Result.X = (FBytesPerRowDup + GRID_FIXED - 1) then + begin + if (X - LRctCell.Left) > (FCharWidth div 2) then + begin + Y := Y + RowHeight; + Result.Y := Result.Y + 1; + Result.X := GRID_FIXED; + X := CellRect(Result.X, Result.Y - 1).Left; + //Dec(X, FCharWidth * FBytesPerRow); + end; + end; + end; +end; + +procedure TCustomMPHexEditor.MouseUP(Button: TMouseButton; Shift: TShiftState; + X, Y: integer); +begin + CheckMouseCoord(X, Y); + inherited; + if FMouseUpCanResetSel then + begin + FMouseUpCanResetSel := False; + ResetSelection(True); + with MouseCoord(x, y) do + MoveColRow(x, y, True, True); + end; + if FShowDrag then + HideDragCell; +end; + +procedure TCustomMPHexEditor.AdjustBookmarks(const From, Offset: integer); +var + LIntLoop: integer; + LBoolChanged: boolean; +begin + LBoolChanged := False; + if From >= 0 then + for LIntLoop := 0 to 9 do + with FBookmarks[LIntLoop] do + if mPosition >= From then + begin + LBoolChanged := True; + Inc(mPosition, Offset); + if mPosition > DataSize then + mPosition := -1; + end; + if LBoolChanged then + BookMarkChanged; +end; + +procedure TCustomMPHexEditor.IntSetCaretPos(const X, Y, aCol: integer); +begin + if Focused then + begin + if aCol <> -1 then + begin + FPosInCharField := (aCol > (GRID_FIXED + FBytesPerRowDup)); + if FLastPosInCharField <> FPosInCharField then + begin + FLastPosInCharField := FPosInCharField; + Invalidate; + end; + end; + SetCaretPos(X, Y); + end; +end; + +procedure TCustomMPHexEditor.TruncMaxPosition(var DataPos: integer); +begin + if DataPos >= DataSize then + begin + DataPos := DataSize - 1; + if InsertMode then + DataPos := DataSize; + end; +end; + +function TCustomMPHexEditor.GetCurrentValue: integer; +var + LIntPos: integer; +begin + Result := -1; + LIntPos := GetPosAtCursor(Col, Row); + if (LIntPos >= DataSize) or (LIntPos < 0) then + Exit; + Result := Data[LIntPos] +end; + +procedure TCustomMPHexEditor.SetInsertMode(const Value: boolean); +var + LIntPos: integer; +begin + if Value = FInsertModeOn then + Exit; + if IsInsertModePossible then + begin + FInsertModeOn := Value; + if (FCaretKind = ckAuto) and Focused then + CreateCaretGlyph; + if DataSize < 1 then + Exit; + if not FInsertModeOn then + begin + if ((DataSize mod FBytesPerRow) = 0) and (DataSize > 0) then + RowCount := RowCount - 1; + LIntPos := GetPosAtCursor(Col, Row); + if LIntPos = DataSize then + SelStart := DataSize - 1; + end + else + begin + if ((DataSize mod FBytesPerRow) = 0) and (DataSize > 0) then + RowCount := RowCount + 1; + end; + FModifiedBytes.Size := 0; + Invalidate; + end; +end; + +function TCustomMPHexEditor.GetModified: boolean; +begin + Result := FModified and ((DataSize > 0) or FileExists(FileName)); +end; + +procedure TCustomMPHexEditor.SetSelection(DataPos, StartPos, EndPos: + integer); +begin + //CheckSelectUnit(StartPos, EndPos); + FSelEnd := Max(-1, Min(EndPos, DataSize - 1)); + FSelPosition := Max(-1, Min(DataPos, DataSize - 1)); + FSelStart := Max(-1, Min(StartPos, DataSize - 1)); +end; + +procedure TCustomMPHexEditor.Resize; +begin + PostMessage(Handle, CM_INTUPDATECARET, 7, 7); + inherited; +end; + +procedure TCustomMPHexEditor.WrongKey; +begin + if Assigned(FOnInvalidKey) then + FOnInvalidKey(self); +end; + +procedure TCustomMPHexEditor.TopLeftChanged; +begin + CheckSetCaret; + if Assigned(FOnTopLeftChanged) then + FOnTopLeftChanged(self); +end; + +function TCustomMPHexEditor.GetOffsetString(const Position: cardinal): string; +begin + Result := ''; + if Assigned(FOnGetOffsetText) and (not FOffsetHandler) then + begin + FOffsetHandler := True; + try + FIsMaxOffset := False; + FOnGetOffsetText(self, Position, Result) + finally + FOffsetHandler := False; + end; + end + else + begin + with FOffsetFormat do + begin + if Format <> '' then + begin + if (MinWidth <> 0) or (Position <> 0) then + begin + if FHexLowercase then + Result := LowerCase(IntToRadixLen(Position div _BytesPerUnit, Radix, + MinWidth)) + else + Result := Uppercase(IntToRadixLen(Position div _BytesPerUnit, Radix, + MinWidth)); + end; + Result := Prefix + Result + Suffix; + end; + end; + end; +end; + +function TCustomMPHexEditor.GetAnyOffsetString(const Position: integer): string; +begin + if FOffsetFormat.Format = '' then + Result := IntToRadix(Position, 16) + else + Result := GetOffsetString(Position); +end; + +function TCustomMPHexEditor.RowHeight: integer; +begin + Result := DefaultRowHeight; +end; + +function TCustomMPHexEditor.GetBookmark(Index: byte): TMPHBookmark; +begin + if Index > 9 then + raise EMPHexEditor.Create(ERR_INVALID_BOOKMARK); + + Result := FBookmarks[Index]; +end; + +procedure TCustomMPHexEditor.SetBookmark(Index: byte; const Value: + TMPHBookmark); +begin + SetBookmarkVals(Index, Value.mPosition, Value.mInCharField); +end; + +procedure TCustomMPHexEditor.SetBookmarkVals(const Index: byte; const Position: + integer; const InCharField: boolean); +begin + if Index > 9 then + raise EMPHexEditor.Create(ERR_INVALID_BOOKMARK); + + if (FBookmarks[Index].mPosition <> Position) or + (FBookmarks[Index].mInCharField <> InCharField) then + begin + FBookmarks[Index].mPosition := Position; + FBookmarks[Index].mInCharField := InCharField; + Invalidate; + end + else + begin + FBookmarks[Index].mPosition := -1; + FBookmarks[Index].mInCharField := InCharField; + Invalidate; + end; + BookmarkChanged; +end; + +{.$DEFINE TESTCOLOR}// check for unneeded drawings + +type + TestColor = TColor; + +procedure TCustomMPHexEditor.Paint; +type + TKindOfCell = (kocData, kocRuler, kocOffset, kocEmpty); +var + DrawInfo: TGridDrawInfo; + LIntCurCol, LIntCurRow: longint; + LRctWhere: TRect; + LBoolOddCol: boolean; + LBoolChanged: boolean; + LIntDataPos, LIntDataSize: integer; + LWStrOutput: WideString; + LColTextColor, LColTextBackColor, LColBackColor: TColor; + LIntPenWidthSave: integer; + LrecSize: TSize; + + LBoolDraw: Boolean; + + LBoolFocused: boolean; + LRect2: TRect; + LIntLastCol: integer; + + // get the width of a wide text + function GetTextWidthW: Integer; + begin + GetTextExtentPoint32W(Canvas.Handle, PWideChar(LWStrOutput), + Length(LWStrOutput), LrecSize); + Result := LRecSize.cx; + end; + + // render an offset/ruler/fixed cell + procedure _TextOut; + begin + with Canvas, LRctWhere do + begin + Brush.Color := TestColor(LColBackColor); + Font.Color := LColTextColor; + SetBKColor(Handle, ColorToRGB(TestColor(LColTextBackColor))); + LRect2 := LRctWhere;//Rect(Left, Top, Left + FCharWidth, Bottom); + LRect2.Right := Left + FCharWidth; + //SetTextColor(Handle, ColorToRGB(LColTextColor)); + + LBoolDraw:= True; + if Assigned(FOnDrawCell) then + begin + if LIntCurCol = 0 then + FOnDrawCell(self, Canvas, LIntCurCol, LIntCurRow, LWStrOutput, + LRctWhere, LBoolDraw) + else + FOnDrawCell(self, Canvas, LIntCurCol, LIntCurRow, LWStrOutput, LRect2, + LBoolDraw) + end; + if LBoolDraw then + begin + + FillRect(LRctWhere); + if LIntCurCol = 0 then + ExtTextOutW(Handle, Right - GetTextWidthW - 4, Top, + ETO_CLIPPED or ETO_OPAQUE, @LRctWhere, PWideChar(LWStrOutput), + Length(LWStrOutput), nil) + else + ExtTextOutW(Handle, Left, Top, + ETO_CLIPPED or ETO_OPAQUE, @LRect2, PWideChar(LWStrOutput), + Length(LWStrOutput), nil); + + end + else + LBoolDraw := True; + + end; + end; + + // render a data cell + procedure _TextOutData; + begin + with Canvas, LRctWhere do + begin + Brush.Color := TestColor(LColBackColor); + Font.Color := LColTextColor; + SetBKColor(Handle, ColorToRGB(TestColor(LColTextBackColor))); + LRect2 := LRctWhere;//Rect(Left, Top, Left + FCharWidth, Bottom); + LRect2.Right := Left + FCharWidth; + //SetTextColor(Handle, ColorToRGB(LColTextColor)); + + LBoolDraw:= True; + if Assigned(FOnDrawCell) then + begin + FOnDrawCell(self, Canvas, LIntCurCol, LIntCurRow, LWStrOutput, LRect2, + LBoolDraw) + end; + if LBoolDraw then + begin + + FillRect(LRctWhere); + ExtTextOutW(Handle, Left, Top, + ETO_CLIPPED or ETO_OPAQUE, @LRect2, PWideChar(LWStrOutput), + Length(LWStrOutput), nil); + + end + else + LBoolDraw := True; + + if FShowDrag and (LIntCurCol = FDropCol) and (LIntCurRow = FDropRow) then + begin + LIntPenWidthSave := Pen.Width; + try + Pen.Width := 2; + Pen.Color := LColTextColor; + MoveTo(Left + 1, Top + 1); + LineTo(Left + 1, Bottom - 1) + finally + Pen.Width := LIntPenWidthSave; + end; + end + end; + end; + + // draw an offset cell + procedure DrawOffsetCell; + var + LIntLoop: integer; + begin + if (LIntCurRow = Row) then + begin + LColBackColor := FColors.CurrentOffsetBackground; + LColTextColor := FColors.CurrentOffset; + end + else + begin + LColBackColor := FColors.OffsetBackground; + LColTextColor := Colors.Offset; + end; + LColTextBackColor := LColBackColor; + + (* text ausgeben *) + LWStrOutput := GetOffsetString((LIntCurRow - GRID_FIXED) * FBytesPerRow); + _TextOut; + + (* auf bookmark prüfen *) + for LIntLoop := 0 to 9 do + with FBookmarks[lIntLoop] do + if (mPosition > -1) and ((mPosition div FBytesPerRow) = (LIntCurRow - + GRID_FIXED)) then + with LRctWhere do + FBookmarkImageList.Draw(Canvas, Left + 3, ((Bottom - Top - 10) div 2) + + Top, lIntLoop + (10 * integer(mInCharField))); + end; + + // draw a ruler cell + procedure DrawRulerCell; + begin + if LIntCurCol <> (GRID_FIXED + FBytesPerRowDup) then + begin + if LIntCurCol > (GRID_FIXED + FBytesPerRowDup) then + begin + LIntDataPos := (LIntCurCol - (GRID_FIXED + FBytesPerRowDup + 1)); + LWStrOutput := FRulerCharString[LIntDataPos + 1]; + end + else + LWStrOutput := FRulerString[LIntCurCol - GRID_FIXED + 1]; + end + else + LWStrOutput := ' '; + LColBackColor := FColors.OffsetBackGround; + if Col = LIntCurCol then + begin + LColTextBackColor := FColors.CurrentOffsetBackGround; + LColTextColor := FColors.CurrentOffset; + end + else + begin + LColTextBackColor := FColors.OffsetBackGround; + LColTextColor := FColors.Offset; + end; + _TextOut; + end; + + // draw a hex/char cell + procedure DrawDataCell(const bIsCharCell, bIsCurrentField: boolean); + begin + (*// caret setzen + if (LIntCurRow = Row) and (LIntCurCol = Col) then + IntSetCaretPos(LRctWhere.Left, LRctWhere.Top);*) + + LIntDataPos := GetPosAtCursor(LIntCurCol, LIntCurRow); + FDrawDataPosition := LIntDataPos; + if bIsCurrentField and (LIntCurCol < LIntLastCol) and + (LIntCurCol <> FIntLastHexCol) then + LColBackColor := FColors.FActiveFieldBackground + else + LColBackColor := FColors.FBackground; + + // nicht zeichnen, falls keine daten + if (LIntDataPos < LIntDataSize) then + begin + if not bIsCharCell then + begin // partie hexadecimale + if ((LIntCurCol - GRID_FIXED) mod 2) = FSwapNibbles then + LWStrOutput := FHexChars[Data[LIntDataPos] shr 4] + else + LWStrOutput := FHexChars[Data[LIntDataPos] and 15] + end + else + begin + if FUnicodeCharacters then + begin + SetLength(LWStrOutput, 1); + LWStrOutput[1] := #0; + ReadBuffer(LWStrOutput[1], LIntDataPos, Min(2, LIntDataSize - + LIntDataPos)); + if FUnicodeBigEndian then + SwapWideChar(LWStrOutput[1]); + if (LWStrOutput[1] < #256) and (Char(LWStrOutput[1]) in FMaskedChars) then + LWStrOutput[1] := WideChar(FReplaceUnprintableCharsBy); + end + else + LWStrOutput := TranslateToAnsiChar(Data[LIntDataPos]); + end; + + // testen ob byte geändert + LBoolChanged := (HasChanged(LIntDataPos)) or ((FUnicodeCharacters and + bIsCharCell) and HasChanged(LIntDataPos + 1)); + LBoolOddCol := (((LIntCurCol - GRID_FIXED) div FBytesPerCol) mod 2) = 0; + + if LBoolChanged then + begin + LColTextColor := FColors.FChangedText; + LColTextBackColor := FColors.FChangedBackground; + end + else + begin + if bIsCurrentField then + LColTextBackColor := FColors.FActiveFieldBackground + else + LColTextBackColor := FColors.FBackground; + + if not FPosInCharField then + begin + if LBoolOddCol then + LColTextColor := Colors.FOddColumn + else + LColTextColor := Colors.FEvenColumn; + end + else + LColTextColor := Font.Color; + end; + + if (FSelPosition <> -1) and IsSelected(LIntDataPos) then + begin + + FIsDrawDataSelected := True; + + if (not FHideSelection) or LBoolFocused then + begin + if (LIntCurCol < LIntLastCol) and (LIntCurCol <> FIntLastHexCol) + and (LIntDataPos <> Max(FSelStart, FSelEnd)) then + LColBackColor := Invert(LColBackColor); + LColTextBackColor := Invert(LColTextBackColor); + LColTextColor := Invert(LColTextColor); + + if FGraySelOnLostFocus and (not LBoolFocused) then + begin + LColTextBackColor := FadeToGray(LColTextBackColor); + LColTextColor := FadeToGray(LColTextColor); + end; + end; + end + + else + FIsDrawDataSelected := False +; + + _TextOutData + end; + + // focus frame auf der anderen seite + if (LIntCurRow = Row) and LBoolFocused and (GetOtherFieldColCheck(Col) = + LIntCurCol) then + begin + with LRctWhere do + if FFocusFrame then + Canvas.DrawFocusRect(Rect(Left, Top, Left + FCharWidth, Bottom - 1)) + else + begin + Canvas.Pen.Color := FColors.CursorFrame; + Canvas.Brush.Style := bsClear; + Canvas.Rectangle(Left, Top, Left + FCharWidth, Bottom - 1); + end; + end; + + // possibly draw a mark at the current position when not focused + if FShowPositionIfNotFocused and (LIntCurRow = Row) and (Col = LIntCurCol) + and (not LBoolFocused) then + begin + with LRctWhere do + if FFocusFrame then + Canvas.DrawFocusRect(Rect(Left, Top, Left + FCharWidth, Bottom - 1)) + else + begin + Canvas.Pen.Color := FColors.NonFocusCursorFrame; + Canvas.Brush.Style := bsClear; + Canvas.Rectangle(Left, Top, Left + FCharWidth, Bottom - 1); + end; + end; + + if FDrawGridLines and (LIntCurCol = LIntLastCol) then + with Canvas, LRctWhere do + begin + Pen.Color := FColors.FGrid; + MoveTo(Right - 1, Top); + LineTo(Right - 1, Bottom - 1); + end; + + end; + + // draw + procedure DrawCells(ACol, ARow: longint; StartX, StartY, StopX, StopY: + integer; + Kind: TKindOfCell); + begin + LIntCurRow := ARow; + LRctWhere.Top := StartY; + while (LRctWhere.Top < StopY) and (LIntCurRow < RowCount) do + begin + LIntCurCol := ACol; + LRctWhere.Left := StartX; + LRctWhere.Bottom := LRctWhere.Top + RowHeights[LIntCurRow]; + while (LRctWhere.Left < StopX) and (LIntCurCol <= LIntLastCol) do + begin + LRctWhere.Right := LRctWhere.Left + ColWidths[LIntCurCol]; + if (LRctWhere.Right > LRctWhere.Left) (*and RectVisible(Canvas.Handle, + LRctWhere) slows down, removed *) then + begin + case Kind of + kocData: + begin + if LIntCurCol < (GRID_FIXED + FBytesPerRowDup) then + DrawDataCell(False, not FLastPosInCharField) + else if LIntCurCol > (GRID_FIXED + FBytesPerRowDup) then + DrawDataCell(True, FLastPosInCharField) + else if FDrawGridLines then + with Canvas do + begin + Pen.Color := FColors.FGrid; + MoveTo(LRctWhere.Left, LRctWhere.Top); + LineTo(LRctWhere.Left, LRctWhere.Bottom - 1); + end; + + if FDrawGridLines then + with Canvas do + begin + Pen.Color := FColors.FGrid; + MoveTo(LRctWhere.Left, LRctWhere.Bottom - 1); + LineTo(LRctWhere.Right, LRctWhere.Bottom - 1); + end; + end; + kocEmpty: + begin + FDrawDataPosition := -1; + LColTextBackColor := FColors.OffsetBackGround; + LColTextColor := FColors.Offset; + LWStrOutput := ''; + _TextOut; + end; + kocRuler: + begin + FDrawDataPosition := -1; + DrawRulerCell; + end; + kocOffset: + begin + FDrawDataPosition := -1; + if LIntCurCol = 1 then + begin + if FDrawGridLines then + with Canvas do + begin + Pen.Color := FColors.FGrid; + MoveTo(LRctWhere.Left, LRctWhere.Bottom - 1); + LineTo(LRctWhere.Right, LRctWhere.Bottom - 1); + end; + end + else + DrawOffsetCell; + end; + end; + end; + LRctWhere.Left := LRctWhere.Right; + Inc(LIntCurCol); + end; + LRctWhere.Top := LRctWhere.Bottom; + Inc(LIntCurRow); + end; + end; +var + LIntTop: integer; +begin + +{$IFDEF DELPHI6UP} + if UseRightToLeftAlignment then + ChangeGridOrientation(True); +{$ENDIF} + + CalcDrawInfo(DrawInfo); + LBoolFocused := Focused; + LIntDataSize := DataSize; + LIntLastCol := GetLastCharCol; + with DrawInfo do + begin + if FShowRuler then + begin + // oben links, fixed + DrawCells(0, 0, 0, 0, Horz.FixedBoundary, Vert.FixedBoundary, kocEmpty); + // oben, fixed + DrawCells(LeftCol, 0, Horz.FixedBoundary, 0, Horz.GridBoundary, + Vert.FixedBoundary, kocRuler); + end; + // links, fixed + DrawCells(0, TopRow, 0, Vert.FixedBoundary, Horz.FixedBoundary, + Vert.GridBoundary, kocOffset); + // daten + DrawCells(LeftCol, TopRow, Horz.FixedBoundary, Vert.FixedBoundary, + Horz.GridBoundary, Vert.GridBoundary, kocData); + + // paint unoccupied space on the right + if Horz.GridBoundary < Horz.GridExtent then + begin + Canvas.Brush.Color := TestColor(Color); + Canvas.FillRect(Rect(Horz.GridBoundary, 0, Horz.GridExtent, + Vert.GridBoundary)); + + // fixed (ruler) + Canvas.Brush.Color := TestColor(FColors.OffsetBackGround); + Canvas.FillRect(Rect(Horz.GridBoundary, 0, Horz.GridExtent, RowHeights[0] + + RowHeights[1])); + end; + + // paint unoccupied space on bottom + if Vert.GridBoundary < Vert.GridExtent then + begin + // hex + chars + Canvas.Brush.Color := TestColor(Color); + Canvas.FillRect(Rect(ColWidths[0] + 1, Vert.GridBoundary, Horz.GridExtent, + Vert.GridExtent)); + + // fixed (position gutter) + Canvas.Brush.Color := TestColor(FColors.OffsetBackGround); + Canvas.FillRect(Rect(0, Vert.GridBoundary, ColWidths[0], + Vert.GridExtent)); + end; + + LIntTop := RowHeights[0] + RowHeights[1]; + + // draw bevel on the right of the offset gutter + if (ColWidths[0] <> 0) then + begin + if FDrawGutter3D then + begin + Canvas.MoveTo(ColWidths[0], LIntTop); + Canvas.Pen.Color := TestColor(clBtnShadow); + Canvas.LineTo(ColWidths[0], Vert.GridExtent); + Canvas.MoveTo(ColWidths[0] - 1, LIntTop); + Canvas.Pen.Color := TestColor(clBtnHighlight); + Canvas.LineTo(ColWidths[0] - 1, Vert.GridExtent); + end + else if FDrawGridLines then + begin + Canvas.MoveTo(ColWidths[0] - 1, LIntTop); + Canvas.Pen.Color := TestColor(FColors.Grid); + Canvas.LineTo(ColWidths[0] - 1, Vert.GridExtent); + end; + end; + + if (FShowRuler) then + begin + if FDrawGutter3D then + begin + Canvas.MoveTo(ColWidths[0] - 1, LIntTop - 1); + Canvas.Pen.Color := TestColor(clBtnShadow); + Canvas.LineTo(Horz.GridExtent, LIntTop - 1); + Canvas.MoveTo(ColWidths[0] - 1, LIntTop - 2); + Canvas.Pen.Color := TestColor(clBtnHighlight); + Canvas.LineTo(Horz.GridExtent, LIntTop - 2); + end + else if FDrawGridLines then + begin + Canvas.MoveTo(ColWidths[0] - 1, LIntTop - 1); + Canvas.Pen.Color := TestColor(FColors.Grid); + Canvas.LineTo(Horz.GridExtent, LIntTop - 1); + end; + end; + end; + +{$IFDEF DELPHI6UP} + if UseRightToLeftAlignment then + ChangeGridOrientation(False); +{$ENDIF} +end; + +procedure TCustomMPHexEditor.SetSelectionAsHex(const s: string); +var + LStrData: string; + LIntAmount: integer; +begin + if s <> '' then + begin + SetLength(LStrData, Length(s)); + ConvertHexToBin(@s[1], @LStrData[1], Length(s), SwapNibbles, LIntAmount); + SetLength(LStrData, LIntAmount); + SetSelectionAsText(LStrData); + end; +end; + +function TCustomMPHexEditor.GetSelectionAsText: string; +begin + if (DataSize < 1) or (SelCount < 1) then + Result := '' + else + begin + SetLength(Result, SelCount); + FDataStorage.ReadBufferAt(Result[1], Min(SelStart, SelEnd), SelCount); + end; +end; + +procedure TCustomMPHexEditor.SetSelectionAsText(const s: string); +begin + if s <> '' then + ReplaceSelection(@s[1], Length(s)); +end; + +procedure TCustomMPHexEditor.SetDrawGridLines(const Value: boolean); +begin + if Value <> FDrawGridLines then + begin + FDrawGridLines := Value; + Invalidate; + end; +end; + +function TCustomMPHexEditor.UndoBeginUpdate: integer; +begin + Result := FUndoStorage.BeginUpdate; +end; + +function TCustomMPHexEditor.UndoEndUpdate: integer; +begin + Result := FUndoStorage.EndUpdate; +end; + +function TCustomMPHexEditor.Undo: boolean; +begin + Result := FUndoStorage.Undo; +end; + +function TCustomMPHexEditor.Redo: boolean; +begin + Result := FUndoStorage.Redo; +end; + +procedure TCustomMPHexEditor.SetGutterWidth(const Value: integer); +begin + if FGutterWidth <> Value then + begin + FGutterWidth := Value; + SetOffsetDisplayWidth; + Invalidate; + end; +end; + +procedure TCustomMPHexEditor.BookmarkBitmapChanged(Sender: TObject); +var + LRctBox: TRect; +begin + // spalte 1 invalidieren + FBookmarkImageList.Clear; + FBookmarkImageList.AddMasked(FBookmarkBitmap, FBookmarkBitmap.Canvas.Pixels[0, + 0]); + if HandleAllocated then + begin + LRctBox := BoxRect(0, TopRow, 0, TopRow + VisibleRowCount); + InvalidateRect(Handle, @LRctBox, False); + end; +end; + +procedure TCustomMPHexEditor.SetBookmarkBitmap(const Value: TBitmap); +begin + if Value = nil then + FBookmarkBitmap.LoadFromResourceName(HINSTANCE, 'BOOKMARKICONS') + else + begin + if (Value.Width <> 200) or (Value.Height <> 10) then + raise EMPHexEditor.Create(ERR_INVALID_BOOKMARKBMP); + FBookmarkBitmap.Assign(Value); + end; + FHasCustomBMP := Value <> nil; +end; + +procedure TCustomMPHexEditor.SelectAll; +var + LgrcPosition: TGridCoord; +begin + if DataSize > 0 then + begin + // position auf ende stzen + if (not InsertMode) then + LgrcPosition := GetCursorAtPos(DataSize - 1, InCharField) + else + LgrcPosition := GetCursorAtPos(DataSize, InCharField); + MoveColRow(LgrcPosition.x, LgrcPosition.y, True, True); + + // alles wählen + NewSelection(0, Pred(DataSize)); + end; +end; + +function TCustomMPHexEditor.GetVersion: string; +begin + Result := MPH_VERSION; +end; + +procedure TCustomMPHexEditor.SetVersion(const Value: string); +begin + // readonly property +end; + +procedure TCustomMPHexEditor.FreeStorage(FreeUndo: boolean = False); +begin + if not FreeUndo then + FDataStorage.Size := 0 + else + FUndoStorage.Size := 0; +end; + +procedure TCustomMPHexEditor.OldCursor; +begin + if Length(FCursorList) > 0 then + begin + Cursor := FCursorList[Pred(Length(FCursorList))]; + SetLength(FCursorList, PRed(Length(FCursorList))); + end; +end; + +procedure TCustomMPHexEditor.WaitCursor; +begin + SetLength(FCursorList, Succ(Length(FCursorList))); + FCursorList[Pred(Length(FCursorList))] := Cursor; + Cursor := crHourGlass; +end; + +function TCustomMPHexEditor.HasCustomBookmarkBitmap: boolean; +begin + Result := FHasCustomBMP; +end; + +procedure TCustomMPHexEditor.PrepareOverwriteDiskFile; +begin + if FIsFileReadonly then + raise EFOpenError.CreateFmt(ERR_FILE_READONLY, [FileName]); +end; + +procedure TCustomMPHexEditor.Changed; +begin + if Assigned(FOnChange) then + FOnChange(self); + SelectionChanged; +end; + +procedure TCustomMPHexEditor.SetDrawGutter3D(const Value: boolean); +begin + if FDrawGutter3D <> Value then + begin + FDrawGutter3D := Value; + Repaint; + end; +end; + +procedure TCustomMPHexEditor.SetShowRuler(const Value: boolean); +begin + if (FShowRuler <> Value) or (csLoading in ComponentState) then + begin + FShowRuler := Value; + AdjustMetrics; + end; +end; + +function TCustomMPHexEditor.DisplayEnd: integer; +begin + if DataSize < 1 then + Result := -1 + else + Result := Min((DataSize - 1), (DisplayStart - 1) + (VisibleRowCount * + BytesPerRow)); +end; + +function TCustomMPHexEditor.DisplayStart: integer; +begin + if DataSize < 1 then + Result := -1 + else + Result := GetPosAtCursor(GRID_FIXED, TopRow); +end; + +procedure TCustomMPHexEditor.SetBytesPerUnit(const Value: integer); +begin + if FBytesPerUnit <> Value then + begin + if FUnicodeCharacters and (Value <> 2) then + raise EMPHexEditor.Create(ERR_INVALID_BPU_U); + if not (Value in [1, 2, 4, 8]) then + raise EMPHexEditor.CreateFmt(ERR_INVALID_BPU, [Value]); + FBytesPerUnit := Value; + if FRulerBytesPerUnit = -1 then + FUsedRulerBytesPerUnit := Value; + with FOffsetFormat do + if offBytesPerUnit in Flags then + _BytesPerUnit := FUsedRulerBytesPerUnit; + AdjustMetrics; + SetRulerString; + if (SelCount mod FBytesPerUnit) <> 0 then + ResetSelection(False); + Invalidate; + end; +end; + +procedure TCustomMPHexEditor.SetRulerString; +var + intLoop, intLen: Integer; + sLoop: string; +begin + FRulerString := ''; + intLen := 2 * FUsedRulerBytesPerUnit; + for intLoop := 0 to Pred(FBytesPerRow div FUsedRulerBytesPerUnit) do + begin + sLoop := IntToRadixLen(intLoop, FRulerNumberBase, intLen); + if Length(sLoop) > intLen then + Delete(sLoop, 1, Length(sLoop) - intLen); + FRulerString := FRulerString + sLoop; + end; + if FHexLowerCase then + FRulerString := LowerCase(FRulerString) + else + FRulerString := UpperCase(FRulerString); + FRulerCharString := ''; + if FUnicodeCharacters then + intLen := FUsedRulerBytesPerUnit div 2 + else + intLen := FUsedRulerBytesPerUnit; + for intLoop := 0 to Pred(FBytesPerRow div FUsedRulerBytesPerUnit) do + begin + sLoop := IntToRadix(intLoop, FRulerNumberBase); + if Length(sLoop) > intLen then + Delete(sLoop, 1, Length(sLoop) - intLen) + else + while Length(sLoop) < intLen do + sLoop := ' ' + sLoop; + FRulerCharString := FRulerCharString + sLoop; + end; + if FHexLowerCase then + FRulerCharString := LowerCase(FRulerCharString) + else + FRulerCharString := UpperCase(FRulerCharString); +end; + +procedure TCustomMPHexEditor.CheckSelectUnit(var AStart, AEnd: Integer); +begin + // assure that the selection covers a whole unit + if AStart <= AEnd then + begin + CheckUnit(AStart); + CheckUnit(AEnd); + Inc(AEnd, FBytesPerUnit - 1); + if (AEnd >= DataSize) then + AEnd := Pred(DataSize); + end + else + begin + CheckUnit(AEnd); + CheckUnit(AStart); + Inc(AStart, FBytesPerUnit - 1); + if (AStart >= DataSize) then + AStart := Pred(DataSize); + end; +end; + +// make sure the value is a multiple of FBytesPerUnit + +procedure TCustomMPHexEditor.CheckUnit(var AValue: Integer); +begin + AValue := AValue div FBytesPerUnit * FBytesPerUnit; +end; + +procedure TCustomMPHexEditor.SelectionChanged; +begin + if FSelectionChangedCount = 0 then + PostMessage(Handle, CM_SELECTIONCHANGED, 0, 0); + Inc(FSelectionChangedCount); +end; + +procedure TCustomMPHexEditor.SyncView(Source: TCustomMPHexEditor; + SyncOffset: integer = 0); +var + curPos, SelS, SelE: integer; + coord: TGridCoord; +begin + if (Source.BytesPerRow = BytesPerRow) and (Source.BytesPerColumn = + BytesPerColumn) and (Source.BytesPerUnit = BytesPerUnit) and (Source.DataSize + = DataSize) and (SyncOffset = 0) then + begin + TopRow := Source.TopRow; + LeftCol := Source.LeftCol; + MoveColRow(Source.Col, Source.Row, True, False); + end + else + begin + // get the current view + curPos := Source.GetCursorPos; + coord := Source.GetCursorAtPos(curPos, Source.InCharField); + with Source.CellRect(coord.X, coord.Y) do + if Left + Bottom = 0 then + begin + curPos := Source.GetPositionAtCursor(Source.LeftCol, Source.TopRow) + + SyncOffset; + if curPos >= DataSize then + curPos := Pred(DataSize); + if curPos < 0 then + curPos := 0; + coord := GetCursorAtPos(curPos, Source.InCharField); + LeftCol := coord.X; + TopRow := coord.Y; + end + else + begin + // use this value if visible, left/top otherwise (when wheeling or scrolling) + curPos := curPos + SyncOffset; + if curPos >= DataSize then + curPos := Pred(DataSize); + if curPos < 0 then + curPos := 0; + coord := GetCursorAtPos(curPos, Source.InCharField); + MoveColRow(coord.X, coord.Y, True, True); + end; + end; + if (Source.SelCount = 0) then + begin + if (SelCount <> 0) then + ResetSelection(True) + end + else + begin + SelS := Source.FSelStart + SyncOffset; + SelE := Source.FSelEnd + SyncOffset; + if SelE >= DataSize then + SelE := DataSize - 1; + if SelS >= DataSize then + SelS := DataSize - 1; + if SelE < 0 then + SelE := 0; + if SelS < 0 then + SelS := 0; + NewSelection(SelS, SelE); + end; +end; + +procedure TCustomMPHexEditor.CMSelectionChanged(var Msg: TMessage); +begin + if (FSelectionChangedCount <> 0) and Assigned(FOnSelectionChanged) then + try + FOnSelectionChanged(self); + finally + FSelectionChangedCount := 0; + end; +end; + +procedure TCustomMPHexEditor.SetRulerBytesPerUnit(const Value: integer); +begin + if FRulerBytesPerUnit <> Value then + begin + if (not (Value in [1, 2, 4, 8])) and (Value <> -1) then + raise EMPHexEditor.CreateFmt(ERR_INVALID_RBPU, [Value]); + FRulerBytesPerUnit := Value; + if Value = -1 then + FUsedRulerBytesPerUnit := FBytesPerUnit + else + FUsedRulerBytesPerUnit := Value; + with FOffsetFormat do + if offBytesPerUnit in Flags then + _BytesPerUnit := FUsedRulerBytesPerUnit; + AdjustMetrics; + SetRulerString; + Invalidate; + end; +end; + +procedure TCustomMPHexEditor.SetShowPositionIfNotFocused(const Value: Boolean); +begin + if FShowPositionIfNotFocused <> Value then + begin + FShowPositionIfNotFocused := Value; + Invalidate; + end; +end; + +function TCustomMPHexEditor.GetDataAt(Index: integer): Byte; +begin +{$IFDEF FASTACCESS} + //FDataStorage.CheckBounds(Index+1); +{$R-} + Result := GetFastPointer^[Index]; +{$ELSE} + ReadBuffer(Result, Index, sizeof(Result)); +{$ENDIF} +end; + +procedure TCustomMPHexEditor.SetDataAt(Index: integer; const Value: Byte); +begin +{$IFDEF FASTACCESS} + //FDataStorage.CheckBounds(Index+1); + GetFastPointer^[Index] := Value; +{$ELSE} + WriteBuffer(Value, Index, sizeof(Value)); +{$ENDIF} +end; + +procedure TCustomMPHexEditor.ReadBuffer(var Buffer; const Index, Count: + Integer); +begin +{$IFDEF FASTACCESS} + //FDataStorage.CheckBounds(Index+Count); + Move(GetFastPointer^[Index], Buffer, Count); +{$ELSE} + FDataStorage.ReadBufferAt(Buffer, Index, Count); +{$ENDIF} +end; + +procedure TCustomMPHexEditor.WriteBuffer(const Buffer; const Index, Count: + Integer); +begin +{$IFDEF FASTACCESS} + //FDataStorage.CheckBounds(Index+Count); + Move(Buffer, GetFastPointer^[Index], Count); +{$ELSE} + FDataStorage.WriteBufferAt(Buffer, Index, Count); +{$ENDIF} +end; + +// fire OnBookmarkChanged + +procedure TCustomMPHexEditor.BookmarkChanged; +begin + if Assigned(FOnBookmarkChanged) then + FOnBookmarkChanged(self); +end; + +procedure TCustomMPHexEditor.DoSetCellWidth(const Index: integer; + Value: integer); +begin + ColWidths[Index] := Value; +end; + +// legacy, do not use + +function TCustomMPHexEditor.GetMemory(const Index: Integer): char; +begin + Result := Char(Data[Index]) +end; + +// legacy, do not use + +procedure TCustomMPHexEditor.SetMemory(const Index: integer; const Value: char); +begin + Data[Index] := Ord(Value); +end; + +procedure TCustomMPHexEditor.SetUnicodeCharacters(const Value: Boolean); +begin + if FUnicodeCharacters <> Value then + begin + if Value then + begin + if (BytesPerRow mod 2) <> 0 then + raise EMPHexEditor.Create(ERR_INVALID_BYTESPERLINE); + if (BytesPerColumn mod 2) <> 0 then + raise EMPHexEditor.Create(ERR_INVALID_BYTESPERCOL); + if (DataSize mod 2) <> 0 then + raise EMPHexEditor.Create(ERR_ODD_FILESIZE_UNICODE); + FTranslation := tkAsIs; + end; + FUnicodeCharacters := Value; + ColCount := CalcColCount; + if Value then + BytesPerUnit := 2 + else + BytesPerUnit := 1; + + CalcSizes; + SetRulerString; + Invalidate; + end; +end; + +procedure TCustomMPHexEditor.SetUnicodeBigEndian(const Value: Boolean); +begin + if FUnicodeBigEndian <> Value then + begin + FUnicodeBigEndian := Value; + if FUnicodeCharacters then + Invalidate; + end; +end; + +function TCustomMPHexEditor.GetPositionAtCursor(const ACol, + ARow: integer): integer; +var + LBoolInCharField: Boolean; +begin + LBoolInCharField := FPosInCharField; + try + Result := GetPosAtCursor(ACol, ARow); + finally + FPosInCharField:=(LBoolInCharField); + end; +end; + +function TCustomMPHexEditor.GetIsCharFieldCol( + const ACol: integer): Boolean; +begin + Result := ACol > (GRID_FIXED + FBytesPerRowDup); +end; + +function TCustomMPHexEditor.IsFileSizeFixed: boolean; +begin + if FFixedFileSizeOverride then + Result := False + else + Result := FFixedFileSize; +end; + +function TCustomMPHexEditor.IsInsertModePossible: boolean; +begin + Result := (not IsFileSizeFixed) and FAllowInsertMode and (not FReadOnlyView) +end; + +function TCustomMPHexEditor.Replace(aBuffer: PChar; aPosition, aOldCount, + aNewCount: integer; + const UndoDesc: string = ''; const MoveCursor: Boolean = False): integer; +var + LBoolInCharField: boolean; +begin + // auswahl berechnen + LBoolInCharField := GetInCharField; + if DataSize - APosition < aOldCount then + begin + if aNewCount = aOldCount then + aNewCount := DataSize - APosition; + aOldCount := DataSize - APosition; + end; + if IsFileSizeFixed then + begin + if aOldCount < aNewCount then + aNewCount := aOldCount + else + aOldCount := aNewCount; + end; + + CreateUndo(ufKindReplace, APosition, aNewCount, aOldCount, UndoDesc); + + if not MoveCursor then + FUndoStorage.AddSelection(APosition, aOldCount); + + if aOldCount = aNewCount then + WriteBuffer(aBuffer^, APosition, aOldCount) + else + begin + if aOldCount > 0 then + InternalDelete(APosition, APosition + aOldCount, Col, Row); + if aNewCount > 0 then + InternalInsertBuffer(aBuffer, aNewCount, APosition); + end; + Result := aNewCount; + if FModifiedBytes.Size >= APosition then + FModifiedBytes.Size := Max(0, APosition); + + if MoveCursor then + begin + with GetCursorAtPos(APosition, LBoolInCharField) do + MoveColRow(x, y, True, True); + end; + Invalidate; + Changed; +end; + +function TCustomMPHexEditor.GotoBookmark(const Index: integer): boolean; +var + LIntRow: integer; + LgrcPosition: TGridCoord; +begin + Result := False; + if FBookmarks[Index].mPosition > -1 then + begin + ResetSelection(True); + LIntRow := FBookmarks[Index].mPosition; + if (LIntRow < DataSize) or ((LIntRow = DataSize) and InsertMode) then + begin + LgrcPosition := GetCursorAtPos(LIntRow, FBookmarks[Index].mInCharField); + MoveColRow(LgrcPosition.x, LgrcPosition.y, True, True); + Result := True; + end + else + SetBookmarkVals(Index, -1, False); + end; +end; + +procedure TCustomMPHexEditor.UpdateGetOffsetText; +begin + SetOffsetDisplayWidth; + Invalidate; +end; + +{$IFDEF FASTACCESS} + +function TCustomMPHexEditor.GetFastPointer: PByteArray; +begin + Result := FDataStorage.Memory; +end; +{$ENDIF} + +procedure TCustomMPHexEditor.SeekToEOF; +var + LgrcPosition: TGridCoord; +begin + InCharField; + if (not InsertMode) then + LgrcPosition := GetCursorAtPos(DataSize - 1, FPosInCharField) + else + LgrcPosition := GetCursorAtPos(DataSize, FPosInCharField); + MoveColRow(LgrcPosition.x, LgrcPosition.y, True, True) +end; + +function TCustomMPHexEditor.CanCreateUndo(const aKind: TMPHUndoFlag; const + aCount, + aReplCount: integer): Boolean; +begin + Result := False; + if DataSize > 0 then + Result := True; + + if not Result then + if aKind in [ufKindInsertBuffer, ufKindAppendBuffer, ufKindAllData] then + Result := True; + + // check for NoSizeChange + if IsFileSizeFixed and Result then + if (aKind in [ufKindByteRemoved, ufKindInsertBuffer, ufKindAppendBuffer, + ufKindNibbleInsert, + ufKindNibbleDelete]) or + ((aKind = ufKindReplace) and (aCount <> aReplCount)) then + Result := False; + + if (not Result) and ((aKind = ufKindCombined) and (FUndoStorage.Count >= + aCount)) then + Result := True; + +end; + +procedure TCustomMPHexEditor.SetDataSize(const Value: integer); +var + iPos: Integer; + iSize: integer; +begin + iSize := DataSize; + if Value <> iSize then + begin + iPos := GetCursorPos; + + // new in 12-16-2003: don't allow change of datasize if nosizechange + // and (new datasize <> 0 and old datasize <> 0) + if (Value <> 0) and (iSize <> 0) and IsFileSizeFixed then + raise EMPHexEditor.Create(ERR_FIXED_FILESIZE); + + FFixedFileSizeOverride := True; + try + // new in 12-16-2003: generate undo + if Value < iSize then + // create a 'bytes deleted' undo + CreateUndo(ufKindByteRemoved, Value, DataSize - Value, 0) + else + // create a 'append buffer' undo + CreateUndo(ufKindAppendBuffer, DataSize, Value - DataSize, 0); + FDataStorage.Size := Value; +{$IFDEF FASTACCESS} + if Value > iSize then + // fill the new data block + FillChar(FastPointer^[iSize], Value - iSize, FSetDataSizeFillByte); +{$ENDIF} + FModified := True; + CalcSizes; + if iPos > DataSize then + begin + ResetSelection(True); + if (DataSize = 0) and (not InsertMode) then + begin + with GetCursorAtPos(0, InCharField) do + MoveColRow(X, Y, True, True); + end + else + SeekToEOF; + end; + finally + FFixedFileSizeOverride := False; + end; + end; +end; + +procedure TCustomMPHexEditor.SetBlockSize(const Value: Integer); +begin + if FBlockSize <> Value then + begin + FBlockSize := Value; + AdjustMetrics; + end; +end; + +procedure TCustomMPHexEditor.SetSepCharBlocks(const Value: boolean); +begin + if FSepCharBlocks <> Value then + begin + FSepCharBlocks := Value; + if Value and (FBlockSize > 1) then + AdjustMetrics; + end; +end; + +procedure TCustomMPHexEditor.SetFindProgress(const Value: boolean); +begin + FFindProgress := Value; +end; + +procedure TCustomMPHexEditor.DefineProperties(Filer: TFiler); +begin + inherited; + Filer.DefineProperty('MaskChar', ReadMaskChar, nil, False); + Filer.DefineProperty('MaskChar_AsInteger', ReadMaskChar_I, WriteMaskChar_I, + FReplaceUnprintableCharsBy <> '.'); +end; + +procedure TCustomMPHexEditor.ReadMaskChar(Reader: TReader); +var + s: string; +begin + s := Reader.ReadString; + if Length(s) <> 1 then + FReplaceUnprintableCharsBy := '.' + else + try + FReplaceUnprintableCharsBy := s[1]; + except + FReplaceUnprintableCharsBy := '.'; + end; +end; + +procedure TCustomMPHexEditor.ReadMaskChar_I(Reader: TReader); +begin + try + Byte(FReplaceUnprintableCharsBy) := Reader.ReadInteger; + except + FReplaceUnprintableCharsBy := '.'; + end; +end; + +procedure TCustomMPHexEditor.WriteMaskChar_I(Writer: TWriter); +begin + Writer.WriteInteger(Byte(FReplaceUnprintableCharsBy)); +end; + +function TCustomMPHexEditor.DoMouseWheelDown(Shift: TShiftState; + MousePos: TPoint): boolean; +begin + if Shift <> [] then + Result := inherited DoMouseWheelDown(Shift, MousePos) + else + begin + // scroll down one page + TopRow := Min(Max(GRID_FIXED, RowCount - VisibleRowCount), + TopRow + VisibleRowCount - 1); + CheckSetCaret; + Result := True; + end; +end; + +function TCustomMPHexEditor.DoMouseWheelUp(Shift: TShiftState; + MousePos: TPoint): boolean; +begin + if Shift <> [] then + Result := inherited DoMouseWheelUp(Shift, MousePos) + else + begin + // scroll up one page + TopRow := Max(GRID_FIXED, TopRow - VisibleRowCount + 1); + CheckSetCaret; + Result := True; + end; +end; + +procedure TCustomMPHexEditor.CheckSetCaret; +begin + with CellRect(Col, Row) do + begin + if Left + Bottom = 0 then + IntSetCaretPos(-50, -50,-1) + else + IntSetCaretPos(Left, Top, Col); + end; +end; + +function TCustomMPHexEditor.CanFocus: Boolean; +var + Form: TCustomForm; +begin + Result := {$IFDEF DELPHI5UP}inherited CanFocus{$ELSE}True{$ENDIF}; + if Result and not (csDesigning in ComponentState) then + begin + Form := GetParentForm(Self); + Result := (not Assigned(Form)) or (Form.Enabled and Form.Visible); + end; +end; + +procedure TCustomMPHexEditor.SetRulerNumberBase(const Value: byte); +begin + if FRulerNumberBase <> Value then + begin + // force number that can be represented using '0'-'9','A'-'F' + if not (Value in [2..16]) then + FRulerNumberBase := 16 + else + FRulerNumberBase := Value; + SetRulerString; + if FShowRuler then + Invalidate; + end; +end; + +procedure TCustomMPHexEditor.SetMaskedChars(const Value: TSysCharSet); +begin + if FMaskedChars <> Value then + begin + FMaskedChars := Value; + Invalidate; + end; +end; + +{ TMPHColors } + +procedure TMPHColors.Assign(Source: TPersistent); +begin + if Source is TMPHColors then + begin + Background := TMPHColors(Source).Background; + ChangedText := TMPHColors(Source).ChangedText; + CursorFrame := TMPHColors(Source).CursorFrame; + NonFocusCursorFrame := TMPHColors(Source).NonFocusCursorFrame; + Offset := TMPHColors(Source).Offset; + OddColumn := TMPHColors(Source).OddColumn; + EvenColumn := TMPHColors(Source).EvenColumn; + ChangedBackground := TMPHColors(Source).ChangedBackground; + CurrentOffsetBackground := TMPHColors(Source).CurrentOffsetBackground; + CurrentOffset := TMPHColors(Source).CurrentOffset; + OffsetBackground := TMPHColors(Source).OffsetBackground; + ActiveFieldBackground := TMPHColors(Source).ActiveFieldBackground; + Grid := TMPHColors(Source).Grid; + end; +end; + +constructor TMPHColors.Create(Parent: TControl); +begin + inherited Create; + FBackground := clWindow; + FActiveFieldBackground := clWindow; + FChangedText := clMaroon; + FCursorFrame := clNavy; + FNonFocusCursorFrame := clAqua; + FOffset := clBlack; + FOddColumn := clBlue; + FEvenColumn := clNavy; + FChangedBackground := $00A8FFFF; + FCurrentOffsetBackground := clBtnShadow; + FCurrentOffset := clBtnHighLight; + FOffsetBackground := clBtnFace; + FGrid := clBtnFace; + FParent := Parent; +end; + +procedure TMPHColors.SetBackground(const Value: TColor); +begin + if FBackground <> Value then + begin + FBackground := Value; + if Assigned(fParent) then + begin + TCustomMPHexEditor(FParent).Color := Value; + fParent.Invalidate; + end; + end; +end; + +procedure TMPHColors.SetChangedBackground(const Value: TColor); +begin + if FChangedBackground <> Value then + begin + FChangedBackground := Value; + if Assigned(fParent) then + fParent.Invalidate; + end; +end; + +procedure TMPHColors.SetCurrentOffsetBackground(const Value: TColor); +begin + if FCurrentOffsetBackground <> Value then + begin + FCurrentOffsetBackground := Value; + if Assigned(fParent) then + fParent.Invalidate; + end; +end; + +procedure TMPHColors.SetNonFocusCursorFrame(const Value: TColor); +begin + if FNonFocusCursorFrame <> Value then + begin + FNonFocusCursorFrame := Value; + if Assigned(fParent) then + fParent.Invalidate; + end; +end; + +procedure TMPHColors.SetChangedText(const Value: TColor); +begin + if FChangedText <> Value then + begin + FChangedText := Value; + if Assigned(fParent) then + fParent.Invalidate; + end; +end; + +procedure TMPHColors.SetCursorFrame(const Value: TColor); +begin + if FCursorFrame <> Value then + begin + FCursorFrame := Value; + if Assigned(fParent) then + fParent.Invalidate; + end; +end; + +procedure TMPHColors.SetEvenColumn(const Value: TColor); +begin + if FEvenColumn <> Value then + begin + FEvenColumn := Value; + if Assigned(fParent) then + fParent.Invalidate; + end; +end; + +procedure TMPHColors.SetOddColumn(const Value: TColor); +begin + if FOddColumn <> Value then + begin + FOddColumn := Value; + if Assigned(fParent) then + fParent.Invalidate; + end; +end; + +procedure TMPHColors.SetOffset(const Value: TColor); +begin + if FOffset <> Value then + begin + FOffset := Value; + if Assigned(fParent) then + fParent.Invalidate; + end; +end; + +procedure TMPHColors.SetOffsetBackGround(const Value: TColor); +begin + if FOffsetBackGround <> Value then + begin + FOffsetBackGround := Value; + if Assigned(fParent) then + fParent.Invalidate; + end; +end; + +procedure TMPHColors.SetCurrentOffset(const Value: TColor); +begin + if FCurrentOffset <> Value then + begin + FCurrentOffset := Value; + if Assigned(fParent) then + fParent.Invalidate; + end; +end; + +procedure TMPHColors.SetParent(const Value: TControl); +begin + FParent := Value; + Assign(self); +end; + +procedure TMPHColors.SetGrid(const Value: TColor); +begin + if FGrid <> Value then + begin + FGrid := Value; + if Assigned(fParent) then + fParent.Invalidate; + end; +end; + +procedure TMPHColors.SetActiveFieldBackground(const Value: TColor); +begin + if FActiveFieldBackground <> Value then + begin + FActiveFieldBackground := Value; + if Assigned(fParent) then + fParent.Invalidate; + end; +end; + +{ TMPHUndoStorage } + +type + + // undo storage + + PUndoSelRec = ^TUndoSelRec; + TUndoSelRec = packed record + SelStart, + SelEnd, + SelPos: integer; + end; + +constructor TMPHUndoStorage.Create(AEditor: TCustomMPHexEditor); +begin + inherited Create; + FEditor := AEditor; + FRedoPointer := nil; + FLastUndo := nil; + FLastUndoSize := 0; + Reset; +end; + +destructor TMPHUndoStorage.Destroy; +begin + Reset; + inherited; +end; + +function TMPHUndoStorage.BeginUpdate: integer; +begin + Inc(FUpdateCount); + Result := FUpdateCount; +end; + +function TMPHUndoStorage.CanUndo: boolean; +begin + Result := (FCount > 0) and (FUpdateCount < 1) and (Size > 0); +end; + +procedure TMPHUndoStorage.CreateUndo(aKind: TMPHUndoFlag; APosition, ACount, + AReplaceCount: integer; const SDescription: string); +var + LPurUndoRec: PMPHUndoRec; + + procedure NewFillBuffer(ASize: integer); + var + i: integer; + begin + i := Position; + (*if FEditor.FSelPosition > -1 then + ASize := ASize+sizeof(TUndoSelRec);*) + + Size := Size + sizeof(TMPHUndoRec) + ASize; + LPurUndoRec := PMPHUndoRec(@(PChar(Memory)[i])); + + FillChar(LPurUndoRec^, SizeOf(TMPHUndoRec) + ASize, 0); + with LPurUndoRec^ do + begin + Flags := [aKind]; + CurPos := FEditor.GetPosAtCursor(FEditor.Col, FEditor.Row); + if not FEditor.FPosInCharField then + with FEditor.GetCursorAtPos(CurPos, FEditor.FPosInCharField) do + if (FEditor.Col - x) <> 0 then + Include(Flags, ufFlag2ndByteCol); + if FEditor.FPosInCharField then + Include(Flags, ufFlagInCharField); + if FEditor.FInsertModeOn then + Include(Flags, ufFlagInsertMode); + Pos := aPosition; + Count := aCount; + ReplCount := aReplaceCount; + CurTranslation := FEditor.FTranslation; + if FEditor.UnicodeChars then + Include(Flags, ufFlagIsUnicode); + if FEditor.UnicodeBigEndian then + Include(Flags, ufFlagIsUnicodeBigEndian); + CurBPU := FEditor.BytesPerUnit; + if FEditor.FModified then + Include(Flags, ufFlagModified); + if FEditor.FSelPosition > -1 then + Include(Flags, ufFlagHasSelection); + if SDescription <> '' then + Include(Flags, ufFlagHasDescription); + end; + end; + + procedure DeleteOldestUndoRec; + var + LintRecSize: integer; + begin + begin + if Size < 4 then + begin + Size := 0; + FCount := 0; + end + else + begin + Seek(0, soFromBeginning); + Read(LIntRecSize, sizeof(integer)); + if LIntRecSize < sizeof(TMPHUndoRec) then + begin + Size := 0; + FCount := 0; + end + else + begin + Move(PChar(Memory)[LIntRecSize], Memory^, Size - LIntRecSize); + Size := Size - LIntRecSize; + Dec(FCount); + end; + end; + end; + end; + + procedure UpdateUndoRecord(Length: integer = 0); + var + LRecSelection: TUndoSelRec; + i: integer; + begin + LPurUndoRec^.DataLen := SizeOf(TMPHUndoRec) + Length + 4; + if ufFlagHasSelection in LPurUndoRec^.Flags then + Inc(LPurUndoRec^.DataLen, sizeof(TUndoSelRec)); + if ufFlagHasDescription in LPurUndoRec^.Flags then + Inc(LPurUndoRec^.DataLen, system.Length(SDescription) + sizeof(i)); + + Position := Size; + if ufFlagHasDescription in LPurUndoRec^.Flags then + begin + write(Sdescription[1], system.Length(SDescription)); + i := system.Length(sDescription); + write(i, sizeof(i)); + Length := Length + i + sizeof(i); + end; + + if ufFlagHasSelection in LPurUndoRec^.Flags then + begin + with LRecSelection do + begin + SelStart := FEditor.FSelStart; + SelEnd := FEditor.FSelEnd; + SelPos := FEditor.FSelPosition; + end; + Write(LRecSelection, sizeof(LRecSelection)); + Length := Length + sizeof(LRecSelection); + end; + + Length := SizeOf(TMPHUndoRec) + 4 + Length; + Write(Length, 4); + end; + +var + LPtrBytes: PByteArray; + LSStDesc: shortstring; +begin + if FUpdateCount < 1 then + begin + ResetRedo; + + if sDescription <> '' then + FDescription := sDescription + else + FDescription := STRS_UNDODESC[aKind]; + + while (FEditor.FMaxUndo > 0) and (FCount > 0) and (Size > FEditor.FMaxUndo) + do + DeleteOldestUndoRec; + + Position := Size; + + Inc(FCount); + + case aKind of + ufKindBytesChanged: + begin + NewFillBuffer(aCount - 1); + LPtrBytes := PByteArray(@LPurUndoRec.Buffer); + FEditor.ReadBuffer(LPtrBytes^, aPosition, aCount); + if FEditor.HasChanged(aPosition) then + Include(LPurUndoRec.Flags, ufFlagByte1Changed); + if (aCount = 2) and FEditor.HasChanged(aPosition + 1) then + Include(LPurUndoRec.Flags, ufFlagByte2Changed); + UpdateUndoRecord(aCount - 1); + end; + ufKindByteRemoved: + begin + NewFillBuffer(aCount - 1); + LPtrBytes := PByteArray(@LPurUndoRec.Buffer); + FEditor.ReadBuffer(LPtrBytes^, aPosition, aCount); + FEditor.AdjustBookmarks(aPosition + aCount, -aCount); + UpdateUndoRecord(aCount - 1); + end; + ufKindInsertBuffer: + begin + NewFillBuffer(0); + FEditor.AdjustBookmarks(aPosition, aCount); + UpdateUndoRecord; + end; + ufKindReplace: + begin + NewFillBuffer(aReplaceCount - 1); + LPtrBytes := PByteArray(@LPurUndoRec.Buffer); + FEditor.ReadBuffer(LPtrBytes^, aPosition, aReplaceCount); + FEditor.AdjustBookmarks(aPosition + aCount, aCount - aReplaceCount); + UpdateUndoRecord(aReplaceCount - 1); + end; + ufKindAppendBuffer: + begin + NewFillBuffer(0); + UpdateUndoRecord; + end; + ufKindNibbleInsert: + begin + NewFillBuffer(0); + LPurUndoRec.Buffer := FEditor.Data[aPosition]; + if FEditor.HasChanged(aPosition) then + Include(LPurUndoRec.Flags, ufFlagByte1Changed); + UpdateUndoRecord; + end; + ufKindNibbleDelete: + begin + NewFillBuffer(0); + LPurUndoRec.Buffer := FEditor.Data[aPosition]; + if FEditor.HasChanged(aPosition) then + Include(LPurUndoRec.Flags, ufFlagByte1Changed); + UpdateUndoRecord; + end; + ufKindConvert: + begin + NewFillBuffer(aCount - 1); + LPtrBytes := PByteArray(@LPurUndoRec.Buffer); + FEditor.ReadBuffer(LPtrBytes^, aPosition, aCount); + UpdateUndoRecord(aCount - 1); + end; + ufKindSelection: + begin + NewFillBuffer(0); + LPurUndoRec^.CurPos := APosition; + UpdateUndoRecord; + AddSelection(APosition, ACount); + end; + ufKindAllData: + begin + aCount := FEditor.DataSize; + if aCount = 0 then + NewFillBuffer(0) + else + NewFillBuffer(aCount - 1); + LPtrBytes := PByteArray(@LPurUndoRec.Buffer); + if aCount > 0 then + FEditor.ReadBuffer(LPtrBytes^, 0, aCount); + if aCount = 0 then + UpdateUndoRecord + else + UpdateUndoRecord(aCount - 1); + end; + ufKindCombined: + begin + LSStDesc := sDescription; + NewFillBuffer(Length(LSStDesc)); + LPurUndoRec.Buffer := aCount; + if FEditor.HasChanged(aPosition) then + Include(LPurUndoRec.Flags, ufFlagByte1Changed); + Move(LSStDesc[0], LPurUndoRec^.Buffer, Length(LSStDesc) + 1); + UpdateUndoRecord(Length(LSStDesc)); + end; + end; + end; +end; + +function TMPHUndoStorage.EndUpdate: integer; +begin + Dec(FUpdateCount); + if FUpdateCount < 0 then + FUpdateCount := 0; + Result := FUpdateCount; +end; + +function TMPHUndoStorage.Undo: boolean; + + procedure PopulateUndo(const aBuffer: TMPHUndoRec); + var + LRecSel: TUndoSelRec; + begin + with FEditor.GetCursorAtPos(aBuffer.CurPos, ufFlagInCharField in + aBuffer.Flags) do + begin + if not (ufFlagInCharField in aBuffer.Flags) then + if FEditor.DataSize > 0 then + if ufFlag2ndByteCol in aBuffer.Flags then + x := x + 1; + + FEditor.MoveColRow(x, y, True, True); + end; + FEditor.FModified := ufFlagModified in aBuffer.Flags; + FEditor.InsertMode := (ufFlagInsertMode in aBuffer.Flags); + if ufFlagHasSelection in aBuffer.Flags then + begin + Position := Size - 4 - sizeof(LRecSel); + Read(LRecSel, sizeof(LRecSel)); + with LRecSel do + begin + if SelEnd = -1 then + FEditor.Seek(SelStart, FILE_BEGIN) + else + FEditor.SetSelection(SelPos, SelStart, SelEnd); + end; + end; + FEditor.UnicodeChars := (ufFlagIsUnicode in aBuffer.Flags); + FEditor.UnicodeBigEndian := (ufFlagIsUnicodeBigEndian in aBuffer.Flags); + if not FEditor.UnicodeChars then + FEditor.Translation := aBuffer.CurTranslation + else + FEditor.FTranslation := aBuffer.CurTranslation; + FEditor.BytesPerUnit := aBuffer.CurBPU; + FEditor.Invalidate; + FEditor.Changed; + end; + +var + LEnumUndo: TMPHUndoFlag; + LRecUndo: TMPHUndoRec; + LIntLoop: integer; + s: string; +begin + Result := False; + if not CanUndo then + begin + Reset(False); + Exit; + end; + + if Size >= sizeof(TMPHUndoRec) then + begin + // letzten eintrag lesen + LEnumUndo := ReadUndoRecord(LRecUndo, s); + // redo erstellen + CreateRedo(LRecUndo); + case LEnumUndo of + ufKindBytesChanged: + begin + FEditor.WriteBuffer(PChar(Memory)[Position - 1], LRecUndo.Pos, + LRecUndo.Count); + FEditor.SetChanged(LRecUndo.Pos, ufFlagByte1Changed in + LRecUndo.Flags); + if LRecUndo.Count = 2 then + FEditor.SetChanged(LRecUndo.Pos + 1, ufFlagByte2Changed in + LRecUndo.Flags); + PopulateUndo(LRecUndo); + FEditor.RedrawPos(LRecUndo.Pos, LRecUndo.Pos + LRecUndo.Count - 1); + RemoveLastUndo; + end; + ufKindByteRemoved: + begin + FEditor.InternalInsertBuffer(Pointer(integer(Memory) + Position - 1), + LRecUndo.Count, LRecUndo.Pos); + PopulateUndo(LRecUndo); + FEditor.AdjustBookmarks(LRecUndo.Pos - LRecUndo.Count, + LRecUndo.Count); + if DWORD(FEditor.FModifiedBytes.Size) >= (LRecUndo.Pos) then + FEditor.FModifiedBytes.Size := LRecUndo.Pos; + FEditor.Invalidate; + RemoveLastUndo; + end; + ufKindInsertBuffer: + begin + FEditor.InternalDelete(LRecUndo.Pos, LRecUndo.Pos + LRecUndo.Count, + -1, 0); + PopulateUndo(LRecUndo); + FEditor.AdjustBookmarks(LRecUndo.Pos, -LRecUndo.Count); + if DWORD(FEditor.FModifiedBytes.Size) >= (LRecUndo.Pos) then + FEditor.FModifiedBytes.Size := LRecUndo.Pos; + FEditor.Invalidate; + RemoveLastUndo; + end; + ufKindSelection: + begin + PopulateUndo(LRecUndo); + RemoveLastUndo; + end; + ufKindAllData: + begin + FEditor.FDataStorage.Size := LRecUndo.Count; + FEditor.FDataStorage.WriteBufferAt(Pointer(integer(Memory) + Position + - 1)^, 0, + LRecUndo.Count); + FEditor.CalcSizes; + PopulateUndo(LRecUndo); + RemoveLastUndo; + end; + ufKindReplace: + begin + FEditor.InternalDelete(LRecUndo.Pos, LRecUndo.Pos + LRecUndo.Count, + -1, 0); + FEditor.InternalInsertBuffer(Pointer(integer(Memory) + Position - 1), + LRecUndo.ReplCount, LRecUndo.Pos); + PopulateUndo(LRecUndo); + FEditor.AdjustBookmarks(LRecUndo.Pos + LRecUndo.ReplCount, + LRecUndo.ReplCount - LRecUndo.Count); + if DWORD(FEditor.FModifiedBytes.Size) >= (LRecUndo.Pos) then + // was: + // FEditor.FModifiedBytes.Size := Max(0, LRecUndo.Pos - 1); + // line above might lead to an integer overflow + begin + if LRecUndo.Pos > 0 then + FEditor.FModifiedBytes.Size := LRecUndo.Pos - 1 + else + FEditor.FModifiedBytes.Size := 0; + end; + + FEditor.Invalidate; + RemoveLastUndo; + end; + ufKindAppendBuffer: + begin + FEditor.Col := GRID_FIXED; + FEditor.FDataStorage.Size := LRecUndo.Pos; + FEditor.CalcSizes; + if DWORD(FEditor.FModifiedBytes.Size) >= (LRecUndo.Pos) then + FEditor.FModifiedBytes.Size := LRecUndo.Pos; + PopulateUndo(LRecUndo); + FEditor.Invalidate; + RemoveLastUndo; + end; + ufKindNibbleInsert: + begin + FEditor.InternalDeleteNibble(LRecUndo.Pos, False); + FEditor.Data[LRecUndo.Pos] := LRecUndo.Buffer; + FEditor.SetChanged(LRecUndo.Pos, ufFlagByte1Changed in + LRecUndo.Flags); + PopulateUndo(LRecUndo); + if DWORD(FEditor.FModifiedBytes.Size) >= (LRecUndo.Pos) then + FEditor.FModifiedBytes.Size := LRecUndo.Pos; + FEditor.FDataStorage.Size := FEditor.FDataStorage.Size - 1; + FEditor.CalcSizes; + FEditor.Invalidate; + RemoveLastUndo; + end; + ufKindNibbleDelete: + begin + FEditor.InternalInsertNibble(LRecUndo.Pos, False); + FEditor.Data[LRecUndo.Pos] := LRecUndo.Buffer; + FEditor.SetChanged(LRecUndo.Pos, ufFlagByte1Changed in + LRecUndo.Flags); + PopulateUndo(LRecUndo); + if DWORD(FEditor.FModifiedBytes.Size) >= (LRecUndo.Pos) then + FEditor.FModifiedBytes.Size := LRecUndo.Pos; + FEditor.FDataStorage.Size := FEditor.FDataStorage.Size - 1; + FEditor.CalcSizes; + FEditor.Invalidate; + RemoveLastUndo; + end; + ufKindConvert: + begin + FEditor.WriteBuffer(PChar(Memory)[Position - 1], LRecUndo.Pos, + LRecUndo.Count); + PopulateUndo(LRecUndo); + if DWORD(FEditor.FModifiedBytes.Size) >= (LRecUndo.Pos) then + FEditor.FModifiedBytes.Size := LRecUndo.Pos; + FEditor.Invalidate; + RemoveLastUndo; + end; + ufKindCombined: + begin + LIntLoop := LRecUndo.Count; + RemoveLastUndo; + for LIntLoop := 1 to LIntLoop do + Undo; + ResetRedo; + end; + end; + end + else + Reset; +end; + +procedure TMPHUndoStorage.RemoveLastUndo; +var + LRecUndo: TMPHUndoRec; + LSStDesc: shortstring; + LIntRecOffs: integer; +begin + if Size < sizeof(TMPHUndoRec) then + Reset(False) + else + begin + Position := Size - 4; + Read(LIntRecOffs, 4); + // restore record in case of a redo + Seek(-LIntRecOffs, soFromCurrent); + ReAllocMem(FLastUndo, LIntRecOffs); + Read(FLastUndo^, LIntRecOffs); + FLastUndoSize := LIntRecOffs; + FLastUndoDesc := FDescription; + + // delete last undo record + SetSize(Max(0, Size - LIntRecOffs)); + Dec(FCount); + if Size < sizeof(TMPHUndoRec) then + begin + Reset(False); + end + else + begin + if ReadUndoRecord(LRecUndo, FDescription) <> ufKindCombined then + begin + if FDescription = '' then + FDescription := STRS_UNDODESC[GetUndoKind(LRecUndo.Flags)] + end + else + begin + if LRecUndo.Buffer = 0 then + LSStDesc := '' + else + begin + Read(LSStDesc[1], LRecUndo.Buffer); + LSStDesc[0] := char(LRecUndo.Buffer); + end; + if LSStDesc = '' then + FDescription := STRS_UNDODESC[GetUndoKind(LRecUndo.Flags)] + else + FDescription := LSStDesc; + end; + end; + end; +end; + +procedure TMPHUndoStorage.SetSize(NewSize: integer); +begin + inherited; + if NewSize < sizeof(TMPHUndoRec) then + FCount := 0; +end; + +procedure TMPHUndoStorage.Reset(AResetRedo: boolean = True); +begin + Size := 0; + FCount := 0; + FUpdateCount := 0; + FDescription := ''; + if AResetRedo then + ResetRedo; +end; + +procedure TMPHUndoStorage.SetCount(const Value: integer); +begin + FCount := Value; + if FCount < 1 then + Reset(False); +end; + +function TMPHUndoStorage.CanRedo: boolean; +begin + Result := Assigned(FRedoPointer); +end; + +function TMPHUndoStorage.Redo: boolean; + + procedure SetEditorStateFromRedoRec(const _2Bytes: Boolean = False); + begin + with FRedoPointer^ do + begin + Move(PChar(FRedoPointer)[FRedoPointer^.DataLen], FEditor.FBookmarks, + sizeof(TMPHBookmarks)); + + with FEditor.GetCursorAtPos(CurPos, ufFlagInCharField in Flags) do + begin + if not (ufFlagInCharField in Flags) then + if FEditor.DataSize > 0 then + if ufFlag2ndByteCol in Flags then + x := x + 1; + + FEditor.MoveColRow(x, y, True, True); + end; + FEditor.FModified := ufFlagModified in Flags; + FEditor.InsertMode := (ufFlagInsertMode in Flags); + + with PUndoSelRec(@(PChar(FRedoPointer)[FRedoPointer^.DataLen + + sizeof(TMPHBookmarks)]))^ do + FEditor.SetSelection(SelPos, SelStart, SelEnd); + + FEditor.Translation := CurTranslation; + FEditor.FTranslation := CurTranslation; + FEditor.UnicodeChars := (ufFlagIsUnicode in Flags); + FEditor.UnicodeBigEndian := (ufFlagIsUnicodeBigEndian in Flags); + FEditor.BytesPerUnit := CurBPU; + + FEditor.InCharField := ufFlagInCharField in Flags; + + FEditor.SetChanged(Pos, ufFlagByte1Changed in Flags); + if _2Bytes then + FEditor.SetChanged(Pos + 1, ufFlagByte2Changed in Flags); + + // restore last undo record + if Assigned(FLastUndo) then + begin + Seek(0, soFromEnd); + Write(FLastUndo^, FLastUndoSize); + Inc(FCount); + FreeMem(FLastUndo); + FLastUndo := nil; + FLastUndoSize := 0; + end; + FDescription := FLastUndoDesc; + + FEditor.Invalidate; + FEditor.BookmarkChanged; + end; + end; +begin + Result := CanRedo; + if Result then + begin + case GetUndoKind(FRedoPointer^.Flags) of + ufKindBytesChanged: + begin + FEditor.WriteBuffer(FRedoPointer^.Buffer, + FRedoPointer^.Pos, FRedoPointer^.Count); + SetEditorStateFromRedoRec(FRedoPointer^.Count = 2); + end; + ufKindByteRemoved: + begin + FEditor.InternalDelete(FRedoPointer^.Pos, + FRedoPointer^.Pos + FRedoPointer^.Count, -1, 0); + SetEditorStateFromRedoRec; + end; + ufKindInsertBuffer: + begin + FEditor.InternalInsertBuffer(PChar(@(FRedoPointer^.Buffer)), + FRedoPointer^.Count, FRedoPointer^.Pos); + SetEditorStateFromRedoRec; + end; + ufKindSelection: + begin + SetEditorStateFromRedoRec; + end; + ufKindAllData: + begin + FEditor.FDataStorage.Size := FRedoPointer^.Count; + FEditor.FDataStorage.WriteBufferAt(FRedoPointer^.Buffer, 0, + FRedoPointer^.Count); + FEditor.CalcSizes; + SetEditorStateFromRedoRec; + end; + ufKindReplace: + begin + FEditor.InternalDelete(FRedoPointer^.Pos, + FRedoPointer^.Pos + FRedoPointer^.ReplCount, -1, 0); + FEditor.InternalInsertBuffer(PChar(@(FRedoPointer^.Buffer)), + FRedoPointer^.Count, FRedoPointer^.Pos); + SetEditorStateFromRedoRec; + end; + ufKindConvert: + begin + FEditor.InternalDelete(FRedoPointer^.Pos, + FRedoPointer^.Pos + FRedoPointer^.Count, -1, 0); + FEditor.InternalInsertBuffer(PChar(@(FRedoPointer^.Buffer)), + FRedoPointer^.Count, FRedoPointer^.Pos); + SetEditorStateFromRedoRec; + end; + ufKindAppendBuffer: + begin + FEditor.InternalAppendBuffer(PChar(@(FRedoPointer^.Buffer)), + FRedoPointer^.Count); + SetEditorStateFromRedoRec; + end; + ufKindNibbleInsert, + ufKindNibbleDelete: + begin + FEditor.FDataStorage.Size := FRedoPointer^.Count; + FEditor.FDataStorage.WriteBufferAt(FRedoPointer^.Buffer, 0, + FRedoPointer^.Count); + FEditor.CalcSizes; + SetEditorStateFromRedoRec; + end; + end; + ResetRedo; + FEditor.Changed; + end; +end; + +procedure TMPHUndoStorage.ResetRedo; +begin + if Assigned(FRedoPointer) then + FreeMem(FRedoPointer); + FRedoPointer := nil; + if Assigned(FLastUndo) then + FreeMem(FLastUndo); + FLastUndo := nil; + FLastUndoSize := 0; + FLastUndoDesc := ''; +end; + +procedure TMPHUndoStorage.CreateRedo(const Rec: TMPHUndoRec); +var + LIntDataSize: integer; + + procedure AllocRedoPointer; + begin + GetMem(FRedoPointer, sizeof(TMPHUndoRec) + sizeof(TMPHBookMarks) + + sizeof(TUndoSelRec) + LIntDataSize); + FRedoPointer^.Flags := [GetUndoKind(Rec.Flags)]; + FRedoPointer^.DataLen := sizeof(TMPHUndoRec) + LIntDataSize; + end; + + procedure FinishRedoPointer; + begin + with FRedoPointer^ do + begin + CurPos := FEditor.GetPosAtCursor(FEditor.Col, FEditor.Row); + if not FEditor.FPosInCharField then + with FEditor.GetCursorAtPos(CurPos, FEditor.FPosInCharField) do + if (FEditor.Col - x) <> 0 then + Include(Flags, ufFlag2ndByteCol); + if FEditor.FPosInCharField then + Include(Flags, ufFlagInCharField); + if FEditor.FInsertModeOn then + Include(Flags, ufFlagInsertMode); + Pos := Rec.pos; + Count := Rec.Count; + ReplCount := Rec.ReplCount; + CurTranslation := FEditor.FTranslation; + if FEditor.UnicodeChars then + Include(Flags, ufFlagIsUnicode); + if FEditor.UnicodeBigEndian then + Include(Flags, ufFlagIsUnicodeBigEndian); + CurBPU := FEditor.BytesPerUnit; + if FEditor.FModified then + Include(Flags, ufFlagModified); + end; + Move(FEditor.FBookmarks, PChar(FRedoPointer)[FRedoPointer^.DataLen], + sizeof(TMPHBookmarks)); + with PUndoSelRec(@(PChar(FRedoPointer)[FRedoPointer^.DataLen + + sizeof(TMPHBookmarks)]))^ do + begin + SelStart := FEditor.FSelStart; + SelPos := FEditor.FSelPosition; + SelEnd := FEditor.FSelEnd; + end; + end; +begin + ResetRedo; + // simple redo, store bookmarks, selection, insertmode, col, row, charfield... + // and bytes to save + + case GetUndoKind(Rec.Flags) of + ufKindBytesChanged: + begin + LIntDataSize := Rec.Count - 1; + AllocRedoPointer; + if FEditor.HasChanged(Rec.Pos) then + Include(FRedoPointer^.Flags, ufFlagByte1Changed); + if Rec.Count = 2 then + if FEditor.HasChanged(Rec.Pos + 1) then + Include(FRedoPointer^.Flags, ufFlagByte2Changed); + FEditor.ReadBuffer(FRedoPointer^.Buffer, Rec.Pos, Rec.Count); + FinishRedoPointer; + end; + ufKindByteRemoved: + begin + LIntDataSize := 0; + AllocRedoPointer; + FinishRedoPointer; + end; + ufKindInsertBuffer, + ufKindReplace, + ufKindConvert: + begin + LIntDataSize := Rec.Count; + AllocRedoPointer; + FEditor.ReadBuffer(FRedoPointer^.Buffer, Rec.Pos, Rec.Count); + FinishRedoPointer; + end; + ufKindSelection: + begin + LIntDataSize := 0; + AllocRedoPointer; + FinishRedoPointer; + end; + ufKindAllData: + begin + LIntDataSize := FEditor.DataSize; + AllocRedoPointer; + FEditor.ReadBuffer(FRedoPointer^.Buffer, 0, FEditor.DataSize); + FinishRedoPointer; + FRedoPointer^.Count := FEditor.DataSize; + end; + ufKindAppendBuffer: + begin + LIntDataSize := FEditor.DataSize - integer(Rec.Pos); + AllocRedoPointer; + FEditor.ReadBuffer(FRedoPointer^.Buffer, Rec.Pos, FEditor.DataSize - + integer(Rec.Pos)); + FinishRedoPointer; + end; + ufKindNibbleInsert, + ufKindNibbleDelete: + begin + LIntDataSize := FEditor.DataSize; + AllocRedoPointer; + FEditor.ReadBuffer(FRedoPointer^.Buffer, 0, FEditor.DataSize); + FinishRedoPointer; + FRedoPointer^.Count := LIntDataSize; + end; + end; + //FEditor.Changed; +end; + +function TMPHUndoStorage.GetUndoKind(const Flags: TMPHUndoFlags): TMPHUndoFlag; +begin + for Result := ufKindBytesChanged to ufKindAllData do + if Result in Flags then + Break; +end; + +procedure TMPHUndoStorage.AddSelection(const APos, ACount: integer); +var + P: PMPHUndoRec; + PSel: PUndoSelRec; + LIntRecOffset: integer; +begin + if CanUndo then + begin + Position := Size - 4; + Read(LIntRecOffset, 4); + Seek(-LIntRecOffset, soFromCurrent); + P := Pointer(Integer(Memory) + Position); + if not (ufFlagHasSelection in P^.Flags) then + begin + Size := Size + SizeOf(TUndoSelRec); + P := Pointer(Integer(Memory) + Position); + Include(P^.Flags, ufFlagHasSelection); + Inc(P^.DataLen, sizeof(TUndoSelRec)); + Inc(LIntRecOffset, sizeof(TUndoSelRec)); + Seek(-4, soFromEnd); + WriteBuffer(LIntRecOffset, 4); + end; + P^.CurPos := APos; + PSel := Pointer(Integer(Memory) + size - 4 - sizeof(TUndoSelRec)); + PSel^.SelStart := APos; + if aCount = 0 then + PSel^.SelEnd := -1 + else + PSel^.SelEnd := APos + Acount - 1; + PSel^.SelPos := PSel^.SelStart; + end; +end; + +function TMPHUndoStorage.ReadUndoRecord( + var aUR: TMPHUndoRec; var SDescription: string): TMPHUndoFlag; +var + LIntRecOffs: integer; + LIntPos: integer; +begin + Position := Size - 4; + Read(LIntRecOffs, 4); + Seek(-LIntRecOffs, soFromCurrent); + Read(aUR, SizeOf(TMPHUndoRec)); + Result := GetUndoKind(aUr.Flags); + if ufFlagHasDescription in aUr.Flags then + begin + LIntPos := Position; + try + Position := size - 4 - sizeof(integer); + if ufFlagHasSelection in aUr.Flags then + Seek(-sizeof(TUndoSelRec), soFromCurrent); + Read(LIntRecOffs, sizeof(integer)); + Seek(-(LIntRecOffs + sizeof(integer)), soFromCurrent); + SetLength(SDescription, LIntRecOffs); + Read(SDescription[1], LIntRecOffs); + finally + Position := LIntPos; + end; + end + else + SDescription := ''; +end; + +function TMPHUndoStorage.GetLastUndoKind: TMPHUndoFlag; +var + recUndo: TMPHUndoRec; + s: string; +begin + Result := ReadUndoRecord(recUndo, s); +end; + +// initialize tkCustom translation tables + +procedure InitializeCustomTables; +var + LBytLoop: byte; +begin + for LBytLoop := 0 to 255 do + begin + MPHCustomCharConv[cctFromAnsi][LBytLoop] := char(LBytLoop); + MPHCustomCharConv[cctToAnsi][LBytLoop] := char(LBytLoop); + end; +end; + +{ TMPHMemoryStream } + +const + MAX_PER_BLOCK = $F000; + +procedure TMPHMemoryStream.CheckBounds(const AMax: Integer); +begin + if (AMax) > Size then + raise EMPHexEditor.Create(ERR_DATA_BOUNDS); +end; + +function TMPHMemoryStream.GetAsHex(const APosition, ACount: integer; + const SwapNibbles: Boolean): string; +begin + CheckBounds(APosition + ACount); + SetLength(Result, ACount * 2); + if ACount > 0 then + ConvertBinToHex(PointerAt(APosition), @Result[1], ACount, SwapNibbles); +end; + +procedure TMPHMemoryStream.Move(const AFromPos, AToPos, ACount: Integer); +begin + MoveMemory(PointerAt(AToPos), PointerAt(AFromPos), ACount); +end; + +function TMPHMemoryStream.PointerAt(const APosition: Integer): Pointer; +begin + Result := Pointer(LongInt(Memory) + APosition); +end; + +procedure TMPHMemoryStream.ReadBufferAt(var Buffer; const APosition, + ACount: Integer); +var + LIntPos: Integer; +begin + CheckBounds(APosition + ACount); + LIntPos := Position; + try + Position := APosition; + ReadBuffer(Buffer, ACount); + finally + Position := LIntPos; + end; +end; + +procedure TMPHMemoryStream.TranslateFromAnsi(const ToTranslation: + TMPHTranslationKind; const APosition, ACount: integer); +begin + if ToTranslation = tkAsIs then + Exit; // no translation needed + CheckBounds(APosition + ACount); + if ACount > 0 then + TranslateBufferFromAnsi(ToTranslation, PointerAt(APosition), + PointerAt(APosition), ACount); +end; + +procedure TMPHMemoryStream.TranslateToAnsi(const FromTranslation: + TMPHTranslationKind; const APosition, ACount: integer); +begin + if FromTranslation = tkAsIs then + Exit; // no translation needed + CheckBounds(APosition + ACount); + if ACount > 0 then + TranslateBufferToAnsi(FromTranslation, PointerAt(APosition), + PointerAt(APosition), ACount); +end; + +procedure TMPHMemoryStream.WriteBufferAt(const Buffer; const APosition, + ACount: Integer); +var + LIntPos: Integer; +begin + CheckBounds(APosition + ACount); + LIntPos := Position; + try + Position := APosition; + WriteBuffer(Buffer, ACount); + finally + Position := LIntPos; + end; +end; + +initialization + + // initialize custom tables + + InitializeCustomTables; + +end. + diff --git a/hexcontrol/mphexeditorex.pas b/hexcontrol/mphexeditorex.pas new file mode 100644 index 0000000..06e40e3 --- /dev/null +++ b/hexcontrol/mphexeditorex.pas @@ -0,0 +1,3928 @@ +(* + + TMPHexEditorEx v 12-29-2004
+ + @author((C) markus stephany, merkes@mirkes.de, all rights reserved.) + @abstract(TMPHexEditorEx, an enhanced TMPHexEditor: print and preview, ole drag and drop, + ole clipboard handling, file backups...) + @lastmod(12-29-2004) + + credits to :

+ - John Hamm, http://users.snapjax.com/john/

+ + - Christophe Le Corfec for introducing the EBCDIC format and the nice idea about + half byte insert/delete

+ + - Philippe Chessa for his suggestions about AsText, AsHex and better support for + the french keyboard layout

+ + - Daniel Jensen for octal offset display and the INS-key recognition stuff

+ + - Shmuel Zeigerman for introducing more flexible offset display formats

+ + - Vaf, http://carradio.al.ru for reporting missing delver.inc and suggesting OnChange

+ + - Eugene Tarasov for reporting that setting the BytesPerColumn value to 4 at design + time didn't work

+ + - FuseBurner for BytesPerUnit/RulerBytesPerUnit related suggestions

+ + - Motzi for SyncView/ShowPositionIfNotFocused related suggestions

+ + - Martin Hsiao for bcb compatibility and reporting some bugs when moving cursor beyond eof

+ + - Miyu for delphi 7 defines

+ + - Nils Hoyer for bcb testing and his help on creating a BCB6 package

+ + - Skamnitsly S.V for reporting a bug when doubleclicking the ruler bar

+ + - Pete Fraser for reporting problems with array properties under BCB

+ + - Andrew Novikov for bug reports and suggestions

+ + - Al for bug reports

+ + - Dieter Köhler for reporting the delphi vcl related CanFocus bug

+ + - Piotr Likus for reporting a cardinal<->integer related bug in the Undo method

+ + - Marc Girod for bug reports

+ +

history:

+

    +
  • v 12-29-2004: december 29, 2004

    + - initialized Result to '' in some string functions/methods to avoid + non empty Result vars at function startup due to compiler + optimizations (particularly on d4), e.g. printing did not work + correctly under d4
    + - updated some of the sample projects (fixed the broken bcb6 sample, + added printing to the hex viewer and the bcb6 editor sample)

  • + +
  • v 12-28-2004: december 28, 2004

    + - changes in the base class (@link(TCustomMPHexEditor)) only

  • + +
  • v 12-21-2004: december 21, 2004

    + - changes in the base class (@link(TCustomMPHexEditor))
    + - support for CF_HTML clipboard format

  • + +
  • v 11-12-2004: november 12, 2004

    + - changes in the base class (@link(TCustomMPHexEditor))
    + - ole drag and drop move operation is now disabled if the editor's + ReadOnlyView property is set to True

  • + +
  • v 10-26-2004: october 26, 2004

    + - changes in the base class (@link(TCustomMPHexEditor))/unit (@link(mphexeditor)) only

  • + +
  • v 08-29-2004: august 29, 2004

    + - changes in the base class (@link(TCustomMPHexEditor))
    + - added pfIncludeRuler to @link(TMPHPrintFlag)

  • + +
  • v 08-14-2004: august 14, 2004

    + - changed printing (color handling, pfSelectionBold meaning)

  • + +
  • v 06-15-2004: june 15, 2004

    + - changes in the base class (@link(TCustomMPHexEditor)) and some more inherited + published properties

  • + +
  • v 06-10-2004: june 10, 2004

    + - changes in the base class (@link(TCustomMPHexEditor)) only

  • + +
  • v 06-07-2004: june 07, 2004

    + - changes in the base class (@link(TCustomMPHexEditor)) only

  • + +
  • v 05-27-2004: may 27, 2004

    + - changes in the base class (@link(TCustomMPHexEditor)) only

  • + +
  • v 05-13-2004: may 13, 2004

    + - changes in the base class (@link(TCustomMPHexEditor)) only

  • + +
  • v 04-18-2004: april 18, 2004

    + - changes in the base class (@link(TCustomMPHexEditor)) only

  • + +
  • v 01-08-2004: january 08, 2004

    + - changes in the base class (@link(TCustomMPHexEditor)) only

  • + +
  • v 12-16-2003: december 16, 2003

    + - changes in the base class (@link(TCustomMPHexEditor)) only

  • + +
  • v 12-10-2003: december 10, 2003

    + - changes in the base class (@link(TCustomMPHexEditor)) only

  • + +
  • v 09-24-2003: september 24, 2003

    + - modified the BCB6 package

  • + +
  • v 09-09-2003: september 09, 2003

    + - changed @link(UndoBeginUpdate) and @link(UndoEndUpdate) behaviour to automatically create an undo record + on UndoBeginUpdate and check it on UndoEndUpdate, see also @link(CreateUndoOnUndoUpdate)
    + - added property @link(CreateUndoOnUndoUpdate)
    + - added defines for delphi7, renamed delver.inc to mpdelver.inc
    + - @link(PasteData) method added

  • + +
  • v 07-05-2003: july 05, 2003

    + - added support for pasting clipboard data in fixed filesize mode
    + - added RegEdit_HexData clipboard support

  • + +
  • v 05-25-2003-b: may 25, 2003

    + - fixed a bug (moving the cursor beyond eof)

  • + +
  • v 05-25-2003: may 25, 2003

    + - no ':' is printed when offset display is not used
    + - added hpp generating statements for bcb compatibility

  • + +
  • v 05-20-2003: may 20, 2003

    + - added unicode support in printing

  • + +
  • v 05-17-2003: may 17, 2003

    + - moved some property related functions to protected
    + - corrected bottom margin handling when printing
    + - corrected upper/lowercase hex chars in printing
    + - the current unit is selected now when doubleclicking data
    + - added flags pfCurrentViewOnly (just print the currently + visible data) to @link(PrintOptions).Flags

  • + +
  • v 08-18-2002: august 18, 2002

    + - first release
  • +

+ +*) + +{$IFDEF BCB} +{$HPPEMIT 'DECLARE_DINTERFACE_TYPE(IDropTarget)'} +{$HPPEMIT 'DECLARE_DINTERFACE_TYPE(IDropSource)'} +{$HPPEMIT 'DECLARE_DINTERFACE_TYPE(IEnumFORMATETC)'} +{$ENDIF} + +unit MPHexEditorEx; + +{$IFNDEF PASDOC} +{$I MPDELVER.INC} +{$ENDIF} + +interface + +uses + Windows, Messages, SysUtils, Classes, Controls, Forms, + MPHexEditor, ActiveX, Graphics, Printers, + ShlObj, Menus; + +type + //@exclude + // is data dropped or pasted + TMPHOLEOperation = (oleDrop, oleClipboard); + + // @exclude(available clipboard / IDataObject formats) + TClipFormats = array of TClipFormat; + + // @exclude(ole drop handler class) + TMPHDropTarget = class; + + // @exclude(persistent print options) + TMPHPrintOptions = class; + + (* print option flags:

+ - pfSelectionOnly: only print data currently selected
+ - pfSelectionBold: render the current selection using either a bold font or inverted colors (if pfSelectionOnly isn't set)
+ - pfMonochrome: don't use colors, print/preview black on white
+ - pfUseBackgroundColor: fill the margin rect with the editor's background color (if pfMonochrome isn't set)
+ - pfCurrentViewOnly: just print the data currently displayed
+ - pfIncludeRuler: draw the ruler at every page's top
+ *) + TMPHPrintFlag = (pfSelectionOnly, pfSelectionBold, pfMonochrome, + pfUseBackgroundColor, pfCurrentViewOnly, pfIncludeRuler); + // @exclude() + TMPHPrintFlags = set of TMPHPrintFlag; + + // @exclude(print header/footer) + TMPHPrintHeaders = array[0..1] of string; + + (* this event is called when @link(PropertiesAsString) is read or written. TMPHexEditorEx + has a fixed list of properties that can be read/written using PropertiesAsString. + you can exclude some of the properties by setting IsPublic to False. + *) + TMPHQueryPublicPropertyEvent = procedure(Sender: TObject; const PropertyName: + string; + var IsPublic: boolean) of object; + + // enhanced hex editor + TMPHexEditorEx = class(TCustomMPHexEditor) + private + { Private-Deklarationen } + FCreateBackups: boolean; + FBackupFileExt: string; + FOleDragDrop: boolean; + FDropTarget: TMPHDropTarget; + FOleFormat: array[TMPHOLEOperation] of TClipFormat; + FOleDragging, FOleStartDrag: boolean; + FOleDragX, FOleDragY: integer; + FOleWasTarget: boolean; + FPrintOptions: TMPHPrintOptions; + FPrintPages: integer; + FPrintFont: TFont; + FUseEditorFontForPrinting: boolean; + FClipboardAsHexText: boolean; + FClipData: IDataObject; + FFlushClipboardAtShutDown: boolean; + FSupportsOtherClipFormats: boolean; + FOffsetPopupMenu: TPopupMenu; + FZoomOnWheel: boolean; + FPaintUpdateCounter: integer; + FOnQueryPublicProperty: TMPHQueryPublicPropertyEvent; + FHasDoubleClicked: boolean; + FBookmarksNoChange: boolean; + FCreateUndoOnUndoUpdate: boolean; + FModifiedNoUndo: boolean; + procedure SetOleDragDrop(const Value: boolean); + function OLEHasSupportedFormat(const dataObj: IDataObject; + const Formats: array of TClipFormat; var Format: TClipFormat): boolean; + function GetMyOLEFormats: TClipFormats; + procedure WMDestroy(var Message: TWMDestroy); message WM_DESTROY; + procedure SetPrintOptions(const Value: TMPHPrintOptions); + function PrintToCanvas(ACanvas: TCanvas; const APage: integer; + const AMargins: TRect): integer; + function PrinterMarginRect: TRect; + procedure SetPrintFont(const Value: TFont); + procedure SetOffsetPopupMenu(const Value: TPopupMenu); + function GetOffsetPopupMenu: TPopupMenu; + function GetBookmarksAsString: string; + procedure SetBookMarksAsString(Value: string); + protected + { Protected-Deklarationen } + function CanCreateUndo(const aKind: TMPHUndoFlag; const aCount, aReplCount: + integer): Boolean; override; +{$IFDEF DELPHI6UP} + // @exclude() + function GetPropertiesAsString: string; virtual; + // @exclude() + procedure SetPropertiesAsString(const Value: string); virtual; + // @exclude() + function IsPropPublic(const PropName: string): boolean; virtual; +{$ENDIF} + // @exclude(check if in offset col, if yes, popup offsetcontextmenu) + procedure Notification(AComponent: TComponent; Operation: TOperation); + override; +{$IFDEF DELPHI6UP} + // @exclude() + procedure DoContextPopup(MousePos: TPoint; var Handled: boolean); override; +{$ENDIF} + // @exclude(parse control keys) + procedure KeyDown(var Key: word; Shift: TShiftState); override; + // @exclude(overwrite mouse wheel for zooming) + function DoMouseWheelDown(Shift: TShiftState; MousePos: TPoint): boolean; + override; + // @exclude(overwrite mouse wheel for zooming) + function DoMouseWheelUp(Shift: TShiftState; MousePos: TPoint): boolean; + override; + // @exclude(create backups in savefile) + procedure PrepareOverwriteDiskFile; override; + // @exclude(overwrite mouse handling for ole drag and drop) + procedure MouseMove(Shift: TShiftState; X, Y: integer); override; + // @exclude(overwrite mouse handling for ole drag and drop) + procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: integer); + override; + // @exclude(overwrite mouse handling for ole drag and drop) + procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: + integer); + override; + // @exclude(reset drop target's HWND) + procedure CreateWnd; override; + // @exclude(supported dnd/clipboard data available?) + function SupportsOLEData(const dataObj: IDataObject; const grfKeyState: + longint; const pt: TPoint; var dwEffect: longint; const Operation: + TMPHOLEOperation): HRESULT; + // @exclude(insert ole-dropped data) + function InsertOLEData(const dataObj: IDataObject; const grfKeyState: + longint; const pt: TPoint; var dwEffect: longint; const Operation: + TMPHOLEOperation): HRESULT; + // @exclude(modify drageffect depending on key states and data format) + function ModifyOLEDropEffect(const grfKeyState: longint; const pt: TPoint; + var dwEffect: longint): HRESULT; + // @exclude(paint handler) + procedure Paint; override; + // @exclude(doubleclick handler for unit selection) + procedure DblClick; override; + // @exclude(override to avoid much updates when using setbookmarksasstring); + procedure BookmarkChanged; override; + public + { Public-Deklarationen } + // @exclude(Init) + constructor Create(AOwner: TComponent); override; + // @exclude(Done) + destructor Destroy; override; + // see inherited @inherited + procedure WriteBuffer(const Buffer; const Index, Count: Integer); override; + (* if set to True (default is False), an undo record is automatically created on calling + @link(UndoBeginUpdate) and on calling @link(UndoEndUpdate) the record is deleted if the + data has not been changed between UndoBegin- and UndoEndUpdate *) + property CreateUndoOnUndoUpdate: boolean read FCreateUndoOnUndoUpdate write + FCreateUndoOnUndoUpdate; + (* each call to BeginUpdate increments an internal counter that prevents from repainting + (see also @link(EndUpdate)) + *) + function BeginUpdate: integer; + (* each call to EndUpdate decrements an internal counter that prevents from repainting. + the return value is the value of this counter. if the counter is reset to zero, + repainting is permitted again (see also @link(BeginUpdate)) + *) + function EndUpdate: integer; + (* each call to UndoBeginUpdate increments an internal counter that prevents using + undo storage and also disables undo functionality (see also @link(UndoEndUpdate)) + *) + function UndoBeginUpdate(const StrUndoDesc: string = ''): integer; + reintroduce; + (* each call to UndoEndUpdate decrements an internal counter that prevents using + undo storage and also disables undo functionality. the return value is the value + of this counter. if the counter is reset to zero, undo creation is permitted again + (see also @link(UndoBeginUpdate)) + *) + function UndoEndUpdate: integer; override; + // create an undo for a range of bytes + procedure CreateRangeUndo(const aStart, aCount: integer; sDesc: string); + // is pasting from clipboard possible? + function CanPaste: boolean; + // is copying to clipboard possible? + function CanCopy: boolean; + // is cutting to clipboard possible? + function CanCut: boolean; + // copy selection to clipboard + function CBCopy: boolean; + // cut selection to clipboard + function CBCut: boolean; + // paste clipboard's contents over current selection + function CBPaste: boolean; + // do we own the clipboard data? + function OwnsClipBoard: boolean; + // flush or empty the clipboard (if we own the IDataObject) + procedure ReleaseClipboard(const Flush: boolean); + // save to file (overwrite) + procedure Save; + // @exclude(dump undo storage) + function DumpUndoStorage(const FileName: string): boolean; + (* creates a TMetaFile object and renders the specified page + on its canvas. Freeing of the TMetaFile is up to the caller! + *) + function PrintPreview(const Page: integer): TMetaFile; + (* print the given page to the default printer. + Printer.BeginDoc, Printer.NewPage and Printer.EndDoc must be issued by the caller! + *) + procedure Print(const Page: integer); + // get the number of pages to print + function PrintNumPages: integer; + // paste data (in clipboardmanner: check current selection and so on) + procedure PasteData(P: Pointer; const ACount: integer; const UndoDesc: string + = ''); + // get/set bookmarks as text (for storing in registry, ini-file) + property BookMarksAsString: string read GetBookmarksAsString write + SetBookMarksAsString; +{$IFDEF DELPHI6UP} + // get set properties as text (for storing in registry, ini-file); + property PropertiesAsString: string read GetPropertiesAsString write + SetPropertiesAsString; +{$ENDIF} + published + { Published-Deklarationen } + // create a backup on save ? (see also @link(BackupExtension)) + property CreateBackup: boolean read FCreateBackups write FCreateBackups + default True; + // add this extension to the file if making backups, see @link(CreateBackup) + property BackupExtension: string read FBackupFileExt write FBackupFileExt; + (* if set To True, OLE drag and drop will used automatically when dragging starts + or supported OLE data has been dropped on the hex editor + *) + property OleDragDrop: boolean read FOleDragDrop write SetOleDragDrop default + False; + // if set to True, CF_TEXT on the clipboard will be treated as hex formatted text + property ClipboardAsHexText: boolean read FClipboardAsHexText write + FClipboardAsHexText default False; + // flush or empty clipboard at shutdown + property FlushClipboardAtShutDown: boolean read FFlushClipboardAtShutDown + write FFlushClipboardAtShutDown default False; + // do we support other formats than CF_MPHEXEDITOR and CF_HDROP? + property SupportsOtherClipFormats: boolean read FSupportsOtherClipFormats + write FSupportsOtherClipFormats default True; + // print/preview options, see @link(TMPHPrintOptions) + property PrintOptions: TMPHPrintOptions read FPrintOptions write + SetPrintOptions; + // print using this font + property PrintFont: TFont read FPrintFont write SetPrintFont; + // if set to True, the editor's font will be used for printing + property UseEditorFontForPrinting: boolean read FUseEditorFontForPrinting + write FUseEditorFontForPrinting default True; + (* if this property is assigned to a TPopupMenu, it will be shown on right clicking + the offset display pane. then the normal PopupMenu will open on right + clicking the character and hex pane. + *) + property OffsetPopupMenu: TPopupMenu read GetOffsetPopupMenu write + SetOffsetPopupMenu; + // auto-zoom on mouse wheel? + property ZoomOnWheel: boolean read FZoomOnWheel write FZoomOnWheel default + True; + (* this event is called when @link(PropertiesAsString) is read or written. + (see @link(TMPHQueryPublicPropertyEvent)) + *) + property OnQueryPublicProperty: TMPHQueryPublicPropertyEvent read + FOnQueryPublicProperty write FOnQueryPublicProperty; + // @exclude(inherited) + property Align; + // @exclude(inherited) + property Anchors; + // @exclude(inherited) + property BiDiMode; + // @exclude(inherited) + property BorderStyle; + // @exclude(inherited) + property Constraints; + // @exclude(inherited) + property Ctl3D; + // @exclude(inherited) + property DragCursor; + // @exclude(inherited) + property DragKind; + // @exclude(inherited) + property DragMode; + // @exclude(inherited) + property Enabled; + // @exclude(inherited) + property Font; + // @exclude(inherited) + property ImeMode; + // @exclude(inherited) + property ImeName; + // @exclude(inherited) + property OnClick; + // @exclude(inherited) + property OnDblClick; + // @exclude(inherited) + property OnDragDrop; + // @exclude(inherited) + property OnDragOver; + // @exclude(inherited) + property OnEndDock; + // @exclude(inherited) + property OnEndDrag; + // @exclude(inherited) + property OnEnter; + // @exclude(inherited) + property OnExit; + // @exclude(inherited) + property OnKeyDown; + // @exclude(inherited) + property OnKeyPress; + // @exclude(inherited) + property OnKeyUp; + // @exclude(inherited) + property OnMouseDown; + // @exclude(inherited) + property OnMouseMove; + // @exclude(inherited) + property OnMouseUp; + // @exclude(inherited) + property OnMouseWheel; + // @exclude(inherited) + property OnMouseWheelDown; + // @exclude(inherited) + property OnMouseWheelUp; + // @exclude(inherited) + property OnStartDock; + // @exclude(inherited) + property OnStartDrag; + // @exclude(inherited) + property ParentBiDiMode; + // @exclude(inherited) + property ParentCtl3D; + // @exclude(inherited) + property ParentFont; + // @exclude(inherited) + property ParentShowHint; + // @exclude(inherited) + property PopupMenu; + // @exclude(inherited) + property ScrollBars; + // @exclude(inherited) + property ShowHint; + // @exclude(inherited) + property TabOrder; + // @exclude(inherited) + property TabStop; + // @exclude(inherited) + property Visible; + + // see inherited @inherited + property BytesPerRow; + // see inherited @inherited + property BytesPerColumn; + // see inherited @inherited + property Translation; + // see inherited @inherited + property OffsetFormat; + // see inherited @inherited + property CaretKind; + // see inherited @inherited + property Colors; + // see inherited @inherited + property FocusFrame; + // see inherited @inherited + property SwapNibbles; + // see inherited @inherited + property MaskChar; + // see inherited @inherited + property NoSizeChange; + // see inherited @inherited + property AllowInsertMode; + // see inherited @inherited + property DrawGridLines; + // see inherited @inherited + property WantTabs; + // see inherited @inherited + property ReadOnlyView; + // see inherited @inherited + property HideSelection; + // see inherited @inherited + property GraySelectionIfNotFocused; + // see inherited @inherited + property GutterWidth; + // see inherited @inherited + property BookmarkBitmap; + + // see inherited @inherited + property Version; + + // see inherited @inherited + property MaxUndo; + // see inherited @inherited + property InsertMode; + // see inherited @inherited + property HexLowerCase; + // see inherited @inherited + property OnProgress; + // see inherited @inherited + property OnInvalidKey; + // see inherited @inherited + property OnTopLeftChanged; + // see inherited @inherited + property OnChange; + // see inherited @inherited + property DrawGutter3D; + // see inherited @inherited + property ShowRuler; + // see inherited @inherited + property BytesPerUnit; + // see inherited @inherited + property RulerBytesPerUnit; + // see inherited @inherited + property ShowPositionIfNotFocused; + // see inherited @inherited + property OnSelectionChanged; + // see inherited @inherited + property UnicodeChars; + // see inherited @inherited + property UnicodeBigEndian; + + // see inherited @inherited + property OnDrawCell; + + // see inherited @inherited + property OnBookmarkChanged; + // see inherited @inherited + property OnGetOffsetText; + // see inherited @inherited + property BytesPerBlock; + // see inherited @inherited + property SeparateBlocksInCharField; + // see inherited @inherited + property FindProgress; + // see inherited @inherited + property RulerNumberBase; + end; + + // @exclude(ole drop target class) + TMPHDropTarget = class(TInterfacedObject, IDropTarget) + private + FEditor: TMPHexEditorEx; + FEditorHandle: THandle; + FActive: boolean; + procedure SetActive(const Value: boolean); + public + constructor Create(Editor: TMPHexEditorEx); + procedure BeforeDestruction; override; + function DragEnter(const dataObj: IDataObject; grfKeyState: longint; pt: + TPoint; var dwEffect: longint): HResult; stdcall; + function DragOver(grfKeyState: longint; pt: TPoint; var dwEffect: longint): + HResult; stdcall; + function DragLeave: HResult; stdcall; + function Drop(const dataObj: IDataObject; grfKeyState: longint; pt: TPoint; + var dwEffect: longint): HResult; stdcall; + property Active: boolean read FActive write SetActive; + end; + + // print / preview options + TMPHPrintOptions = class(TPersistent) + private + FMargins: TRect; + FHeaders: TMPHPrintHeaders; + FFlags: TMPHPrintFlags; + function GetHeader(const Index: integer): string; + function GetMargin(const Index: integer): integer; + procedure SetHeader(const Index: integer; const Value: string); + procedure SetMargin(const Index, Value: integer); + public + // @exclude(Init) + constructor Create; + // @exclude() + procedure Assign(Source: TPersistent); override; + published + // left margin in Millimeters + property MarginLeft: integer index 1 read GetMargin write SetMargin; + // top margin in Millimeters + property MarginTop: integer index 2 read GetMargin write SetMargin; + // right margin in Millimeters + property MarginRight: integer index 3 read GetMargin write SetMargin; + // bottom margin in Millimeters + property MarginBottom: integer index 4 read GetMargin write SetMargin; + (* this line will be rendered on top of the printed page, some characters have special meanings:

+ - the string may contain three parts separated by a "|" (pipe) character (left|center|right)
+ - each part knows some special variables: +
    +
  • %f: substituted with the filename part of the editor's filename
  • +
  • %F: substituted with the expanded name of the editor's filename
  • +
  • %p: substituted with the number of the current page
  • +
  • %P: substituted with the number of pages
  • +
  • %t: substituted with the current time
  • +
  • %d: substituted with the current date
  • +
  • %>: substituted with the long description of the editor's current @link(Translation)
  • +
  • %<: substituted with the short description of the editor's current @link(Translation)
  • +
+ *) + property PageHeader: string index 0 read GetHeader write SetHeader; + // this line will be rendered on the bottom of the printed page (see @link(PageHeader)) + property PageFooter: string index 1 read GetHeader write SetHeader; + (* printing flags:

+ - pfSelectionOnly: only print data currently selected
+ - pfSelectionBold: render the current selection using either a bold font or inverted colors (if pfSelectionOnly isn't set)
+ - pfMonochrome: don't use colors, print/preview black on white
+ - pfUseBackgroundColor: fill the margin rect with the editor's background color (if pfMonochrome isn't set)
+ - pfCurrentViewOnly: just print the data currently displayed + *) + property Flags: TMPHPrintFlags read FFlags write FFlags; + end; + + // default print margins +const + MPH_DEF_PRINT_MARGINS: TRect = (Left: 20; Top: 15; Right: 25; Bottom: 25); + +implementation + +uses + Consts, StdCtrls, ShellAPI, ComObj, TypInfo; + +resourcestring + + // error messages + ERR_NOFILE = 'No Filename specified'; + ERR_INVALID_PAGE = 'Invalid Page Index'; + ERR_PRINTING_FAILED = 'Printing Failed'; + ERR_BACKUP_DELETE = 'Cannot delete previous backup %s. (%s)'; + ERR_BACKUP_CREATE = 'Cannot create backup %s. (%s)'; + ERR_INVALID_BOOKFMT = 'Invalid Bookmark Format'; + + // additional undo descriptions + UNDO_PASTECB = 'Paste from Clipboard'; + UNDO_CUTCB = 'Cut to Clipboard'; + UNDO_DROPPED = 'Data Dropped'; + UNDO_MOVED = 'Data Moved'; + + // select clipb/ole format dialog strings + SELECT_FORMAT_CAPTION = 'Select Data Format'; + SELECT_FORMAT_ASHEX = 'Hex Text'; + + // when data dropped to explorer, give it this filename; first %s filename w/o ext, (second %s original file ext) + STR_SCRAPFILE = 'Dump of %s.bin'; + + // native clipboard format name + MPTH_CF = 'TMPHexeditorEx Clipboard Format'; + + // predefined clipboard format names + STR_CF_TEXT = 'Text'; + STR_CF_BITMAP = 'Bitmap Picture'; + STR_CF_METAFILEPICT = 'Metafile Picture'; + STR_CF_SYLK = 'Microsoft Symbolic Link (SYLK) data'; + STR_CF_DIF = 'Software Arts'' Data Interchange Format'; + STR_CF_TIFF = 'Tagged Image File Format (TIFF) Picture'; + STR_CF_OEMTEXT = 'OEM Text'; + STR_CF_DIB = 'Device Independent Bitmap Picture'; + STR_CF_PALETTE = 'Color Palete'; + STR_CF_PENDATA = 'Pen Data'; + STR_CF_RIFF = 'RIFF Audio Data'; + STR_CF_WAVE = 'Wave Audio'; + STR_CF_UNICODETEXT = 'Unicode Text'; + STR_CF_ENHMETAFILE = 'Enhanced Metafile Picture'; + STR_CF_HDROP = 'File List'; + STR_CF_LOCALE = 'Text Locale'; + +type + // my clipboard data struct + PClipData = ^TClipData; + TClipData = packed record + Signature: DWORD; + Version: DWORD; + Size: integer; + Data: array[0..0] of char; + end; + + PRegEditHexData = ^TRegEditHexData; + TRegEditHexData = packed record + Size: integer; + Data: array[0..0] of char; + end; + +const + // signature of own format clipboard data + CLIP_SIG = $4854504D; // MPTH; + // version of own format clipboard data + CLIP_VER = $00010001; + + // initial file extension of backups + BACKUP_EXT = '.bak'; + + // not so predefined common/known clipboard format names + CFSTR_RTF = 'Rich Text Format'; + CFSTR_LOGICALPERFORMEDDROPEFFECT = 'Logical Performed DropEffect'; + CFSTR_REGEDIT_HEXDATA = 'RegEdit_HexData'; + CFSTR_HTML = 'HTML Format'; + +var + // custom/ shell CF format + CF_MPHEXEDITOR, + CF_RTF, + CF_FILECONTENTS, + CF_PERFORMEDDROPEFFECT, + CF_LOGICALPERFORMEDDROPEFFECT, + CF_FILEDESCRIPTOR, + CF_HTML, + CF_REGEDIT_HEXDATA: TClipFormat; + +type + // private idataobject format enumerator + TFormatEnum = class + private + FFormats: array of TFormatETC; + public + constructor Create(const dataObject: IDataObject); + destructor Destroy; override; + function HasFormat(const cfFormat: TClipFormat): boolean; + function GetFormatETC(const cfFormat: TClipFormat): TFormatETC; + end; + +const + // number of clip formats that we can provide + MY_SUPPORTED_FORMATS = 4; + +type + // ole "public" format enumerator for own data + TMPHEnumFormatETC = class(TInterfacedObject, IEnumFormatETC) + private + FFormats: packed array[0..MY_SUPPORTED_FORMATS - 1] of TFormatETC; + FIndex: integer; + public + constructor Create; + function Next(celt: longint; out elt; pceltFetched: PLongint): HResult; + stdcall; + function Skip(celt: longint): HResult; stdcall; + function Reset: HResult; stdcall; + function Clone(out Enum: IEnumFormatEtc): HResult; stdcall; + end; + + // ole drop source + TMPHDropSource = class(TInterfacedObject, IDropSource) + public + function QueryContinueDrag(fEscapePressed: BOOL; grfKeyState: longint): + HResult; stdcall; + function GiveFeedback(dwEffect: longint): HResult; stdcall; + end; + + // ole data container + TMPHDataObject = class(TInterfacedObject, IDataObject) + private + FData: Pointer; + FDataSize: integer; + FFileName: ShortString; + FHasDropEffect: boolean; + FDropEffect: cardinal; + FTextAsHex: boolean; + FSwapNibbles: boolean; + public + constructor Create(Data: Pointer; DataSize: integer; ScrapFileName: + ShortString; TextAsHex, SwapNibbles: boolean); + constructor CreateFromStream(Stream: TStream; Position, DataSize: integer; + ScrapFileName: ShortString; TextAsHex, SwapNibbles: boolean); + procedure BeforeDestruction; override; + function GetData(const formatetcIn: TFormatEtc; out medium: TStgMedium): + HResult; stdcall; + function GetDataHere(const formatetc: TFormatEtc; out medium: TStgMedium): + HResult; stdcall; + function QueryGetData(const formatetc: TFormatEtc): HResult; stdcall; + function GetCanonicalFormatEtc(const formatetc: TFormatEtc; out + formatetcOut: TFormatEtc): HResult; stdcall; + function SetData(const formatetc: TFormatEtc; var medium: TStgMedium; + fRelease: BOOL): HResult; stdcall; + function EnumFormatEtc(dwDirection: longint; out enumFormatEtc: + IEnumFormatEtc): HResult; stdcall; + function DAdvise(const formatetc: TFormatEtc; advf: longint; const advSink: + IAdviseSink; out dwConnection: longint): HResult; stdcall; + function DUnadvise(dwConnection: longint): HResult; stdcall; + function EnumDAdvise(out enumAdvise: IEnumStatData): HResult; stdcall; + end; + + // draw hex on canvas + TMPHCanvasPrinter = class(TObject) + private + FMargins: TRect; + FHeaders, + FPrintHeaders: TMPHPrintHeaders; + FLinesPerPage: integer; + FFlags: TMPHPrintFlags; + FPages: integer; + FEditor: TMPHexEditorEx; + FCanvas: TCanvas; + function GetLinesPerPage: integer; + function BuildHeader(const S: string; const Page: integer): string; + protected + function DrawOrCalc(const JustCalc: boolean; const Page: integer): integer; + public + constructor Create(AEditor: TMPHexEditorEx; ACanvas: TCanvas; AFlags: + TMPHPrintFlags; AMargins: TRect; AHeaders: TMPHPrintHeaders); + procedure Draw(const Page: integer); + property LinesPerPage: integer read GetLinesPerPage; + property Pages: integer read FPages; + end; + +var + // most recent selected clip format + LAST_USED_CF: integer = -1; + + // returns the stgmedium struct for a given idataobject/format specification + +function GetIDataObjectData(const dataObj: IDataObject; const Format: + TClipFormat; out Medium: TStgMedium): HRESULT; +var + LobjEnum: TFormatEnum; +begin + LobjEnum := TFormatEnum.Create(dataObj); + try + if not LobjEnum.HasFormat(Format) then + Result := E_FAIL + else + Result := dataObj.GetData(LobjEnum.GetFormatETC(Format), Medium); + finally + LobjEnum.Free; + end; +end; + +// cast/copy hglobal to data structure depending on the format + +function GetSomeData(const PData: Pointer; const HGlobal: THandle; Format: + TClipFormat; const DataSize: integer; const UnicodeBigEndian: Boolean): + string; +var + LWStrTemp: widestring; + LRecBmpHeader: TBitmapFileheader; + LRecPalette: TMaxLogPalette; + LIntTemp: integer; + LbmpTemp: TBitmap; + LmefTemp: TMetaFile; + LmstData: TMemoryStream; + LIntLoop: integer; +begin + Result := ''; + + // to use case..of (cf_rtf is not a constant) + if (Format = CF_RTF) or (Format = CF_HTML) then + Format := CF_TEXT; + + if Format = CF_MPHEXEDITOR then + begin + with PClipData(PData)^ do + if (Signature = CLIP_SIG) and (Version = CLIP_VER) then + SetString(Result, Data, Size) + end + else if Format = CF_REGEDIT_HEXDATA then + begin + with PRegEditHexData(PData)^ do + SetString(Result, Data, Size); + end + else + case Format of + CF_TEXT, + CF_OEMTEXT: Result := PChar(PData); + CF_UNICODETEXT: + begin + LWStrTemp := PWideChar(PData); + if UnicodeBigEndian then + begin + for LIntLoop := 1 to Length(LWstrTemp) do + SwapWideChar(LWstrTemp[LIntLoop]); + end; +{$WARNINGS OFF} + // don't convert, get wide data as is + SetString(Result, PChar(LWStrTemp), Length(LWStrTemp) * + (sizeof(widechar) div sizeof(char))); +{$WARNINGS ON} + end; + CF_LOCALE: + begin + // locale id , word pointed to by the global handle + SetLength(Result, sizeof(word)); + Move(PWord(PData)^, Result[1], sizeof(word)); + end; + CF_DIB: + begin + // stored as bitmap without header, so prefix a bmp header + FillChar(LRecBMPHeader, sizeof(LRecBMPHeader), #0); + LRecBMPHeader.bfType := $4D42; // BM + SetLength(Result, sizeof(LRecBMPHeader) + DataSize); + Move(LRecBMPHeader, Result[1], sizeof(LRecBmpHeader)); + Move(PData^, Result[1 + sizeof(LRecBMPHeader)], DataSize); + end; + CF_PALETTE: + begin + // copy palette entries + LIntTemp := 0; + if (GetObject(HGlobal, sizeof(LIntTemp), @LIntTemp) <> 0) and (LIntTemp + > 0) then + begin + with LRecPalette do + begin + palVersion := $0300; + palNumEntries := LIntTemp; + GetPaletteEntries(HGlobal, 0, LIntTemp, palPalEntry); + end; + SetLength(Result, sizeof(TLogPalette) + ((LintTemp - 1) * + sizeof(TPaletteEntry))); + Move(LRecPalette, Result[1], Length(Result)); + end; + end; + CF_BITMAP: + begin + // data not stored in global mem, but as a bitmap handle + LbmpTemp := TBitmap.Create; + try + LbmpTemp.Handle := CopyImage(HGlobal, IMAGE_BITMAP, 0, 0, + LR_COPYRETURNORG); + LmstData := TMemoryStream.Create; + try + LbmpTemp.SaveToStream(LmstData); + SetString(Result, PChar(LmstData.Memory), LmstData.Size); + finally + LmstData.Free; + end; + finally + LbmpTemp.Free; + end; + end; + CF_METAFILEPICT: + begin + // global mem contains mf struct + LIntTemp := GetMetaFileBitsEx(PMetafilePict(PData)^.hMF, 0, nil); + if LIntTemp > 0 then + begin + SetLength(Result, LIntTemp); + GetMetaFileBitsEx(PMetafilePict(PData)^.hMF, LIntTemp, @Result[1]); + end; + end; + CF_ENHMETAFILE: + begin + // emf handle + LmefTemp := TMetaFile.Create; + try + LmefTemp.Handle := CopyEnhMetafile(HGlobal, nil); + LmstData := TMemoryStream.Create; + try + LmefTemp.SaveToStream(LmstData); + SetString(Result, PChar(LmstData.Memory), LmstData.Size); + finally + LmstData.Free; + end; + finally + LmefTemp.Free; + end; + end; + else + // format not yet known + SetString(Result, PChar(PData), DataSize); + end; +end; + +type + // special dialog for format selection + TFormatSelDialog = class(TForm) + private + LbtnOK: TButton; + LbtnCancel: TButton; + LlbxFormats: TListBox; + LcbxTextAsHex: TCheckBox; + procedure ListDoubleClick(Sender: TObject); + procedure ListSelect(Sender: TObject); + end; + + // select a format out of an array of available formats + +function SelectClipFormat(const Formats: array of TClipFormat; var Format: + TClipFormat; var TextIsHexData: boolean): boolean; +var + LfrmDialog: TFormatSelDialog; + LIntLoop: integer; + LWrdCurrent: TClipFormat; + LStrFormatName: string; + LszBuffer: array[0..511] of char; +begin + Result := False; + + // create and show a dialog for clipboard format selection + LfrmDialog := TFormatSelDialog.CreateNew(Application); + with lfrmDialog do + try + BorderStyle := bsDialog; + Width := Screen.Width div 4; + Height := Screen.Height div 4; +{$IFDEF DELPHI6UP} + Position := poOwnerFormCenter; +{$ELSE} + Position := poScreenCenter; +{$ENDIF} + Caption := SELECT_FORMAT_CAPTION; + + LbtnOK := TButton.Create(LfrmDialog); + LbtnCancel := TButton.Create(LfrmDialog); + LcbxTextAsHex := TCheckBox.Create(LfrmDialog); + LlbxFormats := TListBox.Create(LfrmDialog); + try + with lbtnOK do + begin + Parent := LfrmDialog; + ModalResult := mrOk; + Caption := SOKButton; + Default := True; + Width := (LfrmDialog.Width div 2) - 32; + Top := LfrmDialog.ClientHeight - Height - 8; + Left := 16; + Enabled := False; + end; + + with LbtnCancel do + begin + Parent := LfrmDialog; + ModalResult := mrCancel; + Cancel := True; + Caption := SCancelButton; + Width := (LfrmDialog.Width div 2) - 32; + Top := LfrmDialog.ClientHeight - Height - 8; + Left := LfrmDialog.ClientWidth - Width - 16; + end; + + with LcbxTextAsHex do + begin + Parent := LfrmDialog; + Enabled := False; + Caption := SELECT_FORMAT_ASHEX; + Top := LbtnCancel.Top - Height - 8; + Left := LbtnOK.Left; + Width := LfrmDialog.ClientWidth - Left; + Checked := TextIsHexData; + end; + + with LlbxFormats do + begin + Parent := LfrmDialog; + Align := alTop; + Height := LfrmDialog.ClientHeight - 16 - LbtnCancel.Height - 8 - + LcbxTextAsHex.Height; + OnDblClick := ListDoubleClick; + OnClick := ListSelect; + + for LIntLoop := Low(Formats) to High(Formats) do + begin + LWrdCurrent := Formats[LIntLoop]; + case LWrdCurrent of + CF_TEXT: LStrFormatName := STR_CF_TEXT; + CF_BITMAP: LStrFormatName := STR_CF_BITMAP; + CF_METAFILEPICT: LStrFormatName := STR_CF_METAFILEPICT; + CF_SYLK: LStrFormatName := STR_CF_SYLK; + CF_DIF: LStrFormatName := STR_CF_DIF; + CF_TIFF: LStrFormatName := STR_CF_TIFF; + CF_OEMTEXT: LStrFormatName := STR_CF_OEMTEXT; + CF_DIB: LStrFormatName := STR_CF_DIB; + CF_PALETTE: LStrFormatName := STR_CF_PALETTE; + CF_PENDATA: LStrFormatName := STR_CF_PENDATA; + CF_RIFF: LStrFormatName := STR_CF_RIFF; + CF_WAVE: LStrFormatName := STR_CF_WAVE; + CF_UNICODETEXT: LStrFormatName := STR_CF_UNICODETEXT; + CF_ENHMETAFILE: LStrFormatName := STR_CF_ENHMETAFILE; + CF_HDROP: LStrFormatName := STR_CF_HDROP; + CF_LOCALE: LStrFormatName := STR_CF_LOCALE; + else + SetString(LStrFormatName, LszBuffer, + GetClipboardFormatName(LWrdCurrent, LszBuffer, + sizeof(LszBuffer))); + LStrFormatName := Trim(LStrFormatName); + end; + if LStrFormatName = '' then + LStrFormatName := '(' + IntToRadix(LWrdCurrent, 10) + ')'; + Items.AddObject(LStrFormatName, Pointer(LWrdCurrent)); + LbtnOK.Enabled := True; + ItemIndex := Items.IndexOfObject(Pointer(LAST_USED_CF)); + if ItemIndex = -1 then + ItemIndex := 0; + end; + end; + + // enable hextext checkbox depending on selected format + ListSelect(nil); + + if (ShowModal = mrOk) and (LlbxFormats.ItemIndex > -1) then + begin + Format := TClipFormat(LlbxFormats.Items.Objects[LlbxFormats.ItemIndex]); + if Format in [CF_TEXT, CF_OEMTEXT] then + TextIsHexData := LcbxTextAsHex.Checked; + Result := True; + LAST_USED_CF := Format; + end; + finally + // not sure if they automatically get freed? + LbtnOK.Free; + LbtnCancel.Free; + LcbxTextAsHex.Free; + LlbxFormats.Free; + end; + finally + Free; + end; +end; + +// query a data object's supported formats and check if we can "paste" them + +function QueryOLEFormat(const SupportedFormats: array of TClipFormat; const + dataObj: IDataObject; var Format: TClipFormat; var TextIsHexData: boolean): + boolean; +var + LWrdFormats: array of TClipFormat; + LIntLoop: integer; + LobjEnum: TFormatEnum; +begin + Result := False; + LWrdFormats := nil; + LobjEnum := TFormatEnum.Create(dataObj); + try + // enum all available formats + if Length(SupportedFormats) > 0 then + begin + for LIntLoop := Low(SupportedFormats) to High(SupportedFormats) do + if LObjEnum.HasFormat(SupportedFormats[LIntLoop]) then + begin + SetLength(LWrdFormats, Succ(Length(LWrdFormats))); + LWrdFormats[Pred(Length(LWrdFormats))] := SupportedFormats[LIntLoop]; + end; + case Length(LWrdFormats) of + 0: Exit; + 1: + begin + Format := LWrdFormats[0]; + Result := True; + Exit; + end; + else + // show a dialog for data format selection + Result := SelectClipFormat(LWrdFormats, Format, TextIsHexData); + end; + end; + finally + LObjEnum.Free; + LWrdFormats := nil; + end; +end; + +{ TMPHexEditorEx } + +// constructor + +constructor TMPHexEditorEx.Create(AOwner: TComponent); +begin + inherited; + FModifiedNoUndo := False; + FCreateUndoOnUndoUpdate := False; + FBookmarksNoChange := False; + FHasDoubleClicked := False; + FPaintUpdateCounter := 0; + FClipData := nil; + FZoomOnWheel := True; + FCreateBackups := True; + FBackupFileExt := BACKUP_EXT; + FOleDragDrop := False; + FOleStartDrag := False; + FOleDragging := False; + FClipboardAsHexText := False; + FFlushClipboardAtShutDown := False; + FSupportsOtherClipFormats := True; + FPrintOptions := TMPHPrintOptions.Create; + FPrintFont := TFont.Create; + FPrintFont.Assign(Font); + FUseEditorFontForPrinting := True; + FOffsetPopupMenu := nil; + if not (csDesigning in ComponentState) then + FDropTarget := TMPHDropTarget.Create(self); // not in delphi ide +end; + +// destructor + +destructor TMPHexEditorEx.Destroy; +begin + // empty or flush clipboard + ReleaseClipboard(FFlushClipboardAtShutDown); + FPrintOptions.Free; + FPrintFont.Free; + if not (csDesigning in ComponentState) then + FDropTarget.Free; + inherited; +end; + +// cb copy possible + +function TMPHexEditorEx.CanCopy: boolean; +begin + Result := (DataSize > 0) and (SelCount > 0); +end; + +// cb cut possible + +function TMPHexEditorEx.CanCut: boolean; +begin + Result := CanCopy and not (ReadOnlyView or NoSizeChange); +end; + +// cb paste possible + +function TMPHexEditorEx.CanPaste: boolean; +var + LifData: IDataObject; + LIntEffect: integer; +begin + LIntEffect := DROPEFFECT_COPY; + Result := (not (ReadOnlyView (*or NoSizeChange*))) and + Succeeded(OLEGetClipboard(LifData)) and (SupportsOLEData(LifData, 0, + Point(0, + 0), LintEffect, oleClipboard) = S_OK); + if Result and NoSizeChange then + Result := DataSize > 0; +end; + +// copy to clipboard + +function TMPHexEditorEx.CBCopy: boolean; +begin + Result := CanCopy; + if Result then + begin + WaitCursor; + try + FClipData := TMPHDataObject.CreateFromStream(DataStorage, Min(SelStart, + SelEnd), SelCount, ExtractFileName(FileName), FClipboardAsHexText, + SwapNibbles); + OleCheck(OleSetClipboard(FClipData)); + finally + OldCursor; + end; + end; +end; + +// cut to clipboard + +function TMPHexEditorEx.CBCut: boolean; +begin + Result := CanCut and CBCopy; + if Result then + begin + WaitCursor; + try + DeleteSelection(UNDO_CUTCB); + finally + OldCursor; + end; + end; +end; + +// paste from clipboard + +function TMPHexEditorEx.CBPaste: boolean; +var + LifData: IDataObject; + LIntEffect: integer; +begin + LIntEffect := DROPEFFECT_COPY; + Result := CanPaste and Succeeded(OLEGetClipboard(LifData)) and + Succeeded(InsertOLEData(LifData, 0, Point(0, 0), LIntEffect, oleClipboard)); +end; + +// create an undo for a range of bytes + +procedure TMPHexEditorEx.CreateRangeUndo(const aStart, aCount: integer; + sDesc: string); +var + bMod: boolean; +begin + bMod := FModified; + try + if aCount < 1 then + CreateUndo(ufKindAllData, 0, 0, 0, sDesc) + else + CreateUndo(ufKindReplace, aStart, aCount, aCount, sDesc); + finally + FModified := bMod; + end; +end; + +function TMPHexEditorEx.BeginUpdate: integer; +begin + Inc(FPaintUpdateCounter); + Result := FPaintUpdateCounter; +end; + +function TMPHexEditorEx.EndUpdate: integer; +begin + Dec(FPaintUpdateCounter); + if FPaintUpdateCounter < 0 then + FPaintUpdateCounter := 0; + if FPaintUpdateCounter = 0 then + Invalidate; + Result := FPaintUpdateCounter; +end; + +// mouse wheel overriding for zooming (font size) if CTRL/SHIFT is pressed, +// or bytes per line changing if CTRL pressed + +function TMpHexEditorEx.DoMouseWheelDown(Shift: TShiftState; MousePos: TPoint): + boolean; +begin + if FZoomOnWheel and (Shift = [ssCtrl]) and (BytesPerRow > 1) then + begin + Result := True; + BytesPerRow := BytesPerRow - 1; + Invalidate; + end + else if FZoomOnWheel and (Shift = [ssShift, ssCtrl]) and (Font.Size > 2) then + begin + Result := True; + Font.Size := Font.Size - 1; + end + else + Result := inherited DoMouseWheelDown(Shift, MousePos); +end; + +function TMpHexEditorEx.DoMouseWheelUp(Shift: TShiftState; MousePos: TPoint): + boolean; +begin + if FZoomOnWheel and (Shift = [ssCtrl]) and (BytesPerRow < 256) then + begin + Result := True; + BytesPerRow := BytesPerRow + 1; + Invalidate; + end + else if FZoomOnWheel and (Shift = [ssShift, ssCtrl]) then + begin + Result := True; + Font.Size := Font.Size + 1; + end + else + Result := inherited DoMouseWheelUp(Shift, MousePos); +end; + +// overwrite key handling + +procedure TMPHexEditorEx.KeyDown(var Key: word; Shift: TShiftState); +begin + inherited; + case Key of + // CTRL+A: select all + Ord('A'): if Shift = [ssCtrl] then + begin + SelectAll; + end; + + // CTRL+C: copy to clipboard + Ord('C'): if (Shift = [ssCtrl]) and CanCopy then + begin + CBCopy; + end; + + // CTRL+X: cut to clipboard + Ord('X'): if (Shift = [ssCtrl]) and CanCut then + begin + CBCut; + end; + + // CTRL+V: paste from clipboard + Ord('V'): if (Shift = [ssCtrl]) and CanPaste then + begin + CBPaste; + end; + + // CTRL+T/CTRL*SHIFT+Z: undo, redo + Ord('Z'): + begin + // undo + if (Shift = [ssCtrl]) and CanUndo then + begin + Undo; + end + // redo + else if (Shift = [ssShift, ssCtrl]) and CanRedo then + begin + Redo; + end + end; + end; +end; + +// handle backup creation + +procedure TMPHexEditorEx.PrepareOverwriteDiskFile; +var + LStrBackup: string; +begin + inherited; + + if (FCreateBackups and Modified) and FileExists(FileName) then + begin + LStrBackup := FileName + FBackupFileExt; + if FileExists(LStrBackup) and not DeleteFile(LStrBackup) then + raise EMPHexEditor.CreateFmt(ERR_BACKUP_DELETE, + [LStrBackup, SysErrorMessage(GetLastError)]); + + if not MoveFile(PChar(FileName), PChar(LStrBackup)) then + raise EMPHexEditor.CreateFmt(ERR_BACKUP_CREATE, + [LStrBackup, SysErrorMessage(GetLastError)]); + end; +end; + +// save to file (overwrite) + +procedure TMPHexEditorEx.Save; +begin + if not HasFile then + raise EMPHexEditor.Create(ERR_NOFILE); + SaveToFile(FileName); +end; + +// prepare ole dragging + +procedure TMPHexEditorEx.MouseDown(Button: TMouseButton; Shift: TShiftState; X, + Y: integer); +begin + inherited; + if FOleDragDrop and (Button = mbLeft) and MouseOverSelection and (not + IsSelecting) then + begin + FOleStartDrag := True; + FOleDragging := False; + FOleDragX := X; + FOleDragY := Y; + end +end; + +// check and eventually do ole dragging + +procedure TMPHexEditorEx.MouseMove(Shift: TShiftState; X, Y: integer); +var + LHrsOperation: HRESULT; + LIntEffect: integer; + LobjData: TMPHDataObject; +begin + inherited; + + if FOleDragDrop and (ssLeft in Shift) and (not FOleDragging) and FOleStartDrag + and MouseOverSelection and (not IsSelecting) and ((Abs(X - FOleDragX) >= + Mouse.DragThreshold) or (Abs(Y - FOleDragY) >= Mouse.DragThreshold)) then + begin + FOleStartDrag := False; + FOleDragging := True; + FoleWasTarget := False; + // start ole dragging + try + LobjData := TMPHDataObject.CreateFromStream(DataStorage, Min(SelStart, + SelEnd), SelCount, ExtractFileName(FileName), FClipboardAsHexText, + SwapNibbles); + if not ReadOnlyView then + LHrsOperation := DoDragDrop(LobjData, TMPHDropSource.Create, + DROPEFFECT_COPY or DROPEFFECT_MOVE, LIntEffect) + else + LHrsOperation := DoDragDrop(LobjData, TMPHDropSource.Create, + DROPEFFECT_COPY, LIntEffect); + // if feedback has given via idataobject.setdata + if LObjData.FHasDropEffect then + LIntEffect := LObjData.FDropEffect; + // unexcpected result + if (LHrsOperation <> DRAGDROP_S_CANCEL) and (LHrsOperation <> + DRAGDROP_S_DROP) then + OLECheck(LHrsOperation) + else if (LHrsOperation = DRAGDROP_S_DROP) and (LIntEffect = + DROPEFFECT_MOVE) then + begin + // dragged to an other window + if not FOleWasTarget then + DeleteSelection + else + // dragged to me, so on move, selection is already deleted, create a move undo + CombineUndo(2, UNDO_MOVED); + end; + finally + FOleDragging := False; + FOleWasTarget := False; + HideDragCell; + end; + end; +end; + +// cancel dragging and flags + +procedure TMPHexEditorEx.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: + integer); +begin + if FHasDoubleClicked then + begin + MouseUpCanResetSel := False; + FHasDoubleClicked := False; + end; + inherited; + if FOleDragging then + begin + FOleDragging := False; + FOleStartDrag := False; + end; +end; + +// don't allow ole dnd in ide or while loading + +procedure TMPHexEditorEx.SetOleDragDrop(const Value: boolean); +begin + if Value <> FOleDragDrop then + begin + FOleDragDrop := Value; + if ComponentState * [csLoading, csDesigning] = [] then + FDropTarget.Active := Value; + end; +end; + +// if ole dnd allowed, set new window handle in the drop target + +procedure TMPHexEditorEx.CreateWnd; +begin + inherited; + if not (csDesigning in ComponentState) then + begin + FDropTarget.Active := FOleDragDrop; + end; +end; + +// insert idataobject data + +function TMPHexEditorEx.InsertOLEData(const dataObj: IDataObject; const + grfKeyState: longint; const pt: TPoint; var dwEffect: longint; const + Operation: + TMPHOLEOperation): HRESULT; +var + LRecStg: TStgMedium; + LStrData, LStrBin: string; + LIntData, LIntPos, LIntLoop: integer; + LszBuf: array[0..MAX_PATH] of char; + LfstFile: TFileStream; + LPtrLock: Pointer; + LIntGlobalSize: integer; +begin + Result := E_FAIL; + LStrData := ''; + LIntData := 0; + WaitCursor; + try + // haupt-format? + if ((FOLEFormat[Operation] = CF_MPHEXEDITOR) or (FOLEFormat[Operation] = + CF_HDROP)) or QueryOLEFormat(GetMyOLEFormats, dataObj, + FOLEFormat[Operation], FClipboardAsHexText) then + begin + // je nach format daten konvertieren + case FOLEFormat[Operation] of + CF_HDROP: if Succeeded(GetIDataObjectData(dataObj, + FOLEFormat[Operation], LRecStg)) then + try + // link: -> put all filenames + // copy: -> copy contents of first file + LIntLoop := DragQueryFile(LRecStg.hGlobal, cardinal(-1), nil, 0); + if LintLoop > 0 then + begin + for LIntLoop := 0 to Pred(LIntLoop) do + begin + DragQueryFile(LRecStg.hGlobal, LIntLoop, LszBuf, + sizeof(LszBuf)); + Result := S_OK; + if dwEffect = DROPEFFECT_LINK then + begin + LStrData := LStrData + StrPas(LszBuf) + #0; + LIntData := Length(LStrData); + end + else + begin + Result := E_FAIL; + LfstFile := TFileStream.Create(LszBuf, fmOpenRead or + fmShareDenyNone); + try + SetLength(LStrData, LfstFile.Size); + LfstFile.ReadBuffer(LStrData[1], LfstFile.Size); + Result := S_OK; + LIntData := Length(LStrData); + Break; // just 1st file + finally + LfstFile.Free; + end; + end; + end; + end; + finally + ReleaseStgMedium(LRecStg); + end; + else + // format other than CF_HDROP (=files dropped), retrieve data + if Succeeded(GetIDataObjectData(dataObj, FOLEFormat[Operation], LRecStg)) + then + try + if LRecStg.tymed in [TYMED_HGLOBAL, TYMED_MFPICT] then + begin + LPtrLock := GlobalLock(LRecStg.hGlobal); + LIntGlobalSize := GlobalSize(LRecStg.hGlobal); + end + else + begin + LPtrLock := nil; + LIntGlobalSize := 0; + end; + try + LStrData := GetSomeData(LPtrLock, LRecStg.hGlobal, + FOLEFormat[Operation], LIntGlobalSize, UnicodeBigEndian); + if LStrData <> '' then + begin + LIntData := Length(LStrData); + if (FOLEFormat[Operation] in [CF_TEXT, CF_OEMTEXT]) and (Operation + = oleClipboard) and FClipBoardAsHexText then + begin + // convert hex text to data + SetLength(LStrBin, Length(LStrData)); + ConvertHexToBin(@LStrData[1], @LStrBin[1], LIntData, + SwapNibbles, LIntData); + LStrData := Copy(LStrBin, 1, LIntData); + end; + Result := S_OK; + end; + finally + if Assigned(LPtrLock) then + GlobalUnlock(LRecStg.hGlobal); + end; + finally + ReleaseStgMedium(LRecStg); + end + end; + + CheckUnit(LIntData); + + if (LStrData <> '') and (LIntData > 0) then + begin + // insert the data + case Operation of + oleDrop: + begin + LIntPos := DropPosition; + if LIntPos < 0 then + Result := E_FAIL + else + begin + if FOleDragging and (dwEffect = DROPEFFECT_MOVE) then + begin + FFixedFileSizeOverride := True; + try + // delete selection if we have moved data within ourself + FOleWasTarget := True; + if LIntPos > Min(SelStart, SelEnd) then + Dec(LIntPos, SelCount); + DeleteSelection; + if LIntPos >= DataSize then + Appendbuffer(@LStrData[1], LIntData, UNDO_DROPPED) + else + InsertBuffer(@LStrData[1], LIntData, LIntPos, + UNDO_DROPPED); + finally + FFixedFileSizeOverride := False; + end; + end + else + begin + if LIntPos >= DataSize then + begin + if not NoSizeChange then + Appendbuffer(@LStrData[1], LIntData, UNDO_DROPPED) + end + else + begin + if not NoSizeChange then + begin + if IsSelected(LIntPos) then + ReplaceSelection(@LStrData[1], LIntData, UNDO_DROPPED) + else + InsertBuffer(@LStrData[1], LIntData, LIntPos, + UNDO_DROPPED) + end + else + begin + if (SelCount = 0) or (not IsSelected(LIntPos)) then + Replace(@LStrData[1], LIntPos, LIntData, LIntData, + UNDO_DROPPED) + else + ReplaceSelection(@LStrData[1], LIntData, UNDO_DROPPED) + end; + end; + end; + end; + end; + oleClipboard: PasteData(PChar(LStrData), LIntData, UNDO_PASTECB); + end; + end + else + Result := E_FAIL; + end; + + finally + LStrData := ''; + OldCursor; + end; + if Result <> S_OK then + dwEffect := DROPEFFECT_NONE; +end; + +// do we support one of the provided idataobject formats? + +function TMPHexEditorEx.SupportsOLEData(const dataObj: IDataObject; const + grfKeyState: integer; const pt: TPoint; var dwEffect: integer; const + Operation: + TMPHOLEOperation): HRESULT; +begin + Result := S_FALSE; + if (not ReadOnlyView) and OLEHasSupportedFormat(dataObj, GetMyOLEFormats, + FOLEFormat[Operation]) then + Result := S_OK; + if FOLEFormat[Operation] = CF_HDROP then + if dwEffect = DROPEFFECT_MOVE then + dwEffect := DROPEFFECT_LINK; +end; + +function TMPHexEditorEx.OLEHasSupportedFormat(const dataObj: IDataObject; const + Formats: array of TClipFormat; var Format: TClipFormat): boolean; +var + LIntLoop: integer; + LObjEnum: TFormatEnum; +begin + Result := False; + LObjEnum := TFormatEnum.Create(dataObj); + try + if Length(Formats) > 0 then + for LIntLoop := Low(Formats) to High(Formats) do + if LObjEnum.HasFormat(Formats[LIntLoop]) then + begin + Format := Formats[LIntLoop]; + Result := True; + Break; + end; + finally + LObjEnum.Free; + end; +end; + +// modify effect (move/copy/link) depending on key state and data format + +function TMPHexEditorEx.ModifyOLEDropEffect(const grfKeyState: integer; const + pt: TPoint; var dwEffect: integer): HRESULT; +begin + Result := S_OK; + if FOleDragging then + begin + if ReadOnlyView then + dwEffect := DROPEFFECT_COPY + else + begin + if Bool(grfKeyState and MK_CONTROL) then + dwEffect := DROPEFFECT_COPY + else + dwEffect := DROPEFFECT_MOVE; + end; + end + else + begin + if Bool(grfKeyState and MK_SHIFT) and (not ReadOnlyView) then + dwEffect := DROPEFFECT_MOVE + else + dwEffect := DROPEFFECT_COPY; + + if FOLEFormat[oleDrop] = CF_HDROP then + if dwEffect = DROPEFFECT_MOVE then + dwEffect := DROPEFFECT_LINK; + end; +end; + +// return a clipformat array with all supported formats + +function TMPHexEditorEx.GetMyOLEFormats: TClipFormats; +begin + if FSupportsOtherClipFormats then + SetLength(Result, 17) + else + SetLength(Result, 2); + Result[0] := CF_MPHEXEDITOR; + Result[1] := CF_HDROP; + if FSupportsOtherClipFormats then + begin + Result[2] := CF_TEXT; + Result[3] := CF_RTF; + Result[4] := CF_UNICODETEXT; + Result[5] := CF_BITMAP; + Result[6] := CF_PALETTE; + Result[7] := CF_METAFILEPICT; + Result[8] := CF_TIFF; + Result[9] := CF_OEMTEXT; + Result[10] := CF_DIB; + Result[11] := CF_RIFF; + Result[12] := CF_WAVE; + Result[13] := CF_ENHMETAFILE; + Result[14] := CF_LOCALE; + Result[15] := CF_REGEDIT_HEXDATA; + Result[16] := CF_HTML; + end; +end; + +// reset droptarget helper interface on window destruction + +procedure TMPHexEditorEx.WMDestroy(var Message: TWMDestroy); +begin + inherited; + if ComponentState * [csLoading, csDesigning] = [] then + FDropTarget.Active := False; +end; + +// internal + +function TMPHexEditorEx.DumpUndoStorage(const FileName: string): boolean; +begin + Result := False; + if Assigned(UndoStorage) then + try + Result := True; + UndoStorage.SaveToFile(FileName); + except + Result := False; + end; +end; + +// set new printing options + +procedure TMPHexEditorEx.SetPrintOptions(const Value: TMPHPrintOptions); +begin + FPrintOptions.Assign(Value); +end; + +// internal: draw the specified page to a canvas using the given margins and options + +function TMPHexEditorEx.PrintToCanvas(ACanvas: TCanvas; const APage: integer; + const AMargins: TRect): integer; +var + LObjPrinter: TMPHCanvasPrinter; + LSetFlags: TMPHPrintFlags; +begin + if APage < 0 then + raise EMPHexEditor.Create(ERR_INVALID_PAGE); + WaitCursor; + LSetFlags := FPrintOptions.Flags; + try + if SelCount = 0 then + Exclude(LSetFlags, pfSelectionOnly); + LObjPrinter := TMPHCanvasPrinter.Create(self, ACanvas, LSetFlags, AMargins, + FPrintOptions.FHeaders); + try + Result := LObjPrinter.Pages; + if APage > Result then + raise EMPHexEditor.Create(ERR_INVALID_PAGE); + if APage > 0 then + if LObjPrinter.DrawOrCalc(False, APage) < 1 then + raise EMPHexEditor.Create(ERR_PRINTING_FAILED); + finally + LObjPrinter.Free; + end; + finally + OldCursor; + end; +end; + +// create a metafile with the selected page as a print preview + +function TMPHexEditorEx.PrintPreview(const Page: integer): TMetaFile; +var + LcnvMeta: TMetaFileCanvas; + LIntHeight, LIntWidth: integer; +begin + LIntWidth := GetDeviceCaps(Printer.Handle, HORZRES); + LIntHeight := GetDeviceCaps(Printer.Handle, VERTRES); + Result := TMetaFile.Create; + with Result do + begin + Width := LIntWidth; + Height := LIntHeight; + LcnvMeta := TMetaFileCanvas.Create(Result, 0); + with LcnvMeta do + try + if FUseEditorFontForPrinting then + Font.Assign(self.Font) + else + Font.Assign(self.FPrintFont); + SetMapMode(Handle, MM_ANISOTROPIC); + SetWindowExtEx(Handle, LIntWidth, LIntHeight, nil); + SetViewPortExtEx(Handle, LIntWidth, LIntHeight, nil); + Font.Size := Round(Font.Size * GetDeviceCaps(Printer.Handle, LOGPIXELSY) / + Screen.PixelsPerInch); + Brush.Style := bsSolid; + Brush.Color := clWhite; + FillRect(Rect(0, 0, LIntWidth, LIntHeight)); + FPrintPages := PrintToCanvas(LcnvMeta, Page, PrinterMarginRect); + finally + Free; + end; + end; +end; + +// print the given page + +procedure TMPHexEditorEx.Print(const Page: integer); +var + LmtfTemp: TMetaFile; +begin + if Page < 1 then + raise EMPHexEditor.Create(ERR_INVALID_PAGE); + LmtfTemp := PrintPreview(Page); + with LmtfTemp do + try + Printer.Canvas.StretchDraw(Rect(0, 0, Printer.PageWidth, + Printer.PageHeight), LmtfTemp); + finally + Free; + end; +end; + +// calculate margins from margins in print options + +function TMPHexEditorEx.PrinterMarginRect: TRect; +var + LIntLogX, LIntLogY, LIntPhysWidth, LIntPhysHeight: integer; +begin + Result := FPrintOptions.FMargins; + LIntLogX := GetDeviceCaps(Printer.Handle, LOGPIXELSX); + // pixels per inch in x dir + LIntLogY := GetDeviceCaps(Printer.Handle, LOGPIXELSY); + // pixels per inch in Y dir + LIntPhysWidth := Printer.PageWidth; + LIntPhysHeight := Printer.PageHeight; + Result.Left := Round(Result.Left / 25.4 * LIntLogX); + Result.Top := Round(Result.Top / 25.4 * LIntLogY); + Result.Right := LIntPhysWidth - Round(Result.Right / 25.4 * LIntLogX); + Result.Bottom := LIntPhysHeight - Round(Result.Bottom / 25.4 * LIntLogY); +end; + +// calculate page count + +function TMPHexEditorEx.PrintNumPages: integer; +begin + PrintPreview(0).Free; + Result := FPrintPages; +end; + +// empty or flush ole contents in clipboard that have been stored by this instance + +procedure TMPHexEditorEx.ReleaseClipboard(const Flush: boolean); +begin + if OwnsClipboard then + begin + if Flush then + OleCheck(OleFlushClipboard) + else + OleSetClipboard(nil); + end; +end; + +// is there data on the clipboard created by us? + +function TMPHexEditorEx.OwnsClipBoard: boolean; +begin + Result := OleIsCurrentClipBoard(FClipData) = S_OK; +end; + +procedure TMPHexEditorEx.SetPrintFont(const Value: TFont); +begin + FPrintFont.Assign(Value); + FUseEditorFontForPrinting := False; +end; + +{$IFDEF DELPHI6UP} + +procedure TMPHexEditorEx.DoContextPopup(MousePos: TPoint; var Handled: boolean); +begin + inherited; + if (not Handled) and (Assigned(FOffsetPopupMenu)) then + begin + // is mouse over offset col + with MousePos do + if ((X > -1) and (X < (ColWidths[0] + ColWidths[1]))) or ((Y > -1) and (Y + < (RowHeights[0] + RowHeights[1]))) then + begin + // in fixed range + if FOffsetPopupMenu.AutoPopup then + begin + Handled := True; + SendCancelMode(nil); + FOffsetPopupMenu.PopupComponent := Self; + MousePos := ClientToScreen(MousePos); + if InvalidPoint(MousePos) then + MousePos := ClientToScreen(Point(0, 0)); + FOffsetPopupMenu.Popup(MousePos.X, MousePos.Y); + end; + end; + end +end; +{$ENDIF} + +procedure TMPHexEditorEx.SetOffsetPopupMenu(const Value: TPopupMenu); +begin + FOffsetPopupMenu := Value; + if Assigned(Value) then + with Value do + begin + ParentBiDiModeChanged(self); + FreeNotification(self); + end; +end; + +function TMPHexEditorEx.GetOffsetPopupMenu: TPopupMenu; +begin + Result := FOffsetPopupMenu; +end; + +procedure TMPHexEditorEx.Notification(AComponent: TComponent; Operation: + TOperation); +begin + inherited; + if AComponent = FOffsetPopupMenu then + if Operation = opRemove then + OffsetPopupMenu := nil; +end; + +function TMPHexEditorEx.CanCreateUndo(const aKind: TMPHUndoFlag; + const aCount, aReplCount: integer): Boolean; +begin + Result := inherited CanCreateUndo(aKind, aCount, aReplCount); + if Result and (UndoStorage.UpdateCount > 0) then + FModifiedNoUndo := True; +end; + +function TMPHexEditorEx.GetBookmarksAsString: string; +var + LIntLoop, + LIntCheck: integer; +begin + Result := ''; + for LIntLoop := Low(TMPHBookMarks) to High(TMPHBookMarks) do + with BookMark[LIntLoop] do + if mPosition <> -1 then + Result := Result + IntToRadixLen(LIntLoop, 16, 2) + + IntToRadixLen(mPosition, 16, 16) + + IntToRadixLen(integer(mInCharField), + 16, 2); + if Result <> '' then + begin + LIntCheck := 0; + for LIntLoop := 1 to Length(Result) do + LIntCheck := LIntCheck + Ord(Result[LIntLoop]); + Result := IntToRadixLen(LIntCheck, 16, 8) + Result; + end; +end; + +procedure TMPHexEditorEx.SetBookMarksAsString(Value: string); +var + LIntLoop, LIntCheck, LIntCheck1, LIntPos: integer; + LBoolChars: boolean; + LRecBook: TMPHBookMark; +begin + BeginUpdate; + FBookmarksNoChange := True; + try + // empty all bookmarks + LRecBook.mPosition := -1; + LRecBook.mInCharField := InCharField; + for LIntLoop := Low(TMPHBookMarks) to High(TMPHBookMarks) do + Bookmark[LIntLoop] := LRecBook; + + if Value <> '' then + begin + try + // check sum + LIntCheck := RadixToInt(Copy(Value, 1, 8), 16); + Delete(Value, 1, 8); + + // calc check sum + LIntCheck1 := 0; + for LIntLoop := 1 to Length(Value) do + LIntCheck1 := LIntCheck1 + Ord(Value[LIntLoop]); + + if LIntCheck1 <> LIntCheck then + raise EMPHexEditor.Create(ERR_INVALID_BOOKFMT); + + // set bookmarks + //for LIntLoop := Low(TMPHBookMarks) to High(TMPHBookMarks) do + while Value <> '' do + begin + LIntLoop := RadixToInt(Copy(Value, 1, 2), 16); + Delete(Value, 1, 2); + LIntPos := RadixToInt(Copy(Value, 1, 16), 16); + Delete(Value, 1, 16); + LBoolChars := boolean(RadixToInt(Copy(Value, 1, 2), 16)); + Delete(Value, 1, 2); + LRecBook := Bookmark[LIntLoop]; + if (LRecBook.mPosition <> LIntPos) or (LRecBook.mInCharField <> + LBoolChars) then + begin + LRecBook.mPosition := LIntPos; + LRecBook.mInCharField := LBoolChars; + Bookmark[LIntLoop] := LRecBook; + end; + end; + + except + raise EMPHexEditor.Create(ERR_INVALID_BOOKFMT); + end; + end; + finally + EndUpdate; + FBookmarksNoChange := False; + BookmarkChanged; + end; +end; + +{$IFDEF DELPHI6UP} +const + PUBLIC_PROPS: array[0..66] of string = ('ShowRuler', + 'DrawGutter3D', + 'CreateBackup', + 'BackupExtension', + 'OleDragDrop', + 'ClipboardAsHexText', + 'FlushClipboardAtShutDown', + 'SupportsOtherClipFormats', + 'UseEditorFontForPrinting', + 'ZoomOnWheel', + 'BytesPerRow', + 'BytesPerColumn', + 'Translation', + 'OffsetFormat', + 'CaretKind', + 'FocusFrame', + 'SwapNibbles', + 'MaskChar', + 'NoSizeChange', + 'AllowInsertMode', + 'DrawGridLines', + 'ReadOnlyView', + 'HideSelection', + 'GraySelectionIfNotFocused', + 'GutterWidth', + 'MaxUndo', + 'InsertMode', + 'HexLowerCase', + 'Colors.Background', + 'Colors.ChangedBackground', + 'Colors.ChangedText', + 'Colors.CursorFrame', + 'Colors.NonFocusCursorFrame', + 'Colors.Offset', + 'Colors.OddColumn', + 'Colors.EvenColumn', + 'Colors.CurrentOffsetBackground', + 'Colors.OffsetBackGround', + 'Colors.CurrentOffset', + 'Colors.ActiveFieldBackground', + 'Colors.Grid', + 'PrintFont.Charset', + 'PrintFont.Color', + 'PrintFont.Name', + 'PrintFont.Size', + 'PrintFont.Style', + 'PrintOptions.MarginLeft', + 'PrintOptions.MarginTop', + 'PrintOptions.MarginRight', + 'PrintOptions.MarginBottom', + 'PrintOptions.PageHeader', + 'PrintOptions.PageFooter', + 'PrintOptions.Flags', + 'Font.Charset', + 'Font.Color', + 'Font.Name', + 'Font.Size', + 'Font.Style', + 'BytesPerUnit', + 'RulerBytesPerUnit', + 'ShowPositionIfNotFocused', + 'UnicodeChars', + 'UnicodeBigEndian', + 'BytesPerBlock', + 'SeparateBlocksInCharField', + 'FindProgress', + 'RulerNumberBase' + ); + +function TMPHexEditorEx.IsPropPublic(const PropName: string): boolean; +var + LIntLoop: integer; +begin + Result := False; + for LIntLoop := Low(PUBLIC_PROPS) to High(PUBLIC_PROPS) do + if AnsiCompareText(PropName, PUBLIC_PROPS[LIntLoop]) = 0 then + begin + Result := True; + Break; + end; + if Result and Assigned(FOnQueryPublicProperty) then + FOnQueryPublicProperty(self, PropName, Result); +end; + +function TMPHexEditorEx.GetPropertiesAsString: string; + + procedure Recurse(Ref: TObject; const Prefix: string); + var + LPtrProps: PPropList; + LIntCount: integer; + begin + if Ref = nil then + Exit; + LIntCount := GetPropList(Ref, LPTrProps); + if LIntCount > 0 then + try + for LIntCount := 0 to Pred(LIntCount) do + with LPtrProps^[LIntCount]^ do + if PropType^^.Kind = tkClass then + Recurse(GetObjectProp(Ref, Name), Prefix + Name + '.') + else if IsPropPublic(Prefix + Name) then + Result := Result + Prefix + Name + '=' + + string(GetPropValue(Ref, Name)) + #13#10; + + finally + FreeMem(LPtrProps); + end; + end; +begin + Result := ''; + Recurse(self, ''); +end; + +procedure TMPHexEditorEx.SetPropertiesAsString(const Value: string); +var + LStrData: TStrings; + LIntLoop, LIntDot: integer; + LStrProp, LStrVal: string; + LObjProp: TObject; +begin + BeginUpdate; + try + LStrData := TStringList.Create; + with LStrData do + try + Text := Value; + if Count > 0 then + for LIntLoop := 0 to Pred(Count) do + begin + LStrProp := Names[LIntLoop]; + if IsPropPublic(LStrProp) then + begin + LStrVal := Values[LStrProp]; + LObjProp := self; + repeat + LIntDot := Pos('.', LStrProp); + if LIntDot > 0 then + begin + LObjProp := GetObjectProp(LObjProp, Copy(LStrProp, 1, LIntDot - + 1)); + System.Delete(LStrProp, 1, LIntDot); + end; + until LIntDot = 0; + if Assigned(LObjProp) then + SetPropValue(LObjProp, LStrProp, LStrVal); + end; + end; + finally + Free; + end; + finally + EndUpdate; + end; +end; +{$ENDIF} + +procedure TMPHexEditorEx.Paint; +begin + //inherited; + if FPaintUpdateCounter < 1 then + inherited; +end; + +procedure TMPHexEditorEx.DblClick; +var + LptMouse: TPoint; + LIntPos: Integer; +begin + // get the position where the mouse is + Windows.GetCursorPos(LptMouse); + LptMouse := ScreenToClient(LptMouse); + with CheckMouseCoord(LptMouse.X, LptMouse.Y) do + LIntPos := GetPosAtCursor(X, Y); + if (LIntPos > -1) and (LIntPos < DataSize) then + begin + NewSelection(LIntPos, LIntPos); + FHasDoubleClicked := True; + MouseUpCanResetSel := False; + end; + inherited; +end; + +procedure TMPHexEditorEx.PasteData(P: Pointer; const ACount: integer; + const UndoDesc: string); +var + LgrcCoords: TGridCoord; + LIntPos: integer; +begin + // assure that we are positionned at the beginning of a unit + LIntPos := 0; + if SelCount = 0 then + begin + LIntPos := GetPosAtCursor(Col, Row); + if (LIntpos mod BytesPerUnit) <> 0 then + begin + while (LIntPos mod BytesPerUnit) <> 0 do + Dec(LIntPos); + LGrcCoords := GetCursorAtPos(LIntPos, InCharField); + with LGrcCoords do + begin + Col := X; + Row := Y; + end; + end; + end; + if (SelCount = 0) and NoSizeChange then + begin + SelStart := LIntPos; + SelEnd := Min(DataSize - 1, LIntPos + ACount - 1); + end; + ReplaceSelection(P, ACount, UndoDesc); +end; + +procedure TMPHexEditorEx.BookmarkChanged; +begin + if not FBookmarksNoChange then + inherited; +end; + +function TMPHexEditorEx.UndoBeginUpdate(const StrUndoDesc: string = ''): + integer; +begin + if (UndoStorage.UpdateCount = 0) and (FCreateUndoOnUndoUpdate or (StrUndoDesc + <> '')) then + begin + FCreateUndoOnUndoUpdate := True; + CreateRangeUndo(0, 0, StrUndoDesc); + FModifiedNoUndo := False; + end; + Result := inherited UndoBeginUpdate; +end; + +function TMPHexEditorEx.UndoEndUpdate: integer; +begin + Result := inherited UndoEndUpdate; + if (Result = 0) and FCreateUndoOnUndoUpdate then + begin + if FModifiedNoUndo then + FModifiedNoUndo := False + else + begin + UndoStorage.RemoveLastUndo; + end; + end; +end; + +procedure TMPHexEditorEx.WriteBuffer(const Buffer; const Index, + Count: Integer); +begin + inherited; + FModified := True; + if UndoStorage.UpdateCount > 0 then + FModifiedNoUndo := True; +end; + +{ TMPHDropTarget } + +// constructor + +constructor TMPHDropTarget.Create(Editor: TMPHexEditorEx); +begin + inherited Create; + FEditor := Editor; + FEditorHandle := 0; + FActive := False; + _AddRef; // don't free automatically because it's an object in TMPHexEditorEx +end; + +// tinterfacedobject auto-destructor hook + +procedure TMPHDropTarget.BeforeDestruction; +begin + Dec(FRefCount); // see create above + Active := False; + inherited; +end; + +// do we support data format? if yes, set desired drop effect + +function TMPHDropTarget.DragEnter(const dataObj: IDataObject; grfKeyState: + integer; pt: TPoint; var dwEffect: integer): HResult; +begin + Result := FEditor.SupportsOLEData(dataObj, grfKeyState, pt, dwEffect, + oleDrop); + if Result = S_OK then + begin + Result := FEditor.ModifyOLEDropEffect(grfKeyState, pt, dwEffect); + if Result = S_OK then + begin + pt := FEditor.ScreenToClient(pt); + FEditor.ShowDragCell(pt.X, pt.Y) + end; + end + else + dwEffect := DROPEFFECT_NONE; +end; + +// dragged out of window + +function TMPHDropTarget.DragLeave: HResult; +begin + Result := S_OK; + FEditor.HideDragCell; +end; + +// dragging over window + +function TMPHDropTarget.DragOver(grfKeyState: integer; pt: TPoint; var dwEffect: + integer): HResult; +begin + Result := FEditor.ModifyOLEDropEffect(grfKeyState, pt, dwEffect); + if Result = S_OK then + begin + pt := FEditor.ScreenToClient(pt); + FEditor.ShowDragCell(pt.X, pt.Y) + end + else + begin + dwEffect := DROPEFFECT_NONE; + FEditor.HideDragCell; + end; +end; + +// dropped! + +function TMPHDropTarget.Drop(const dataObj: IDataObject; grfKeyState: integer; + pt: TPoint; var dwEffect: integer): HResult; +begin + try + Result := FEditor.SupportsOLEData(dataObj, grfKeyState, pt, dwEffect, + oleDrop); + if Result = S_OK then + begin + Result := FEditor.ModifyOLEDropEffect(grfKeyState, pt, dwEffect); + if Result = S_OK then + try + Result := FEditor.InsertOLEData(dataObj, grfKeyState, pt, dwEffect, + oleDrop); + except + Result := E_FAIL; + ShowException(ExceptObject, ExceptAddr); + end; + end; + finally + FEditor.HideDragCell; + end; +end; + +// retrieve window handle from associated hex editor and (de)activate drop target + +procedure TMPHDropTarget.SetActive(const Value: boolean); +begin + if FActive <> Value then + begin + FActive := Value; + if not Value then + begin + OleCheck(RevokeDragDrop(FEditorHandle)); + OleCheck(CoLockObjectExternal(self, False, True)); + end + else + begin + FEditorHandle := FEditor.Handle; + OleCheck(RegisterDragDrop(FEditor.Handle, self)); + OleCheck(CoLockObjectExternal(self, True, False)); + end; + end; +end; + +{ TFormatEnum } + +// constructor + +constructor TFormatEnum.Create(const dataObject: IDataObject); +var + LRecFormat: TFormatETC; + LifEnum: IEnumFormatETC; +begin + FFormats := nil; + if Succeeded(dataObject.EnumFormatEtc(DATADIR_GET, LifEnum)) then + begin + while LifEnum.Next(1, LRecFormat, nil) = S_OK do + begin + SetLength(FFormats, Succ(Length(FFormats))); + FFormats[Pred(Length(FFormats))] := LRecFormat; + end; + end; +end; + +// destructor + +destructor TFormatEnum.Destroy; +begin + FFormats := nil; + inherited; +end; + +// return the desired formatetc struct + +function TFormatEnum.GetFormatETC(const cfFormat: TClipFormat): TFormatETC; +var + LBoolOK: boolean; + LIntLoop: integer; +begin + LBoolOK := False; + if Length(FFormats) > 0 then + for LIntLoop := 0 to Pred(Length(FFormats)) do + if FFormats[LIntLoop].cfFormat = cfFormat then + begin + LBoolOK := True; + Result := FFormats[LIntLoop]; + Break; + end; + if not LBoolOK then + FillChar(Result, sizeof(Result), #$FF); +end; + +// is the desired format available? + +function TFormatEnum.HasFormat(const cfFormat: TClipFormat): boolean; +var + LIntLoop: integer; +begin + Result := False; + if Length(FFormats) > 0 then + for LIntLoop := 0 to Pred(Length(FFormats)) do + if FFormats[LIntLoop].cfFormat = cfFormat then + begin + Result := True; + Break; + end; +end; + +{ TMPHEnumFormatETC } + +// constructor + +constructor TMPHEnumFormatETC.Create; +begin + inherited Create; + FIndex := 0; + with FFormats[0] do + begin + cfFormat := CF_MPHEXEDITOR; + ptd := nil; + dwAspect := DVASPECT_CONTENT; + lindex := -1; + tymed := TYMED_HGLOBAL; + end; + with FFormats[1] do + begin + cfFormat := CF_TEXT; + ptd := nil; + dwAspect := DVASPECT_CONTENT; + lindex := -1; + tymed := TYMED_HGLOBAL; + end; + with FFormats[2] do + begin + cfFormat := CF_FILEDESCRIPTOR; + ptd := nil; + dwAspect := DVASPECT_CONTENT; + lindex := -1; + tymed := TYMED_HGLOBAL; + end; + with FFormats[3] do + begin + cfFormat := CF_FILECONTENTS; + ptd := nil; + dwAspect := DVASPECT_CONTENT; + lindex := -1; + tymed := TYMED_HGLOBAL; + end; +end; + +// clone myself + +function TMPHEnumFormatETC.Clone(out Enum: IEnumFormatEtc): HResult; +begin + Enum := TMPHEnumFormatETC.Create; + Result := S_OK; +end; + +// iterate over all format records + +function TMPHEnumFormatETC.Next(celt: integer; out elt; pceltFetched: PLongint): + HResult; +var + LIntLoop: integer; + LRecOut: packed array[0..MY_SUPPORTED_FORMATS - 1] of TFormatETC absolute elt; +begin + LIntLoop := 0; + while (LIntLoop < celt) and (FIndex < MY_SUPPORTED_FORMATS) do + begin + LRecOut[LIntLoop] := FFormats[FIndex]; + Inc(FIndex); + Inc(LIntLoop); + end; + + if pceltFetched <> nil then + pceltFetched^ := LIntLoop; + + if LIntLoop = celt then + Result := S_OK + else + Result := S_FALSE; +end; + +// reset iteration + +function TMPHEnumFormatETC.Reset: HResult; +begin + FIndex := 0; + Result := S_OK; +end; + +// skip entries + +function TMPHEnumFormatETC.Skip(celt: integer): HResult; +begin + if (celt < MY_SUPPORTED_FORMATS - FIndex) then + begin + FIndex := FIndex + celt; + Result := S_OK; + end + else + Result := S_FALSE; +end; + +{ TMPHDropSource } + +// use default ole dnd cursors + +function TMPHDropSource.GiveFeedback(dwEffect: integer): HResult; +begin + case dwEffect and 7 of + DROPEFFECT_NONE, + DROPEFFECT_COPY, + DROPEFFECT_MOVE: Result := DRAGDROP_S_USEDEFAULTCURSORS; + else + Result := E_INVALIDARG; + end; +end; + +// standard behaviour + +function TMPHDropSource.QueryContinueDrag(fEscapePressed: BOOL; grfKeyState: + integer): HResult; +begin + if fEscapePressed then + Result := DRAGDROP_S_CANCEL + else if (grfKeyState and MK_LBUTTON) = 0 then + Result := DRAGDROP_S_DROP + else + Result := S_OK; +end; + +{ TMPHDataObject } + +// constructor + +constructor TMPHDataObject.Create(Data: Pointer; DataSize: integer; + ScrapFileName: ShortString; TextAsHex, SwapNibbles: boolean); +begin + inherited Create; + FData := nil; + FHasDropEffect := False; + FTextAsHex := TextAsHex; + FSwapNibbles := SwapNibbles; + if Assigned(Data) and (DataSize > 0) then + begin + FDataSize := DataSize; + FFileName := Format(STR_SCRAPFILE, + [ChangeFileExt(ExtractFileName(ScrapFileName), ''), + ExtractFileExt(ScrapFileName)]); + GetMem(FData, DataSize); + Move(Data^, FData^, FDataSize); + end; +end; + +constructor TMPHDataObject.CreateFromStream(Stream: TStream; Position, DataSize: + integer; ScrapFileName: ShortString; TextAsHex, SwapNibbles: boolean); +begin + inherited Create; + FData := nil; + FHasDropEffect := False; + FTextAsHex := TextAsHex; + FSwapNibbles := SwapNibbles; + if Assigned(Stream) and (DataSize > 0) then + begin + FDataSize := DataSize; + FFileName := Format(STR_SCRAPFILE, + [ChangeFileExt(ExtractFileName(ScrapFileName), ''), + ExtractFileExt(ScrapFileName)]); + GetMem(FData, DataSize); + Stream.Position := Position; + Stream.ReadBuffer(FData^, FDataSize); + end; +end; + +// destructor hook + +procedure TMPHDataObject.BeforeDestruction; +begin + if Assigned(FData) and (FDataSize > 0) then + FreeMem(FData); + FData := nil; + FDataSize := 0; + inherited; +end; + +// advise not supported + +function TMPHDataObject.DAdvise(const formatetc: TFormatEtc; advf: integer; const + advSink: IAdviseSink; out dwConnection: integer): HResult; +begin + Result := OLE_E_ADVISENOTSUPPORTED; +end; + +function TMPHDataObject.DUnadvise(dwConnection: integer): HResult; +begin + Result := OLE_E_ADVISENOTSUPPORTED; +end; + +function TMPHDataObject.EnumDAdvise(out enumAdvise: IEnumStatData): HResult; +begin + Result := OLE_E_ADVISENOTSUPPORTED; +end; + +// create a formetc enumerator, only for getdata + +function TMPHDataObject.EnumFormatEtc(dwDirection: integer; out enumFormatEtc: + IEnumFormatEtc): HResult; +begin + enumFormatETC := nil; + if dwDirection = DATADIR_GET then + begin + enumFormatETC := TMPHEnumFormatETC.Create; + Result := S_OK; + end + else + Result := E_NOTIMPL; +end; + +// always same format + +function TMPHDataObject.GetCanonicalFormatEtc(const formatetc: TFormatEtc; out + formatetcOut: TFormatEtc): HResult; +begin + formatetcOut := formatetc; + formatetcOut.ptd := nil; + Result := DATA_S_SAMEFORMATETC; +end; + +// render and return data depending on the desired format + +function TMPHDataObject.GetData(const formatetcIn: TFormatEtc; out medium: + TStgMedium): HResult; +var + LIntDataSize: integer; + LPtrLocal: PClipData; + LRecSysTime: TSystemTime; +begin + FillChar(medium, sizeof(medium), #0); + Result := QueryGetData(formatetcIn); + if Result = S_OK then + begin + if formatetcIn.cfFormat = CF_MPHEXEDITOR then + LIntDataSize := sizeof(TClipData) - 1 + FDataSize + else if formatetcIn.cfFormat = CF_TEXT then + begin + if not FTextAsHex then + LIntDataSize := Min(FDataSize, StrLen(PChar(FData))) + 1 + else + LIntDataSize := (FDataSize * 2) + 1; + end + else if formatetcIn.cfFormat = CF_FILEDESCRIPTOR then + LIntDataSize := sizeof(TFileGroupDescriptor) + else + LIntDataSize := FDataSize; + medium.hGlobal := GlobalAlloc(GMEM_SHARE or GMEM_MOVEABLE or GMEM_ZEROINIT, + LIntDataSize); + if medium.hGlobal = 0 then + Result := E_OUTOFMEMORY + else + begin + LPtrLocal := GlobalLock(medium.hGlobal); + try + try + medium.tymed := TYMED_HGLOBAL; + if formatetcIn.cfFormat = CF_TEXT then + begin + if FTextAsHex then + ConvertBinToHex(FData, PChar(LPtrLocal), FDataSize, FSwapNibbles) + else + Move(FData^, LPtrLocal^, LIntDataSize - 1); + PChar(LPtrLocal)[LIntDataSize - 1] := #0; + end + else if formatetcIn.cfFormat = CF_MPHEXEDITOR then + begin + LPtrLocal^.Signature := CLIP_SIG; + LPtrLocal^.Version := CLIP_VER; + LPtrLocal^.Size := FDataSize; + Move(FData^, LPtrLocal^.Data, FDataSize); + end + else if formatetcIn.cfFormat = CF_FILEDESCRIPTOR then + begin + with PFileGroupDescriptor(LPtrLocal)^ do + begin + cItems := 1; + with fgd[0] do + begin + dwFlags := FD_FILESIZE or FD_WRITESTIME; // or FD_PROGRESSUI; + nFileSizeLow := FDataSize; + nFileSizeHigh := 0; + GetSystemTime(LRecSysTime); + SystemTimeToFileTime(LRecSysTime, ftLastWriteTime); + Move(FFileName[1], cFileName, Min(Length(FFileName), + sizeof(cFileName) - 1)); + end; + end; + end + else + begin + Move(FData^, LPtrLocal^, LIntDataSize); + end; + except + Result := E_OUTOFMEMORY; + GlobalFree(medium.hGlobal); + medium.hGlobal := 0; + end; + finally + GlobalUnlock(medium.hGlobal); + end; + end; + end; +end; + +// what's this? + +function TMPHDataObject.GetDataHere(const formatetc: TFormatEtc; + out medium: TStgMedium): HResult; +begin + Result := DV_E_FORMATETC; +end; + +// do we support the desired format? + +function TMPHDataObject.QueryGetData(const formatetc: TFormatEtc): HResult; +begin + Result := DV_E_FORMATETC; + with formatetc do + begin + if dwAspect <> DVASPECT_CONTENT then + Result := DV_E_DVASPECT + else if not Bool(tymed and TYMED_HGLOBAL) + {// multiple tymeds may be queried (e.g. from explorer, wordpad...)}then + Result := DV_E_TYMED + else if (lindex <> -1) and ((cfFormat <> CF_FILECONTENTS) and (Lindex <> 0)) + then + Result := DV_E_LINDEX + else if (cfFormat = CF_MPHEXEDITOR) or (cfFormat = CF_TEXT) or + (cfFormat = CF_FILEDESCRIPTOR) or (cfFormat = CF_FILECONTENTS) then + Result := S_OK; + end; +end; + +// check for dropeffect calls (dodragdrop not always return the real effect) + +function TMPHDataObject.SetData(const formatetc: TFormatEtc; var medium: + TStgMedium; fRelease: BOOL): HResult; +var + LPtrEffect: PDWORD; +begin + Result := E_NOTIMPL; + if ((formatetc.cfFormat = CF_PERFORMEDDROPEFFECT) or (formatetc.cfFormat = + CF_LOGICALPERFORMEDDROPEFFECT)) and (medium.tymed = TYMED_HGLOBAL) then + begin + Result := S_OK; + // check drop effect + LPtrEffect := GlobalLock(medium.hGlobal); + try + FHasDropEffect := True; + FDropEffect := LPtrEffect^; + finally + GlobalUnLock(medium.hGlobal); + end; + end; + if fRelease then + ReleaseStgMedium(medium); +end; + +{ TMPHCanvasPrinter } + +// init + +constructor TMPHCanvasPrinter.Create(AEditor: TMPHexEditorEx; ACanvas: TCanvas; + AFlags: TMPHPrintFlags; AMargins: TRect; AHeaders: TMPHPrintHeaders); +begin + inherited Create; + FMargins := AMargins; + FCanvas := ACanvas; + FFlags := AFlags; + FEditor := AEditor; + FHeaders[0] := AHeaders[0]; + FHeaders[1] := AHeaders[1]; + GetLinesPerPage; +end; + +// convert %s variables + +function TMPHCanvasPrinter.BuildHeader(const S: string; const Page: integer): + string; +var + LIntLoop: integer; +begin + Result := ''; + LIntLoop := 1; + while LIntLoop <= Length(S) do + begin + if (S[LIntLoop] = '%') and (LIntLoop < Length(S)) then + begin + Inc(LIntLoop); + case S[LIntLoop] of + 'f': Result := Result + ExtractFileName(FEditor.Filename); + 'F': Result := Result + FEditor.Filename; + 'p': Result := Result + IntToRadix(Page, 10); + 'P': Result := Result + IntToRadix(FPages, 10); + 't': Result := Result + TimeToStr(now); + 'd': Result := Result + DateToStr(now); + '>': + begin + if not FEditor.UnicodeChars then + Result := Result + MPHTranslationDesc + [FEditor.Translation] + else + begin + if not FEditor.UnicodeBigEndian then + Result := Result + MPH_UC + else + Result := Result + MPH_UC_BE + end; + end; + '<': + begin + if not FEditor.UnicodeChars then + Result := Result + + MPHTranslationDescShort + [FEditor.Translation] + else + begin + if not FEditor.UnicodeBigEndian then + Result := Result + MPH_UC_S + else + Result := Result + MPH_UC_BE_S + end; + end + else + Result := Result + '%' + S[LIntLoop]; + end; + end + else + Result := Result + S[LIntLoop]; + Inc(LIntLoop); + end; +end; + +// calculate and draw page + +procedure TMPHCanvasPrinter.Draw(const Page: integer); +begin + DrawOrCalc(False, Page); +end; + +type + // text and color attributes per character + TCellAttribute = record + Back: TColor; + Fore: TColor; + Bold: boolean; + end; + + TCellAttributes = array of TCellAttribute; + + TTextWithAttr = record + Text: WideString; + Attributes: TCellAttributes; + end; + + // calculate lines per page and/or draw page + +function TMPHCanvasPrinter.DrawOrCalc(const JustCalc: boolean; const Page: + integer): integer; + + // return one line of data + function GetOneLine(CurPosition, EndPosition: integer; const MinLen: integer): + TTextWithAttr; + + // add spacer + procedure AddSpacer(UseDefAttr: boolean = False); + begin + Result.Text := Result.Text + ' '; + SetLength(Result.Attributes, Length(Result.Attributes) + 1); + if UseDefAttr or (Length(Result.Attributes) = 1) then + with Result.Attributes[Length(Result.Attributes) - 1] do + begin + Bold := False; + Fore := FEditor.Font.Color; + Back := FEditor.Colors.Background; + end + else + Result.Attributes[Length(Result.Attributes) - 1] := + Result.Attributes[Length(Result.Attributes) - 2] + end; + + // get hex representation of data (or empty if > datasize) + function GetByteHex(CurPosition, EndPosition: integer): string; + begin + if CurPosition > EndPosition then + Result := ' ' + else + begin + if FEditor.HexLowerCase then + Result := LowerCase(IntToRadixLen(FEditor.Data[CurPosition], 16, 2)) + else + Result := UpperCase(IntToRadixLen(FEditor.Data[CurPosition], 16, 2)); + if FEditor.SwapNibbles and (Length(Result) = 2) then + Result := Result[2] + Result[1]; + end; + end; + var + LIntLoop, + LIntLoopAttr: integer; + LStrPart: string; + LWChrText: WideChar; + lBold: boolean; + lOdd: boolean; + lFore, + lBack: TColor; + begin + Application.ProcessMessages; + LStrPart := FEditor.GetOffsetString(CurPosition); + + if LStrPart <> '' then + begin + LStrPart := StringOfChar(' ', MinLen - Length(LStrPart)) + LStrPart; + if (not (pfUseBackgroundColor in FFlags)) or (pfMonochrome in FFlags) then + LStrPart := LStrPart + ':'; + LStrPart := LStrPart + ' '; + end; + + Result.Text := LStrPart; + SetLength(Result.Attributes, Length(Result.Text)); + lBold := (FEditor.Row - FEditor.FixedRows) = (CurPosition div + FEditor.BytesPerRow); + for lIntLoop := 1 to Length(Result.Text) do + with Result.Attributes[lIntLoop - 1] do + begin + Bold := lBold; + if (lIntLoop = Length(Result.Text)) or (not (pfUseBackgroundColor in + FFlags)) then + begin + if (lIntLoop = Length(Result.Text)) and (pfUseBackgroundColor in + FFlags) then + Bold := False; + Fore := FEditor.Font.Color; + Back := FEditor.Colors.Background; + end + else + begin + if lBold then + begin + if (pfUseBackgroundColor in FFlags) and not (pfMonochrome in FFlags) + then + Bold := False; + Fore := FEditor.Colors.CurrentOffset; + Back := FEditor.Colors.CurrentOffsetBackground; + end + else + begin + Fore := FEditor.Colors.Offset; + Back := FEditor.Colors.OffsetBackground; + end; + end; + end; + + LFore := FEditor.Colors.OddColumn; + if FEditor.InCharField then + LBack := FEditor.Colors.Background + else + lBack := FEditor.Colors.ActiveFieldBackground; + lOdd := True; + for LIntLoop := 1 to FEditor.BytesPerRow do + begin + LStrPart := GetByteHex(CurPosition - 1 + LIntLoop, EndPosition); + Result.Text := Result.Text + LStrPart; + LIntLoopAttr := Length(Result.Attributes); + SetLength(Result.Attributes, Length(Result.Attributes) + + Length(LStrPart)); + + for LIntLoopAttr := LIntLoopAttr to Pred(Length(Result.Attributes)) do + with Result.Attributes[LIntLoopAttr] do + begin + Bold := FEditor.IsSelected(CurPosition - 1 + LIntLoop); + Fore := LFore; + Back := LBack; + if FEditor.ByteChanged[CurPosition - 1 + LIntLoop] then + begin + Fore := FEditor.Colors.ChangedText; + Back := FEditor.Colors.ChangedBackGround; + end; + + end; + + if LIntLoop < FEditor.BytesPerRow then + begin + + if (FEditor.BytesPerBlock > 1) and ((LIntLoop mod FEditor.BytesPerBlock) + = 0) then + AddSpacer; + + if (LIntLoop mod FEditor.BytesPerColumn) = 0 then + begin + AddSpacer; + lOdd := not lOdd; + if lOdd then + begin + LFore := FEditor.Colors.OddColumn; + if FEditor.InCharField then + LBack := FEditor.Colors.Background + else + lBack := FEditor.Colors.ActiveFieldBackground; + end + else + begin + LFore := FEditor.Colors.EvenColumn; + if FEditor.InCharField then + LBack := FEditor.Colors.Background + else + lBack := FEditor.Colors.ActiveFieldBackground; + end; + end; + end; + end; + + AddSpacer(True); + AddSpacer(True); + + if not FEditor.UnicodeChars then + begin + for LIntLoop := 1 to FEditor.BytesPerRow do + begin + if (CurPosition + LIntLoop - 1) > EndPosition then + Result.Text := Result.Text + ' ' + else + Result.Text := Result.Text + + FEditor.TranslateToAnsiChar(FEditor.Data[CurPosition + LIntLoop - + 1]); + + SetLength(Result.Attributes, Length(Result.Attributes) + 1); + + with Result.Attributes[Pred(Length(Result.Attributes))] do + begin + Bold := FEditor.IsSelected(CurPosition - 1 + LIntLoop); + if FEditor.ByteChanged[CurPosition - 1 + LIntLoop] then + begin + Fore := FEditor.Colors.ChangedText; + Back := FEditor.Colors.ChangedBackGround; + end + else + begin + Fore := FEditor.Font.Color; + if not FEditor.InCharField then + Back := FEditor.Colors.Background + else + Back := FEditor.Colors.ActiveFieldBackground; + end; + + end; + + if LIntLoop < FEditor.BytesPerRow then + begin + if (FEditor.BytesPerBlock > 1) and FEditor.SeparateBlocksInCharField and + ((LIntLoop mod FEditor.BytesPerBlock) = 0) then + AddSpacer; + + if (FEditor.UsedRulerBytesPerUnit <> 1) and + ((LIntLoop mod FEditor.UsedRulerBytesPerUnit) = 0) then + AddSpacer; + end; + end; + end + else + for LIntLoop := 0 to Pred(FEditor.BytesPerRow) div 2 do + begin + if (CurPosition + (LIntLoop * 2)) > EndPosition then + Result.Text := Result.Text + ' ' + else + begin + FEditor.ReadBuffer(LWChrText, CurPosition + (LIntLoop * 2), 2); + if FEditor.UnicodeBigEndian then + SwapWideChar(LWChrText); + if (LWChrText < #256) and (Char(LWChrText) in FEditor.MaskedChars) + then + LWChrText := WideChar(FEditor.MaskChar); + Result.Text := Result.Text + LWChrText; + end; + + SetLength(Result.Attributes, Length(Result.Attributes) + 1); + + with Result.Attributes[Pred(Length(Result.Attributes))] do + begin + Bold := FEditor.IsSelected(CurPosition + (LIntLoop * 2)); + if FEditor.ByteChanged[CurPosition + (LIntLoop * 2)] or + FEditor.ByteChanged[(CurPosition + (LIntLoop * 2)) + 1] then + begin + Fore := FEditor.Colors.ChangedText; + Back := FEditor.Colors.ChangedBackGround; + end + else + begin + Fore := FEditor.Font.Color; + if not FEditor.InCharField then + Back := FEditor.Colors.Background + else + Back := FEditor.Colors.ActiveFieldBackground; + end; + end; + + if LIntLoop < FEditor.BytesPerRow then + begin + if (FEditor.BytesPerBlock > 1) and FEditor.SeparateBlocksInCharField + and + (((LIntLoop + 1) mod (FEditor.BytesPerBlock div 2)) = 0) then + AddSpacer; + + if (FEditor.UsedRulerBytesPerUnit <> 2) and (((LIntLoop * 2) mod + FEditor.UsedRulerBytesPerUnit) = 0) then + AddSpacer; + end; + end; + end; + + // return ruler line + function GetRulerLine(MinLen: integer): TTextWithAttr; + + // add spacer + procedure AddSpacer; + begin + Result.Text := Result.Text + ' '; + SetLength(Result.Attributes, Length(Result.Attributes) + 1); + with Result.Attributes[Length(Result.Attributes) - 1] do + begin + Bold := False; + Fore := FEditor.Colors.Offset; + Back := FEditor.Colors.OffsetBackground; + end + end; + + var + LIntLoop: integer; + LStrPart: string; + lBold: boolean; + begin + Application.ProcessMessages; + + if MinLen > 0 then + begin + LStrPart := StringOfChar(' ', MinLen); + if (not (pfUseBackgroundColor in FFlags)) or (pfMonochrome in FFlags) then + LStrPart := LStrPart + ' '; + LStrPart := LStrPart + ' '; + end; + + Result.Text := LStrPart; + SetLength(Result.Attributes, Length(Result.Text)); + for lIntLoop := 1 to Length(Result.Text) do + with Result.Attributes[lIntLoop - 1] do + begin + Fore := FEditor.Colors.Offset; + Back := FEditor.Colors.OffsetBackGround; + end; + + for lIntLoop := 1 to Length(FEditor.FRulerString) do + begin + Result.Text := Result.Text + FEditor.FRulerString[lIntLoop]; + SetLength(Result.Attributes, Succ(Length(Result.Attributes))); + lBold := (FEditor.Col - 1) = lIntLoop; + with Result.Attributes[Pred(Length(Result.Attributes))] do + begin + Bold := lBold; + if (lIntLoop = Length(Result.Text)) or (not (pfUseBackgroundColor in + FFlags)) then + begin + if (lIntLoop = Length(Result.Text)) and (pfUseBackgroundColor in + FFlags) then + Bold := False; + Fore := FEditor.Font.Color; + Back := FEditor.Colors.Background; + end + else + begin + if lBold then + begin + if (pfUseBackgroundColor in FFlags) and not (pfMonochrome in FFlags) + then + Bold := False; + Fore := FEditor.Colors.CurrentOffset; + Back := FEditor.Colors.CurrentOffsetBackground; + end + else + begin + Fore := FEditor.Colors.Offset; + Back := FEditor.Colors.OffsetBackground; + end; + end; + end; + if lIntLoop <> Length(FEditor.FRulerString) then + begin + if (FEditor.BytesPerBlock > 1) and ((LIntLoop mod (FEditor.BytesPerBlock * + 2)) = 0) then + AddSpacer; + if (LIntLoop mod (FEditor.BytesPerColumn * 2)) = 0 then + AddSpacer; + end; + end; + + AddSpacer; AddSpacer; + + for lIntLoop := 1 to Length(FEditor.FRulerCharString) do + begin + Result.Text := Result.Text + FEditor.FRulerCharString[lIntLoop]; + SetLength(Result.Attributes, Succ(Length(Result.Attributes))); + lBold := (FEditor.Col - 2 - (FEditor.BytesPerRow * 2)) = lIntLoop; + with Result.Attributes[Pred(Length(Result.Attributes))] do + begin + Bold := lBold; + if (lIntLoop = Length(Result.Text)) or (not (pfUseBackgroundColor in + FFlags)) then + begin + if (lIntLoop = Length(Result.Text)) and (pfUseBackgroundColor in + FFlags) then + Bold := False; + Fore := FEditor.Font.Color; + Back := FEditor.Colors.Background; + end + else + begin + if lBold then + begin + if (pfUseBackgroundColor in FFlags) and not (pfMonochrome in FFlags) + then + Bold := False; + Fore := FEditor.Colors.CurrentOffset; + Back := FEditor.Colors.CurrentOffsetBackground; + end + else + begin + Fore := FEditor.Colors.Offset; + Back := FEditor.Colors.OffsetBackground; + end; + end; + end; + if lIntLoop <> Length(FEditor.FRulerCharString) then + begin + if not FEditor.UnicodeChars then + begin + if (FEditor.BytesPerBlock > 1) and FEditor.SeparateBlocksInCharField and + ((LIntLoop mod FEditor.BytesPerBlock) = 0) then + AddSpacer; + + if (FEditor.UsedRulerBytesPerUnit <> 1) and + ((LIntLoop mod FEditor.UsedRulerBytesPerUnit) = 0) then + AddSpacer; + end + else + begin + if (FEditor.BytesPerBlock > 1) and FEditor.SeparateBlocksInCharField + and + ((LIntLoop mod (FEditor.BytesPerBlock div 2)) = 0) then + AddSpacer; + + if (FEditor.UsedRulerBytesPerUnit <> 2) and ((((LIntLoop-1) * 2) mod + FEditor.UsedRulerBytesPerUnit) = 0) then + AddSpacer; + end; + end; + end; + end; + + // render a header to the canvas + procedure DrawHeader(const LeftPos, Y, RightPos: integer; StrText: string); + var + LStrLeft, LStrCenter, LStrRight: string; + LIntPipe, LIntOldBKMode, LIntOldAlign: integer; + begin + LStrLeft := ''; + LStrCenter := ''; + LStrRight := ''; + LIntPipe := Pos('|', StrText); + if LIntPipe > 0 then + begin + LStrLeft := Copy(StrText, 1, LIntPipe - 1); + Delete(StrText, 1, LIntPipe); + LIntPipe := Pos('|', StrText); + if LIntPipe > 0 then + begin + LStrCenter := Copy(StrText, 1, LIntPipe - 1); + Delete(StrText, 1, LIntPipe); + if StrText <> '' then + LStrRight := StrText; + end + else + LStrCenter := StrText; + end + else + LStrLeft := StrText; + + LIntOldAlign := GetTextAlign(FCanvas.Handle); + LIntOldBKMode := GetBKMode(FCanvas.Handle); + try + SetBKMode(FCanvas.Handle, TRANSPARENT); + if LStrLeft <> '' then + begin + SetTextAlign(FCanvas.Handle, TA_TOP or TA_LEFT); + TextOut(FCanvas.Handle, LeftPos, Y, PChar(LStrLeft), + Length(LStrLeft)); + end; + if LStrCenter <> '' then + begin + SetTextAlign(FCanvas.Handle, TA_TOP or TA_CENTER); + TextOut(FCanvas.Handle, LeftPos + ((RightPos - LeftPos) div 2), Y, + PChar(LStrCenter), Length(LStrCenter)); + end; + if LStrRight <> '' then + begin + SetTextAlign(FCanvas.Handle, TA_TOP or TA_RIGHT); + TextOut(FCanvas.Handle, RightPos, Y, PChar(LStrRight), + Length(LStrRight)); + end; + finally + SetTextAlign(FCanvas.Handle, LIntOldAlign); + SetBKMode(FCanvas.Handle, LIntOldBKMode); + end; + end; +var + LfntTemp: TFont; + LRecTextAttr: TTextWithAttr; + LIntWidth, + LIntHeight, + LIntDataPos, + LIntLeft, + LIntY, + LIntMaxY, + LIntDataEnd, + LIntDataStart: integer; + LclrFSave: TColor; + LclrBSave: TColor; + LfstSave: TFontStyles; + LIntLoop: integer; + LIntMinWidth: integer; + LRectOut: TRect; +begin + FPrintHeaders[0] := BuildHeader(FHeaders[0], Page); + FPrintHeaders[1] := BuildHeader(FHeaders[1], Page); + Result := -1; + if (not Assigned(FEditor)) or (FEditor.DataSize < 1) then + Exit; + + LIntMinWidth := Length(FEditor.GetOffsetString(FEditor.DataSize)); + + if (not JustCalc) and (FLinesPerPage < 1) then + Exit; + + if (pfSelectionOnly in FFlags) and (FEditor.SelCount > 0) then + begin + LIntDataEnd := FEditor.SelEnd; + LIntDataStart := FEditor.SelStart; + if LIntDataStart > LIntDataEnd then + begin + LIntDataStart := FEditor.SelEnd; + LIntDataEnd := FEditor.SelStart; + end; + end + else + begin + if (pfCurrentViewOnly in FFlags) then + begin + LIntDataStart := FEditor.DisplayStart; + LIntDataEnd := FEditor.DisplayEnd; + end + else + begin + LIntDataStart := 0; + LIntDataEnd := Pred(FEditor.DataSize); + end; + end; + + if not (JustCalc) then + LIntDataStart := LIntDataStart + ((Page - 1) * (fLinesPerPage * + FEditor.BytesPerRow)); + + if LIntDataStart > LIntDataEnd then + Exit; + + Result := 0; +// länge einer zeile berechnen + LRecTextAttr := GetOneLine(LIntDataStart, LIntDataEnd, LIntMinWidth); + LfntTemp := TFont.Create; + LfntTemp.Assign(FCanvas.Font); + try + if (pfMonochrome in FFlags) or (not (pfUseBackgroundColor in FFlags)) + then + FCanvas.Brush.Color := clWhite + else + FCanvas.Brush.Color := FEditor.Colors.Background; + FCanvas.Brush.Style := bsSolid; + if (pfMonochrome in FFlags) then + FCanvas.Font.Color := clBlack + else + FCanvas.Font.Color := FEditor.Font.Color; + if not JustCalc then + FCanvas.FillRect(FMargins); + LIntWidth := FCanvas.TextWidth(LRecTextAttr.Text); + while (LIntWidth > (FMargins.Right - FMargins.Left)) and + (FCanvas.Font.Size + > 1) do + begin + FCanvas.Font.Size := FCanvas.Font.Size - 1; + LIntWidth := FCanvas.TextWidth(LRecTextAttr.Text); + end; + + LIntHeight := FCanvas.TextHeight(LRecTextAttr.Text); + + LIntDataPos := LIntDataStart; + LIntY := FMargins.Top; + LIntMaxY := FMargins.Bottom; + FPrintHeaders[0] := BuildHeader(FHeaders[0], Page); + FPrintHeaders[1] := BuildHeader(FHeaders[1], Page); + if FPrintHeaders[0] <> '' then + begin + if not JustCalc then + begin + DrawHeader(FMargins.Left, LIntY, FMargins.Right, FPrintHeaders[0]); + FCanvas.MoveTo(FMargins.Left, LIntY + LIntHeight); + FCanvas.LineTo(FMargins.Right, LIntY + LIntHeight); + end; + LIntY := LIntY + LIntHeight + LIntHeight; + end; + + if FPrintHeaders[1] <> '' then + LIntMaxY := LIntMaxY - LIntHeight; + + if (pfIncludeRuler in FFlags) and FEditor.ShowRuler then + begin + if not JustCalc then + begin + LRecTextAttr := GetRulerLine(LIntMinWidth); + + LclrFSave := FCanvas.Font.Color; + LclrBSave := FCanvas.Brush.Color; + LfstSave := FCanvas.Font.Style; + LIntLeft := FMargins.Left; + for LIntLoop := 1 to Length(LRecTextAttr.Text) do + begin + if not (pfMonochrome in FFlags) then + begin + FCanvas.Font.Color := LRecTextAttr.Attributes[LIntLoop - + 1].Fore; + if pfUseBackgroundColor in FFlags then + FCanvas.Brush.Color := LRecTextAttr.Attributes[LIntLoop - + 1].Back + else + if LRecTextAttr.Attributes[LIntLoop - 1].Fore = clWhite then + FCanvas.Font.Color := clBlack; + end; + + if FFlags * [pfSelectionBold, pfSelectionOnly] = [pfSelectionBold] + then + begin + if FFlags * [pfMonochrome, pfUseBackgroundColor] = + [pfUseBackGroundColor] then + begin + FCanvas.Font.Style := []; + if LRecTextAttr.Attributes[LIntLoop - 1].Bold then + begin + FCanvas.Font.Color := ColorToRGB(FCanvas.Font.Color) xor + $FFFFFF; + FCanvas.Brush.Color := ColorToRGB(FCanvas.Brush.Color) xor + $FFFFFF; + end; + end + else + begin + if LRecTextAttr.Attributes[LIntLoop - 1].Bold then + FCanvas.Font.Style := [fsBold] + else + FCanvas.Font.Style := []; + end; + end; + + LRectOut := Rect(LIntLeft, LIntY, LIntLeft + + FCanvas.TextWidth('w'), + LIntY + LIntHeight); + if (not (pfUseBackgroundColor in FFlags)) or (pfMonochrome in + FFlags) then + LRectOut.Bottom := LIntY + (LIntHeight * 3 div 2); + ExtTextOutW(FCanvas.Handle, LIntLeft, LIntY, ETO_CLIPPED or + ETO_OPAQUE, @LRectOut, @LRecTextAttr.Text[LIntLoop], + 1, nil); + if (not (pfUseBackgroundColor in FFlags)) or (pfMonochrome in + FFlags) then + begin + FCanvas.MoveTo(LRectOut.Left, LIntY + LIntHeight + 1); + FCanvas.LineTo(LRectOut.Right + 1, LIntY + LIntHeight + 1); + end; + LIntLeft := LRectOut.Right; + end; + FCanvas.Font.Color := LclrFSave; + FCanvas.Brush.Color := LclrBSave; + FCanvas.Font.Style := LfstSave; + + LRecTextAttr := GetOneLine(LIntDataStart, LIntDataEnd, + LIntMinWidth); + end; + if (not (pfUseBackgroundColor in FFlags)) or (pfMonochrome in FFlags) + then + LIntY := LIntY + (LIntHeight * 3 div 2) + else + LIntY := LIntY + LIntHeight; + end; + + while (LIntHeight + LIntY) <= LIntMaxY do + begin + if not JustCalc then + begin + LclrFSave := FCanvas.Font.Color; + LclrBSave := FCanvas.Brush.Color; + LfstSave := FCanvas.Font.Style; + LIntLeft := FMargins.Left; + for LIntLoop := 1 to Length(LRecTextAttr.Text) do + begin + if not (pfMonochrome in FFlags) then + begin + FCanvas.Font.Color := LRecTextAttr.Attributes[LIntLoop - + 1].Fore; + if pfUseBackgroundColor in FFlags then + FCanvas.Brush.Color := LRecTextAttr.Attributes[LIntLoop - + 1].Back + else + if LRecTextAttr.Attributes[LIntLoop - 1].Fore = clWhite then + FCanvas.Font.Color := clBlack; + end; + + if FFlags * [pfSelectionBold, pfSelectionOnly] = [pfSelectionBold] + then + begin + if FFlags * [pfMonochrome, pfUseBackgroundColor] = + [pfUseBackGroundColor] then + begin + FCanvas.Font.Style := []; + if LRecTextAttr.Attributes[LIntLoop - 1].Bold then + begin + FCanvas.Font.Color := ColorToRGB(FCanvas.Font.Color) xor + $FFFFFF; + FCanvas.Brush.Color := ColorToRGB(FCanvas.Brush.Color) xor + $FFFFFF; + end; + end + else + begin + if LRecTextAttr.Attributes[LIntLoop - 1].Bold then + FCanvas.Font.Style := [fsBold] + else + FCanvas.Font.Style := []; + end; + end; + + LRectOut := Rect(LIntLeft, LIntY, LIntLeft + + FCanvas.TextWidth('w'), + LIntY + LIntHeight); + ExtTextOutW(FCanvas.Handle, LIntLeft, LIntY, ETO_CLIPPED or + ETO_OPAQUE, @LRectOut, @LRecTextAttr.Text[LIntLoop], + 1, nil); + LIntLeft := LRectOut.Right; + end; + FCanvas.Font.Color := LclrFSave; + FCanvas.Brush.Color := LclrBSave; + FCanvas.Font.Style := LfstSave; + end; + Inc(Result); + LIntDataPos := LIntDataPos + FEditor.BytesPerRow; + if LIntDataPos > LIntDataEnd then + begin + Break; + end; + if not JustCalc then + LRecTextAttr := GetOneLine(LIntDataPos, LIntDataEnd, LIntMinWidth); + LIntY := LIntY + LIntHeight; + end; + + if FPrintHeaders[1] <> '' then + if not JustCalc then + begin + DrawHeader(FMargins.Left, FMargins.Bottom - LIntHeight, + FMargins.Right, + FPrintHeaders[1]); + FCanvas.MoveTo(FMargins.Left, FMargins.Bottom - LIntHeight); + FCanvas.LineTo(FMargins.Right, FMargins.Bottom - LIntHeight); + end; + + finally + FCanvas.Font.Assign(LfntTemp); + LfntTemp.Free; + end; +end; + +// count number of lines per page (as well as number of pages) + +function TMPHCanvasPrinter.GetLinesPerPage: integer; +var + LIntSize: integer; + LSetTempFlags: TMPHPrintFlags; +begin + LSetTempFlags := FFlags; + Exclude(FFlags, pfSelectionOnly); + try + Result := DrawOrCalc(True, 1); + finally + FFlags := LSetTempFlags; + end; + FLinesPerPage := Result; + if pfSelectionOnly in FFlags then + LIntSize := Abs(FEditor.SelStart - FEditor.SelEnd) + else if pfCurrentViewOnly in FFlags then + begin + LIntSize := Abs(FEditor.DisplayEnd - FEditor.DisplayStart); + end + else + LIntSize := FEditor.DataSize; + + while (LIntSize mod FEditor.BytesPerRow) <> 0 do + Inc(LIntSize); + LIntSize := LIntSize div FEditor.BytesPerRow; + while (LIntSize mod FLinesPerPage) <> 0 do + Inc(LIntSize); + FPages := LIntSize div FLinesPerPage; +end; + +{ TMPHPrintOptions } + +// init + +constructor TMPHPrintOptions.Create; +begin + inherited; + FMargins := MPH_DEF_PRINT_MARGINS; + FFlags := [pfMonochrome, pfSelectionBold]; +end; + +// copy props + +procedure TMPHPrintOptions.Assign(Source: TPersistent); +begin + inherited; + if Source is TMPHPrintOptions then + with TMPHPrintOptions(Source) do + begin + self.FMargins := FMargins; + self.FHeaders := FHeaders; + self.FFlags := FFlags; + end; +end; + +// header/footer + +function TMPHPrintOptions.GetHeader(const Index: integer): string; +begin + Result := FHeaders[Index]; +end; + +// margin (mm) + +function TMPHPrintOptions.GetMargin(const Index: integer): integer; +begin + case Index of + 1: Result := FMargins.Left; + 2: Result := FMargins.Top; + 3: Result := FMargins.Right; + else + Result := FMargins.Bottom; + end; +end; + +// set haeder/footer + +procedure TMPHPrintOptions.SetHeader(const Index: integer; const Value: + string); +begin + FHeaders[Index] := Value; +end; + +// set margin (mm) + +procedure TMPHPrintOptions.SetMargin(const Index, Value: integer); +begin + case Index of + 1: FMargins.Left := Value; + 2: FMargins.Top := Value; + 3: FMargins.Right := Value; + else + FMargins.Bottom := Value; + end; +end; + +{ TFormatSelDialog } + +// ok on list doubleclick + +procedure TFormatSelDialog.ListDoubleClick(Sender: TObject); +begin + ModalResult := mrOk; +end; + +// enable checkbox if cf_text or cf_oemtext + +procedure TFormatSelDialog.ListSelect(Sender: TObject); +begin + with LlbxFormats do + LcbxTextAsHex.Enabled := (ItemIndex > -1) and + (TClipFormat(Items.Objects[ItemIndex]) in [CF_TEXT, CF_OEMTEXT]) +end; + +initialization +// register clip formats +OleInitialize(nil); +CF_MPHEXEDITOR := RegisterClipboardFormat(PChar(MPTH_CF)); +CF_REGEDIT_HEXDATA := RegisterClipboardFormat(CFSTR_REGEDIT_HEXDATA); +CF_RTF := RegisterClipboardFormat(CFSTR_RTF); +CF_HTML := RegisterClipboardFormat(CFSTR_HTML); +CF_FILECONTENTS := RegisterClipboardFormat(CFSTR_FILECONTENTS); +CF_FILEDESCRIPTOR := RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR); +CF_PERFORMEDDROPEFFECT := RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT); +CF_LOGICALPERFORMEDDROPEFFECT := + RegisterClipboardFormat(CFSTR_LOGICALPERFORMEDDROPEFFECT); + +finalization + OleUninitialize; +end. + diff --git a/hexcontrol/mphexeditorreg.pas b/hexcontrol/mphexeditorreg.pas new file mode 100644 index 0000000..4c6ed18 --- /dev/null +++ b/hexcontrol/mphexeditorreg.pas @@ -0,0 +1,120 @@ +unit MPHexEditorReg; + +{$I MPDELVER.INC} + +(********************************************************************************************** +* * +* TMPHexEditor v 12-29-2004 * +* * +* (C) markus stephany, vcl[at]mirkes[dot]de, all rights reserverd. * +* * +* IDE Registration Unit for TMPHexEditor and TMPHexEditorEx * +* * +**********************************************************************************************) + +interface + +uses + Classes, MPHexEditor, MPHexEditorEx{$IFDEF DELPHI6UP}, DesignIntf, + DesignEditors{$ELSE}, dsgnintf{$ENDIF}; + +type + TBytesPerUnitProperty = class(TIntegerProperty) + public + function GetAttributes: TPropertyAttributes; override; + procedure GetValues(Proc: TGetStrProc); override; + end; + + TRulerNumberBaseProperty = class(TIntegerProperty) + public + function GetAttributes: TPropertyAttributes; override; + procedure GetValues(Proc: TGetStrProc); override; + end; + +procedure Register; + +implementation +uses + SysUtils; + +procedure Register; +begin + RegisterComponents('mirkes.de', [TMPHexEditor, TMPHexEditorEx]); + RegisterPropertyEditor(TypeInfo(Integer), TCustomMPHexEditor, 'BytesPerUnit', + TBytesPerUnitProperty); + RegisterPropertyEditor(TypeInfo(Byte), TCustomMPHexEditor, 'RulerNumberBase', + TRulerNumberBaseProperty); +{$IFDEF DELPHI6UP} + RegisterPropertiesInCategory(sVisualCategoryName, TCustomMPHexEditor, + ['DrawGridLines', 'Colors', 'CaretStyle', 'BytesPerRow', 'FocusFrame', + 'BytesPerColumn', + 'GraySelectionIfNotFocused', 'MaskChar', 'OffsetFormat', 'ReadOnlyView', + 'HexLowerCase', 'ZoomOnWheel', 'DrawGutter3D', 'ShowRuler', + 'GutterWidth', 'HideSelection', 'PrintOptions', 'ScrollBars', + 'Translation', 'SeparateBlocksInCharField', + 'BytesPerUnit', 'CaretKind', 'RulerBytesPerUnit', 'BytesPerBlock', + 'ShowPositionIfNotFocused', 'UnicodeChars', 'UnicodeBigEndian', + 'RulerNumberBase']); + RegisterPropertyInCategory(sDragNDropCategoryName, TCustomMPHexEditor, + 'OleDragDrop'); + RegisterPropertyInCategory(sInputCategoryName, TCustomMPHexEditor, + 'OnInvalidKey'); +{$ENDIF} +end; + +{ TBytesPerUnitProperty } + +type + TInt_Hexer = class(TCustomMPHexEditor); // propagate protected properties + +function TBytesPerUnitProperty.GetAttributes: TPropertyAttributes; +var + bRO: boolean; + i: integer; +begin + bRo := False; + if PropCount > 0 then + for i := 0 to Pred(PropCount) do + begin + if GetComponent(i) is TCustomMPHexEditor then + if TInt_Hexer(GetComponent(i)).UnicodeChars then + begin + bRO := True; + Break; + end; + end; + + Result := [paValueList, paSortList, paRevertable, paMultiSelect]; + if bRO then + Include(Result, paReadOnly); +end; + +procedure TBytesPerUnitProperty.GetValues(Proc: TGetStrProc); +begin + if not (paReadOnly in GetAttributes) then // unicode? no. + begin + Proc('1'); + Proc('2'); + Proc('4'); + Proc('8'); + end + else + Proc('2'); // unicode +end; + +function TRulerNumberBaseProperty.GetAttributes: TPropertyAttributes; +begin + Result := [paValueList, paRevertable, paMultiSelect]; +end; + +procedure TRulerNumberBaseProperty.GetValues(Proc: TGetStrProc); +var + i: integer; +begin + for i := 2 to 16 + do + Proc(IntToStr(i)); +end; + +end. + diff --git a/resources/icons/CHIP.ICO b/resources/icons/CHIP.ICO new file mode 100644 index 0000000000000000000000000000000000000000..951ad5d90212a19943dba58595d4c81dfa8cdf76 GIT binary patch literal 766 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 WqXHA}MnR)j3b>0t%|Fnl)PDk_&-;MbA zCgM1AaEM%&?PjP}xlW?0!w|$1a`!LL4 z(U$#6U*_GfTV_4gEj*|1K~rd-tbH(&hx%4W7VTW zLq$Pu)^~I?R1`>zzR}T8QDAZOm5zpr0vkqO=xC@YP-J*NoqIz?fx=36%5=8uEbpXt zWSJcu4HX3he&}eZD3HVt9Ss!)7V<+!Lq&njRHnNcDhd?fhk}NR0!8_uqoJaJ5mR(D zR1_HSLq|hJfdqc&Xs9T#h#xxgswl7#Kir|AqQK_-(9uv)pddeVG*lEYVF?`#6$Ju* z=xC@YFv$;na6)$#Sip~yLPJG?P57asp`yUX{Ls-*QJ@$8Y&9h_@NJ{ zLf7YIf*)m&pJjf6`MF8vhmQO_@>9syZi9T4`P%Xopz^cu06Sd^|QgP8i~~u#DEibIksd zn`f}*W&g8>!#dvE?TyE%~o8I;k{QQ9Gy z8W1@p!dMs&%j`js8MJIXA+tjwtwSPH76TJtd3Gi7nLTW7^QwyQC)H}!@T3b>Ns@$# z5dS0u>jAe(_W1QmlT2wZXPtI8>$JOBhcZZ7H3bh26ykZL=-|PD0)Y}dI(TrP zK(Yjf4jvpRP*8%Rg9ir+EE3N;<>|qJLVN=uWto(cDd{PpDKe2MI(TrPfPf!#@Zdm! zApD?%2L}oy;s+f(I8dM%tD|#!aG<~f90dw^aG=1V_(2B`4iqrL6gqftpuhk>=-|PD z0s;6z2M-PuNWu>~*zG`pLioWQcyORVar~fz2L}o)h#z$D;6MQrq@aTb2MToXgAN`X zC@_g1^ymbgTY&`pa8lsGfdWPFgAN`XC{P$b=-|PD0*m1X9XvQtzyPJt!Gi+@9{53z zr-DvvHxu^h;)jx8pP+qi1mg!C?DMcs!CpHB>{Z%pvsYxV!;YQ-I@_}&wBxd)v170; z+xBcLX4@>99b4L#v67Z26hJf^*cMkiHb}Q#isCJaXagO z&I3IX!aO!$Wo6}${VW_C3E?GaPqP|UXKiimx9i0GCxkzT*AjlzH|wmgukTqW=HDmm zAv}Xu&|)v@2Lt8~6y{ z;T>3=?eZ_9UxmCz?!)$A>n9Sv-jfj5+AjYBF&81PahLw%j4xLtT%Q`*HrKqwn&*f; z50}99a!taAnSroya_78!A2I(dIaUKcot7|n;^>a+<#~A<^Up~5R+2D(d?egFFwRu* TjCUJbe$H-j|KHr-{m%RZ!FiQU literal 0 HcmV?d00001 diff --git a/resources/icons/Status_Stop.ico b/resources/icons/Status_Stop.ico new file mode 100644 index 0000000000000000000000000000000000000000..4b1037b601254990e26037ecd335a59cb85941b3 GIT binary patch literal 2862 zcmdtiKW`LA6aer?Fo7ac>~CPXqGlv&3V#K+6cHMcC|wZl(22?tLewKg)a2Tv2^P{p zaYb}hLTIsf)<-p;&vJGG)U*(GZeBTzo^IB&# zDuqnc&BSR`3I;~k#A#FtCP!DrX;calMpwjXR06v9_QqD}D8EUiX zY<>U)2I|2)Fa;Sin#Cm zIH4ZPkdI|Pg88@^tR8Xl@yJIZZ@UC}EAzJHEy~-GTdyPT@^TAvyK-xC8}gF#^72x$ z>?|?M$)$6dOqtVjXoe%+Fz1jl2kf)QE<0>}?P1}?6!ZB!4i67wJRZlv!9ncr@5kQW zUhMAf#?H=8Y;9Rr^Z)(jNk+`hDQIKb?9Tn{v`00;l?Y zU`f9_TC`8TkbXk$!Qji^v)j3)ab=_b`ru%x-0T03t1e}g71#Rc(l=fAhxw2Bn`b;{ z&I?|XxtHYV=ihgJ%tP`u{SW{0lvk{qf6thFo&Lt3WR4>;N7k@z{%vFKaGWFS_=~I~ zuVdZ(o5tMYIL8COvyRM>d$Df*&&K@1=^V$`l6$&t{xxHMT$1C-N6gRsKkySb7;%?h X`JEN(=Y7q4xXQA7|KE*&*?Z<4yMmeg literal 0 HcmV?d00001 diff --git a/resources/icons/Stop.ico b/resources/icons/Stop.ico new file mode 100644 index 0000000000000000000000000000000000000000..7edf329abcbf021026aaa259c168319e4e23b031 GIT binary patch literal 318 zcmbV`!3}^g3z!0kQtL3ON+Lq;LF-9tu0(}fmRIb8m;UV4e5Jf+tNJ$r^3uQ`>(vzFPadHff!VywZAek`%%ku7yXDbrv84R;CfBx7{ zqCi^MYrU3zzUli@dX@BH+fw?no%1XRI0UwvZT)tXvr4t+XgtfU5sw-UlTBCTacI)b zL~M(u>oydW-fl4L4Q9H|CJ0(h$+{z^8iV=g`Uel`%bY9tjlnAZJ}8HemHggPO^;PF zzkzQa^nZBYzv~fC_m5m*>ipfq7eRgH(YTB#)zTXYN>@CH*fyG=dDAuPu{&axn!x=8!8wEL0XPtq#yqx5o+mi1QtThZ*gp5vqSo$emlFZ*Wy zJm)sg=86RY2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb O2tWV=5P-nHBX9-Fw?Ga6 literal 0 HcmV?d00001 diff --git a/resources/icons/button_restart4.jpg b/resources/icons/button_restart4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..25b3a29987c923fa0d85f4442969558623a38086 GIT binary patch literal 861 zcmex=^(PF6}rMnOeST|r4lSw=>~TvNxu(8R<ECr+Na zbot8FYu9hwy!G(W<0ns_J%91?)yGetzkL1n{m0K=Ab&A3Fhjfr_ZgbM1cClyVqsxs zVF&q(k*OSrnFU!`6%E;h90S=C3x$=88aYIqCNA7~kW<+>=!0ld(M2vX6_bamA3|Yd<5Y7!VpSLBd9xvb@#z#k zc6J-#2^PXS^K3sH=Jm_=)X!l*Yq~1V^l&S)%G}4w%=RVos->1M`H}D1_fh%kf@|S{ zm!E{*DE}#^#;HLJ%C^1n*VgBXy?ga=;eDN>OFo@U*I;-uM~;onKQS)m^$P8i zckic^Cw-n{YO&(s%4PFjuB~5xofl1OQ>vMJoUR literal 0 HcmV?d00001 diff --git a/resources/icons/camera.ico b/resources/icons/camera.ico new file mode 100644 index 0000000000000000000000000000000000000000..cd8b85102acef9affe6c6e864ad73fe3f1db967d GIT binary patch literal 22486 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�`7_p9EVR8>(O5J~l2f@JmpE3?1L2 zJl~%285jE(d!3jRael_<9AJE6y3m91!OzqHV5>2<0rm|xrn$ziCD%rO>}+h$#MUh@ zR}Lw(U)SLo#^*jZHFPuQjLA8`Ikq=`7vsaH`0l>(HvD*U=-A-+HTXu@iL4v^LhRb) zDcRR*hh2&*QFsyK6ANS>0O$P9n4ANg`_i9&#^#1!SP#Y{HbJhK+zc(YAF)XMSLo-P zt8Ux8$uCt(6i#J)_L0enat{Bq&o~DdpEblkWB#la^dcq+;MZb5=hnsL#Ll_qJ~?js zF)y>GU6T@ji7)9-o_ZH_fu@G8@FU|JdNIE5`V-s1zk?3MSX zfCv4cA^Zq*X|Lm#@QzO`4;vrg9DZTFIR_Y@+6uG(iL*ls-iLo|VkyvCzx}lI`&Uc9 zZMA(%a+c)!@H6SpyMa#a+WZn1QutNoPh1b41~?}-3D0m2Fuw2pM_xgHe0}^s=E~Uk zvHJaNZr)M9SG9d}#-|VckugBWwyl1x98_=(yXN+&+0*vp=f5en|2XY#p zAHE|G4G#J(t)<@qTlyWarQg8X@eYM!CB^Q}66Xat?vbI2&>27Qpb$U1V9sL(4sYSQh*buOlO%HSYr_yH>yW18a4Ewm_EYcg?xL zK4srx0~onT%Qg4W1@IK_LH8k#v3JnH#G;`GK+X_d;ENk_ka(J1?-TIgN_>Qe8`Pf< zPw-CmIOAixz^_~b&=tD`y#zn`#)3`l7x)nq1~2GD{1n~99x$oE61hblk$FHP>{0j*TCgXf z1A2(_s_}6bwWj}3#=qGSU7>sf^?~|oZ0SXfmu2+r)B7axM)o8!8(Ohv@e7bYzO**y z=qqdn_85KaSYO%DC!zz3MBHrb8~w(&G%=OJ6P=YRj4*N6o;`Y;C%#MW27ZSpiRt1y z4IcRT>6T4hJ7X$U^f)V&?H5xnBtD{4!9e`n8;$X2?<;>M*mzZ{^pdke`CiVlAyxUl ziSLD~2M0U&j)hp8_A~YKe|D(<$aAOkzoaCK_gmd$kr0JD75?J~?*ue7A%rk2{f8t( zDu`2D`EEBkJT zhqiYmU+3F|_>#pVs+TU_utwS7snyE{tgBkG=*DWLi!X}}^B)smvWQF1|9%(>m3Ujvq7Xry)-a{w=X-qn~0*1%6#Q(D6Z)(83w|e5&uez^ih} z;{I`^O6m6!&d3R4lYd3Gp^MRh)B+%9^_$$^lUg+S#eP>1@AAv2Sw3h*T*;!NtCjR` zP)+Bna_$ps9`rIg51o(PWZxqTXnC)GAN+eCcPH<=f*ps|#@~_G$B4e*0~|4;mmtc^=yU8_oFG zJc}=b%mp9xkEsvE?}Ofq0UdarXARx3b4+Z3H9=N04}1>lWRQ`(m*04=KEIO|`wg3v z@mPQCaOew=3*vcbfes>$gDgb;Dg8IAPFjeLMP4Hxp*t<~VJ`SS`0lJd`W||sJMEg? z;6g9M|FO}LEqZo;WW4h6lJ)D>?!&&o4#s|BtL(^^9%!LHw@YcTPWdI7`wE($2r8U>r*l`bysxrqy^4_e3R@J?6;>)7&4E?g TzNK(l;U|Te+W%|oH--NKrW8e; literal 0 HcmV?d00001 diff --git a/resources/icons/console-default.ico b/resources/icons/console-default.ico new file mode 100644 index 0000000000000000000000000000000000000000..e6040bb9f50c7cc225b150567e62a2073a2eb8a2 GIT binary patch literal 8166 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 literal 0 HcmV?d00001 diff --git a/resources/icons/cpanel.ico b/resources/icons/cpanel.ico new file mode 100644 index 0000000000000000000000000000000000000000..35d80be9877f1cfc62756e321e0f6c2857f06859 GIT binary patch literal 1078 zcmb_cv1-FG5Pfb6G6YIGbts{pO2$BcAVatQj0*)T7#cwbX*2nkg5Ayila3uSWNV;< z`p&8yCnQivpRLoKp6+y(P6i^JAxTCMe4YWf>_?+uPa@!A08BWRu+7*aoKq5bE*7HR(ppK V?i*8=-S|u6Kfv&MzBl(2_yL$VM>GHc literal 0 HcmV?d00001 diff --git a/resources/icons/folder-x.ico b/resources/icons/folder-x.ico new file mode 100644 index 0000000000000000000000000000000000000000..41f9f1a7aa85327e5ca7645c6f8e10d8ae492769 GIT binary patch literal 4710 zcmeH~L2Db^6@X7H7Y|c|VjBVi)98&h(jp$?iz^389YXuM3cO9$*$rKEA!N#EQLx?< zMBcfk5cQVwc#F2Qi*}J;AZTf!8R#zAH1LWSDMlcLz>J}f+VQ=dHPKqm-ZiiL*(_c zNRwfmc88|%RQklaTKX8V^!sJUm0KB)$MkA&{!g6KI~pHVaalU(9Uw!Oru($c3U*}0 z6{)Kh>1@py(}>AT;)J1;WsTLxEF|T$_RF~p= zKd6$UvSZqNTc$3(X3sIHGT7UcN= zhjLt`#gHc_BIAO#FX=(5L^?n^qWDPF4~P1|rKBk_-wrim>3L>9n>TmA*za|u-8~rW zHV+29L)nw+;IvbjNpC3C&At6y9;hJ!%ep5rl+VChYPYMK670?7m9%$6_GYJt?R3xW zq{B*QsV(~=vprT~DebWQOFmnLdFQ1%JU_Z{-`C?M@~Oy21Cf0kUmkLDwB6)D0i;c| z*KxHkcb1q_88h!o+Dw+g|JLrQ+;m^bzI$D9?)9!;aJMdEKK5C%I({?rT-HrpM2uvXYy3sB$GcsoyyY} zFXX4c`#p&aEumRjRt^n1gU+Bcs&l1Ff<8f?pij^z=yjS!Z_!%@7QIDJCmH$-eTF_m zk)d~t9b=dI%!aEWzafxelHq&n({nK}7#Iu;1_qA=gMq=oU|=vXcuW`!3>g{z~UbnmW1R9i4uY)u=r)bm~fN8lEC7Z z1&#!c1darb1P;Gca3nA!FeEVO(j{;tFeET!{*9HDOGuvb!`Lsct`1b>w)pv_Cbxyj zlGKvG!eq&9FDXz01>HnIj8 z`7+otI5P72tyK=zAcG@Q4j#;JH|aiP^~D!_kNd2yeh0K8o*!vk9Xbhv$x(4MnXFDuXp?EJ78FGh z)bd&oOp73F@FNxS(Z48aG-`Pss5!)8I?cnzcC6CcXf=^mlw%2kJoNTj7)J3Z&f^$! zE5{+iQi>+vF4Flm4G}QC8L2T?EBt)Dk4D>g5TQnn<_4NP;QSZF_o1Tg?a_9;9azl~ z>-^wkVKhC0Q>PQ30Ha@YHAGDQFrVI9*yz>D*=oljILi^oVOZ-CZdB7p@bj4-Xb1qi zaU9ix-uAaQM#Yq8^LCx@TMXw$6h1$>b?er2!n@Mj8!cV8NEZi*8eyLNs9Q{qSiI7q zNY+-TO)!vF1L{e5H2DMOY2i}@tETm8}=)-(VVWIf_cyekl4Dsb~e?>Z>& zn|$w@FXg+!;e9F_D{mYG&C!TG%$wcOsN1sL5tsqn9p;?R?lcTr%58fitGX68n|l>* zP4(;HgO{HZjtDwxma7-qcuJC7ox*MqYn&Q`*v36osV47s^LWZTvX$p?&sq}3B(i!b zPg<@Stv@sYJ*QmfhG!}Kee3+dYc5{$uCo7^!rS40UZ-5=om8jI>+v!7S4*E71q<&q z-*M&kYt`e@^Y!=vv%OTx`nx`_yXW=xyw0B2*Ymo1UQf^K=z0B2um!%~KBh67W$-Jm zV|0C8>8~kY;rp8ZXUg|<`G3pXbt>z*JMWFxCmD%Y9N%%}_Cr0#Z#O^Xv=8;%hkA~h z>;JKy3;CUWQ_rnVv}}7@zj1q^UTeNnpIxrUF4td|>#fW6)#Z9h=bx{iy!~9g1fEJ+ h9yMY05|#DPd`OvA%76ZG8|!QDz3)Ygm*nz$_TP46g|`3z literal 0 HcmV?d00001 diff --git a/resources/icons/folder.ico b/resources/icons/folder.ico new file mode 100644 index 0000000000000000000000000000000000000000..340cbf815965140bee0822caf3529093f839633f GIT binary patch literal 10134 zcmeI2&x;&I6vy9Y*Q8m3NiI1^Ak$vPT+BkBa@vqvA7XpSIi8xF_o9f9(L)q35roY2 zMMTuZgO~gTBZ3Fh=j37_kV7O0W@OMoL(0#4)jj=Vde$){C}e84s=8jiAK!jeUHz(h zL^kB0^m<1{)6WM)QtF)@Ys!t6Mc&vH`ipiT^7}E7qn=$Zy(IG4E86yi*EU2B!n?EM z+m{ZCydH|YhPbLYOWu$zN|6D!y3>G8dIkeuQF1Bc@t9gWoc)V)dPU|>Nm@MP^ueo* z)Vq4Uk$P9(ZKQR7!#&q~R~eLA>V5dOPapXt!Jp>8cjm+99Xyz~1C^f5n<6S$b=NxV zvS!OJ;bcZWm$Wfp#*H_eySM4wqeI@Z&U1Zq-Xd=Z4-09qZB<7xpc684+A2yeZJv*R z8q1>}zm~17EeV1^4j(=&$BrG7I5Ne(@Sob43-q*DTY#%reHBQ(3x_Pf+Youxdj|4I8tz=;7Gw?t^!92h7=4b7}RwsI8rdA zV90I7%DoG*g?Q{Gh{zSM2VgP*dtK;)5J}IoNV=XdoojY>(?p?Wm|GqqY_)sR3iOgm*dGwc)FF*gB=Xij3 z^%>9#eb!RF+H|tLPAhpJ)U*<(F@^oaBvzFP!@VT#7a}XzQT`;>R0{2n&G?fdtsoPI z!;-tG_qVgiuVSU4MQmX>YUwYVCbJ;0oZp*i3+&6E^cOrFhNbf$SDaeLUG zhQB=krE=;n{qeKpZ{Kj(@9(R>4M_Qy4Y12L7s(x`Xh+?2!{D|W8U zU!M)S)D_DQViV^^3;an?;m1XIXs8Ga2dL{>6E3I>1jDe8-qCbgiZyw4riSsPG%yNO zm#GY6zUx_@C%f|Oal%Q=TSPj8y2);g4)SE@U(NRApF4N#_rTTKg_iyXnLhdPQ z?oT}aO{<+8n(2KxIb!$k#wUGEsrPTy3U%{5rr4)$sWZCLfITX7Vp^*xxm@p4*^{tE zA+PtT>wPMF$@M<<`MlZemAhOu@9^TK+|9$La308X1p(TxsSI}7JdI69$y77d=$|X9 z7#f>EvqVt~AO5+&9`zLp8uC?8RjRM=wK_LtRdhs5)uF!zaN+*UNChD5*P_@Gz_8K3 z5NapG%FBdv8d^7a8vRA!S`WaJQFQ~lC%3^PUM^EIisu04w-?6qaCvW3-Qc`m?Py3! zfIK2PEk|&^(=vb*(Gn1GG)z5IEA5?VxG1O?Y6k-Iv+ZHQF8pENf-E#u4(-ZP-{-4!cFS~(t?ViWOV`_K+Gxt-cng1cN z-}u?YywN+|{Cz;#>YZ-Wd&eOV}^U(R6aFRE!S_4(YZJ)Z~u zm`SjRaDRNKv!4P!aJ83;D&aoet=eg!ck-q`_T0-mq literal 0 HcmV?d00001 diff --git a/resources/icons/green.ico b/resources/icons/green.ico new file mode 100644 index 0000000000000000000000000000000000000000..fdfee7dd6ca5722c94b09365874c9998f1253e93 GIT binary patch literal 13502 zcmeH~J#GRq5Jo>lkuqJBE|fVydUCU%oF>QMC>(*3B9h}ltk-zi@oWW=p26UqpJ)3? z6iBQ5+H90RU-k1Ly-0e`Gl%qh`*tP|pr;u(W zKpjiEv}wWE+*?h1gOMKC@Ud>&)`1$ka3^1VhlLgGw0g)iJ4$K}Bt1jtj zH(5TAs%VmGIo7`|@}NS|yd8$ieoZ{cJv~3hSnGI@@Am^tPOqJ3T(p~cyD%m5&Xj#u ZPMGa|q7WqT?#do7^Ib=ko%|RWtO52!GeZCX literal 0 HcmV?d00001 diff --git a/resources/icons/move.ico b/resources/icons/move.ico new file mode 100644 index 0000000000000000000000000000000000000000..a5035c3f03ee0529f7b8cdc16faffb415cd7de2a GIT binary patch literal 766 zcmZuvJx{|h6ub~5Qe_AeYdbPV%7WCP{G@Iff`p|GiG)O1`#U-mse{8msl)&iONUl6 zVD34tQwlFRKi|9eVOa(uexuPP#NN+X{xfn+DT8{$H9k*g%=a}Kfli@>|~4}o{}tDc*x zy0^CH?7wfCz`=$JPY9MY4F-!q-Da^kN|4A2{lf>p>*2UA@(`8=rl&xxq0?;<5@KVF zUOLVb(XwT}j-8zB$|6n5orn1xTC>mWR~xfBVsZ4%>mkJZ_Xdf_qIqzClUa2)`~f3?(4znV literal 0 HcmV?d00001 diff --git a/resources/icons/muldoc.ico b/resources/icons/muldoc.ico new file mode 100644 index 0000000000000000000000000000000000000000..93d0d018457373382b017b885aff1013d346bee3 GIT binary patch literal 1078 zcmd^;F>b>!3`M__0`AoK2Juwn7R`~ewOhF*n~!82rDI2L?hwFvl&nTgvJ^q5()yx4 z`6a+2B~@CeZ5z3LE~ICC(>Ku5ADMZ86s&OEzBe+5<)YihH2U|0ixzUH)%3+W3RNAt>%=ZwLy0SRp95 zxd48ct-B#`eIbbSTmhSTKEe4Qt!LDDNA*Wo`uRs`1=e)l3wZZFZ$Jxjp9j|2Ja@MK y{yvZG&57QYUXMHc;Pqqw*RV_8>&@lb4*tk1^76(2Z;^H2T?gLxZ}7pMfxRcIx4rNH literal 0 HcmV?d00001 diff --git a/resources/icons/newcsl.ico b/resources/icons/newcsl.ico new file mode 100644 index 0000000000000000000000000000000000000000..9a450be04b38dc0f825f6bcdfb356fbcf1a3be3c GIT binary patch literal 10134 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 literal 0 HcmV?d00001 diff --git a/resources/icons/nuke.ico b/resources/icons/nuke.ico new file mode 100644 index 0000000000000000000000000000000000000000..af6f3a5ae2f7ef09b2e4ec2e2c03f55c25bd763d GIT binary patch literal 767 zcmb7Cu};G<5WTcWam5f87??W3U+^LP0#YT021Tge;>{gv38{Pn5<{q1@&R2LkvcMZ zizOtEcXp$;5*>K<`TYFu&URv=3~f9Hh-2%WS}4kM=2 zwk^B}96K?~42C94G60gLRdR9Ra2UC{k8&zDa?Z^W4CW~iImgbapp;jnkQsSl>rNNC zGbG{8EkY^E^X4Pu xkGFns{q6_Ze1m8Aj4*h4gK;h|RKw&iA3=t7+=}ueR_Khm9WrlnT&G~Z`~|z&O}PL7 literal 0 HcmV?d00001 diff --git a/resources/icons/play.ico b/resources/icons/play.ico new file mode 100644 index 0000000000000000000000000000000000000000..eff617b7455bffeacf9d93c28f225e3f31f999fc GIT binary patch literal 318 zcma)$yA8uI3`Cz018G&IPU$VYn�I+$Do3cly973?n4(NFFJOkmNaMYd zBsMWb=PN3ppp=osDv1bX4_Xgeb0x?vnf|sqH|s3wER?%)_G5AF-+wUf@GbBW!Z?Jd O@c4my>zaFSz54-xd@p%SZG2ak1@jy&)&J_sMevd$rN(f`($}JJlbsYs6 zUWn+9;G-p7{yhvky2GvxTG~ZJdQdQ~;vmj)7)C;a&AUkc;SSZtIxnW^hH{3HwDj4l z-&Hov;8W3gjHLC-?71;ATO;L$cKSJ+ry&$SCy6vzSGdExX9t-+4a+qP{RpBt@FFEf8dLw0FLP5G~fM*VI%Uz slRt=8Vo6xpUR+Cx_Y&`bj;=t|1&_>61&;Z);Dz~(hy}a|PJxHXHwNP!XaE2J literal 0 HcmV?d00001 diff --git a/resources/icons/reboot.ico b/resources/icons/reboot.ico new file mode 100644 index 0000000000000000000000000000000000000000..f46bf7c1a10400a107c6ea6ab2288ab57ea44bb4 GIT binary patch literal 10134 zcmeI2KZqO49mju;op+Wnkxjs)FxYYR+!?lVbq^zu8pp43?NrMvQykG10jUty!4hbctwkRby4I` zO~t$X7vC3o^h3qF{8v8^`Rk8FJ|%mD>ocx%f&#*Tw$_IMg_H~iF5*s74i68BwZPb) z7^5jJ{#2y54+^d2s;0NM*XviBTD!w`yFCiZTRn0rm^j4o&}Q3hSEIG-v+a%rv?%WO zdVAb0;8+u;rL}B+uBv-IgIp`Lc98dm$c7=`HORXCop#ricWjHA{~35-RBg8(qo~Sv zcH5(+{Jh=w`5o}X4*5!7*X(xY9U8IvcWM8)Gw*voqOlpx`^X<_hqdw1Xx^hq%U=n% z(Z>yX3JCd;&59bNI}F`d0GV1=TE`&vdakzCX&v`^{YBQPdA~aYxd?N{_3*qg12O*; z>HkAydoFUFt2L1Umuwxq#|2KYEv>6M(6w!Jx(&`5bF{T+kCe{y%Un0O!aP4Dy*><> z?g)~0{&a|SKy*IrbeUQArPaQ_(9z{^&IQ;-1fQ}uMP5+awaHGLN!msZ-`z#vQ|?|Z<33p(RNdMubPL{(2c-`YEr45&2oKQ zhV-buTe})Mt=LrUOmLFZ<#o(=nYK?q27h;a#6|^9H>!H9o0aAdS(5AVfOV5Ra0*xA zZ4vgVfh@=DUaY~2|A;X>*exxer8w|rZ?^rY;zOT@sO@)%vBS1eI|5&d)v_vIA1}Xm zUQ{+RxL~6-w}D!XYv3a8B<((P_{T$;{NXop;lc&Ec=4iKx^zjdT)84wuU?gF*RILU zn>Xdwty}Wo!2`K>@1ESfdspt+ zx9x;0ci9M8ge*c9sf^AkBt{;Sh>^$0W8_*bL7pH_$Rx-UiM%0i za@%smJwkbeqKHZn6UjEuErLS?hX@W494sX`L~w}U5WyjWgQW?F2o4b(A~-~Fu+-rY z!6AY}1cwL?kwFHJ2p*C0VDMmNS;X99xWsUY;bOZCmzc(58j4XG!^N6`!k9!1ml!VA zEbxfo5yK;fM+^^ZDtN?jh~W^!K}{FKBZfl^hn%(ycWXl0aew5kdDS|wqU{8fH8pJ~ za7t)2p@9TW32i47CMZd0JE3hm9JX!bw4cy^Li;*I3GFA?CV@i&hXgz5uwfP3dnCl% zbr`WhPJ21ra(LvlXNOjKV1pbUx$>Z6cDSjO!zG7H4vCxw?G%7ZPQ@Hhjv$8%KQ8nz zCOKSkwTI=Rvq47z85|583>*v`3>*v`>~w-d4hI7V1BVAAJ!on_(9zBw0&z{MP7cb=b^XKyF z)hl`Z`n9}y^G4pjeJe##$b3GR$={9q>dP;Ajt5v*p8<`~XLG7ovqqX9Gq}adtZ&jx z53-anZWQYfvqt*$z6)tKT}7D5*caL;Z7c)KB&`>d&dM5#lg0b{TQ$JU*G{voM&@ax zld1`mxkjO7nx@gb-qwc! z?MH?EO!2|;WgZ%Y5da7)@}vt2`L-4 z?lrm`8)B_-FlB>t`OI$2<&*uVcb5M`rCXDH0bH??wB zq`Diq1zl6RM}~d3>9)DWKGrlvW8ybO)ld)aeT{R(>zpb6+vkqnALRSR`oN;f?`)lA zJ!N4%^9S+I@Pp3$L42k&f3UtR{V%3#uf*@{o|)pcTDMglS3K9#-`!^?@h3fnMWZvE z-pcD`*J^6sC!Qx9XOwL z<1Fwfuj7NkmhrIholPfr`MCM``8fJ``k}%kc>6SC%t5eC!{GIMPH{*S8Qw|L(*H#V1{LJ)dK+}tfXTqCO#vwv=il8V)cJjnlcp6Rows`osiGf-> zf}Cv3y#Cq&KxH2WrEqU>o(99=&mQOsOk6k}Z#%wX|1di_Bq=jJCb4|RoRs*p5Acf@ zL*_u3>dZr?vBN{&kamHg33Qlm?qrFQ_ri0ARSi?dsSC$0%IAQ|6O7NmRq9DFZTzS} zG~*aX@yzScFV1onbe8eDrd4=Pet*1zX9{KENm5K0FOMIOm+8;~r)9MY%Xqr8e7d7u z*^KA0ISHr2ENd(lobW*Vr%W}jhFN{V@yzY=vSi9yEyaiNba;+OS&nLu8STz{r5|?u*%lQ{{ry=dPx8P literal 0 HcmV?d00001 diff --git a/resources/icons/replace.ico b/resources/icons/replace.ico new file mode 100644 index 0000000000000000000000000000000000000000..04a6f429fee2886d7fef6d3e28f9c51e27db4e4c GIT binary patch literal 766 zcmbV|u};G<5QhH%63GNCOo(LcTeOeWjT>93QZv?4q`pSStdSz|8jRVR2*L56oumns z7&zx_|NeaEy8sbKmOaAk;|6%)@XKqoJC07ILKd9P{&S6J3 z{kBbS;_?|1B&K5qnKc5~g=<}#y^}l3FyV>w_eXvePt5y04`s+C-Y|8~_o0fBo9>zm z-|-%+nDOJBS5v-Kb?hqS*Ntq8su+0#bk14VAzypm)vmbYZ`V5Kd*aIT(1%*I<+mo- zWyk2cVm=oT!@ zV1Z|D3Ir_bG(IHDevlmOKftq>EW{1D*_Rkhbl6}CW^6&Xf{iP`s-BTXt8q3Yfk5!n z)W>_T`n_LQ)l~OLh`P-bx&uEvHO5ML85?mDg)+_S4C-U=o>o4_R{XpdH zB@rKWh4K-_#{|iM%cLB+LT7)!#hc$lvMnS^}5K#dZkvY`AS4d<3p)5b~anqZ;I6; zzdkw&E9$6LTW!^9n`(5lS%@r*Ofjmo3%M=Q{!aQCYUR}Ca4RR*6a+xhvXM%+9J$aO zxz5(@vX!k3s~>NevA$5}iUqYh(dCXmlC5Uao=nH9`lAmT8>jk0{ozBEoa(ZpR5P8@ zFRI!`Z%TjIn5%bB^$!~LmAR>ScbZ8m{)yjfCJBZUp0tggq|GtkLfe>xwJOFpPIoNj z{n~Z3GQK-!oZQKH#Jd}N;nfSBCfji7YiaKMRet=ga2nLB$L@74uvw5LC?ZxV8F#(vf9gmfwnOzM&YoV9c8`rmgKh zU!tr~Ci{Fqf3|ek<9OK9uA!#;FYWVQnxs8Gy(B5R?WAjxb~|BOlXhX!B55aSyZCaq zYkJ9Q>&vZOlO}g|ziQ&|zH07lB!(qXDn(D)jatvPTg_kiwq-4U*)~aP*Q{&Ow!N#u zbX{xvr9k~3$=UOO)j>M*?+V$$X$N%5hMKOMr@>zaGWyeJGCw~rp6AJ>OPA#G<;!yA z$`!eK^{Vh)mbJAtxq0)Z+_-T=u3x_{*REZY&ZkB`{@BQG&VR7<-J}UZ$~Y>>5R5*Vt(!!=7Q!uxA)D z>;~PSo6Kca)QaGWK#GeL=bKAUrC?AnC>S`DN+cK*3*c@7>*bYH&t-NFvKv#FxaV!;fP^~VaVLco|RKbp7IafO|P9DsK~7u=cbz68YWFr zO#%&*Cb!0^$*sw)$?cZI<;G5aO@2*&yF{A&nz_+1Xc#o}V3&uqGKSnVk>E zVt0dG1#B=F7z_*s1_OhE!EGlPG8haD28Ijq&9 znBhXhpqLmjdJKngvF(^xek8w>Teofr2Zr3edspt=yC;tyKb8j%9>~+DPvzOOXR^P) zFE3uake4rC%JK2Bj9wf0?fv_Fk2}n+{SMfMcuuo%t!mqUF8OxO0O~wNSS@Ya{L~x; zEM*YoK@f(aueDDT1o^~l%z6+7kx2vH@Atzfifp(bH5f5liRMBqa{)4!I=>0mjR`R$ zL`uUj*Z%nWFwgUTpz|u&&kd;hbK!b!wpli>nBlh>*4NjgC>$0u{pN5DC&`P$Vt772^tk;X zDh}V_-^N{=^VbfGnb&W#1tOn!%e9&L*)DtDu=pVZ4)l5c6t7oUIjGfI=jO-T^Qz2B zDTiwcV z`&{{+@)5szW=n^66g-@1uA!#;{yl|f5|1d#`}dUh?b-tx$W!e)L*JSgHBUpvBQN|kU)+v(4wQP;4^v)9c$!qpSUhAy9#1Fw{V4GL zgFj4ko}7E1)clrxxVgZ4JP8j!@e3E2hT8I+e}~v>{{h@1tJMGi literal 0 HcmV?d00001 diff --git a/resources/icons/text.ico b/resources/icons/text.ico new file mode 100644 index 0000000000000000000000000000000000000000..b47bd9cf80af604dd08238658240aa331f2a8a59 GIT binary patch literal 1398 zcmd^YdMqH1>sTg+y6ea zdaiaPQb;LnTZ`iDM&uD)*XHjEk-Jie69D)4&$l8w>=r)3LTFG}7qic#7vUH0O(8KQ zgn-sSOuxja7Ua_=6$@-%Pq+wf)prb=@HKzzC&d5MUsIIQm-Cf#nSFQMTio&!-HY|e z@yfz;zN=}19MALpOzSp3xYXfy*)X57g9jJ~DPRdJU2=xjc@0~b>)d0n!&Bo#sOhAT znBS}Olt$)qq+c)U3O`*e{W(_}KkRLM^0jRRF}fHyx{*CsZVXl58G zd%z!q_4Zi!0Jg9~vT%J7doJjKr?3nWaS$Vy#J^@8g B+WG(h literal 0 HcmV?d00001 diff --git a/resources/icons/thdtree.ico b/resources/icons/thdtree.ico new file mode 100644 index 0000000000000000000000000000000000000000..bbfccedef82bfd198bc5c2b20a3fc8c87d96527a GIT binary patch literal 766 zcmb_ZF>Zt~5FB!m9EpOf(n6VEBu}E-lPGYPg3r(eDGkp_Nq6b?W{qt@bSP4kF=Kl@ zyX&z43XY~Z!|?S0Jac^^8}f}T@W>hP)b7Jmq~iU$aORfunCF>-h7YQ?BRTBIraz;V zeQ7Ihd*Hh1qXnEKOrOKHq8u=dKt&S_)UR^iX zebo}u`LfkdW%&WC->mC`C(F0{B9?7E+k`mRi`>P zRDY{wwq@_zaCpg-zT7q|s4rcL{SUUxe*T`>hablNC-0m6a>uZjwb>tfW`Et4zU}|m z?BP!|KK4KPk=f5iW*^WUl0PDE84gG#bFEG#N;G8|D@vL+o6V@D2>#DPP>al;L@M4g zZI70d+4eUFi?lza&n#{FW`9I~VZZ17P8-5%{leXW+t2%>IDXzgaQ(tv2wxM(y(Rqt z{q5!cKC55gq4k#0_}?489Ilz!#nS8o`91R1oAM0(A^8Z~Wgq`;Tl-vFe@O23`Hc4J zRIu&2GEr1@6+ynXt%=fL-edoG~%ZPM2J`@6bTvO1MagofaFMM?9m;)J*VN;>uH>^eiU8_PfAuFcPP zwS2fnK0^M-vs?DyU^E)u;4nFpT6s6wQtkHd%FmMDuIc`}PFs&g{{qh$dv#a&>Evgl zpg#7ej?2#F{%CmmT-B6f7}({@m+i`xD|YqjRlPp#`t|E}_wHT0bLWoT zzJ1$n-n?n$uY!I2aj@UmGYele_NQlmvu9s?VZZ+Db0*5j1-T|Sv6~^wkY&g+$q2Gc z3giWi0(pVFK(1jm@)~(fr$$~Qr?LilgStB#VS*t8Lk5Nn3>g?4Iv6rAWMIg^kbxl!$iR_-BNGlb z5vUwRL0N#M080TDUouz^#)WU4yA+(wYfsFxAXmqpX>`X6~A~ z{c-r*$eF)p{+jvqh-&7qiA@cL8VogY&|@PizIr6olzNQBpkclSYz;UX=JQ7@9K@gj zMF}OGz@GoY7jJF;U5`4Jd*}2jpE^0v>Wskkbxn9A%G!( zA%G!(!M76_8ZZPf1TZvUzzrFDFa$6JFa$8*nv5I_0So~Q0RVyd8=M6O28;@P6!Z&l z1othQ!V>$P-Me?s?%%&}4Ei@~dvJ-A!6VAWi!HL9*zdZl%c}A@}=9dRpNY44}uxHqk+ag~qLbby|ImEs+nx&H4@Fd&#L5Qk3`_hhVb?RfNz?xKtd zOI*@dNIGR(`wBFEisczy*JUY~b-hik2t*$8hVtZE2#0(cDDAbIQ3VF(BAinu}vPN1UrxG+>D^D+n1BtmTvI zCjqt?Bn6&R?B_#PEhHHXAcj>n0l7FH#@zV_{ei>X zFvhutIT>5FPccozuiHsPTLlDiE7xMMG#CB_O&W=IcWH^ein~VF5J&+6x!jWC(q)wl zc6Xu0&YNZIu3@UZoE}^~j)?Xt^w#a=*>Sja4kKnZN2nol|MHjgg~xHY?{U?W^&;`$ z7&7n&Es(Mo4!IT%xVYH36K)qt7iZzY^JKsut-*nB(QlG?xDV@?x`w2>RLDP`CsN}c zYpUMy>+t%Cm_5||b?8-GLYPID9LEE3m$eFcK5H+kUro53aLoWug{NLmmkkfiI(yw5 z;=P8)6R_6?V}Gu+7n-H*&r3NLfAcpu2sB^KYHxXDHfp}Z;Z#Fc4i2B=G(eDu#H~F7!#oFwa`dt1l zsy@_z6jSR0_Q32rD&xDoe;?<2{h|7NCokVcjlY@v+=~9U+IbxZm&_Jli}&zB>?=~z zz1v#%)Jx9#4Eo z(bCZm>(O`IRwBX#;}MZXBEE$ra%D>Yfa@GN>5F{93#(})a^ml>S7>4H^|U(X>n{$( v(R1%Z7~>40j-(|3|9P`Lll^S|PUkl7hC|MAorCkF_XDz{Yx6U_JIn2#(5gcD literal 0 HcmV?d00001 diff --git a/resources/icons/xbe.ico b/resources/icons/xbe.ico new file mode 100644 index 0000000000000000000000000000000000000000..c2bb117e34f1ce4b7732342d5b5fd6f964c31b1e GIT binary patch literal 8478 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 QGEA6F``3-9-fOi#0ovxH)&Kwi literal 0 HcmV?d00001 diff --git a/resources/xheader.bmp b/resources/xheader.bmp new file mode 100644 index 0000000000000000000000000000000000000000..90ee2b37760e26e3df4c8f7485d01896300e71b3 GIT binary patch literal 7308 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 literal 0 HcmV?d00001 diff --git a/resources/xwmark.bmp b/resources/xwmark.bmp new file mode 100644 index 0000000000000000000000000000000000000000..3b12e7ca43842824c637f8af787c7d86531564b1 GIT binary patch literal 26136 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 YYo;mve&yPshAhxCvF9mAK7mQ}U)AQ#z5oCK literal 0 HcmV?d00001 diff --git a/src/AppGlobal.pas b/src/AppGlobal.pas new file mode 100644 index 0000000..a65b9c6 --- /dev/null +++ b/src/AppGlobal.pas @@ -0,0 +1,203 @@ +unit AppGlobal; + +interface + +uses Tool, LogStream, Classes, Clipbrd, SysUtils, StrUtils, IdGlobal, XBOXManager; + +{ type TMemSect = record + Offset,Size,Flags,Loc:Cardinal; + end; } + + type TParsedParams = record + Key:TStringList; + Value:TStringList; + end; + + type TStatus = (stNorm,stDump,stBreak,stGetXBEInfo,stGetContext); + + function ConvPC2XBOX(PCOffset:Cardinal;XBOXOffset:PCardinal):Integer; + function ConvXBOX2PC(XBOXOffset:Cardinal;Section:PInteger;PCOffset:PCardinal):Integer; + function ConvError(Err:Integer):String; + function TextToClip(Text:String):Boolean; + function MakeOffset(const AString:String):String; + function ParseParams(const AInput:String):TParsedParams; + +const + EConvOkay = 0; + EConvNotFound = -1; + EConvBadPointer = -2; + EConvOutOfRange = -3; + EConvNoSections = -4; + +var + Tools: array of TTool; +// Sections: array of TMemSect; +// MemBuffer:TMemoryStream; + Log:TLogStream; + ProgStatus:TStatus; + DebugBox:TXBOX; + +implementation + +function ParseParams(const AInput:String):TParsedParams; +begin + {202- multiline response follows + timestamp=0x40d52299 checksum=0x00000000 + name="E:\UnleashX\default.xbe" + .} + +end; + +function MakeOffset(const AString:String):String; +var +XPos:Integer; +NewString:String; +PadStr:String; +begin + Result := '0x00000000'; + NewString := Uppercase(AString); + NewString := AnsiReplaceText(NewString,' ',''); + + XPos := AnsiPos('0X',NewString); + + if (XPos < 0) then + NewString := '0x' + NewString + else if (XPos > 1) then + NewString := AnsiRightStr(NewString,Length(NewString) - XPos+1); + + if (Length(NewString) > 10) then + SetLength(NewString,10) + else if Length(NewString) < 10 then + begin + for XPos := 0 to (10 - Length(NewString) - 2) do + PadStr := PadStr + '0'; + NewString := StuffString(NewString,3,0,PadStr); + end; + +// if(IsHexidecimal(NewString)) then +{TODO: Fix this so it will check for valid hex} + Result := NewString + +end; + +function ConvXBOX2PC(XBOXOffset:Cardinal;Section:PInteger;PCOffset:PCardinal):Integer; +var +Count:Integer; +begin + if not Assigned(PCOffset) then + begin + Result := EConvBadPointer; + Exit; + end; + +with DebugBox.Memory do +begin + if(Length(Sections) <= 0) then + begin + Result := EConvNoSections; + Exit; + end; + + if(XBOXOffset < Sections[Low(Sections)].Offset) or + (XBOXOffset > (Sections[High(Sections)].Offset + Sections[High(Sections)].Size)) then + begin + Result := EConvOutOfRange; + Exit; + end; + + for Count := Low(Sections) to High(Sections) do + begin + with Sections[Count] do + begin + if (XBOXOffset < Offset) or (XBOXOffset > (Offset+Size)) then Continue; + + if Assigned(Section) then Section^ := Count; + + if Assigned(PCOffset) then + PCOffset^ := Cardinal(DebugBox.Memory.Buffer.Memory) + Loc - Size + XBOXOffset - Offset; +// PCOffset^ := Cardinal(MemBuffer.Memory) + Loc - Size + (XBOXOffset - Offset); + Result := EConvOkay; + Exit; + end; + end; + Result := EConvNotFound; +end; +end; + +function ConvPC2XBOX(PCOffset:Cardinal;XBOXOffset:PCardinal):Integer; +var +Pos:Integer; +Base:Cardinal; +begin + if not Assigned(XBOXOffset) then + begin + Result := EConvBadPointer; + Exit; + end; + +// if (PCOffset < Cardinal(MemBuffer.Memory)) or +// (PCOffset > (Cardinal(MemBuffer.Memory) + MemBuffer.Size)) then + if (PCOffset < Cardinal(DebugBox.Memory.Buffer.Memory)) or + (PCOffset > (Cardinal(DebugBox.Memory.Buffer.Memory) + DebugBox.Memory.Buffer.Size)) then + begin + Result := EConvOutOfRange; + Exit; + end; + +with DebugBox.Memory do +begin + for Pos := High(Sections) downto Low(Sections) do + begin +// Base := Cardinal(MemBuffer.Memory) + Sections[Pos].Loc; + Base := Cardinal(DebugBox.Memory.Buffer.Memory) + Sections[Pos].Loc; + if ( (PCOffset >= (Base - Sections[Pos].Size)) and (PCOffset < Base) ) then + begin + Result := EConvOkay; + if Assigned(XBOXOffset) then + XBOXOffset^ := Sections[Pos].Offset + + (PCOffset - +// Cardinal(MemBuffer.Memory) - + Cardinal(DebugBox.Memory.Buffer.Memory) - + (Sections[Pos].Loc - + Sections[Pos].Size)); + Exit; + end; + end; +end; + Result := EConvNotFound; +end; + +function ConvError(Err:Integer):String; +begin + case Err of + EConvOutOfRange: Result := 'Offset was not found in the buffer range. Check the log to see if the buffer has jumped around. If it hasn''t check and make sure you are putting the correct address. Also, make sure you are searching the correct memory ranges with your search application.'; + EConvNotFound: Result := 'The offset was found in the buffer range, but it was not found inside of any of the memory sections that are saved. This could be a mathematical mistake, or some other coding problem. It shouldn''t be caused by anything you did.'; + EConvBadPointer: Result := 'The pointer supplied for returning the offset in was empty. No offset was returned.'; + EConvNoSections: Result := 'There are no saved memory sections. Are you connected?'; + EConvOkay: Result := 'Everything went okay.'; + else + Result := 'An unknown error occured while trying to convert the offset.'; + end; + + +end; + +function TextToClip(Text:String):Boolean; +var + Clipboard:TClipboard; +begin + Result := false; + try + Clipboard := TClipboard.Create; + if Assigned(Clipboard) then + begin + Clipboard.AsText := Text; + FreeAndNil(Clipboard); + Result := true; + end; + except + on E: Exception do Result := false; + end; +end; + +end. diff --git a/src/Breakpoint.pas b/src/Breakpoint.pas new file mode 100644 index 0000000..6b16cd2 --- /dev/null +++ b/src/Breakpoint.pas @@ -0,0 +1,47 @@ +unit Breakpoint; + +interface + +uses SysUtils; + +type TBPTypes = (Read,Write,Addr,Execute); + +type + TBreakpoint = record + Enabled:Boolean; + Offset: Cardinal; + Size: Cardinal; + BPType:TBPTypes; + Desc: String; +end; + +function LocateBreakpoint(fBreak:TBreakpoint):Integer; + +var + Breakpoints: array of TBreakpoint; + +implementation + +function LocateBreakpoint(fBreak:TBreakpoint):Integer; +var +Counter:Integer; +begin + Result := -1; + if Length(Breakpoints) <= 0 then + begin + Result := -1; + Exit; + end; + + for Counter := Low(Breakpoints) to High(Breakpoints) do + begin + if CompareMem(@fBreak,@Breakpoints[Counter],SizeOf(TBreakpoint)) then + begin + Result := Counter; + Break; + end; + end; + +end; + +end. diff --git a/src/LogStream.pas b/src/LogStream.pas new file mode 100644 index 0000000..e228ee0 --- /dev/null +++ b/src/LogStream.pas @@ -0,0 +1,47 @@ +unit LogStream; + +interface + +uses Classes,Windows; + +type TLogStream = class(TStringStream) + UpdateMsg:Cardinal; + UpdateWindow:THandle; + procedure AddLn(const AString:String); + procedure SaveToFile(const FileName:String); + constructor Create(const AString:String;Msg:Cardinal;Handle:THandle); + procedure Clear; +end; + +implementation + +constructor TLogStream.Create(const AString:String;Msg:Cardinal;Handle:THandle); +begin + inherited Create(AString); + UpdateMsg := Msg; + UpdateWindow := Handle; +// SendMessage(UpdateWindow,UpdateMsg,0,Self.Size); +end; + +procedure TLogStream.Clear; +begin + SetSize(0); +end; + +procedure TLogStream.AddLn(const AString:String); +begin + WriteString(AString + #13#10); + SendMessage(UpdateWindow,UpdateMsg,Self.Size-(Length(AString)+2),Length(AString)+2); +end; + +procedure TLogStream.SaveToFile(const FileName:String); +var +logfile:TextFile; +begin + AssignFile(logfile,FileName); + ReWrite(logfile); + WriteLn(logfile,Self.DataString); + CloseFile(logfile); + AddLn('Log saved to ' + FileName + '.'); +end; +end. diff --git a/src/Main.dfm b/src/Main.dfm new file mode 100644 index 0000000..7b99037 --- /dev/null +++ b/src/Main.dfm @@ -0,0 +1,1724 @@ +object frmMain: TfrmMain + Left = 0 + Top = 0 + Caption = 'XDK Assist' + ClientHeight = 573 + ClientWidth = 715 + Color = clBtnFace + Constraints.MinHeight = 403 + Constraints.MinWidth = 489 + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + Menu = mnuMain + OldCreateOrder = False + Position = poDesktopCenter + OnClose = FormClose + OnCloseQuery = FormCloseQuery + OnCreate = FormCreate + OnDestroy = FormDestroy + DesignSize = ( + 715 + 573) + PixelsPerInch = 96 + TextHeight = 13 + object grpConsole: TGroupBox + Left = 8 + Top = 8 + Width = 707 + Height = 185 + Anchors = [akLeft, akTop, akRight] + Caption = 'Console:' + Color = clBtnFace + ParentColor = False + TabOrder = 0 + DesignSize = ( + 707 + 185) + object richLog: TRichEdit + Left = 9 + Top = 16 + Width = 688 + Height = 137 + TabStop = False + Anchors = [akLeft, akTop, akRight, akBottom] + BorderStyle = bsNone + Color = clBlack + Font.Charset = DEFAULT_CHARSET + Font.Color = clWhite + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + Constraints.MinHeight = 89 + Constraints.MinWidth = 200 + ParentFont = False + ReadOnly = True + ScrollBars = ssVertical + TabOrder = 0 + OnChange = richLogChange + end + object edInput: TEdit + Left = 9 + Top = 153 + Width = 688 + Height = 22 + Anchors = [akLeft, akRight, akBottom] + BorderStyle = bsNone + Color = clBlack + Font.Charset = DEFAULT_CHARSET + Font.Color = clWhite + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + ParentFont = False + TabOrder = 1 + OnKeyUp = edInputKeyUp + end + end + object StatusBar: TStatusBar + Left = 0 + Top = 556 + Width = 715 + Height = 17 + Panels = < + item + Width = 50 + end> + SimplePanel = True + end + object pgControl: TPageControl + Left = 8 + Top = 200 + Width = 705 + Height = 350 + ActivePage = tbBreakpoints + Anchors = [akLeft, akTop, akRight, akBottom] + HotTrack = True + Images = ImageList1 + TabOrder = 1 + object tbBreakpoints: TTabSheet + Caption = 'Breakpoints' + ImageIndex = -1 + DesignSize = ( + 697 + 321) + object lbBPType: TLabel + Left = 184 + Top = 0 + Width = 28 + Height = 13 + Caption = 'Type:' + end + object bpUnset: TButton + Left = 632 + Top = 13 + Width = 57 + Height = 25 + Anchors = [akTop, akRight] + Caption = 'Unset' + TabOrder = 5 + OnClick = bpUnsetClick + end + object bpSet: TButton + Left = 568 + Top = 13 + Width = 57 + Height = 25 + Anchors = [akTop, akRight] + Caption = 'Set' + TabOrder = 4 + OnClick = bpSetClick + end + object edBPOffset: TLabeledEdit + Left = 8 + Top = 18 + Width = 81 + Height = 21 + EditLabel.Width = 35 + EditLabel.Height = 13 + EditLabel.Caption = 'Offset:' + MaxLength = 10 + TabOrder = 1 + OnKeyPress = edBPOffsetKeyPress + end + object lvBreak: TListView + Left = 8 + Top = 48 + Width = 681 + Height = 154 + Anchors = [akLeft, akTop, akRight, akBottom] + Checkboxes = True + Columns = < + item + Caption = 'Offset' + MinWidth = 90 + Width = 90 + end + item + Caption = 'Type' + Width = 75 + end + item + Caption = 'Size' + end + item + Caption = 'Last Hit' + MinWidth = 90 + Width = 90 + end + item + AutoSize = True + Caption = 'Description' + end> + FlatScrollBars = True + GridLines = True + MultiSelect = True + RowSelect = True + TabOrder = 0 + ViewStyle = vsReport + OnChange = lvBreakChange + OnKeyUp = lvBreakKeyUp + OnSelectItem = lvBreakSelectItem + end + object cmbBPType: TComboBox + Left = 184 + Top = 18 + Width = 83 + Height = 21 + Style = csDropDownList + ItemHeight = 13 + TabOrder = 3 + end + object edBPSize: TLabeledEdit + Left = 96 + Top = 18 + Width = 81 + Height = 21 + EditLabel.Width = 23 + EditLabel.Height = 13 + EditLabel.Caption = 'Size:' + TabOrder = 2 + OnKeyPress = edBPSizeKeyPress + end + object btGetRegisters: TButton + Left = 616 + Top = 282 + Width = 75 + Height = 25 + Anchors = [akRight, akBottom] + Caption = 'Update' + TabOrder = 6 + OnClick = btGetRegistersClick + end + object lvRegisters: TListView + Left = 8 + Top = 210 + Width = 497 + Height = 99 + Anchors = [akLeft, akRight, akBottom] + Columns = < + item + MaxWidth = 45 + MinWidth = 5 + Width = 30 + end + item + MinWidth = 5 + Width = 74 + end + item + MaxWidth = 45 + MinWidth = 5 + Width = 30 + end + item + MinWidth = 5 + Width = 74 + end + item + MaxWidth = 45 + MinWidth = 5 + Width = 30 + end + item + MinWidth = 5 + Width = 74 + end + item + MinWidth = 5 + Width = 74 + end + item + MinWidth = 5 + Width = 74 + end> + FlatScrollBars = True + GridLines = True + ShowColumnHeaders = False + TabOrder = 7 + ViewStyle = vsReport + end + object edBPDesc: TLabeledEdit + Left = 272 + Top = 18 + Width = 289 + Height = 21 + Anchors = [akLeft, akTop, akRight] + EditLabel.Width = 57 + EditLabel.Height = 13 + EditLabel.Caption = 'Description:' + TabOrder = 8 + end + end + object tbDumping: TTabSheet + Caption = 'Dumping' + ImageIndex = 8 + DesignSize = ( + 697 + 321) + object pbDump: TProgressBar + Left = 208 + Top = 8 + Width = 481 + Height = 17 + Anchors = [akLeft, akTop, akRight] + Smooth = True + TabOrder = 0 + end + object lvDump: TListView + Left = 208 + Top = 32 + Width = 481 + Height = 163 + Anchors = [akLeft, akTop, akRight, akBottom] + Columns = < + item + Caption = '#' + MaxWidth = 40 + MinWidth = 15 + Width = 30 + end + item + AutoSize = True + Caption = 'Offset' + MinWidth = 80 + end + item + AutoSize = True + Caption = 'Size' + MinWidth = 80 + end + item + AutoSize = True + Caption = 'Flags' + MinWidth = 80 + end + item + AutoSize = True + Caption = 'Start' + MinWidth = 80 + end + item + AutoSize = True + Caption = 'End' + MinWidth = 80 + end> + FlatScrollBars = True + GridLines = True + HotTrack = True + RowSelect = True + TabOrder = 1 + ViewStyle = vsReport + OnSelectItem = lvDumpSelectItem + end + object grpMemEdit: TGroupBox + Left = 512 + Top = 204 + Width = 177 + Height = 97 + Anchors = [akRight, akBottom] + Caption = 'Memory Editing:' + TabOrder = 4 + object Label1: TLabel + Left = 8 + Top = 12 + Width = 34 + Height = 13 + Caption = 'Action:' + end + object cbMemEdit: TComboBox + Left = 8 + Top = 28 + Width = 73 + Height = 21 + Style = csDropDownList + ItemHeight = 13 + ItemIndex = 0 + TabOrder = 0 + Text = 'GETMEM' + OnChange = cbMemEditChange + Items.Strings = ( + 'GETMEM' + 'SETMEM') + end + object edMemEditOffset: TLabeledEdit + Left = 88 + Top = 28 + Width = 81 + Height = 21 + EditLabel.Width = 35 + EditLabel.Height = 13 + EditLabel.Caption = 'Offset:' + MaxLength = 8 + TabOrder = 1 + end + object edMemEditParam: TLabeledEdit + Left = 8 + Top = 68 + Width = 121 + Height = 21 + CharCase = ecUpperCase + EditLabel.Width = 37 + EditLabel.Height = 13 + EditLabel.Caption = 'Length:' + TabOrder = 2 + OnKeyUp = edMemEditParamKeyUp + end + object btMemEdit: TButton + Left = 136 + Top = 64 + Width = 33 + Height = 25 + Caption = 'Go' + TabOrder = 3 + OnClick = btMemEditClick + end + end + object grpConvOffset: TGroupBox + Left = 320 + Top = 204 + Width = 185 + Height = 97 + Anchors = [akRight, akBottom] + Caption = 'Offset Conversion:' + TabOrder = 3 + object Label2: TLabel + Left = 8 + Top = 12 + Width = 67 + Height = 13 + Caption = 'Original Type:' + end + object lbConvOffStat: TLabel + Left = 80 + Top = 12 + Width = 97 + Height = 33 + AutoSize = False + end + object cbOffsetConvert: TComboBox + Left = 8 + Top = 28 + Width = 65 + Height = 21 + Style = csDropDownList + ItemHeight = 13 + ItemIndex = 0 + TabOrder = 0 + Text = 'PC' + Items.Strings = ( + 'PC' + 'XBOX') + end + object edConvOffsetTo: TLabeledEdit + Left = 96 + Top = 68 + Width = 81 + Height = 21 + EditLabel.Width = 55 + EditLabel.Height = 13 + EditLabel.Caption = 'Converted:' + MaxLength = 10 + ReadOnly = True + TabOrder = 2 + end + object edConvOffsetFrom: TLabeledEdit + Left = 8 + Top = 68 + Width = 81 + Height = 21 + EditLabel.Width = 40 + EditLabel.Height = 13 + EditLabel.Caption = 'Original:' + MaxLength = 10 + TabOrder = 1 + OnKeyPress = edConvOffsetFromKeyPress + OnKeyUp = edConvOffsetFromKeyUp + end + end + object GroupBox1: TGroupBox + Left = 8 + Top = 4 + Width = 193 + Height = 191 + Anchors = [akLeft, akTop, akBottom] + Caption = 'Section Flags:' + TabOrder = 2 + DesignSize = ( + 193 + 191) + object lbSectFlags: TCheckListBox + Left = 8 + Top = 16 + Width = 177 + Height = 163 + Anchors = [akLeft, akTop, akRight, akBottom] + ItemHeight = 13 + Sorted = True + TabOrder = 0 + end + end + object btDump: TButton + Left = 8 + Top = 204 + Width = 305 + Height = 97 + Anchors = [akLeft, akRight, akBottom] + Caption = + 'Here is AcidFlash'#39's dump button, so he doesn'#39't have to put down ' + + 'the controller for a split second.' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [fsBold] + ParentFont = False + TabOrder = 5 + WordWrap = True + OnClick = btDumpClick + end + end + object tbTools: TTabSheet + Caption = 'Tools' + ImageIndex = 11 + object GroupBox3: TGroupBox + Left = 0 + Top = 0 + Width = 697 + Height = 321 + Align = alClient + Caption = 'Tools' + Constraints.MinHeight = 160 + Constraints.MinWidth = 210 + TabOrder = 0 + DesignSize = ( + 697 + 321) + object edToolPath: TLabeledEdit + Left = 8 + Top = 32 + Width = 649 + Height = 21 + Anchors = [akLeft, akTop, akRight] + EditLabel.Width = 26 + EditLabel.Height = 13 + EditLabel.Caption = 'Path:' + Enabled = False + TabOrder = 0 + end + object edToolCaption: TLabeledEdit + Left = 8 + Top = 72 + Width = 577 + Height = 21 + Anchors = [akLeft, akTop, akRight] + EditLabel.Width = 41 + EditLabel.Height = 13 + EditLabel.Caption = 'Caption:' + TabOrder = 1 + end + object lvToolList: TListView + Left = 8 + Top = 104 + Width = 681 + Height = 209 + Anchors = [akLeft, akTop, akRight, akBottom] + Columns = < + item + AutoSize = True + Caption = 'Caption' + end + item + AutoSize = True + Caption = 'Path' + end + item + AutoSize = True + Caption = 'Launch' + end> + FlatScrollBars = True + GridLines = True + SortType = stText + TabOrder = 2 + ViewStyle = vsReport + end + object chkToolLaunch: TCheckBox + Left = 592 + Top = 72 + Width = 97 + Height = 17 + Anchors = [akTop, akRight] + Caption = 'Launch On Load' + TabOrder = 3 + end + object btnToolSelect: TButton + Left = 664 + Top = 32 + Width = 25 + Height = 21 + Anchors = [akTop, akRight] + Caption = '...' + TabOrder = 4 + OnClick = btnToolSelectClick + end + end + end + object tbMemView: TTabSheet + Caption = 'Memory View' + ImageIndex = 12 + DesignSize = ( + 697 + 321) + object edByteSearch: TLabeledEdit + Left = 256 + Top = 24 + Width = 361 + Height = 21 + Anchors = [akLeft, akTop, akRight] + CharCase = ecUpperCase + EditLabel.Width = 62 + EditLabel.Height = 13 + EditLabel.Caption = 'Byte Search:' + TabOrder = 1 + OnKeyPress = edByteSearchKeyPress + OnKeyUp = edByteSearchKeyUp + end + object edViewOffset: TLabeledEdit + Left = 160 + Top = 24 + Width = 89 + Height = 21 + CharCase = ecUpperCase + EditLabel.Width = 78 + EditLabel.Height = 13 + EditLabel.Caption = 'Jump To Offset:' + MaxLength = 10 + TabOrder = 0 + OnKeyPress = edViewOffsetKeyPress + OnKeyUp = edViewOffsetKeyUp + end + object hxMemView: TMPHexEditor + Left = 8 + Top = 56 + Width = 681 + Height = 251 + Cursor = crIBeam + Anchors = [akLeft, akTop, akRight, akBottom] + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -15 + Font.Name = 'Courier New' + Font.Style = [] + ParentFont = False + PopupMenu = popMemView + TabOrder = 3 + BytesPerRow = 16 + BytesPerColumn = 1 + Translation = tkAsIs + OffsetFormat = '-!10:0x|' + Colors.Background = clWindow + Colors.ChangedBackground = 11075583 + Colors.ChangedText = clMaroon + Colors.CursorFrame = clNavy + Colors.Offset = clBlack + Colors.OddColumn = clBlue + Colors.EvenColumn = clNavy + Colors.CurrentOffsetBackground = clMoneyGreen + Colors.OffsetBackGround = clWindow + Colors.CurrentOffset = clGreen + Colors.Grid = clSkyBlue + Colors.NonFocusCursorFrame = clAqua + Colors.ActiveFieldBackground = clWindow + FocusFrame = True + NoSizeChange = True + AllowInsertMode = False + DrawGridLines = True + GraySelectionIfNotFocused = True + Version = 'December 29, 2004; '#169' markus stephany, vcl[at]mirkes[dot]de' + OnTopLeftChanged = hxMemViewTopLeftChanged + OnChange = hxMemViewChange + DrawGutter3D = False + FindProgress = True + ExplicitHeight = 253 + end + object btMemSrchReset: TButton + Left = 624 + Top = 19 + Width = 65 + Height = 25 + Anchors = [akTop, akRight] + Caption = 'Reset' + TabOrder = 2 + OnClick = btMemSrchResetClick + end + object chkHighlightDumpChanges: TCheckBox + Left = 8 + Top = 23 + Width = 137 + Height = 17 + Hint = + 'This will add a lot of time to your dumps, but will in turn allo' + + 'w you to see the changes between different dumps in the memory v' + + 'iew window.' + Caption = 'Highlight Dump Changes' + ParentShowHint = False + ShowHint = True + TabOrder = 4 + WordWrap = True + end + end + object tbNotes: TTabSheet + Caption = 'Notes' + ImageIndex = 3 + DesignSize = ( + 697 + 321) + object moNotes: TMemo + Left = 8 + Top = 8 + Width = 681 + Height = 298 + Anchors = [akLeft, akTop, akRight, akBottom] + ScrollBars = ssVertical + TabOrder = 0 + WantTabs = True + ExplicitHeight = 300 + end + end + object tbSettings: TTabSheet + Caption = 'Settings' + ImageIndex = 10 + object GroupBox2: TGroupBox + Left = 8 + Top = 8 + Width = 257 + Height = 169 + Caption = 'Searcher Offsets:' + TabOrder = 0 + object edSearcherStart: TLabeledEdit + Left = 8 + Top = 56 + Width = 105 + Height = 21 + EditLabel.Width = 70 + EditLabel.Height = 13 + EditLabel.Caption = 'Start Address:' + MaxLength = 10 + TabOrder = 0 + end + object edSearcherEnd: TLabeledEdit + Left = 8 + Top = 96 + Width = 105 + Height = 21 + EditLabel.Width = 64 + EditLabel.Height = 13 + EditLabel.Caption = 'End Address:' + MaxLength = 10 + TabOrder = 1 + end + object chkUseSearchRange: TCheckBox + Left = 120 + Top = 18 + Width = 133 + Height = 17 + Caption = 'Automatically set range' + Checked = True + State = cbChecked + TabOrder = 2 + end + object edSearcherCaption: TLabeledEdit + Left = 120 + Top = 56 + Width = 113 + Height = 21 + EditLabel.Width = 82 + EditLabel.Height = 13 + EditLabel.Caption = 'Window Caption:' + TabOrder = 3 + end + object edSearcherClass: TLabeledEdit + Left = 120 + Top = 96 + Width = 113 + Height = 21 + EditLabel.Width = 59 + EditLabel.Height = 13 + EditLabel.Caption = 'Class Name:' + TabOrder = 4 + end + object edSearcherState: TLabeledEdit + Left = 8 + Top = 136 + Width = 105 + Height = 21 + EditLabel.Width = 84 + EditLabel.Height = 13 + EditLabel.Caption = 'Enabled Address:' + TabOrder = 5 + end + object cbRangePresets: TComboBox + Left = 8 + Top = 18 + Width = 105 + Height = 21 + Style = csDropDownList + ItemHeight = 13 + Sorted = True + TabOrder = 6 + end + end + object GroupBox4: TGroupBox + Left = 263 + Top = 8 + Width = 170 + Height = 81 + Caption = 'Dump:' + TabOrder = 1 + object chkDumpAutoStop: TCheckBox + Left = 8 + Top = 19 + Width = 81 + Height = 17 + Caption = 'Auto-Pause' + Checked = True + State = cbChecked + TabOrder = 0 + end + object chkCopyOffToClip: TCheckBox + Left = 8 + Top = 36 + Width = 155 + Height = 17 + Caption = 'Copy Converts To Clipboard' + Checked = True + State = cbChecked + TabOrder = 1 + WordWrap = True + end + end + object GroupBox5: TGroupBox + Left = 263 + Top = 88 + Width = 170 + Height = 89 + Caption = 'Miscellaneous:' + TabOrder = 2 + object chkWarnConnected: TCheckBox + Left = 9 + Top = 30 + Width = 158 + Height = 19 + Caption = 'Warn on exit if connected' + Checked = True + State = cbChecked + TabOrder = 0 + WordWrap = True + end + object chkVerboseLog: TCheckBox + Left = 9 + Top = 14 + Width = 105 + Height = 17 + Caption = 'Verbose Logging' + Checked = True + State = cbChecked + TabOrder = 1 + end + object chkShowMainLog: TCheckBox + Left = 8 + Top = 48 + Width = 153 + Height = 17 + Caption = 'Show main log' + Checked = True + State = cbChecked + TabOrder = 2 + OnClick = chkShowMainLogClick + end + end + end + end + object SaveDialog: TSaveDialog + DefaultExt = '.bin' + Filter = + 'Dumps (*.bin)|*.bin|Breakpoint List (*.bpl)|*.bpl|Notes file (*.' + + 'notes, *.txt)|*.notes;*.txt|All files (*.*)|*.*' + Options = [ofOverwritePrompt, ofHideReadOnly, ofPathMustExist, ofEnableSizing] + Title = 'Save..' + Left = 640 + Top = 64 + end + object ImageList1: TImageList + Left = 672 + Top = 96 + Bitmap = { + 494C0101100013002C0010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600 + 0000000000003600000028000000400000005000000001002000000000000050 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000FFFFFF00FFFFFF00FFFFFF00FFFFFF000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000FFFFFF008080 + 8000008000000080000080808000FFFFFF00FFFFFF0000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000008080 + 8000808080000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000FFFFFF00008000000080 + 000000800000008000000080000080808000FFFFFF00FFFFFF00008000000080 + 0000008000000080000080808000808080000000000000000000000000000000 + 0000000000008080800000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000008080800080808000808080008080 + 8000808080008080800080808000808080008080800080808000C0C0C00000FF + FF0000FFFF008080800000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000080808000008000000080 + 0000008000000080000080808000FFFFFF0000000000FFFFFF00008000000080 + 000000800000008000000080000000800000000000008080800000000000FF00 + FF0000000000FFFFFF00FFFFFF0000000000FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF0000000000FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0080808000C0C0C00000000000FFFF + FF0000FFFF008080800000000000000000000000000000000000000000000000 + 0000000000FF5F5F5FFF00000000000000000000000000000000000000000000 + 00FF5F5F5FFF000000000000000000000000FFFFFF0000800000008000000080 + 00008080800080808000FFFFFF000000000000000000FFFFFF00008000000080 + 0000008000000080000000800000808080000000000000000000FF00FF00FF00 + FF00FFFFFF0000000000FFFFFF00FFFFFF0080808000FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF0000000000FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000000000000FFFF0000FFFF000000 + 0000C0C0C0000000000000000000000000000000000000000000000000000000 + 000000000000000000FF5F5F5FFF000000000000000000000000000000FF5F5F + 5FFF000000000000000000000000000000000080000000800000008000000080 + 000080808000FFFFFF00000000000000000000000000FFFFFF00008000000080 + 0000008000000080000000800000808080000000000000000000FF00FF00FFFF + FF000000000000FFFF0000000000FFFFFF0000000000FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF0000000000FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF008080800000FFFF0000FF + FF00808080000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000FF5F5F5FFF00000000000000FF5F5F5FFF0000 + 0000000000000000000000000000000000000080000000800000008000008080 + 8000FFFFFF000000000000000000000000000000000080808000008000000080 + 0000008000000080000000800000808080000000000000000000808080008080 + 8000FFFFFF0000FFFF0000FFFF0000000000000000008080800080808000FFFF + FF00FFFFFF00FFFFFF00FFFFFF0000000000FFFFFF0000000000FFFFFF008080 + 8000808080008080800080808000808080008080800080808000000000008080 + 8000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000FF000000FF5F5F5FFF000000000000 + 0000000000000000000000000000000000000080000000800000008000008080 + 8000FFFFFF000000000000000000000000000000000080808000808080008080 + 8000008000000080000000800000808080008080800000000000000000000000 + 000080808000FFFFFF0000FFFF00FFFFFF0000000000FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF0000000000FFFFFF00FFFFFF00FFFFFF008080 + 8000808080008080800080808000808080008080800080808000808080008080 + 8000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000FF000000FF5F5F5FFF000000000000 + 0000000000000000000000000000000000000080000000800000008000008080 + 8000FFFFFF0000000000000000000000000000000000FFFFFF00FFFFFF00FFFF + FF0000800000008000000080000080808000000000000000000000000000FFFF + 000000000000FFFFFF00FFFFFF0080808000FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF0000000000FFFFFF0000000000FFFFFF008080 + 8000808080008080800080808000808080008080800080808000808080008080 + 8000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000FF5F5F5FFF00000000000000FF5F5F5FFF0000 + 0000000000000000000000000000000000000080000000800000008000008080 + 8000FFFFFF00FFFFFF0000000000000000000000000000000000000000008080 + 8000008000000080000000800000808080000000000000000000FFFFFF000000 + 000000000000FFFFFF0080808000C0C0C000FFFFFF0080808000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF0000000000FFFFFF00FFFFFF00FFFFFF008080 + 8000808080008080800080808000FFFFFF008080800080808000808080008080 + 8000000000000000000000000000000000000000000000000000000000000000 + 000000000000000000FF5F5F5FFF000000000000000000000000000000FF5F5F + 5FFF000000000000000000000000000000008080800000800000008000000080 + 000080808000FFFFFF00FFFFFF000000000000000000FFFFFF00FFFFFF000080 + 0000008000000080000080808000FFFFFF000000000000000000808080000000 + 00000000000000000000FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF0000000000FFFFFF00FFFFFF00FFFFFF008080 + 8000808080008080800080808000FFFFFF008080800080808000808080008080 + 8000000000000000000000000000000000000000000000000000000000000000 + 0000000000FF5F5F5FFF00000000000000000000000000000000000000000000 + 00FF5F5F5FFF000000000000000000000000FFFFFF0000800000008000000080 + 00008080800080808000FFFFFF00FFFFFF00FFFFFF00FFFFFF00008000000080 + 0000008000000080000080808000FFFFFF000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF0000000000FFFFFF0000000000FFFFFF008080 + 8000808080008080800080808000FFFFFF008080800080808000808080008080 + 8000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000080808000008000000080 + 0000008000000080000080808000808080008080800000800000008000000080 + 00000080000080808000FFFFFF00000000000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF0000000000FFFFFF0000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000FFFFFF00808080000080 + 0000008000000080000000800000008000000080000000800000008000000080 + 000080808000FFFFFF0000000000000000000000000000000000800000008000 + 0000800000008000000080000000800000008000000080000000800000008000 + 000080000000800000008000000080000000FFFFFF0000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000FFFFFF00FFFF + FF0080808000008000000080000000800000008000000080000080808000FFFF + FF00FFFFFF000000000000000000000000000000000000000000800000008000 + 0000800000008000000080000000800000008000000080000000800000008000 + 0000800000008000000080000000800000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000F5F5F50AFCFCFC03000000000000000004040400040404000404 + 0400040404000404040004040400040404000404040004040400040404000404 + 0400040404000404040000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000AAAAAA16202020F24C4C4CD1E6E6E6060000000086868600FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00C0C0C00000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000FFFFFF00C0C0C000C0C0 + C000C0C0C000C0C0C00080808000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000000000000000000000000000000000000000000000000000B4B6 + B41A1F1F1FF2929292FF383838FFAFB0AF040000000086868600FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00C0C0C00000000000000000000080800000808000008080000080 + 8000008080000080800000808000008080000080800000808000008080000080 + 80000080800000808000008080000000000080808000C0C0C000C0C0C000C0C0 + C000C0C0C000C0C0C00000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000000000001D1D1D1F2424 + 24F19C9C9CFF373737FEE7E7E704000000000000000086868600CBCBCB00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00C6D6EF00C0C0C0000000000000000000FFFFFF0000FFFF0000FFFF0000FF + FF0000FFFF0080000000FF0000000000000000FFFF00FF00FF000000800000FF + FF0000FFFF0000FFFF0000FFFF0000000000FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF0080808000C0C0C000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000F3F3F302000000000000000000000000D8D8D821262626F0A6A6 + A6FF393939FE0000000000000000000000000000000086868600FFFFFF0090A9 + AD0066FFCC00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0000CC + 9900FFFFFF00C0C0C0000000000000000000FFFFFF0000FFFF0000FFFF0000FF + FF0000FFFF0080000000FF0000000000000000FFFF00FF00FF000000800000FF + FF0000FFFF0000FFFF0000FFFF000000000080808000C0C0C000C0C0C000C0C0 + C000C0C0C000C0C0C000C0C0C000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000D5D5D50CCCCC + CC23B8B8B838BABABA3BB8B8B834C6C6C632F3F3F3052D2D2DE9B3B3B3FF3A3A + 3AFD000000000000000000000000000000000000000086868600FFFFFF00FFFF + FF0033CC990000CC9900FFFFFF00FFFFFF00FFFFFF00F0FBFF0000CC6600F8F8 + F800FFFFFF00C0C0C0000000000000000000FFFFFF0000FFFF0000FFFF0000FF + FF0000FFFF0000FFFF008000000000FFFF0000FFFF00000080000000800000FF + FF0000FFFF0000FFFF0000FFFF000000000080808000C0C0C000C0C0C000C0C0 + C000808080008080800080808000000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000E9E9E90A7D7D7D96C2C2 + C2CECECECE6EDDDDDD64CDCDCDB3888888BF7A7A7A7EAEAEAEFF3E3E3EFD0000 + 0000000000000000000000000000000000000000000086868600FFFFFF00FFFF + FF00FFFFFF000099330000CC9900FFFFFF0099FFFF0000993300C0DCC000EAEA + EA00FFFFFF00C0C0C0000000000000000000FFFFFF0000FFFF0000FFFF0000FF + FF0000FFFF0000FFFF00C0C0C00000FFFF0000FFFF00808080000000000000FF + FF0000FFFF0000FFFF0000FFFF00000000000000000080808000808080008080 + 8000000000000000FF000000FF00000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000959595DECFCFCF2DD9D9 + D934DEDEDE4DEEEEEE50D9D9D93EDBDBDB1CB9B9B9F1828282B3000000000000 + 0000000000000000000000000000000000000000000086868600FFFFFF00FFFF + FF00FFFFFF00FFFFFF0000CC660000CC660000CC990033996600D7D7D700EAEA + EA00F1F1F100C0C0C0000000000000000000FFFFFF0000FFFF0000FFFF000000 + 000000FFFF0000000000C0C0C0000000000000FFFF00000000000000000000FF + FF0000FFFF0000FFFF0000FFFF00000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000009E9F9F64D9D9D934E0E0E03BECEC + EC56ECECEC55E8E8E855E6E6E657EDEDED49E5E5E519898989CCD4D4D4240000 + 0000000000000000000000000000000000000000000086868600FFFFFF00FFFF + FF00FFFFFF00FFFFFF0000CC9900009933000099330090A9AD00D7D7D700E3E3 + E300F1F1F100C0C0C0000000000000000000FFFFFF0000FFFF0000FFFF00C0C0 + C00080808000C0C0C000C0C0C000C0C0C00080808000FFFFFF000000000000FF + FF0000FFFF0000FFFF0000FFFF00000000000000000000000000000000000000 + 000000000000000000000000FF00000000000000000000000000000000000000 + 000000000000000000000000000000000000929292E0DDDDDD26CFCECE46D4D4 + D440E7E7E745E0E0E050D7D7D743D5D5D549DEDEDE32A9A9A9A2AEAEAE320000 + 0000000000000000000000000000000000000000000086868600FFFFFF00FFFF + FF00FFFFFF0000CC99000099330099CC9900008000000099330099CCCC00EAEA + EA00FFFFFF00C0C0C0000000000000000000FFFFFF0000FFFF0000FFFF00FFFF + FF0080808000FFFFFF00FFFFFF00FFFFFF00FFFFFF00C0C0C0008080800000FF + FF0000FFFF0000FFFF0000FFFF00000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000000000000000000000FF + FF0000000000000000000000000000000000929292A4DCDCDB2ADDDDDD33E2E1 + E22AE8E8E82FE9E9E937E2E2E12CDFE0DF32DCDBDB32C3C3C250B1B1B0410000 + 0000000000000000000000000000000000000000000086868600FFFFFF00FFFF + FF0000CC6600C0DCC000EAEAEA00DDDDDD00DDDDDD00CBCBCB000099660099CC + CC00FFFFFF00C0C0C0000000000000000000FFFFFF0000FFFF0000FFFF0000FF + FF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF00C0C0C0008080800000FF + FF0000FFFF0000FFFF0000FFFF00000000000000000000000000000000000000 + 0000000000000000000000000000000000000080800000FFFF0000FFFF0000FF + FF00FFFFFF000080800000808000000000008A8989ABDCDCDC27E5E4E524BFBE + BE1DCACACA25ECEBEC24D2D0D11FC1C0C023C2C1C1299E9E9E5FA9A9A92D0000 + 0000000000000000000000000000000000000000000086868600FFFFFF00CCCC + CC00FFFFFF00FFFFFF00FFFFFF00F1F1F100F1F1F100FFFFFF00FFFFFF00DDDD + DD00F1F1F100C0C0C0000000000000000000FFFFFF0000FFFF0000FFFF0000FF + FF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF00808080008080800000FF + FF0000FFFF0000FFFF0000FFFF00000000000000000000000000000000008080 + 8000C0C0C000C0C0C00000000000000000000000000000FFFF0000FFFF000080 + 8000FFFFFF00000000000000000000000000939292EDE2E1E131DBDBDA20BCBC + BC22EBEBEB36DFDFDF33C7C6C620D2D2D222DADADA2A7D7D7DCA000000000000 + 0000000000000000000000000000000000000000000086868600FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00C0C0C0000000000000000000FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00000000000000000000000000000000000000 + 00008080800080808000808080008080800000FFFF0000FFFF00000000000080 + 8000FFFFFF00FFFFFF000080800000000000D5D4D457C8C7C762F7F7F753FBFA + FA45FAF9F947FAF9F941FAF8F93AF0F0F050D3D2D24F9B9A9AB4000000000000 + 0000000000000000000000000000000000000000000086868600FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00C0C0C0000404040000000000000000008080800000FFFF0000FFFF0000FF + FF0000FFFF0000FFFF0000FFFF00808080000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000C0C0C000FFFF0000FFFF0000C0C0C0000000000000FFFF00008080000000 + 0000FFFFFF0000808000000000000000000000000000C1C1C1D0D1D1D162FFFF + FF5DFFFFFF69FFFFFF68FFFFFF5CE3E3E349BBBBBBFB00000000000000000000 + 0000000000000000000000000000000000000000000086868600FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00C0C0 + C000040404000000000000000000000000000000000080808000FFFFFF00FFFF + FF00FFFFFF00FFFFFF0080808000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000C0C0C000FFFF0000FFFF0000C0C0C0000080800000FFFF000000000000FF + FF00FFFFFF000080800000808000000000000000000000000000D1D1D1748888 + 88F3939393B29D9D9DA87D7D7DE9DDDDDDA60000000000000000000000000000 + 0000000000000000000000000000000000000000000086868600FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000404 + 0400000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000C0C0C000C0C0C000C0C0C000C0C0C00000000000000000000080800000FF + FF00008080000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000000000000000000000000000FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000000000000000000000000000000 + 000000000000DBF7DF2E9FECAB6092E99E6D92E89D6DB2EEB84DF6FCF7190000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000CC660099CCCC0099CCCC0000CC + 660000993300009966000099660099CCCC00006633000099660000993300F0FB + FF0000993300FFFFFF0000993300F0FBFF00000000000000000000000000CBF6 + D44296ECA7699AECAA659FEDAE60A1EDAE5EA1ECAD5E9EEBA9619AEAA46597E8 + 9F68F9FDF9110000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000E3E3E300CCCC9900CC996600CC996600F0CAA600FFFFFF000000 + 000000000000000000000000000000000000FFFFFF0000CC660000996600F0FB + FF0000800000009933000099330066CC990000CC6600FFFFFF00FFFFFF0000CC + 9900FFFFFF0000800000FFFFFF00FFFFFF0000000000000000009FEFB2609CEF + B063A9F0BA56BAF4C745C7F5D138BAECC345C3F0CB3CC3F3CB3CB3F0BC4CA3EC + AD5C99E9A166E8F9EA2800000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000000000000000000000000000000000000000000000000000FFFF + FF00CC999900996633009966330099663300996633009966330099663300F0CA + A6000000000000000000000000000000000000CC6600F0FBFF00F0FBFF000099 + 660000996600009933000099330099CCCC00009933000099330000993300F0FB + FF0000996600FFFFFF0000996600F0FBFF0000000000A7F1BC589DF0B462B2F3 + C44DC5F2D03A21AE2FDE00E420FF00DE19FF00D910FF00CD06FF7DCB8382C4F3 + CB3BA8EDB15799E9A166F9FDF911000000000000000000000000000000000000 + 0000000000003130310031303100313031003130310031303100000000000000 + 0000000000000000000000000000000000000000000000000000FFFFFF00CC99 + 6600996633009966330099663300996633009966330099663300996633009966 + 3300C0C0C000000000000000000000000000FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00ECFCF11C9AF1B565AFF4C450C1F2 + CE3E01D82FFF12F658FF0ADE3AFF02E230FF00CF1DFF00DE1AFF00D00BFF27A6 + 28D8C4F3CB3BA3ECAD5C97E89F68000000000000000000000000000000003130 + 3100313031003130310031303100313031003130310031303100313031003130 + 3100000000000000000000000000000000000000000000000000CCCC99009966 + 330099663300CC996600E3E3E300FFFFFF00FFFFFF00F0CAA600996633009966 + 330099663300F1F1F1000000000000000000FFFFFF00FFFFFF00FFECCC00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00F0FBFF00FFFFFF00FFFFFF009FF3BB60A2F3BE5DC6F7D73907D5 + 38F832E469FF4FE882FF54EB8CFF32E161FF16D941FF01D224FF00CC17FF00D5 + 0CFF7DCB8382B3F0BC4C9AEAA465F6FCF7190000000000000000000000003130 + 3100313031003130310031303100313031003130310031303100313031003130 + 31000000000000000000000000000000000000000000FFFFFF00996633009966 + 330099663300FFFFFF00CC9999009966330099663300CCCCCC00E3E3E3009966 + 330099663300CC9999000000000000000000FFFFFF00FFFFFF00FFFFFF006699 + 6600FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF0099FF + FF00C0DCC000FFFFFF00FFFFFF00FFFFFF0096F3B869ACF7C75399E1A76621E5 + 65FF7DEFACFF82EFA9FF72EC9CFF63EA92FF49E67CFF20DB4DFF03D92BFF00D3 + 18FF00C608FFC3F3CB3C9EEBA961B2EEB94D0000000000000000313031003130 + 3100313031003130310031303100313031003130310031303100313031003130 + 31003130310000000000000000000000000000000000FFFFFF00996633009966 + 3300CCCC9900CCCCCC0099663300996633009966330099663300F1F1F1009999 + 330099663300999966000000000000000000FFFFFF00FFFFFF00FFFFFF00FFFF + FF000099660099FFCC00FFFFFF00FFFFFF00FFFFFF00FFFFFF0066CC990099CC + CC00F0FBFF00FFFFFF00FFFFFF00FFFFFF0096F4BB69B1F7CD4E40D865BF55EF + 9EFFB6F7CFFFAAF6C7FFA1F4C0FF7FEFA7FF66EB95FF3FE26DFF1AE04CFF00CC + 1CFF00D511FFC3F0CB3CA1ECAE5E92E89D6D0000000000000000313031003130 + 3100313031003130310031303100313031003130310031303100313031003130 + 31003130310000000000000000000000000000000000F1F1F100996633009966 + 3300D7D7D700CC99990099663300CCCC99009999660099663300DDDDDD009999 + 660099663300996633000000000000000000FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF000066330066CC9900FFFFFF00FFFFFF0000CC660066CC9900C0DC + C000FFECCC00F0FBFF00FFFFFF00FFFFFF0096F6BF69B2F7D04D37DB6DC85EF3 + AFFFBEF9D8FFCBFAE0FFADF7CDFFA2F5C3FF7AEFA6FF55E886FF2EE260FF04D8 + 2FFF00D71AFFBAECC345A1EDAF5E92E99F6D0000000000000000313031003130 + 3100313031003130310031303100313031003130310031303100313031003130 + 31003130310000000000000000000000000000000000FFFFFF00996633009966 + 3300CC999900DDDDDD0099663300DDDDDD009999660099663300FFFFFF009966 + 330099663300CC9966000000000000000000FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00B2B2B2000099660000CC990000CC660000996600A4A0A000C0DC + C000FFECCC00F0FBFF00FFFFFF00FFFFFF0095F7C16AAFF9D15069E28C965AF5 + ADFFD8FCEAFFDBFCECFFC9FBE1FFAEF8CEFF8BF3B3FF71F0ABFF41E97BFF0BDF + 3EFF00E224FFC7F5D1389FEDAE609FECAC600000000000000000313031003130 + 3100313031003130310031303100313031003130310031303100313031003130 + 31003130310000000000000000000000000000000000FFFFFF00996666009966 + 330099663300F1F1F100F0CAA600DDDDDD00CC996600EAEAEA00F0CAA6009966 + 330099663300CCCC99000000000000000000FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF0000CC66000099660000993300A4A0A00086868600B2B2 + B200FFECCC00F0FBFF00FFFFFF00FFFFFF0096F7C569A6F9CF59D3FCE72C10F2 + 96FFC9FDE5FFEBFEF5FFCEFCE5FFADF9D0FFA4F6C6FF63EC96FF3DE677FF0CDE + 40FF21BC32DEBAF4C8459AEDAB65DBF7E02E0000000000000000313031003130 + 310031303100313031003130310031303100FFFFFF00FFFFFF00FFFFFF003130 + 3100313031000000000000000000000000000000000000000000CCCCCC009966 + 33009966330099663300CCCC9900DDDDDD00CC996600CC999900996633009966 + 330099663300FFFFFF000000000000000000FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF0066CCCC000099330000CC66000033330000663300A4A0A000C0DC + C000FFECCC00F0FBFF00FFFFFF00FFFFFF00C0FBDE469EFBCD61BBFCDD445CD4 + 87A341FBB6FFB6FDDDFFD4FCE9FF99F7C4FF90F4BCFF64EFA3FF2EED7DFF00E0 + 3EFFC5F2D03AA9F2BB5696ECA969000000000000000000000000000000003130 + 310031303100313031003130310031303100FFFFFF00FFFFFF00FFFFFF003130 + 310000000000000000000000000000000000000000000000000000000000CC99 + 9900996633009966330099663300DDDDDD00CC99660099663300996633009966 + 3300DDDDDD00000000000000000000000000FFFFFF00FFFFFF00FFFFFF00FFFF + FF0066CC990066CC6600C0DCC000A4A0A000A4A0A0007777770000333300FFEC + CC00F0FBFF00FFFFFF00FFFFFF00FFFFFF000000000098FCCD67A6FBD559C4FD + E33B60E783B018F7A2FF4DF6ACFF52F4AFFF4DF2ADFF26F596FF07E352F8C1F2 + CF3EB2F4C54D9CEFB263CBF7D542000000000000000000000000000000003130 + 3100313031003130310031303100313031003130310031303100313031003130 + 3100000000000000000000000000000000000000000000000000000000000000 + 0000F0CAA6009966330099663300996633009966330099663300CC996600E3E3 + E30000000000000000000000000000000000FFFFFF00FFFFFF00FFFFFF00C0DC + C000FFECCC00FFECCC00FFECCC00C0DCC000FFECCC00FFECCC00F0FBFF00A4A0 + A000F0FBFF00FFFFFF00FFFFFF00FFFFFF0000000000F3FEF81999FDD266A6FC + D659BBFCDD44D3FDE82C61E08FB037E171C840DE71BF96E6A66AC6F7D839AFF4 + C6509DF1B6629FF0B46000000000000000000000000000000000000000000000 + 0000000000003130310031303100313031003130310031303100000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000FFFFFF00DDDDDD00F0CAA600D7D7D700EAEAEA00000000000000 + 000000000000000000000000000000000000FFFFFF00FFFFFF00FFFFFF00FFFF + FF00F0FBFF00F0FBFF00F0FBFF00F0FBFF00F0FBFF00F0FBFF00F0FBFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000000000000000000F3FEF91998FD + D0679EFBCF61A6FAD059AFFAD350B2F8D24DB1F7CF4EACF7C953A2F4BF5D9AF3 + B765A7F2BD580000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000000000000000000000000000FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF000000000000000000000000000000 + 0000C0FCDF4696F9C76995F7C36A96F7C16996F5BD6996F4BA699FF4BD60ECFC + F11C000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000080800000000000808080000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000FFFF0000000000000000008080 + 8000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000008080800000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000808000000000000000000080808000808080000000 + 0000808080000000000000000000000000000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF008080800000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000FFFF00000000008080800000000000000000008080 + 8000000000008080800000000000000000000000000000000000FFFFFF008000 + 0000800000008000000080000000800000008000000080000000800000008000 + 0000FFFFFF008080800000000000000000000000000000000000000000000000 + 00000000FF000000FF000000FF000000FF000000FF000000FF000000FF000000 + FF00000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000008080000000000000000000808080000000000000000000000000000000 + 0000808080000000000080808000000000000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF008080800000000000000000000000000000000000000000000000 + 00000000FF000000FF000000FF000000FF000000FF000000FF000000FF000000 + FF00000000000000000000000000000000000000000000000000000000000000 + 000000FF000000FF000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000FFFF000000000080808000000000000000000000000000000000000000 + 0000000000008080800080808000000000000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF008080800000000000000000000000000000000000000000000000 + 00000000FF000000FF000000FF000000FF000000FF000000FF000000FF000000 + FF00000000000000000000000000000000000000000000000000000000000000 + 000000FF000000FF000000FF000000FF00000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000008080000000 + 0000000000008080800000000000000000000000000000000000808080000000 + 0000000000000000000000000000008080000000000000000000FFFFFF008000 + 0000800000008000000080000000800000008000000080000000800000008000 + 0000FFFFFF008080800000000000000000000000000000000000000000000000 + 00000000FF000000FF000000FF000000FF000000FF000000FF000000FF000000 + FF00000000000000000000000000000000000000000000000000000000000000 + 000000FF000000FF000000FF000000FF000000FF000000FF0000000000000000 + 000000000000000000000000000000000000000000000000000000FFFF000000 + 0000808080000000000000000000000000000000000000000000000000000000 + 0000000000000000000000FFFF0000FFFF000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF008080800000000000000000000000000000000000000000000000 + 00000000FF000000FF000000FF000000FF000000FF000000FF000000FF000000 + FF00000000000000000000000000000000000000000000000000000000000000 + 000000FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF + 0000000000000000000000000000000000000080800000000000000000008080 + 8000000000000000000000000000808080008080800000000000000000000000 + 0000000000000080800000000000000000000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF008080800000000000000000000000000000000000000000000000 + 00000000FF000000FF000000FF000000FF000000FF000000FF000000FF000000 + FF00000000000000000000000000000000000000000000000000000000000000 + 000000FF000000FF000000FF000000FF000000FF000000FF0000000000000000 + 00000000000000000000000000000000000000FFFF0000000000808080000000 + 0000000000000000000080808000808080000000000000000000000000000000 + 000000FFFF0000FFFF0000000000000000000000000000000000FFFFFF008000 + 0000800000008000000080000000800000008000000080000000800000008000 + 0000FFFFFF008080800000000000000000000000000000000000000000000000 + 00000000FF000000FF000000FF000000FF000000FF000000FF000000FF000000 + FF00000000000000000000000000000000000000000000000000000000000000 + 000000FF000000FF000000FF000000FF00000000000000000000000000000000 + 0000000000000000000000000000000000000000000080808000000000000000 + 0000000000000000000080808000000000000000000000000000000000000080 + 8000000000000000000000000000000000000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFF + FF00FFFFFF008080800000000000000000000000000000000000000000000000 + 00000000FF000000FF000000FF000000FF000000FF000000FF000000FF000000 + FF00000000000000000000000000000000000000000000000000000000000000 + 000000FF000000FF000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000008080800000000000000000008080 + 800080808000808080000000000000000000000000000000000000FFFF0000FF + FF00000000000000000000000000000000000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000008080 + 8000808080008080800000000000000000000000000000808000000000000000 + 0000000000000000000000000000000000000000000000000000FFFFFF008000 + 00008000000080000000800000008000000080000000FFFFFF00FFFFFF00C0C0 + C000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000FFFF0000FFFF00000000000000 + 0000000000000000000000000000000000000000000000000000FFFFFF00FFFF + FF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00FFFFFF00C0C0C0000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000008080000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000FFFF0000FFFF000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 000000000000000000000000000000000000424D3E000000000000003E000000 + 2800000040000000500000000100010000000000800200000000000000000000 + 000000000000000000000000FFFFFF0000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000FFDFFFFFFFFFF0FFFF9FFFCFFFFFC040 + FF3FFF83FFFF8000C87F0001FFFF808080000001F3E7018000000001F9CF0380 + C0000003FC9F0780C0000007FE3F078040000007FE3F078040000007FC9F03E0 + 80000007F9CF0180C0000007F3E70000C0000007FFFF8001C0000007FFFF8003 + C0000007FFFFC007C0000007FFFFF01FFFF98003FFFF80FFFFF08003FFFF80FF + FFE08003000000FFFFC18003000000FFFB878003000000FFC00F8003000001FF + 801F8003000089FF803F80030000FFFF001F80030000FDE7001F80030000FF81 + 001F80030000F001001F80030000E001003F80030001F000003F800300FFF001 + 807F800781FFF001C0FF800FFFFFF043FFFF0000F81FFFFFFFFF0000E007F01F + F81F0000C003E00FE00F00008001C007C007000000018003C003000000000001 + 8003000000000001800300000000000180030000000000018003000000000001 + 8003000000000001C003000000010001E007000080018003F00F00008003C007 + F83F0000C007E00FFFFF0000F00FF01FFFFFFFFFFF5FFFFFFFFFFFFFFF0FC003 + E007FFFFFD07C003C003E3FFFC03C003C003C0FFF401C003C003C03FF001C003 + C003C007D000C003C003C003C004C003C003C0034003C003C003C0030013C003 + C003C007000FC003C003C03F004FC007C003C0FF803FC00FE007E3FFC13FC01F + FFFFFFFFE0FFC03FFFFFFFFFF4FFFFFF00000000000000000000000000000000 + 000000000000} + end + object mnuMain: TMainMenu + Images = ImageList1 + Left = 608 + Top = 96 + object Application1: TMenuItem + Caption = '&Application' + object Tools1: TMenuItem + Caption = 'Tools' + end + object N4: TMenuItem + Caption = '-' + end + object SaveLog1: TMenuItem + Caption = 'Save Log' + ShortCut = 16467 + OnClick = SaveLog1Click + end + object Settings1: TMenuItem + Caption = 'Settings' + ImageIndex = 10 + object SetXBOXAddress1: TMenuItem + Caption = 'Set XBOX Address' + OnClick = SetXBOXAddress1Click + end + object SetListenPort1: TMenuItem + Caption = 'Set Listen Port' + OnClick = SetListenPort1Click + end + end + object N2: TMenuItem + Caption = '-' + end + object Exit1: TMenuItem + Caption = 'E&xit' + ImageIndex = 14 + ShortCut = 49240 + OnClick = Exit1Click + end + end + object XDK1: TMenuItem + Caption = '&XDK' + object Connect1: TMenuItem + Caption = '&Connect' + ImageIndex = 7 + ShortCut = 112 + OnClick = Connect1Click + end + object Dumpmemory1: TMenuItem + Caption = '&Dump memory' + ImageIndex = 8 + ShortCut = 16452 + OnClick = Dumpmemory1Click + end + object N1: TMenuItem + Caption = '-' + end + object Stop1: TMenuItem + Caption = 'Stop' + ImageIndex = 0 + ShortCut = 114 + OnClick = Stop1Click + end + object Go1: TMenuItem + Caption = 'Go' + ImageIndex = 1 + ShortCut = 115 + OnClick = Go1Click + end + object ContinueThread1: TMenuItem + Caption = 'Continue Thread' + ImageIndex = 1 + ShortCut = 116 + OnClick = ContinueThread1Click + end + object Modules1: TMenuItem + Caption = 'Modules' + ImageIndex = 2 + ShortCut = 16461 + OnClick = Modules1Click + end + object Threads1: TMenuItem + Caption = 'Threads' + ImageIndex = 13 + ShortCut = 16468 + OnClick = Threads1Click + end + object GetProcessID1: TMenuItem + Caption = 'Get Process ID' + ImageIndex = 13 + ShortCut = 16464 + OnClick = GetProcessID1Click + end + object XBEInfo1: TMenuItem + Caption = 'XBE Info' + ImageIndex = 9 + ShortCut = 16457 + OnClick = XBEInfo1Click + end + object WarmReboot1: TMenuItem + Caption = 'Reboot Warm' + ImageIndex = 4 + ShortCut = 120 + OnClick = WarmReboot1Click + end + object RebootCold1: TMenuItem + Caption = 'Reboot Cold' + ImageIndex = 4 + ShortCut = 121 + OnClick = RebootCold1Click + end + object RestartTitle1: TMenuItem + Caption = 'Restart Title' + ImageIndex = 15 + ShortCut = 123 + OnClick = RestartTitle1Click + end + end + object Help1: TMenuItem + Caption = '&Help' + object About1: TMenuItem + Caption = '&About' + OnClick = About1Click + end + end + end + object ClientThread: TIdThreadComponent + Active = False + Loop = True + Priority = tpNormal + StopMode = smTerminate + OnRun = ClientThreadRun + Left = 672 + Top = 32 + end + object popMemView: TPopupMenu + Left = 640 + Top = 96 + object JumpbyPCAddress1: TMenuItem + Caption = 'Jump by PC Address' + OnClick = JumpbyPCAddress1Click + end + object JumpbyXBOXAddress1: TMenuItem + Caption = 'Jump by XBOX Address' + OnClick = JumpbyXBOXAddress1Click + end + object N3: TMenuItem + Caption = '-' + end + object SavetoFile1: TMenuItem + Caption = 'Save to File' + OnClick = SavetoFile1Click + end + end + object XClient: TIdTCPClient + OnStatus = XClientStatus + ConnectTimeout = 0 + Host = '192.168.1.153' + IPVersion = Id_IPv4 + Port = 731 + ReadTimeout = 0 + Left = 672 + Top = 64 + end + object Server: TIdTCPServer + Bindings = <> + DefaultPort = 2000 + MaxConnections = 1 + OnExecute = ServerExecute + Left = 640 + Top = 32 + end + object odToolSelect: TOpenDialog + DefaultExt = '*.exe' + Filter = 'Programs|*.exe|All Files|*.*' + Options = [ofHideReadOnly, ofExtensionDifferent, ofPathMustExist, ofFileMustExist, ofEnableSizing, ofDontAddToRecent] + Title = 'Select a tool' + Left = 608 + Top = 64 + end +end diff --git a/src/Main.pas b/src/Main.pas new file mode 100644 index 0000000..dcac108 --- /dev/null +++ b/src/Main.pas @@ -0,0 +1,1843 @@ +unit Main; + +interface + +uses + Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, + Dialogs, Menus, StdCtrls, ExtCtrls, ComCtrls,CommCtrl, IdThreadComponent, IdGlobal, + IdTCPConnection, IdBaseComponent, IdComponent, IdTCPClient, + IdStreamVCL, StrUtils, IdExceptionCore,IdException, + Breakpoint, Tabs, CategoryButtons, IdContext,INIFiles,IdIPAddress, + TypInfo, IdTCPServer, ShellApi, IdStack, Grids, MPHexEditor, AppGlobal, LogStream, + CheckLst, ImgList, ValEdit, XBOXManager, IdCustomTCPServer; + +const + WM_NEWTEXT = WM_USER+0; + WM_NEWDUMP = WM_USER+1; + PAGE_WRITECOMBINE = $400; + MEM_LARGE_PAGES = $20000000; + MEM_4MB_PAGES = $80000000; + +type + TfrmMain = class(TForm) + grpConsole: TGroupBox; + edInput: TEdit; + mnuMain: TMainMenu; + Application1: TMenuItem; + Exit1: TMenuItem; + XDK1: TMenuItem; + Connect1: TMenuItem; + Dumpmemory1: TMenuItem; + Help1: TMenuItem; + About1: TMenuItem; + ClientThread: TIdThreadComponent; + richLog: TRichEdit; + StatusBar: TStatusBar; + N1: TMenuItem; + Modules1: TMenuItem; + Threads1: TMenuItem; + WarmReboot1: TMenuItem; + GetProcessID1: TMenuItem; + XBEInfo1: TMenuItem; + RebootCold1: TMenuItem; + Stop1: TMenuItem; + Go1: TMenuItem; + N2: TMenuItem; + pgControl: TPageControl; + tbBreakpoints: TTabSheet; + bpUnset: TButton; + bpSet: TButton; + edBPOffset: TLabeledEdit; + lvBreak: TListView; + tbDumping: TTabSheet; + ContinueThread1: TMenuItem; + XClient: TIdTCPClient; + Server: TIdTCPServer; + pbDump: TProgressBar; + lvDump: TListView; + cmbBPType: TComboBox; + edBPSize: TLabeledEdit; + tbTools: TTabSheet; + tbMemView: TTabSheet; + edByteSearch: TLabeledEdit; + edViewOffset: TLabeledEdit; + hxMemView: TMPHexEditor; + btMemSrchReset: TButton; + tbNotes: TTabSheet; + moNotes: TMemo; + SetXBOXAddress1: TMenuItem; + SetListenPort1: TMenuItem; + SaveDialog: TSaveDialog; + Tools1: TMenuItem; + N4: TMenuItem; + Settings1: TMenuItem; + popMemView: TPopupMenu; + JumpbyPCAddress1: TMenuItem; + JumpbyXBOXAddress1: TMenuItem; + N3: TMenuItem; + SavetoFile1: TMenuItem; + tbSettings: TTabSheet; + grpMemEdit: TGroupBox; + cbMemEdit: TComboBox; + Label1: TLabel; + edMemEditOffset: TLabeledEdit; + edMemEditParam: TLabeledEdit; + btMemEdit: TButton; + grpConvOffset: TGroupBox; + cbOffsetConvert: TComboBox; + Label2: TLabel; + edConvOffsetTo: TLabeledEdit; + edConvOffsetFrom: TLabeledEdit; + lbConvOffStat: TLabel; + lbBPType: TLabel; + GroupBox1: TGroupBox; + lbSectFlags: TCheckListBox; + ImageList1: TImageList; + btDump: TButton; + GroupBox2: TGroupBox; + edSearcherStart: TLabeledEdit; + edSearcherEnd: TLabeledEdit; + chkUseSearchRange: TCheckBox; + GroupBox4: TGroupBox; + chkDumpAutoStop: TCheckBox; + chkCopyOffToClip: TCheckBox; + edSearcherCaption: TLabeledEdit; + edSearcherClass: TLabeledEdit; + edSearcherState: TLabeledEdit; + RestartTitle1: TMenuItem; + SaveLog1: TMenuItem; + btGetRegisters: TButton; + lvRegisters: TListView; + cbRangePresets: TComboBox; + chkHighlightDumpChanges: TCheckBox; + GroupBox5: TGroupBox; + chkWarnConnected: TCheckBox; + chkVerboseLog: TCheckBox; + chkShowMainLog: TCheckBox; + edBPDesc: TLabeledEdit; + GroupBox3: TGroupBox; + edToolPath: TLabeledEdit; + edToolCaption: TLabeledEdit; + lvToolList: TListView; + chkToolLaunch: TCheckBox; + btnToolSelect: TButton; + odToolSelect: TOpenDialog; + procedure btnToolSelectClick(Sender: TObject); + procedure chkShowMainLogClick(Sender: TObject); + procedure btGetRegistersClick(Sender: TObject); + procedure hxMemViewTopLeftChanged(Sender: TObject); + procedure SaveLog1Click(Sender: TObject); + procedure lvBreakSelectItem(Sender: TObject; Item: TListItem; + Selected: Boolean); + procedure RestartTitle1Click(Sender: TObject); + procedure btDumpClick(Sender: TObject); + procedure lvDumpSelectItem(Sender: TObject; Item: TListItem; + Selected: Boolean); + procedure lvBreakChange(Sender: TObject; Item: TListItem; + Change: TItemChange); + procedure edMemEditParamKeyUp(Sender: TObject; var Key: Word; + Shift: TShiftState); + procedure edConvOffsetFromKeyUp(Sender: TObject; var Key: Word; + Shift: TShiftState); + procedure edConvOffsetFromKeyPress(Sender: TObject; var Key: Char); + procedure GenericMenuItemClick(Sender: TObject); + procedure btMemEditClick(Sender: TObject); + procedure cbMemEditChange(Sender: TObject); + procedure hxMemViewChange(Sender: TObject); + procedure SavetoFile1Click(Sender: TObject); + procedure JumpbyPCAddress1Click(Sender: TObject); + procedure JumpbyXBOXAddress1Click(Sender: TObject); + procedure SetListenPort1Click(Sender: TObject); + procedure SetXBOXAddress1Click(Sender: TObject); + procedure btMemSrchResetClick(Sender: TObject); + procedure edByteSearchKeyUp(Sender: TObject; var Key: Word; + Shift: TShiftState); + procedure edByteSearchKeyPress(Sender: TObject; var Key: Char); + procedure edViewOffsetKeyUp(Sender: TObject; var Key: Word; + Shift: TShiftState); + procedure edViewOffsetKeyPress(Sender: TObject; var Key: Char); + procedure FormDestroy(Sender: TObject); + procedure edBPSizeKeyPress(Sender: TObject; var Key: Char); + procedure edBPOffsetKeyPress(Sender: TObject; var Key: Char); + procedure FormClose(Sender: TObject; var Action: TCloseAction); + procedure ServerExecute(AContext: TIdContext); + procedure ContinueThread1Click(Sender: TObject); + procedure bpUnsetClick(Sender: TObject); + procedure bpSetClick(Sender: TObject); + procedure lvBreakKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); + procedure Go1Click(Sender: TObject); + procedure Stop1Click(Sender: TObject); + procedure RebootCold1Click(Sender: TObject); + procedure XBEInfo1Click(Sender: TObject); + procedure GetProcessID1Click(Sender: TObject); + procedure WarmReboot1Click(Sender: TObject); + procedure Threads1Click(Sender: TObject); + procedure Modules1Click(Sender: TObject); + procedure ClientThreadRun(Sender: TIdThreadComponent); + procedure richLogChange(Sender: TObject); + procedure XClientStatus(ASender: TObject; const AStatus: TIdStatus; + const AStatusText: string); + procedure Exit1Click(Sender: TObject); + procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); + procedure edInputKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); + procedure FormCreate(Sender: TObject); + procedure About1Click(Sender: TObject); + procedure Dumpmemory1Click(Sender: TObject); + procedure Connect1Click(Sender: TObject); + + procedure OnNewText(var Msg: TMessage); message WM_NEWTEXT; + procedure OnNewDump(var Msg: TMessage); message WM_NEWDUMP; + + private + { Private declarations } + MemSrchPos:Integer; + public + { Public declarations } + function SendData(Data:String):Boolean; + procedure ConnectToggle(Tog:Boolean); + end; + + procedure FillDumpPages(hDump:HWND); + procedure GetDumpData(hProgress:HWND;hWin:HWND;Verbose:Boolean); + function IsValidHexBoxInput(var Key: Char):Boolean; + function IsValidIP(const AAddr : String): Boolean; +// function LaunchAsChild(EXEName:String;EXEClass:PAnsiChar;EXECaption:PAnsiChar;Parent:HWND):HWND; + +var +// XBEName:String; + ProgressBar:HWND; + DumpList:HWND; + SavedDump:TMemoryStream; + frmMain: TfrmMain; + +implementation + +{$R *.dfm} + +procedure TfrmMain.Connect1Click(Sender: TObject); +begin + if XClient.Connected then + begin + XClient.Disconnect; + Exit; + end; + + try + XClient.Connect; + except + On E : Exception do Log.AddLn('Connect: ' + E.Message); + end; +end; + +procedure TfrmMain.Dumpmemory1Click(Sender: TObject); +begin + if (XClient.Connected) and + (ProgStatus = stNorm) then + begin + ProgStatus := stDump; + SendData('WALKMEM'); + end; +end; + +procedure TfrmMain.About1Click(Sender: TObject); +begin + ShowMessage('Coded by ddh for EvoX-T.'+#13#10+'Current official download site:'+#13#10+'http://trainers.evolutionx.info'); +end; + +procedure TfrmMain.FormCreate(Sender: TObject); +var +count:TBPTypes; +iniSet:TINIFile; +Sections:TStringList; +Counter:Integer; +//r:TRect; +mi:TMenuItem; +ExIcon:HICON; +NewIcon:TIcon; +begin + Log := TLogStream.create('',WM_NEWTEXT,frmMain.Handle); + DebugBox := TXBOX.Create(@XClient); + +// richlog.SelStart := richlog.GetTextLen; +// richlog.seltext := '{\rtf1\ansi\deff0\deftab720\fnil\deflang1033\pard{\colortbl\red0\green0\blue0;\red0\green200\blue200;}\cf1 test \par}'; +// log.AddLn('{\rtf1\ansi\fnil{\colortbl\red110\green0\blue0;\red0\green200\blue20;}\cf0 Another test \par}'); + + lbSectFlags.Clear; + lbSectFlags.AddItem('PAGE_NOACCESS',Self); + lbSectFlags.AddItem('PAGE_READONLY',Self); + lbSectFlags.AddItem('PAGE_READWRITE',Self); + lbSectFlags.AddItem('PAGE_WRITECOPY',Self); + lbSectFlags.AddItem('PAGE_EXECUTE',Self); + lbSectFlags.AddItem('PAGE_EXECUTE_READ',Self); + lbSectFlags.AddItem('PAGE_EXECUTE_READWRITE',Self); + lbSectFlags.AddItem('PAGE_EXECUTE_WRITECOPY',Self); + lbSectFlags.AddItem('PAGE_GUARD',Self); + lbSectFlags.AddItem('PAGE_NOCACHE',Self); + lbSectFlags.AddItem('PAGE_WRITECOMBINE',Self); + lbSectFlags.AddItem('MEM_COMMIT',Self); + lbSectFlags.AddItem('MEM_RESERVE',Self); + lbSectFlags.AddItem('MEM_DECOMMIT',Self); + lbSectFlags.AddItem('MEM_RELEASE',Self); + lbSectFlags.AddItem('MEM_FREE',Self); + lbSectFlags.AddItem('MEM_PRIVATE',Self); + lbSectFlags.AddItem('MEM_MAPPED',Self); + lbSectFlags.AddItem('MEM_RESET',Self); + lbSectFlags.AddItem('MEM_TOP_DOWN',Self); + lbSectFlags.AddItem('MEM_LARGE_PAGES',Self); + lbSectFlags.AddItem('MEM_4MB_PAGES',Self); + lbSectFlags.AddItem('SEC_RESERVE',Self); + + cmbBPType.Clear; + for count := Low(TBPTypes) to High(TBPTypes) do + begin + cmbBPType.Items.Add(GetEnumName(TypeInfo(TBPTypes),ord(count))); + end; + cmbBPType.ItemIndex := 0; + + edInput.Enabled := false; + lvBreak.Enabled := false; +// Membuffer := TMemoryStream.Create; + SavedDump := TMemoryStream.Create; + + try + begin + iniSet := TINIFile.Create(ExtractFilePath(Application.EXEName) + 'xdkassist.ini'); + XClient.Host := iniSet.ReadString('Connection','Host','192.168.1.153'); + Server.DefaultPort := iniSet.ReadInteger('Connection','Port',2000); + cmbBPType.ItemIndex := cmbBPType.Items.IndexOf( + iniSet.ReadString('Breakpoints','Type','Read')); + if cmbBPType.ItemIndex < 0 then cmbBPType.ItemIndex := 0; + + Sections := TStringList.Create; + +{ tbTools.TabVisible := false;} + + iniSet.ReadSections(Sections); + for Counter := 0 to (Sections.Count-1) do + begin + if (not AnsiStartsText('Tool',Sections.Strings[Counter])) then Continue; + + SetLength(Tools,Length(Tools)+1); + with Tools[High(Tools)] do + begin + Name := iniSet.ReadString(Sections[Counter],'Name',''); +// WinClass := iniSet.ReadString(Sections[Counter],'Class',''); + WinText := iniSet.ReadString(Sections[Counter],'Caption',''); + Load := iniSet.ReadBool(Sections[Counter],'Load',false); + + if (Name = '') or (not FileExists(Name)){and (WinClass = '') and (WinText = '')} then + begin + SetLength(Tools,Length(Tools)-1); + Continue; + end; + + mi := TMenuItem.Create(mnuMain); + if WinText <> '' then + mi.Caption := WinText + else if Name <> '' then + begin + mi.Caption := Name; + end +{ else if WinClass <> '' then + mi.Caption := WinClass } + else + mi.Caption := 'Unknown Tool #' + IntToStr(High(Tools)); + + mi.OnClick := GenericMenuItemClick; + mi.Tag := High(Tools); + + if Name <> '' then + begin + ExIcon := ExtractIcon(Handle,PAnsiChar(Name+#0),0); + if ExIcon <> 0 then + begin + NewIcon := TIcon.Create; + NewIcon.Handle := ExIcon; + mi.ImageIndex := ImageList1.AddIcon(NewIcon); + end; + end; + + Tools1.Add(mi); + + if (Load = true) then ShellExecute(0,'open',PAnsiChar(Name),#0,#0,SW_NORMAL); + +{ if (Load = true) then + begin + tbTools.TabVisible := true; + + Handle := LaunchAsChild(Name,PAnsiChar(WinClass+#0),PAnsiChar(WinText+#0),ScrollBox1.handle); + if Handle <> 0 then + begin + Log.AddLn('Launched and captured ' + WinText + ' for your pleasure!'); + if Length(Tools) > 1 then + begin + GetWindowRect(Tools[Length(Tools)-2].handle,r); + SetWindowPos(Handle,0,r.right+5,5,0,0,SWP_NOSIZE or SWP_NOZORDER); + end + else + SetWindowPos(Handle,0,5,5,0,0,SWP_NOSIZE or SWP_NOZORDER); + end; + end; } + end; + end; + FreeAndNil(Sections); + + frmMain.Width := iniSet.ReadInteger('Window','Width',frmMain.Width); + frmMain.Height := iniSet.ReadInteger('Window','Height',frmMain.Height); + frmMain.WindowState := TWindowState(iniSet.ReadInteger('Window','State',Integer(frmMain.WindowState))); + pgControl.ActivePageIndex := iniSet.ReadInteger('Window','LastTab',pgControl.ActivePageIndex); + chkDumpAutoStop.Checked := iniSet.ReadBool('Dumping','AutoStop',true); + chkCopyOffToClip.Checked := iniSet.ReadBool('Dumping','AutoCopy',true); + chkHighlightDumpChanges.Checked := iniSet.ReadBool('Dumping','Highlight',false); + chkVerboseLog.Checked := iniSet.ReadBool('Logging','Verbose',true); + + chkWarnConnected.Checked := iniSet.ReadBool('Misc','WarnConClose',true); + chkShowMainLog.Checked := iniSet.ReadBool('Layout','ShowMainLog',true); + + chkUseSearchRange.Checked := iniSet.ReadBool('Range','Enabled',true); + edSearcherStart.Text := iniSet.ReadString('Range','Start',''); + edSearcherEnd.Text := iniSet.ReadString('Range','End',''); + edSearcherCaption.Text := iniSet.ReadString('Range','Caption',''); + edSearcherClass.Text := iniSet.ReadString('Range','Class',''); + edSearcherState.Text := iniSet.ReadString('Range','State',''); + + FreeAndNil(iniSet); + Log.AddLn('Settings have been read.'); + end + except + on E:Exception do Log.AddLn('Failed loading settings: ' + E.Message); + end; + + if Length(Tools) > 0 then + Log.AddLn(IntToStr(Length(Tools)) + ' tools were loaded.'); {If any were set to autolaunch, and the application ' + + 'crashes, or is terminated without it being able to clean up you ' + + 'will have to manually close the launched tools with task manager.'); } + + ConnectToggle(false); + {if ((tbTools.TabVisible = false) and (pgControl.ActivePage = tbTools)) then + pgControl.ActivePage := tbDumping; } + Log.AddLn(Application.Title + ' started.'); + + Progressbar := pbDump.Handle; + DumpList := lvDump.Handle; + if FileExists('xdkassist.notes') then + begin + moNotes.Lines.LoadFromFile('xdkassist.notes'); + Log.addln('Notes loaded from a previous session.'); + end; + +end; + +procedure TfrmMain.edInputKeyUp(Sender: TObject; var Key: Word; + Shift: TShiftState); +var +Pos:Cardinal; +Buf:String; +ConvOff:Int64; +ret:Integer; +begin + if Key = VK_RETURN then + begin + if StartsStr('/conv',edInput.Text) then + begin + Buf := edInput.Text; + Pos := AnsiPos(' ',Buf); + if (Pos = 0) then + begin + Log.AddLn('/conv offset'); + Exit; + end; + + Buf := RightStr(Buf,(Cardinal(Length(Buf)) - Pos)); + ret := ConvPC2XBOX(StrToInt64(Buf),@ConvOff); + if(ret <> EConvOkay) then begin + Log.AddLn(ConvError(ret)); + end else begin + Log.AddLn(Format('0x%.80x -> 0x%.80x',[Cardinal(Buf),ConvOff])); + if(chkCopyOffToClip.Checked) then + TextToClip(IntToHex(ConvOff,8)); + end; + end + else if AnsiStartsStr('/clear',edInput.Text) then + begin + Log.Clear; + richlog.Clear; + end + else + begin + SendData(edInput.Text); + end; + edInput.Text := ''; + end; +end; + +function TfrmMain.SendData(Data:String):Boolean; +begin + Result := DebugBox.IsConnected; + try + if (DebugBox.IsConnected = true) then + begin + Log.AddLn('s: ' + Data); + DebugBox.SendCmd(AnsiString(Data)); + end + else + begin + Log.AddLn('Not connected.'); + end; + except + on E: Exception do + begin + Log.AddLn('Send Data: ' + E.message); + Result := false; + end; + end; +end; + +procedure TfrmMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean); +var +Ret: Integer; +begin + CanClose := true; + if ((DebugBox.IsConnected = true) and (chkWarnConnected.Checked)) then + begin + Ret := MessageDlg('You are still connected to an XBOX. Are you sure you want to disconnect?', + mtConfirmation,[mbYes, mbNo],0); + + if Ret = mrYes then XClient.Disconnect + else CanClose := false; + end; +end; + +procedure TfrmMain.Exit1Click(Sender: TObject); +begin + frmMain.Close; +end; + +procedure TfrmMain.XClientStatus(ASender: TObject; const AStatus: TIdStatus; + const AStatusText: string); +begin + { +hsResolving A host name is being resolved to an IP Address. +hsConnecting A connection is being opened. +hsConnected A connection has been made. +hsDisconnecting The connection is being closed. +hsDisconnected The connection has been closed. +hsStatusText The connection is generating an informational message. +ftpTransfer An FTP connection is beginning its transfer. +ftpReady An FTP connection is ready. +ftpAborted An FTP transfer has been aborted. +} + StatusBar.SimpleText := AStatusText; + try + begin + if AStatus = hsDisconnecting then + begin + try + try + begin + DebugBox.Notify(Server.DefaultPort,true); + ClientThread.Terminate; + end + except + on E: Exception do Log.AddLn('Status (Disconnecting): ' + E.Message); + end; + finally + ProgStatus := stNorm; + end; + end + else if AStatus = hsDisconnected then + begin + Log.AddLn('Disconnected from ' + XClient.Host + '.'); + ConnectToggle(false); + end + else if AStatus = hsConnected then + begin + try + Log.AddLn('Connected to ' + XClient.Host + '. Listening on port ' + IntToStr(Server.DefaultPort) + '.'); + except + on E: Exception do Log.AddLn('Status (Connected): ' + E.Message); + end; + ConnectToggle(true); + end; + end + except + on E : Exception do Log.AddLn('Status: ' + E.Message); + end; +end; + +procedure TfrmMain.richLogChange(Sender: TObject); +begin + richLog.Perform(EM_SCROLL,SB_PAGEDOWN,0); +end; + +procedure TfrmMain.ClientThreadRun(Sender: TIdThreadComponent); +var +Read: String; +Time:Cardinal; +Buffer:PAnsiChar; +begin + while (not ClientThread.Terminated) and + (XClient.Connected) do + begin + try + Read := ''; + if Assigned(XClient.IOHandler) then + Read := XClient.IOHandler.ReadLn; + except + on E: Exception do + begin + if (E is EIdReadTimeout) or (E is EIdNoDataToRead) then Continue + else if (E is EIdConnClosedGracefully) or + (E is EIdNotConnected) or (E is EIdTCPConnectionError) then + begin + ClientThread.Terminate; + end + else if (E is EIdSocketError) then + begin + Log.AddLn('Thread: ' + E.Message); + XClient.Socket.Close; + ConnectToggle(false); + ClientThread.Terminate; + Continue; + end; + Log.AddLn('Thread: ' + E.Message); + Continue; + end; + end; + + if Read = '' then Continue + else if Read = '201- connected' then + begin + DebugBox.Connect(Server.DefaultPort); +{ SendData('NOTIFYAT PORT=' + IntToStr(Server.DefaultPort)); + SendData('DEBUGGER CONNECT');} + end + else if Read = '202- bye' then + begin + XClient.Disconnect; + ClientThread.Terminate; + end + else if Read = '202- Valid Virtual Address Ranges Follow' then + begin + if ProgStatus = stDump then + begin + Log.AddLn(Read); + if chkDumpAutoStop.Checked then SendData('STOP'); + PostMessage(ProgressBar,PBM_SETPOS,0,0); + PostMessage(DumpList,LVM_DELETEALLITEMS,0,0); + + Time := GetTickCount; + FillDumpPages(DumpList); + GetDumpData(ProgressBar,frmMain.handle,frmMain.chkVerboseLog.Checked); + + Log.AddLn(Format('All done (0x%.8p - 0x%.8x). The dump took %.2fs.', +// [Membuffer.memory,Integer(MemBuffer.Memory) + Membuffer.position, + [DebugBox.Memory.Buffer.Memory,Integer(DebugBox.Memory.Buffer.Memory) + DebugBox.Memory.Buffer.Position, + (Windows.GetTickCount - Time) / 1000])); + + PostMessage(frmMain.Handle,WM_NEWDUMP,0,0); + Read :=''; + end; + end + else if Read = '202- multiline response follows' then + begin + if (ProgStatus = stGetXBEInfo) then + begin + {202- multiline response follows + timestamp=0x40d52299 checksum=0x00000000 + name="E:\UnleashX\default.xbe" + .} + log.addln('r: ' + Read); + Read := XClient.IOHandler.ReadLn; + log.addln('r: ' + Read); + Read := XClient.IOHandler.ReadLn; + DebugBox.XBE.Name := AnsiMidStr(Read,7,Length(Read)-7); + progStatus := stNorm; + end + else if(ProgStatus = stGetContext) then + begin + while Read <> '.' do + begin + Read := XClient.IOHandler.ReadLn; + log.AddLn(read); + end; + + progStatus := stNorm; + end; + end + else if Read = '203- binary response follows' then + begin + Log.AddLn(Read); + repeat + Read := XClient.IOHandler.ReadString(XClient.IOHandler.InputBuffer.Size); + if (Length(Read) > 0) then + begin + Buffer := StrAlloc(Length(Read) * 2 + 1); + BinToHex(PAnsiChar(Read),Buffer,Length(Read)); + Log.AddLn(String(Buffer)); + StrDispose(Buffer); + end; + until XClient.IOHandler.InputBuffer.Size = 0; + Read := ''; + end; + if Read <> '' then + begin + Log.AddLn('r: ' + Read); + end; + end; +end; + +procedure TfrmMain.Modules1Click(Sender: TObject); +begin + SendData('MODULES'); +end; + +procedure TfrmMain.Threads1Click(Sender: TObject); +begin + SendData('THREADS'); +end; + +procedure TfrmMain.WarmReboot1Click(Sender: TObject); +begin +// SendData('REBOOT WAIT WARM'); + if(DebugBox.Reboot(DebugBox.rbWait and DebugBox.rbWarm and DebugBox.rbNoDebug,'')) then + DebugBox.Disconnect; +// if XClient.Connected then +// XClient.Disconnect; +end; + +procedure TfrmMain.GetProcessID1Click(Sender: TObject); +begin + SendData('GETPID'); +end; + +procedure TfrmMain.XBEInfo1Click(Sender: TObject); +begin + if DebugBox.IsConnected then + begin + progStatus := stGetXBEInfo; + DebugBox.SendCmd('XBEINFO RUNNING'); + end; +end; + +procedure TfrmMain.RebootCold1Click(Sender: TObject); +begin +// SendData('REBOOT STOP NODEBUG'); + if(DebugBox.Reboot(DebugBox.rbStop and DebugBox.rbNoDebug,'')) then +// if XClient.Connected then + DebugBox.Disconnect; +// XClient.Disconnect; +end; + +procedure TfrmMain.Stop1Click(Sender: TObject); +begin + DebugBox.SendCmd('STOP'); +end; + +procedure TfrmMain.Go1Click(Sender: TObject); +begin + DebugBox.SendCmd('GO'); +end; + +procedure TfrmMain.lvBreakKeyUp(Sender: TObject; var Key: Word; + Shift: TShiftState); +begin + if key = VK_INSERT then + begin + end + else if key = VK_DELETE then + begin + if (lvBreak.ItemIndex >= 0) then + begin + + with lvBreak.Selected do + begin + if(Checked = true) then + SendData(Format('BREAK %s=%s SIZE=%s CLEAR', + [SubItems.Strings[0],Caption,SubItems.Strings[1]])); + {TODO: Delete from BP list in memory} + Delete; + end; + end; + end; +end; + +procedure TfrmMain.bpSetClick(Sender: TObject); +var +NewItem:TListItem; +NewBP:TBreakpoint; +begin + if edBPOffset.Text = '' then Exit; + + if not AnsiStartsStr('0x',edBPOffset.Text) then + edBPOffset.Text := '0x' + edBPOffset.Text; + + if (edBPSize.Text = '') or (StrToInt(edBPSize.Text) <= 0) then edBPSize.Text := '1'; + +// SendData(Format('BREAK %s=%s SIZE=%s',[cmbBPType.Text,edBPOffset.Text,edBPSize.Text])); + + NewBP.Size := StrToInt(edBPSize.Text); + NewBp.Offset := StrToInt64(edBPOffset.Text); + NewBP.Desc := edBPDesc.Text; + NewBP.BPType := TBPTypes(GetEnumValue(TypeInfo(TBPTypes),cmbBPType.Text)); + + SetLength(Breakpoints,Length(Breakpoints)+1); + Breakpoints[High(Breakpoints)] := NewBP; + NewItem := lvBreak.Items.Add; + lvBreak.Items.BeginUpdate; + NewItem.Caption := edBPOffset.Text; + NewItem.SubItems.Add(cmbBPType.Text); + NewItem.SubItems.Add(edBPSize.Text); + NewItem.SubItems.Add('Never'); + NewItem.SubItems.Add(edBPDesc.Text); + NewItem.Data := Pointer(NewItem.Checked); + NewItem.Checked := true; + lvBreak.Items.EndUpdate; + + +end; + +procedure TfrmMain.bpUnsetClick(Sender: TObject); +var +Counter:Integer; +begin + if edBPOffset.Text = '' then Exit; + + if not AnsiStartsStr('0x',edBPOffset.Text) then + edBPOffset.Text := '0x' + edBPOffset.Text; + + if edBPSize.Text = '' then edBPSize.Text := '1'; + + Counter := 0; +repeat + if lvBreak.Items.Count = 0 then Break; + with lvBreak.Items[Counter] do + begin + if Caption = edBPOffset.Text then + begin + if ( (SubItems.IndexOf(edBPSize.Text) >= 0) and + (SubItems.IndexOf(cmbBPType.Text) >= 0) ) then + Checked := false; + + end; + end; + Inc(Counter); +until Counter = lvBreak.Items.Count; +end; + +procedure TfrmMain.ContinueThread1Click(Sender: TObject); +begin + SendData('CONTINUE THREAD=' + InputBox('Which thread?','Which thread do you wish to continue?','28')); +end; + +procedure TfrmMain.ServerExecute(AContext: TIdContext); +var +buf:String; +Index:Integer; +Pos:Integer; +Pos2:Integer; +Off:Cardinal; +HitOff:Cardinal; +BPType:String; +begin + with AContext do + begin + buf := Connection.IOHandler.ReadLn; + if ((AnsiStartsText('data',buf)) or (AnsiStartsText('break',buf))) then + begin + //data write=0x02414d80 addr=0x0007fc5d thread=28 stop + if (AnsiStartsText('data',buf)) then + begin + Pos := AnsiPos('addr=',buf) + 5; + HitOff := StrToInt64Def(AnsiMidStr(buf,Pos,10),0); + + Pos := AnsiPos('data ',buf) + 5; + Pos2 := AnsiPos('=0x',buf); + BPType := AnsiMidStr(buf,Pos,Pos2-Pos); + + Pos := AnsiPos(BPType + '=',buf) + Length(BPType) + 1; + Off := StrToInt64Def(AnsiMidStr(buf,Pos,10),0); + end + else + begin + //break addr=0x0007fc5d thread=28 stop + BPType := 'addr'; + + Pos := AnsiPos('addr=',buf) + 5; + HitOff := StrToInt64Def(AnsiMidStr(buf,Pos,10),0); + + Off := HitOff + end; +{r: . +s: getcontext thread=28 int control +r: 202- multiline response follows +r: Ebp=0xd0059b6c +r: Esp=0xd0059b30 +r: Eip=0x0007fc5d +r: EFlags=0x00000206 +r: Eax=0x00000000 +r: Ebx=0x024148f0 +r: Ecx=0x0134fb20 +r: Edx=0x004b2a60 +r: Edi=0xd0059b14 +r: Esi=0xd0059b48} + +{TODO: Get registers} + Log.AddLn(Format('Breakpoint detected (%s,0x%.80x,0x%.80x).',[BPType,Off,HitOff])); + + for index := 0 to High(Breakpoints) do + begin + if ((Breakpoints[Index].Offset = Off) and + (Breakpoints[Index].BPType = TBPTypes(GetEnumValue(TypeInfo(TBPTypes),BPType)))) then + begin + lvBreak.Items.Item[Index].SubItems.Strings[2] := '0x' + IntToHex(HitOff,8); + end; + end; + ProgStatus := stGetContext; + SendData('GETCONTEXT thread=28 int control'); + + end + else + begin + Log.AddLn('n: ' + buf); + end; + end; +end; + +procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction); +var +iniSet:TINIFile; +Counter:Integer; +begin + iniSet := TINIFile.Create(ExtractFilePath(Application.EXEName) + 'xdkassist.ini'); + iniSet.WriteString('Connection','Host',XClient.Host); + iniSet.WriteInteger('Connection','Port',Server.defaultport); + iniSet.WriteBool('Dumping','AutoStop',chkDumpAutoStop.Checked); + iniSet.WriteBool('Dumping','AutoCopy',chkCopyOffToClip.Checked); + iniSet.WriteBool('Dumping','Highlight',chkHighlightDumpChanges.Checked); + iniSet.WriteBool('Logging','Verbose',chkVerboseLog.Checked); + iniSet.WriteString('Breakpoints','Type',cmbBPType.Text); + iniSet.WriteInteger('Window','State',Integer(frmMain.WindowState)); + if(frmMain.WindowState = wsMaximized) then + frmMain.WindowState := wsNormal; + iniSet.WriteInteger('Window','Width',frmMain.Width); + iniSet.WriteInteger('Window','Height',frmMain.Height); + iniSet.WriteInteger('Window','LastTab',pgControl.ActivePageIndex); + iniSet.WriteInteger('Window','State',Integer(frmMain.WindowState)); + + iniSet.WriteBool('Misc','WarnConClose',chkWarnConnected.Checked); + iniSet.WriteBool('Layout','ShowMainLog',chkShowMainLog.Checked); + + + iniSet.WriteBool('Range','Enabled',chkUseSearchRange.Checked); + iniSet.WriteString('Range','Start',edSearcherStart.Text); + iniSet.WriteString('Range','End',edSearcherEnd.Text); + iniSet.WriteString('Range','Caption',edSearcherCaption.Text); + iniSet.WriteString('Range','Class',edSearcherClass.Text); + iniSet.WriteString('Range','State',edSearcherState.Text); + + for Counter:=0 to High(Tools) do + begin + With Tools[Counter] do + begin + iniSet.WriteString('Tool' + IntToStr(Counter),'Name',Name); + iniSet.WriteString('Tool' + IntToStr(Counter),'Class',WinClass); + iniSet.WriteString('Tool' + IntToStr(Counter),'Caption',WinText); + iniSet.WriteBool('Tool' + IntToStr(Counter),'Load',Load); + end; + end; + + FreeAndNil(iniSet); + + while not ClientThread.Terminated do + begin + ClientThread.Terminate; + if not ClientThread.Terminated then + begin + ShowMessage('Internet thread not terminated. Waiting, and then will try again.' + + 'If you continue to get this message I am afraid you must end the task manually.'); + Sleep(2000); + end; + + end; + if Assigned(SavedDump) then FreeAndNil(SavedDump); +end; + +procedure FillDumpPages(hDump:HWND); +var +Read:String; +begin +with DebugBox.Memory do +begin + SetLength(Sections,0); + SetLength(DebugBox.Memory.Sections,0); + Read := ''; + while (Read <> '.') and + Assigned(frmMain.XClient) and + (not frmMain.ClientThread.Terminated) and + (frmMain.XClient.Connected = true) do + begin + try + Read := frmMain.XClient.IOHandler.ReadLn; + except + on E: Exception do + begin + if (E is EIdReadTimeout) or (E is EIdNoDataToRead) then Continue + else if E is EIdConnClosedGracefully then + begin + SetLength(Sections,0); + Exit; + end + else + Log.AddLn(E.Message); + end; + end; + + if (Read = '') or (Read = '.') then Continue; + SetLength(Sections,Length(Sections)+1); + with Sections[High(Sections)] do + begin + Offset := StrToInt( AnsiMidStr(Read,AnsiPos('base=',Read) + Length('base='),10)); + Size := StrToInt( AnsiMidStr(Read,AnsiPos('size=',Read) + Length('size='),10)); + Flags := StrToInt( AnsiMidStr(Read,AnsiPos('protect=',Read) + Length('protect='),10)); + Loc := 0; + end; + end; +end; +end; + +procedure GetDumpData(hProgress:HWND;hWin:HWND;Verbose:Boolean); +var +Counter:Cardinal; +NewSize:Cardinal; +Time:Cardinal; +MemStream:TMemoryStream; +begin +// MemBuffer.Clear; + DebugBox.Memory.Buffer.Clear; + if(frmMain.hxMemView.DataSize > 0) then + begin + frmMain.hxMemView.CreateEmptyFile(''); + end; +with DebugBox.Memory do +begin + NewSize := 0; + for Counter := Low(Sections) to High(Sections) do + NewSize := NewSize + Sections[Counter].Size; + + PostMessage(hProgress,PBM_SETRANGE32,0,High(Sections)); + + //MemBuffer.SetSize(NewSize); + DebugBox.Memory.Buffer.SetSize(NewSize); +try +begin + MemStream := TMemoryStream.Create; + if (not Assigned(MemStream)) then + begin + log.addln('Failed to create memory stream for dumping.'); + Exit; + end; + + for Counter := 0 to High(Sections) do + begin + with Sections[Counter] do + begin + Time := GetTickCount; + MemStream.Clear; + MemStream.SetSize(Size); + + frmMain.XClient.IOHandler.WriteLn(Format('GETMEM2 ADDR=0x%.8x LENGTH=0x%.8x',[Offset,Size])); + while (frmMain.XClient.IOHandler.ReadLn <> '203- binary response follows') and (frmMain.XClient.Connected) do; + while (Cardinal(frmMain.XClient.IOHandler.InputBuffer.Size) < Size) and (frmMain.XClient.Connected) do; + + frmMain.XClient.IOHandler.ReadStream(MemStream,Size); + PostMessage(hProgress,PBM_SETPOS,Counter,0); + + try + begin +// MemBuffer.CopyFrom(MemStream,0); + DebugBox.Memory.Buffer.CopyFrom(MemStream,0); +// Loc := Membuffer.Position; + Loc := DebugBox.Memory.Buffer.Position; + if Verbose then + Log.AddLn(Format('Dumped %d bytes in %.2f seconds from 0x%.8x (0x%.8x)', + [Size,(GetTickCount-Time)/1000,Offset, + //Cardinal(MemBuffer.Memory) + Cardinal(DebugBox.Memory.Buffer.Memory) + + Loc-Size])); + end + except + on E: Exception do + log.AddLn('Dump (MemStream): ' + E.Message); + end; + + if (not frmMain.XClient.IOHandler.InputBufferIsEmpty) and + (frmMain.XClient.Connected = true) then + begin + Log.AddLn(IntToStr(frmMain.XClient.IOHandler.InputBuffer.size) + ' bytes still on input buffer.'); + frmMain.XClient.IOHandler.ReadStream(MemStream,frmMain.XClient.IOHandler.InputBuffer.Size); + end; + end; + end; + FreeAndNil(MemStream); +end +except + on E: Exception do + Log.AddLn('Dump: ' + E.Message); +end; +end; +end; + +procedure TfrmMain.edBPOffsetKeyPress(Sender: TObject; var Key: Char); +begin + if (not IsValidHexBoxInput(Key)) and (not (Key in ['x','X'])) then + Key := #0; +end; + +procedure TfrmMain.edBPSizeKeyPress(Sender: TObject; var Key: Char); +begin + if not (Key in ['0'..'9',Char(VK_BACK),Char(VK_DELETE)]) then Key := #0; +end; +{ +function LaunchAsChild(EXEName:String;EXEClass:PAnsiChar;EXECaption:PAnsiChar;Parent:HWND):HWND; +var +Win:HWND; +r:TRect; +GWL:LongInt; +begin + Result := 0; + if (not FileExists(EXEName)) then Exit; + + Win := FindWindow(EXEClass,EXECaption); + + if Win = 0 then + begin + ShellExecute(Parent,'open',PAnsiChar(EXEName),#0,#0,SW_HIDE); + Sleep(150); + Win := FindWindow(EXEClass,EXECaption); + if Win = 0 then Exit; + end; + ShowWindow(Win,SW_HIDE); + SetParent(Win,Parent); + + ShowWindow(Win,SW_HIDE); + SetParent(Win,Parent); + + GWL := GetWindowLong(Win,GWL_STYLE); + +{ if (GWL and WS_BORDER) = WS_BORDER then + GWL := GWL and (not WS_BORDER); + if (GWL and WS_OVERLAPPEDWINDOW) = WS_OVERLAPPEDWINDOW then + GWL := GWL and (not WS_OVERLAPPEDWINDOW); + if (GWL and WS_DLGFRAME) = WS_DLGFRAME then + GWL := GWL and (not WS_DLGFRAME); +} { if (GWL and WS_THICKFRAME) = WS_THICKFRAME then + GWL := GWL and (not WS_THICKFRAME); + if (GWL and WS_POPUP) = WS_POPUP then + GWL := GWL and (not WS_POPUP); } +{ if (GWL and WS_MINIMIZEBOX) = WS_MINIMIZEBOX then + GWL := GWL and (not WS_MINIMIZEBOX); +} { if (GWL and WS_MAXIMIZEBOX) = WS_MAXIMIZEBOX then + GWL := GWL and (not WS_MAXIMIZEBOX); } +{ if (GWL and WS_SYSMENU) = WS_SYSMENU then + GWL := GWL and (not WS_SYSMENU); +}{ if (GWL and WS_OVERLAPPED) = WS_OVERLAPPED then + GWL := GWL and (not WS_OVERLAPPED); } +{ if (GWL and WS_CAPTION) = WS_CAPTION then + GWL := GWL and (not WS_CAPTION); } +// GWL := GWL or WS_CHILD; + { SetWindowLong(Win,GWL_STYLE,GWL); + + GWL := GetWindowLong(Win,GWL_EXSTYLE); + if (GWL and WS_EX_CLIENTEDGE) = WS_EX_CLIENTEDGE then + GWL := GWL and (not WS_EX_CLIENTEDGE); + if (GWL and WS_EX_DLGMODALFRAME) = WS_EX_DLGMODALFRAME then + GWL := GWL and (not WS_EX_DLGMODALFRAME); + if (GWL and WS_EX_APPWINDOW) = WS_EX_APPWINDOW then + GWL := GWL and (not WS_EX_APPWINDOW); } + +// GWL := GWL or WS_EX_TOOLWINDOW; +{ GWL := GWL or WS_EX_STATICEDGE; + GWL := GWL or WS_EX_CONTROLPARENT; + SetWindowLong(Win,GWL_EXSTYLE,GWL); + + GetWindowRect(Win,r); + ShowWindow(Win,SW_SHOW); + MoveWindow(Win,0,0,r.Right-r.Left+1,r.Bottom-r.Top,true); + + Result := Win; +end; + } +procedure TfrmMain.FormDestroy(Sender: TObject); +var +Counter:Integer; +begin + for Counter := 0 to Length(Tools)-1 do + if Tools[Counter].Handle <> 0 then PostMessage(Tools[Counter].Handle, WM_QUIT,0,0); + + if (moNotes.Lines.Count > 0) then + moNotes.Lines.SaveToFile('xdkassist.notes') + else + if FileExists('xdkassist.notes') then DeleteFile('xdkassist.notes'); +end; + +procedure TfrmMain.ConnectToggle(Tog:Boolean); +begin + ProgStatus := stNorm; + + try + if Server.DefaultPort <> -1 then + Server.Active := tog; + except + on E: Exception do Log.AddLn('Server Toggle: ' + E.Message); + end; + + hxMemView.Enabled := Tog; + edInput.Enabled := Tog; + lvBreak.Enabled := Tog; + lvDump.Enabled := Tog; + edBPOffset.Enabled := Tog; + edBPDesc.Enabled := Tog; + edBPSize.Enabled := Tog; + cmbBPType.Enabled := Tog; + bpSet.Enabled := Tog; + bpUnset.Enabled := Tog; + grpConvOffset.Enabled := Tog; + grpMemEdit.Enabled := Tog; + pbDump.Enabled := Tog; + lbBPType.Enabled := Tog; + edViewoffset.Enabled := Tog; + edByteSearch.Enabled := Tog; + btMemSrchReset.Enabled := Tog; + lvRegisters.Enabled := Tog; + btGetRegisters.Enabled := Tog; + + if Tog then + begin + try + ClientThread.Start; + except + on E: Exception do Log.AddLn('Recv Startup: ' + E.Message); + end; + frmmain.Connect1.Caption := 'Disconnect'; + frmMain.Connect1.ImageIndex := 6; + end + else + begin + ClientThread.Stop; + frmmain.Connect1.Caption := 'Connect'; + frmMain.Connect1.ImageIndex := 7; + + lvBreak.Items.Clear; + lvDump.Items.Clear; + pbDump.Position := 0; +// MemBuffer.Clear; + DebugBox.Memory.Buffer.Clear; + SavedDump.Clear; +// if hxMemView.DataSize > 0 then +// hxMemView.LoadFromStream(SavedDump); +// hxMemView.CreateEmptyFile('Empty'); + + end; +end; + +procedure TfrmMain.OnNewText(var Msg: TMessage); +var +Change:String; +begin + try + begin + if (Msg.WParam < 0) then Exit; + Log.Seek(Msg.WParam,soFromBeginning); + Change := Log.ReadString(Msg.Lparam); + richlog.SelStart := richlog.GetTextLen; + richLog.SelText := Change; + end + except + on E: Exception do ShowMessage('Logging update: ' + E.Message); + end; +end; + +procedure TfrmMain.edViewOffsetKeyPress(Sender: TObject; var Key: Char); +begin + if (not IsValidHexBoxInput(Key)) and (not (Key in ['x','X'])) then Key := #0; +end; + +procedure TfrmMain.edViewOffsetKeyUp(Sender: TObject; var Key: Word; + Shift: TShiftState); +var +Off:Cardinal; +Sect:Integer; +ret:Integer; +begin + if Key <> VK_RETURN then Exit; + + if AnsiPos('0X',edViewOffset.Text) <= 0 then + edViewOffset.Text := Format('0x%.8x',[StrToInt64Def('0x' + edViewOffset.Text,0)]); + + //if (MemBuffer.Size <= 0) or + // (Length(Sections) <= 0) then + if (DebugBox.Memory.Buffer.Size <= 0) or + (Length(DebugBox.Memory.Sections) <= 0) then + begin + log.AddLn('There is no dump.'); + Exit; + end; + + ret := ConvXBOX2PC(StrToInt64Def(edViewOffset.Text,0),@Sect,@Off); + if ret <> EConvOkay then + log.AddLn(ConvError(ret)); + + if (Sect >= 0) then + hxMemView.SetTopLeftPosition(Off - + //Cardinal(Membuffer.Memory) + Cardinal(DebugBox.Memory.Buffer.Memory) + ,false); + +end; + +procedure TfrmMain.edByteSearchKeyPress(Sender: TObject; var Key: Char); +begin + if not IsValidHexBoxInput(Key) then Key := #0; +end; + +procedure TfrmMain.edByteSearchKeyUp(Sender: TObject; var Key: Word; + Shift: TShiftState); +var +BinBuf:PAnsiChar; +BinLen:Integer; +begin + if Key <> VK_RETURN then Exit; + + BinLen := Length(edByteSearch.Text) div 2 + Length(edByteSearch.Text) mod 2; + + if (BinLen <= 0) or + //(MemBuffer.Size <= 0) + (DebugBox.Memory.Buffer.Size <= 0) + then Exit; + + BinBuf := StrAlloc(BinLen); + + HexToBin(PAnsiChar(edByteSearch.Text),BinBuf,BinLen); + + BinBuf := PansiChar(hxMemView.PrepareFindReplaceData(String(BinBuf),false,false)); + + MemSrchPos := hxMemView.Find(BinBuf,BinLen,MemSrchPos,hxMemView.DataSize,false); + + if MemSrchPos > -1 then + begin + hxMemView.Seek(MemSrchPos,0); + hxMemView.SelStart := MemSrchPos; + hxMemView.SelEnd := MemSrchPos + BinLen - 1; + Inc(MemSrchPos); + end + else + log.AddLn('Pattern not found.'); + +end; + +procedure TfrmMain.OnNewDump(var Msg:TMessage); +var +Count:Integer; +ti:TListItem; +Searcher:THandle; +pid:Cardinal; +phandle:THandle; +data:Cardinal; +bytes:Cardinal; +off:Cardinal; +state:WORD; +begin + try + begin + hxMemView.OffsetFormat := '1%1!10:0x|'; +// hxMemView.LoadFromStream(MemBuffer); + hxMemView.LoadFromStream(DebugBox.Memory.Buffer); + + PostMessage(ProgressBar,PBM_SETPOS, + SendMessage(ProgressBar,PBM_GETRANGE,wparam(false),lparam(nil)),0); + + if chkDumpAutoStop.Checked then SendData('GO'); + +// for Count := 0 to High(Sections) do + for Count := 0 to High(DebugBox.Memory.Sections) do + begin + with DebugBox.Memory.Sections[Count] do +// with Sections[Count] do + begin + ti := lvDump.Items.Add; + ti.Caption := IntToStr(ti.Index+1); + ti.SubItems.Add('0x' + IntToHex(Offset,8));//Offset + ti.SubItems.Add('0x' + IntToHex(Size,8));//Size + ti.SubItems.Add('0x' + IntToHex(Flags,8));//Flags +// ti.SubItems.Add('0x' + IntToHex(Cardinal(MemBuffer.Memory) + Loc-Size,8));//Start +// ti.SubItems.Add('0x' + IntToHex(Cardinal(MemBuffer.Memory) +Loc,8));//End + ti.SubItems.Add('0x' + IntToHex(Cardinal(DebugBox.Memory.Buffer.Memory) + Loc - Size, 8)); + ti.SubItems.Add('0x' + IntToHex(Cardinal(DebugBox.Memory.Buffer.Memory) + Loc, 8)); + end; + end; + + end + except + on E: Exception do log.Addln(E.Message); + end; + + ProgStatus := stNorm; + + if(chkUseSearchRange.Checked) then + begin + Searcher := FindWindow(PAnsiChar(edSearcherClass.Text+#0),PAnsiChar(edSearcherCaption.Text+#0)); + if(Searcher <> 0) then + begin + GetWindowThreadProcessId(Searcher,@pid); + phandle := OpenProcess(PROCESS_ALL_ACCESS,false,pid); + if phandle <> 0 then + begin + off := StrToInt64(MakeOffset(edSearcherStart.Text)); + if (off <> 0) then + begin +// data := Cardinal(MemBuffer.Memory); + data := Cardinal(DebugBox.Memory.Buffer.Memory); + WriteProcessMemory(phandle,Pointer(off),@data,4,bytes); + end; + + off := StrToInt64(MakeOffset(edSearcherEnd.Text)); + if (off <> 0) then + begin +// data := Cardinal(MemBuffer.Memory) + Cardinal(MemBuffer.Size); + Data := Cardinal(DebugBox.Memory.Buffer.Memory) + Cardinal(DebugBox.Memory.Buffer.Size); + WriteProcessMemory(phandle,Pointer(off),@data,4,bytes); + end; + + off := StrToInt64(MakeOffset(edSearcherState.Text)); + if (off <> 0) then + begin + state := 1; + WriteProcessMemory(phandle,Pointer(off),@state,2,bytes); + end; + + log.AddLn(Format('Patched %s (%s) at %s and %s',[edSearcherCaption.Text,edSearcherClass.Text,edSearcherStart.Text,edSearcherEnd.Text])); + CloseHandle(phandle); + end + else + log.addln('Couldn''t set range in your search application. Is it running?') + end + else + log.addln('Couldn''t set range in your search application. Is it running?') + end; +end; + +procedure TfrmMain.btMemSrchResetClick(Sender: TObject); +begin + MemSrchPos := 0; +end; + +function IsValidHexBoxInput(var Key: Char):Boolean; +begin + Result := false; + + if (Key in ['A'..'F','a'..'f','0'..'9',Char(VK_BACK),Char(VK_DELETE)]) or + (Key in ['V','v','X','x'{,#22,#3,#24}]) or + (Key < ' ') then Result := true; + +end; + +function IsValidIP(const AAddr : String): Boolean; +var LIP : TIdIPAddress; +begin + LIP := TIdIPAddress.MakeAddressObject(AAddr); + Result := Assigned(LIP); + if Result then + begin + FreeAndNil(LIP); + end; +end; + +procedure TfrmMain.SetXBOXAddress1Click(Sender: TObject); +var +NewIP:String; +begin + NewIP := InputBox('Enter a new IP','Enter in the new address for your XBOX.',XClient.Host); + + while (not IsValidIP(NewIP)) do + begin + if InputQuery('Enter a new, valid, IP.', + 'The IP that was entered into the box was invalid. Please enter a new one.', + NewIP) = false then Exit; + end; + + XClient.Host := NewIP; +end; + +procedure TfrmMain.SetListenPort1Click(Sender: TObject); +var +NewPort:String; +begin + NewPort := InputBox('Enter a new port.','Type in the port you would like the XBOX to connect to your machine on.' + #13#10 + + 'Enter -1 if you want to disable this feature, however this is not recommended because you will miss out in nice information, and ' + + 'some features will not work properly.',IntToStr(Server.DefaultPort)); + + while not IsNumeric(NewPort) do + begin + if InputQuery('Enter a new, valid, port', + 'The port that was entered into the box was invalid. Please enter a new one.', + NewPort) = false then Exit; + end; + + Server.DefaultPort := StrToInt(NewPort); +end; + +procedure TfrmMain.JumpbyXBOXAddress1Click(Sender: TObject); +var +Offset:String; +begin + if InputQuery('Jump by XBOX addy.','This will let you jump around the dump using an xbox address.',Offset) = false then Exit; + +end; + +procedure TfrmMain.JumpbyPCAddress1Click(Sender: TObject); +var +Offset:String; +begin + if InputQuery('Jump by PC addy.','This will let you jump around the dump using a PC address.',Offset) = false then Exit; + +end; + +procedure TfrmMain.SavetoFile1Click(Sender: TObject); +var +CurDir:String; +begin + CurDir := GetCurrentDir; + SaveDialog.InitialDir := GetCurrentDir; + + if SaveDialog.Execute then + begin + hxMemView.SaveToFile(SaveDialog.FileName); + log.addln('Saved the dump to ' + SaveDialog.filename); + SetCurrentDir(CurDir); + end; + +end; + +procedure TfrmMain.hxMemViewChange(Sender: TObject); +var +Pos:Integer; +Mem:Char; +XBOX:Cardinal; +begin + if(not XClient.Connected) then Exit; + if(ProgStatus = stDump) then Exit; + Pos := hxMemView.GetCursorPos; + Mem := hxMemView.GetMemory(Pos); +// Pos := ConvPC2XBOX(Cardinal(MemBuffer.Memory) + Cardinal(Pos),@xbox); + Pos := ConvPC2XBOX(Cardinal(DebugBox.Memory.Buffer.Memory) + Cardinal(Pos),@XBOX); + if (Pos <> EConvOkay) then + Log.AddLn(ConvError(Pos)) + else + SendData(Format('SETMEM ADDR=0x%.80x DATA=%s',[xbox,IntToHex(Integer(Mem),2)])); +end; + +procedure TfrmMain.cbMemEditChange(Sender: TObject); +begin + if (cbMemEdit.Text = 'GETMEM') then + edMemEditParam.EditLabel.Caption := 'Length:' + else + edMemEditParam.EditLabel.Caption := 'Data:'; + +end; + +procedure TfrmMain.btMemEditClick(Sender: TObject); +begin + if(cbMemEdit.Text = 'GETMEM') then + SendData(Format('GETMEM ADDR=0x%s LENGTH=%s',[edMemEditOffset.Text,edMemEditParam.Text])) + else //Setmem + SendData(Format('SETMEM ADDR=0x%s DATA=%s',[edMemEditOffset.Text,edMemEditParam.Text])); +end; + +procedure TfrmMain.edConvOffsetFromKeyPress(Sender: TObject; var Key: Char); +begin + if (not IsValidHexBoxInput(Key)) and (not (Key in ['x','X'])) then Key := #0; +end; + +procedure TfrmMain.edConvOffsetFromKeyUp(Sender: TObject; var Key: Word; + Shift: TShiftState); +var +ConvOffset:Cardinal; +Original:Cardinal; +Ret:Integer; +begin + if Key <> VK_RETURN then Exit; + + if (not IsHexidecimal(edConvOffsetFrom.Text) and + (not IsNumeric(edConvOffsetFrom.Text))) then + begin + lbConvOffStat.Caption := 'Bad input'; + Log.AddLn('An invalid input string was specified. Please make sure that the string was a hexidecimal string. The 0x is optional.'); + Exit; + end; + + if (not AnsiStartsStr('0X',Uppercase(edConvOffsetFrom.Text))) then + edConvOffsetFrom.Text := '0x' + edConvOffsetFrom.Text; + + Original := StrToInt(edConvOffsetFrom.Text); + + if(cbOffsetConvert.Text = 'PC') then begin + Ret := ConvPC2XBOX(Original,@ConvOffset); + end else begin //XBOX + Ret := ConvXBOX2PC(Original,nil,@ConvOffset); + end; + + case Ret of + EConvOkay: lbConvOffStat.Caption := 'Converted.'; + EConvNotFound: lbConvOffStat.Caption := 'Not found.'; + EConvBadPointer: lbConvOffStat.Caption := 'Bad pointer.'; + EConvOutOfRange: lbConvOffStat.Caption := 'Out of range.'; + EConvNoSections: lbConvOffStat.Caption := 'Missing sections.'; + end; + + if (Ret <> EConvOkay) then + begin + Log.AddLn(ConvError(ret)); + end + else + begin + Log.AddLn(edConvOffsetFrom.Text + ' converted to 0x' + IntTohex(ConvOffset,8)); + edConvOffsetTo.Text := '0x' + IntTohex(ConvOffset,8); + if (chkCopyOffToClip.Checked) then + TextToClip(IntToHex(ConvOffset,8)); + end; + +end; + +procedure TfrmMain.edMemEditParamKeyUp(Sender: TObject; var Key: Word; + Shift: TShiftState); +begin + if Key <> VK_RETURN then Exit; + + btMemEditClick(Sender); + +end; + +procedure TfrmMain.GenericMenuItemClick(Sender: TObject); +begin + + if ((TMenu(Sender).Tag < Low(Tools)) or (TMenu(Sender).Tag > High(Tools))) then + begin + Log.AddLn('Tool index was out of valid range.'); + Exit; + end; + + with Tools[TMenu(Sender).Tag] do + begin +{ if( Load ) then + LaunchAsChild(Name,PAnsiChar(WinClass+#0),PAnsiChar(WinText+#0),frmMain.Handle) + else} + ShellExecute(0{frmMain.Handle},'open',PAnsiChar(Name),#0,#0,SW_NORMAL); + end; +end; + +procedure TfrmMain.lvBreakChange(Sender: TObject; Item: TListItem; + Change: TItemChange); +var +buf:String; +begin + if ( Change <> ctState) then Exit; + + if (Boolean(Item.Data) <> Item.Checked) then + begin + buf := Format('BREAK %s=%s SIZE=%s', + [Item.SubItems.Strings[0], + Item.Caption, + Item.SubItems.Strings[1]]); + + if (not Item.Checked) then + buf := buf + ' CLEAR'; + SendData(buf); + Item.Data := Pointer(Item.Checked); + end; +end; + +procedure TfrmMain.lvDumpSelectItem(Sender: TObject; Item: TListItem; + Selected: Boolean); +var +Flags:Cardinal; +begin + if not Selected then Exit; + + for Flags := 0 to lbSectFlags.Items.Count-1 do + lbSectFlags.Checked[Flags] := false; + + Flags := StrToInt64Def(Item.SubItems.Strings[2],0); + + if (Flags and PAGE_NOACCESS) = PAGE_NOACCESS then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('PAGE_NOACCESS')] := true; + + + if (Flags and PAGE_READONLY) = PAGE_READONLY then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('PAGE_READONLY')] := true; + + if (Flags and PAGE_READWRITE) = PAGE_READWRITE then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('PAGE_READWRITE')] := true; + + if (Flags and PAGE_EXECUTE) = PAGE_EXECUTE then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('PAGE_EXECUTE')] := true; + + if (Flags and PAGE_EXECUTE_READ) = PAGE_EXECUTE_READ then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('PAGE_EXECUTE_READ')] := true; + + if (Flags and PAGE_EXECUTE_READWRITE) = PAGE_EXECUTE_READWRITE then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('PAGE_EXECUTE_READWRITE')] := true; + + if (Flags and PAGE_GUARD) = PAGE_GUARD then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('PAGE_GUARD')] := true; + + if (Flags and PAGE_NOCACHE) = PAGE_NOCACHE then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('PAGE_NOCACHE')] := true; + + if (Flags and PAGE_WRITECOMBINE) = PAGE_WRITECOMBINE then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('PAGE_WRITECOMBINE')] := true; + + if (Flags and MEM_COMMIT) = MEM_COMMIT then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('MEM_COMMIT')] := true; + + if (Flags and MEM_DECOMMIT) = MEM_DECOMMIT then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('MEM_DECOMMIT')] := true; + + if (Flags and MEM_RELEASE) = MEM_RELEASE then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('MEM_RELEASE')] := true; + + if (Flags and MEM_RESERVE) = MEM_RESERVE then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('MEM_RESERVE')] := true; + + if (Flags and MEM_FREE) = MEM_FREE then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('MEM_FREE')] := true; + + if (Flags and MEM_PRIVATE) = MEM_PRIVATE then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('MEM_PRIVATE')] := true; + + if (Flags and MEM_MAPPED) = MEM_MAPPED then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('MEM_MAPPED')] := true; + + if (Flags and MEM_RESET) = MEM_RESET then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('MEM_RESET')] := true; + + if (Flags and MEM_TOP_DOWN) = MEM_TOP_DOWN then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('MEM_TOP_DOWN')] := true; + + if (Flags and MEM_LARGE_PAGES) = MEM_LARGE_PAGES then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('MEM_LARGE_PAGES')] := true; + + if (Flags and MEM_4MB_PAGES) = MEM_4MB_PAGES then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('MEM_4MB_PAGES')] := true; + + if (Flags and SEC_RESERVE) = SEC_RESERVE then + lbSectFlags.Checked[lbSectFlags.Items.IndexOf('SEC_RESERVE')] := true; + +end; +procedure TfrmMain.btDumpClick(Sender: TObject); +begin + DumpMemory1Click(Sender); +end; + +procedure TfrmMain.RestartTitle1Click(Sender: TObject); +begin + if (DebugBox.XBE.Name <> '') then + DebugBox.Reboot(0,DebugBox.XBE.Name) +// if (XBEName <> '') then +// SendData('magicboot title="'+XBEName+'" debug') + else + Log.AddLn('Please run the XBE Info command from the menu first.'); + +end; + +procedure TfrmMain.lvBreakSelectItem(Sender: TObject; Item: TListItem; + Selected: Boolean); +begin + edBPOffset.Text := Item.Caption; + edBPSize.Text := Item.SubItems.Strings[1]; + edBPDesc.Text := Item.SubItems.Strings[3]; + cmbBPType.ItemIndex := cmbBPType.Items.IndexOf(Item.SubItems.Strings[2]); + +end; + +procedure TfrmMain.SaveLog1Click(Sender: TObject); +begin + Log.SaveToFile('xdkassist.log'); +end; + +procedure TfrmMain.hxMemViewTopLeftChanged(Sender: TObject); +var +i:Integer; +DStart,DEnd:Integer; +begin + if (not chkHighlightDumpChanges.Checked) or + (SavedDump.Size <= 0) or + (hxMemView.DataSize <= 0) then Exit; + + + DStart := hxMemView.DisplayStart; + DEnd := hxMemView.DisplayEnd; + + if ((DStart > SavedDump.Size) or (DEnd > SavedDump.Size)) then + begin + //New dump is larger than old dump + for i := DStart to DEnd do + begin + if (i > hxMemView.DataSize-1) then break; + hxMemView.ByteChanged[i] := true; + end; + Exit; + end; + + for i := DStart to DEnd do + begin + if (i > hxMemView.DataSize-1) then break; + + if ((PByteArray(SavedDump.Memory)[i]) <> hxMemView.Data[i]) then + hxMemView.ByteChanged[i] := true; + end; +end; + +procedure TfrmMain.btGetRegistersClick(Sender: TObject); +begin + if(not XClient.Connected) then Exit + else if (ProgStatus <> stNorm) then + begin + log.AddLn('Busy with another action.'); + Exit; + end; + + ProgStatus := stGetContext; + + SendData('HALT'); + SendData('GETCONTEXT THREAD=28 CONTROL INT FP'); + SendData('CONTINUE THREAD=28'); + SendData('GO'); +end; + +procedure TfrmMain.chkShowMainLogClick(Sender: TObject); +begin + if chkShowMainLog.Checked then + begin + pgControl.Top := grpConsole.Top + grpConsole.Height + pgControl.Left; + pgControl.Height := frmMain.ClientHeight - (pgControl.Left * 4) - grpConsole.height; + grpConsole.Visible := true; + end + else + begin + grpConsole.Visible := false; + pgControl.Top := grpConsole.Top; + pgControl.Height := frmMain.ClientHeight - (pgControl.left*3); + end; + + +end; + +procedure TfrmMain.btnToolSelectClick(Sender: TObject); +begin + odToolSelect.FileName := ''; + if not odToolSelect.Execute() then Exit; + edToolPath.Text := odToolSelect.FileName; +end; + +end. diff --git a/src/Settings.pas b/src/Settings.pas new file mode 100644 index 0000000..7933363 --- /dev/null +++ b/src/Settings.pas @@ -0,0 +1,101 @@ +unit Settings; + +interface + +uses INIFiles, Forms, SysUtils; + +type TConnection = record + Host:String; + Port:Integer; +end; + +type TDumping = record + AutoStop:Boolean; + AutoCopy:Boolean; + Verbose:Boolean; +end; + +type TBP = record + LastType:String; +end; + +type TWindow = record + Width,Height,LastTab:Integer; + State:TWindowState; +end; + +type TSettings = class + Connection:TConnection; + Dumping:TDumping; + Breakpoint:TBP; + Window:TWindow; + + function Save(FileName:String):Boolean; + function Load(FileName:String):Boolean; +end; + +implementation + +function TSettings.Load(FileName:String):Boolean; +var +iniSet:TINIFile; +begin + Result:= false; + + try + iniSet := TINIFile.Create(FileName); + except + on E: Exception do + begin + Exit; + end; + end; + + Connection.Host := iniSet.ReadString('Connection','Host','192.168.1.153'); + Connection.Port := iniSet.ReadInteger('Connection','Port',2000); + Breakpoint.LastType := iniSet.ReadString('Breakpoints','Type','Read'); + Window.Width := iniSet.ReadInteger('Window','Width',614); + Window.Height := iniSet.ReadInteger('Window','Height',713); + Window.State := TWindowState(iniSet.ReadInteger('Window','State',Integer(wsNormal))); + Window.LastTab := iniSet.ReadInteger('Window','LastTab',0); + Dumping.AutoStop :=iniSet.ReadBool('Dumping','AutoStop',true); + Dumping.AutoCopy := iniSet.ReadBool('Dumping','AutoCopy',true); + Dumping.Verbose := iniSet.ReadBool('Dumping','Verbose',true); + + FreeAndNil(iniSet); + + Result := true; +end; + +function TSettings.Save(FileName:String):Boolean; +var +iniSet:TINIFile; +begin + Result := false; + + try + iniSet := TINIFile.Create(FileName); + except + on E: Exception do + begin + Exit; + end; + end; + + iniSet.WriteString('Connection','Host',Connection.Host); + iniSet.WriteInteger('Connection','Port',Connection.Port); + iniSet.WriteBool('Dumping','AutoStop',Dumping.AutoStop); + iniSet.WriteBool('Dumping','AutoCopy',Dumping.AutoCopy); + iniSet.WriteBool('Dumping','Verbose',Dumping.Verbose); + iniSet.WriteString('Breakpoints','Type',Breakpoint.LastType); + iniSet.WriteInteger('Window','Width',Window.Width); + iniSet.WriteInteger('Window','Height',Window.Height); + iniSet.WriteInteger('Window','LastTab',Window.LastTab); + iniSet.WriteInteger('Window','State',Integer(Window.State)); + + FreeAndNil(iniSet); + + Result := true; +end; + +end. diff --git a/src/Tool.pas b/src/Tool.pas new file mode 100644 index 0000000..7f89840 --- /dev/null +++ b/src/Tool.pas @@ -0,0 +1,15 @@ +unit Tool; + +interface +uses StdCtrls; +type TTool = record + Name:String; + WinClass:String; + WinText:String; + Load:Boolean; + Handle:Cardinal; +end; + +implementation + +end. diff --git a/src/XBOXManager.pas b/src/XBOXManager.pas new file mode 100644 index 0000000..22e00e5 --- /dev/null +++ b/src/XBOXManager.pas @@ -0,0 +1,194 @@ +unit XBOXManager; + +interface + +uses Classes,SysUtils,IdTCPClient; + +type TRegisters = record + EBP,ESP,EIP,EAX,EBX,ECX,EDX,EDI,ESI,EFlags,Cr0NpxState:Cardinal; +end; + +type TMemSection = record + Offset,Size,Flags,Loc:Cardinal; +end; + +//type TBPTypes = (Read,Write,Addr,Execute); + { +type + TBreakpoint = record + Enabled:Boolean; + Offset: Cardinal; + Size: Cardinal; + BPType:TBPTypes; + Desc: String; +end; } + +type TXBOXMemManage = class + Sections: array of TMemSection; + Buffer:TMemoryStream; + constructor Create(); + destructor Free(); +end; + +type TXBOXBreakpointManage = class +// Item: array of TBreakpoint; +// function Add(); +// function Delete(); +// function IndexOf(); +end; + +type TXBE = record + Name:String; + TimeStamp:Cardinal; +end; + +type PIdTCPClient = ^TIdTCPClient; + +type TXBOX = class + Registers:TRegisters; + Memory:TXBOXMemManage; + XBE:TXBE; + Link:PIdTCPClient; + NotifyPort:Cardinal; + + function Reboot(Flags:Cardinal;Title:String):Boolean; + function SendCmd(Cmd:String):Boolean; + function IsConnected():Boolean; + function Disconnect():Boolean; + function Connect(Port:Cardinal):Boolean; + function Notify(Port:Cardinal;Drop:Boolean):Boolean; + constructor Create(TCPCon:PIdTCPClient); + destructor Free(); + +const + rbWait = $00000001; + rbStop = $00000002; + rbWarm = $00000004; + rbNoDebug = $00000008; +end; + +implementation + +constructor TXBOXMemManage.Create; +begin + Buffer := TMemoryStream.Create; +end; + +destructor TXBOXMemManage.Free; +begin + Buffer.Free; + Buffer := nil; +end; + + + +function TXBOX.IsConnected():Boolean; +begin + Result := Link.Connected; +end; + +function TXBOX.Connect(Port:Cardinal):Boolean; +begin + if not IsConnected then + begin + Result := false; + Exit; + end; + + Result := true; + + SendCmd('DEBUGGER CONNECT'); + if(Port > 0) then Notify(Port,false); +end; + +function TXBOX.Disconnect():Boolean; +begin + if not IsConnected then + begin + Result := false; + Exit; + end; + + if NotifyPort > 0 then Notify(NotifyPort,true); + + SendCmd('DEBUGGER DISCONNECT'); + Result := SendCmd('BYE'); +end; + +function TXBOX.Notify(Port:Cardinal;Drop:Boolean):Boolean; +var +Send:String; +begin + if (Port = 0) or (not IsConnected) then + begin + Result := false; + Exit; + end; + + Send := 'NOTIFYAT PORT=' + IntToStr(Port); + + if Drop then + begin + Send := Send + ' DROP'; + NotifyPort := 0; + end + else + begin + NotifyPort := Port; + end; + + Result := SendCmd(Send); +end; + +function TXBOX.Reboot(Flags:Cardinal;Title:String):Boolean; +var +Style,Send:String; +begin + if ((Flags and rbWarm) = rbWarm) then + Style := ' WARM'; + + if ((Flags and rbWait) = rbWait) then + Style := Style + ' WAIT' + else if ((Flags and rbStop) = rbStop) then + Style := Style + ' STOP'; + + if(Title = '') then + begin + if ((Flags and rbNoDebug) = rbNoDebug) then + Style := Style + ' NODEBUG'; + Send := Format('REBOOT%s',[Style]); + end + else + begin + if ((Flags and rbNoDebug) <> rbNoDebug) then + Style := Style + ' DEBUG'; + Send := Format('magicboot title=%s%s',[Title,Style]); + end; + Result := SendCmd(Send); +end; + +function TXBOX.SendCmd(Cmd:String):Boolean; +begin + if(not Link.Connected) then + begin + Result := false; + Exit; + end; + + Link.IOHandler.WriteLn(Cmd); + Result := true; +end; + +constructor TXBOX.Create(TCPCon:PIdTCPClient); +begin + Memory := TXBOXMemManage.Create; + Link := TCPCon; +end; + +destructor TXBOX.Free; +begin + Memory.Free; + Memory := nil; +end; + +end.