├── .gitattributes ├── .gitignore ├── HexEditControl.Tests ├── DataRangeTests.cs ├── EditTests.cs ├── Extensions.cs ├── Helpers.cs ├── HexEditControl.Tests.csproj ├── Properties │ └── AssemblyInfo.cs └── app.config ├── HexEditControl ├── ByteBuffer.cs ├── ByteRange.cs ├── Commands │ ├── AddBulkTextCommand.cs │ ├── AddByteCommand.cs │ ├── DeleteBulkTextCommand.cs │ └── HexEditCommandBase.cs ├── DataRange.cs ├── FileRange.cs ├── HexEdit.Commands.cs ├── HexEdit.Prperties.cs ├── HexEdit.xaml ├── HexEdit.xaml.cs ├── IHexEdit.cs ├── OffsetAndHeight.cs ├── OffsetRange.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── Range.cs ├── Zodiacon.HexEditControl.csproj ├── app.config └── packages.config ├── HexStudio.sln ├── HexStudio ├── App.config ├── App.xaml ├── App.xaml.cs ├── ByteFinder.cs ├── Constants.cs ├── Controls │ ├── EditChange.cs │ ├── HexEdit.xaml │ └── HexEdit.xaml.cs ├── Converters │ ├── OverwriteToTextConverter.cs │ └── RgbColorToBrushConverter.cs ├── HexStudio.csproj ├── Icons │ ├── app.ico │ ├── close.ico │ ├── copy.ico │ ├── cut.ico │ ├── delete.ico │ ├── file.ico │ ├── file_new.ico │ ├── find.ico │ ├── open.ico │ ├── paste.ico │ ├── recycle.ico │ ├── redo.ico │ ├── save.ico │ ├── save_as.ico │ ├── selection_delete.ico │ └── undo.ico ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Models │ └── RgbColor.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── Resources │ └── Templates.xaml ├── Settings.cs ├── TabControlProperties.cs ├── ViewModels │ ├── FindDialogViewModel.cs │ ├── MainViewModel.cs │ └── OpenFileViewModel.cs ├── Views │ ├── FindDialogView.xaml │ ├── FindDialogView.xaml.cs │ ├── GenericWindow.xaml │ ├── GenericWindow.xaml.cs │ ├── MainView.xaml │ ├── MainView.xaml.cs │ ├── OpenFileView.xaml │ └── OpenFileView.xaml.cs └── packages.config └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | [Ss]tyle[Cc]op.* 182 | ~$* 183 | *~ 184 | *.dbmdl 185 | *.dbproj.schemaview 186 | *.pfx 187 | *.publishsettings 188 | node_modules/ 189 | orleans.codegen.cs 190 | 191 | # RIA/Silverlight projects 192 | Generated_Code/ 193 | 194 | # Backup & report files from converting an old project file 195 | # to a newer Visual Studio version. Backup files are not needed, 196 | # because we have git ;-) 197 | _UpgradeReport_Files/ 198 | Backup*/ 199 | UpgradeLog*.XML 200 | UpgradeLog*.htm 201 | 202 | # SQL Server files 203 | *.mdf 204 | *.ldf 205 | 206 | # Business Intelligence projects 207 | *.rdl.data 208 | *.bim.layout 209 | *.bim_*.settings 210 | 211 | # Microsoft Fakes 212 | FakesAssemblies/ 213 | 214 | # GhostDoc plugin setting file 215 | *.GhostDoc.xml 216 | 217 | # Node.js Tools for Visual Studio 218 | .ntvs_analysis.dat 219 | 220 | # Visual Studio 6 build log 221 | *.plg 222 | 223 | # Visual Studio 6 workspace options file 224 | *.opt 225 | 226 | # Visual Studio LightSwitch build output 227 | **/*.HTMLClient/GeneratedArtifacts 228 | **/*.DesktopClient/GeneratedArtifacts 229 | **/*.DesktopClient/ModelManifest.xml 230 | **/*.Server/GeneratedArtifacts 231 | **/*.Server/ModelManifest.xml 232 | _Pvt_Extensions 233 | 234 | # LightSwitch generated files 235 | GeneratedArtifacts/ 236 | ModelManifest.xml 237 | 238 | # Paket dependency manager 239 | .paket/paket.exe 240 | 241 | # FAKE - F# Make 242 | .fake/ 243 | -------------------------------------------------------------------------------- /HexEditControl.Tests/DataRangeTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Zodiacon.HexEditControl; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | namespace HexEditControl.Tests { 8 | [TestClass] 9 | public class DataRangeTests { 10 | [TestMethod] 11 | public void TestOverwrite() { 12 | var bytes = Helpers.CreateByteArray(100); 13 | var filename = Path.GetTempPath() + "test.dat"; 14 | File.WriteAllBytes(filename, bytes); 15 | using (var buffer = new ByteBuffer(filename)) { 16 | 17 | var dr1 = new ByteRange(20, new byte[] { 65, 66, 67, 68 }); 18 | buffer.Overwrite(dr1); 19 | 20 | var dr2 = new ByteRange(18, new byte[] { 65, 66, 67, 68, 69, 3, 4, 5, 6, 7, 8 }); 21 | buffer.Overwrite(dr2); 22 | 23 | var dr3 = new ByteRange(50, new byte[] { 65, 66, 67, 68 }); 24 | buffer.Overwrite(dr3); 25 | 26 | var dr4 = new ByteRange(48, new byte[] { 65, 66, 67, 68 }); 27 | buffer.Overwrite(dr4); 28 | 29 | var dr5 = new ByteRange(50, new byte[] { 11, 33, 37, 5, 5, 6, 7, 8, 9 }); 30 | buffer.Overwrite(dr5); 31 | 32 | foreach (var dr in buffer.DataRanges) 33 | Debug.WriteLine(dr); 34 | 35 | Assert.IsTrue(buffer.DataRanges.Count() == 6); 36 | Assert.IsTrue(buffer.Size == 100); 37 | } 38 | } 39 | 40 | [TestMethod] 41 | public void TestInsert() { 42 | var bytes = Helpers.CreateByteArray(100); 43 | var filename = Path.GetTempPath() + "test.dat"; 44 | File.WriteAllBytes(filename, bytes); 45 | using (var buffer = new ByteBuffer(filename)) { 46 | 47 | var dr1 = new ByteRange(20, new byte[] { 65, 66, 67, 68 }); 48 | buffer.Insert(dr1); 49 | 50 | var dr2 = new ByteRange(50, new byte[] { 65, 66, 67, 68, 80 }); 51 | buffer.Insert(dr2); 52 | 53 | var dr3 = new ByteRange(48, new byte[] { 65, 66, 67, 68, 77, 33, 55 }); 54 | buffer.Insert(dr3); 55 | 56 | Assert.IsTrue(buffer.Size == 116); 57 | } 58 | } 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /HexEditControl.Tests/EditTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace HexEditControl.Tests { 5 | [TestClass] 6 | public class EditTests { 7 | [TestMethod] 8 | public void TestMethod1() { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /HexEditControl.Tests/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace HexEditControl.Tests { 9 | static class Extensions { 10 | public static int Hash(this byte[] bytes, int index = 0, int size = 0) { 11 | if (size == 0) 12 | size = bytes.Length; 13 | 14 | int hash = size; 15 | for (int i = index; i < size + index; i++) 16 | hash = unchecked(hash * 17 + bytes[i]); 17 | return hash; 18 | } 19 | 20 | public static byte[] InsertBytes(this byte[] bytes, int index, params byte[] data) { 21 | Debug.Assert(data != null); 22 | Array.Resize(ref bytes, bytes.Length + data.Length); 23 | Array.Copy(bytes, index, bytes, index + data.Length, bytes.Length - index - data.Length); 24 | Array.Copy(data, 0, bytes, index, data.Length); 25 | 26 | return bytes; 27 | } 28 | 29 | public static byte[] ReplaceBytes(this byte[] bytes, int index, params byte[] data) { 30 | Debug.Assert(data != null); 31 | 32 | Array.Copy(data, 0, bytes, index, data.Length); 33 | 34 | return bytes; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /HexEditControl.Tests/Helpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace HexEditControl.Tests { 8 | static class Helpers { 9 | public static byte[] CreateByteArray(int size) { 10 | return Enumerable.Range(0, size).Select(i => (byte)(65 + i % 26)).ToArray(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /HexEditControl.Tests/HexEditControl.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {5E43DD3C-BF88-4EA5-B5D7-E9D212D11DA6} 7 | Library 8 | Properties 9 | HexEditControl.Tests 10 | HexEditControl.Tests 11 | v4.5.2 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | False 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | {2b53ab2b-dad7-4794-9f36-50e6004b5806} 63 | Zodiacon.HexEditControl 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | False 74 | 75 | 76 | False 77 | 78 | 79 | False 80 | 81 | 82 | False 83 | 84 | 85 | 86 | 87 | 88 | 89 | 96 | -------------------------------------------------------------------------------- /HexEditControl.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("HexEditControl.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("HexEditControl.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("5e43dd3c-bf88-4ea5-b5d7-e9d212d11da6")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /HexEditControl.Tests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /HexEditControl/ByteBuffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.IO.MemoryMappedFiles; 6 | using System.Linq; 7 | 8 | namespace Zodiacon.HexEditControl { 9 | public sealed class ByteBuffer : IDisposable { 10 | MemoryMappedViewAccessor _accessor; 11 | MemoryMappedFile _memFile; 12 | string _filename; 13 | SortedList _dataRanges = new SortedList(64); 14 | 15 | public long Size { get; private set; } 16 | 17 | public event Action SizeChanged; 18 | 19 | public bool IsReadOnly { get; private set; } 20 | 21 | void OnSizeChanged(long oldSize) { 22 | SizeChanged?.Invoke(oldSize, Size); 23 | } 24 | 25 | public ByteBuffer(string filename) { 26 | Open(filename); 27 | } 28 | 29 | void Open(string filename) { 30 | _filename = filename; 31 | Size = new FileInfo(filename).Length; 32 | try { 33 | _memFile = MemoryMappedFile.CreateFromFile(filename); 34 | } 35 | catch (UnauthorizedAccessException) { 36 | _memFile = MemoryMappedFile.CreateFromFile(filename, FileMode.Open, null, 0, MemoryMappedFileAccess.Read); 37 | IsReadOnly = true; 38 | } 39 | _accessor = _memFile.CreateViewAccessor(0, 0, IsReadOnly ? MemoryMappedFileAccess.Read : MemoryMappedFileAccess.ReadWrite); 40 | _dataRanges.Clear(); 41 | _dataRanges.Add(0, new FileRange(Range.FromStartAndCount(0, Size), 0, _accessor)); 42 | OnSizeChanged(0); 43 | } 44 | 45 | public ByteBuffer(long size, long limit) { 46 | _memFile = MemoryMappedFile.CreateNew(null, limit); 47 | _accessor = _memFile.CreateViewAccessor(); 48 | Size = size; 49 | } 50 | 51 | public void SetData(byte[] data, long limit = 1 << 20) { 52 | Dispose(); 53 | _memFile = MemoryMappedFile.CreateNew(null, limit); 54 | _accessor = _memFile.CreateViewAccessor(); 55 | Size = data.Length; 56 | _dataRanges.Clear(); 57 | _accessor.WriteArray(0, data, 0, data.Length); 58 | _dataRanges.Add(0, new FileRange(Range.FromStartAndCount(0, data.Length), 0, _accessor)); 59 | } 60 | 61 | public ByteBuffer(byte[] buffer) { 62 | SetData(buffer); 63 | } 64 | 65 | public void ApplyChanges() { 66 | Dispose(); 67 | long fileSize = new FileInfo(_filename).Length; 68 | _memFile = MemoryMappedFile.CreateFromFile(_filename, FileMode.Open, null, Math.Max(Size, fileSize)); 69 | _accessor = _memFile.CreateViewAccessor(); 70 | 71 | foreach (var dr in _dataRanges.Values.OfType()) { 72 | dr.WriteData(dr.Start, _accessor); 73 | } 74 | foreach (var dr in _dataRanges.Values.OfType()) { 75 | dr.WriteData(dr.Start, _accessor); 76 | } 77 | 78 | if (fileSize > Size) { 79 | Dispose(); 80 | using (var stm = File.OpenWrite(_filename)) 81 | stm.SetLength(Size); 82 | File.SetLastWriteTime(_filename, DateTime.Now); 83 | _memFile = MemoryMappedFile.CreateFromFile(_filename, FileMode.Open, null, 0); 84 | _accessor = _memFile.CreateViewAccessor(); 85 | } 86 | 87 | DiscardChanges(); 88 | } 89 | 90 | public static int MoveBufferSize { get; set; } = 1 << 21; 91 | public IEnumerable DataRanges => _dataRanges.Select(item => item.Value); 92 | 93 | public void DiscardChanges() { 94 | if (_filename != null) { 95 | _dataRanges.Clear(); 96 | var oldSize = Size; 97 | Size = new FileInfo(_filename).Length; 98 | OnSizeChanged(oldSize); 99 | _dataRanges.Add(0, new FileRange(Range.FromStartAndCount(0, Size), 0, _accessor)); 100 | } 101 | } 102 | 103 | public void Dispose() { 104 | if (_accessor != null) { 105 | _accessor.Dispose(); 106 | _accessor = null; 107 | } 108 | if (_memFile != null) { 109 | _memFile.Dispose(); 110 | _memFile = null; 111 | } 112 | 113 | } 114 | 115 | public void SaveToFile(string filename) { 116 | if (string.IsNullOrEmpty(_filename)) { 117 | // new file, just get everything out 118 | 119 | byte[] bytes = new byte[Size]; 120 | GetBytes(0, (int)Size, bytes); 121 | File.WriteAllBytes(filename, bytes); 122 | Open(filename); 123 | } 124 | else { 125 | Dispose(); 126 | File.Copy(_filename, filename, true); 127 | _filename = filename; 128 | ApplyChanges(); 129 | } 130 | } 131 | 132 | public int GetBytes(long start, int count, byte[] buffer, IList changes = null) { 133 | if (start + count > Size) 134 | count = (int)(Size - start); 135 | 136 | int index = 0; 137 | bool first = true; 138 | foreach (var dr in _dataRanges.Values) { 139 | if (dr.Start > start) 140 | break; 141 | 142 | if (dr.End < start) 143 | continue; 144 | 145 | int n; 146 | if (first) { 147 | n = (int)Math.Min(dr.End - start + 1, count); 148 | dr.GetData((int)(start - dr.Start), buffer, index, n); 149 | first = false; 150 | } 151 | else { 152 | Debug.Assert(dr.Start == start); 153 | n = (int)Math.Min(count, dr.Count); 154 | if (n == 0) 155 | break; 156 | dr.GetData(0, buffer, index, n); 157 | } 158 | if (changes != null && dr is ByteRange) 159 | changes.Add(new OffsetRange(start, n)); 160 | 161 | index += n; 162 | start += n; 163 | count -= n; 164 | } 165 | return index; 166 | } 167 | 168 | public void Overwrite(ByteRange change) { 169 | DataRange dr; 170 | if (_dataRanges.TryGetValue(change.Start, out dr) && change.Count == dr.Count) { 171 | // just replace 172 | _dataRanges.Remove(change.Start); 173 | _dataRanges.Add(change.Start, change); 174 | return; 175 | } 176 | 177 | var ranges = _dataRanges.Values; 178 | int index = -1; 179 | 180 | for (int i = 0; i < ranges.Count; i++) { 181 | dr = ranges[i]; 182 | 183 | // are we off the grid? 184 | if (change.End < dr.Start) 185 | break; 186 | 187 | // skip ranges eariler than the change 188 | if (change.Start > dr.End) 189 | continue; 190 | 191 | if (index < 0) 192 | index = i; 193 | if (change.Range.ContainsEntirely(dr.Range)) { 194 | // range can be removed 195 | _dataRanges.RemoveAt(i); 196 | i--; 197 | continue; 198 | } 199 | } 200 | if (index < 0) 201 | return; 202 | 203 | if (index >= ranges.Count) { 204 | // add at the end 205 | _dataRanges.Add(change.Start, change); 206 | var oldSize = Size; 207 | Size = change.End + 1; 208 | OnSizeChanged(oldSize); 209 | return; 210 | } 211 | 212 | dr = ranges[index]; 213 | 214 | // some non trivial intersection 215 | var isec = change.Range.GetIntersection(dr.Range); 216 | var left = dr.GetSubRange(Range.FromStartToEnd(dr.Start, change.Start - 1)); 217 | var right = dr.GetSubRange(Range.FromStartToEnd(change.End + 1, dr.End)); 218 | 219 | var next = index < ranges.Count - 1 ? ranges[index + 1] : null; 220 | 221 | _dataRanges.RemoveAt(index); 222 | if (!left.Range.IsEmpty) 223 | _dataRanges.Add(left.Start, left); 224 | _dataRanges.Add(change.Start, change); 225 | if (change.End >= Size) { 226 | var oldSize = Size; 227 | Size = change.End + 1; 228 | OnSizeChanged(oldSize); 229 | } 230 | 231 | if (!right.Range.IsEmpty) 232 | _dataRanges.Add(right.Start, right); 233 | if (next != null) { 234 | // check next range for overlap 235 | var isec2 = change.Range.GetIntersection(next.Range); 236 | if (!isec2.IsEmpty) { 237 | right = next.GetSubRange(Range.FromStartToEnd(change.End + 1, next.End)); 238 | _dataRanges.Remove(next.Start); 239 | if (!right.IsEmpty) 240 | _dataRanges.Add(right.Start, right); 241 | } 242 | } 243 | } 244 | 245 | public void Insert(ByteRange change) { 246 | // find first affected range 247 | var ranges = _dataRanges.Values; 248 | DataRange dr = null; 249 | 250 | int i = 0; 251 | for (; i < ranges.Count; i++) { 252 | dr = ranges[i]; 253 | if (dr.Range.Contains(change.Start)) 254 | break; 255 | } 256 | if (i == ranges.Count) { 257 | // just add the change 258 | Debug.Assert(change.Start == Size); 259 | _dataRanges.Add(change.Start, change); 260 | var oldSize = Size; 261 | Size = change.End + 1; 262 | OnSizeChanged(oldSize); 263 | } 264 | else { 265 | // split current 266 | var left = dr.GetSubRange(Range.FromStartToEnd(dr.Start, change.Start - 1)); 267 | var right = dr.GetSubRange(Range.FromStartToEnd(change.Start, dr.End)); 268 | 269 | _dataRanges.Remove(dr.Start); 270 | i--; 271 | 272 | //shift the rightmost ranges in reverse order to prevent accidental overlap 273 | ranges = _dataRanges.Values; 274 | 275 | for (int j = ranges.Count - 1; j > i; --j) { 276 | dr = ranges[j]; 277 | _dataRanges.Remove(dr.Start); 278 | dr.Shift(change.Count); 279 | _dataRanges.Add(dr.Start, dr); 280 | } 281 | 282 | if (!left.Range.IsEmpty) 283 | _dataRanges.Add(left.Start, left); 284 | 285 | if (!right.Range.IsEmpty) { 286 | right.Shift(change.Count); 287 | _dataRanges.Add(right.Start, right); 288 | } 289 | 290 | // finally, insert the change 291 | _dataRanges.Add(change.Start, change); 292 | Size += change.Count; 293 | OnSizeChanged(Size - change.Count); 294 | } 295 | } 296 | 297 | public void Delete(Range range) { 298 | int i; 299 | DataRange exactRange; 300 | if ((i = _dataRanges.IndexOfKey(range.Start)) >= 0 && (exactRange = _dataRanges.Values[i]).Count == range.Count) { 301 | _dataRanges.RemoveAt(i); 302 | for (int j = i; j < _dataRanges.Count; ++j) { 303 | var dr1 = _dataRanges.Values[j]; 304 | dr1.Shift(-range.Count); 305 | _dataRanges.RemoveAt(j); 306 | _dataRanges.Add(dr1.Start, dr1); 307 | } 308 | Size -= range.Count; 309 | OnSizeChanged(Size + range.Count); 310 | return; 311 | } 312 | 313 | var ranges = _dataRanges.Values; 314 | for (i = ranges.Count - 1; i >= 0 && i < ranges.Count; --i) { 315 | var dr = ranges[i]; 316 | if (dr.Range.ContainsEntirely(range)) { 317 | // split dr into two ranges 318 | var left = dr.GetSubRange(Range.FromStartToEnd(dr.Start, range.Start - 1)); 319 | var right = dr.GetSubRange(Range.FromStartToEnd(range.End + 1, dr.End)); 320 | 321 | // remove range and replace with two other 322 | _dataRanges.RemoveAt(i); 323 | 324 | for (int j = _dataRanges.Count - 1; j >= i; --j) { 325 | var dr1 = _dataRanges.Values[j]; 326 | dr1.Shift(-range.Count); 327 | _dataRanges.RemoveAt(j); 328 | _dataRanges.Add(dr1.Start, dr1); 329 | } 330 | 331 | if (!left.Range.IsEmpty) 332 | _dataRanges.Add(left.Start, left); 333 | 334 | if (!right.Range.IsEmpty) { 335 | right.Shift(-range.Count); 336 | _dataRanges.Add(right.Start, right); 337 | } 338 | Size -= range.Count; 339 | OnSizeChanged(Size + range.Count); 340 | break; 341 | } 342 | if (range.ContainsEntirely(dr.Range)) { 343 | _dataRanges.RemoveAt(i); 344 | for (int j = i; j < _dataRanges.Count; j++) { 345 | var r = _dataRanges.Values[j]; 346 | _dataRanges.RemoveAt(j); 347 | r.Shift(-dr.Count); 348 | _dataRanges.Add(r.Start, r); 349 | } 350 | continue; 351 | } 352 | if (dr.Range.Intersects(range)) { 353 | // complex overlap 354 | var right = i < ranges.Count - 1 ? ranges[i + 1] : null; 355 | var left = i > 0 ? ranges[i - 1] : null; 356 | if (left == null && right == null) { 357 | _dataRanges.Clear(); 358 | var oldSize = Size; 359 | Size = 0; 360 | OnSizeChanged(oldSize); 361 | break; 362 | } 363 | 364 | if (left != null) { 365 | _dataRanges.Remove(left.Start); 366 | left = left.GetSubRange(Range.FromStartToEnd(left.Start, range.Start - 1)); 367 | } 368 | if (right != null) { 369 | _dataRanges.Remove(right.Start); 370 | right = right.GetSubRange(Range.FromStartToEnd(right.Start, range.End)); 371 | } 372 | 373 | if (!left.IsEmpty) { 374 | _dataRanges.Add(left.Start, left); 375 | } 376 | if (!right.IsEmpty) { 377 | _dataRanges.Add(right.Start, right); 378 | } 379 | } 380 | } 381 | } 382 | 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /HexEditControl/ByteRange.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO.MemoryMappedFiles; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Zodiacon.HexEditControl { 10 | [DebuggerDisplay("{Range} (Byte)")] 11 | public class ByteRange : DataRange { 12 | byte[] Data; 13 | 14 | public ByteRange(long offset, params byte[] data) : base(Range.FromStartAndCount(offset, data.Length)) { 15 | Data = data; 16 | } 17 | 18 | public ByteRange(long offset) : base(Range.FromStartAndCount(offset, 0)) { 19 | Data = new byte[8]; 20 | } 21 | 22 | public void SetData(int index, byte data) { 23 | Data[index] = data; 24 | } 25 | 26 | public void AddData(byte data) { 27 | if (Count >= Data.Length) 28 | Array.Resize(ref Data, (int)Count * 2); 29 | Data[Count] = data; 30 | Range = Range.FromStartAndCount(Start, Count + 1); 31 | } 32 | 33 | public override DataRange GetSubRange(Range range) { 34 | if (range.IsEmpty) 35 | return EmptyDataRange.Instance; 36 | 37 | var isec = range.GetIntersection(Range); 38 | if (isec.IsEmpty) 39 | return EmptyDataRange.Instance; 40 | 41 | return new ByteRange(range.Start, Data.Skip((int)(isec.Start - Range.Start)).Take((int)isec.Count).ToArray()); 42 | } 43 | 44 | public override string ToString() { 45 | return $"{{{Range}}} ({Count}) (Byte)"; 46 | } 47 | 48 | public override void Shift(long offset) { 49 | Range = Range.Offset(offset); 50 | } 51 | 52 | public override void GetData(int srcIndex, byte[] buffer, int dstIndex, int count) { 53 | Buffer.BlockCopy(Data, srcIndex, buffer, dstIndex, count); 54 | } 55 | 56 | public override void WriteData(long position, MemoryMappedViewAccessor accessor) { 57 | accessor.WriteArray(Start, Data, 0, (int)Count); 58 | } 59 | 60 | public void SwapLastBytes(int n) { 61 | switch (n) { 62 | case 8: 63 | SwapBytes(); 64 | SwapWords(); 65 | ulong value = ((ulong)BitConverter.ToUInt32(Data, (int)Count - 8) << 32) | BitConverter.ToUInt32(Data, (int)Count - 4); 66 | Array.Copy(BitConverter.GetBytes(value), 0, Data, Count - 8, 8); 67 | break; 68 | 69 | case 4: 70 | SwapBytes(); 71 | SwapWords(); 72 | break; 73 | 74 | case 2: 75 | SwapBytes(); 76 | break; 77 | } 78 | } 79 | 80 | private void SwapWords() { 81 | uint value = ((uint)BitConverter.ToUInt16(Data, (int)Count - 4) << 16) | BitConverter.ToUInt16(Data, (int)Count - 2); 82 | Array.Copy(BitConverter.GetBytes(value), 0, Data, Count - 4, 4); 83 | } 84 | 85 | private void SwapBytes() { 86 | var temp = Data[Count - 1]; 87 | Data[Count - 1] = Data[Count - 2]; 88 | Data[Count - 2] = temp; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /HexEditControl/Commands/AddBulkTextCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Zodiacon.HexEditControl.Commands { 8 | class AddBulkTextCommand : HexEditCommandBase { 9 | ByteRange _data; 10 | bool _overwrite; 11 | 12 | public AddBulkTextCommand(HexEdit hexEdit, ByteRange data, bool overwrite) : base(hexEdit) { 13 | _data = data; 14 | _overwrite = overwrite; 15 | } 16 | 17 | public override void Execute() { 18 | if (_overwrite) 19 | HexEdit.Buffer.Overwrite(_data); 20 | else 21 | HexEdit.Buffer.Insert(_data); 22 | Invalidate(); 23 | } 24 | 25 | public override void Undo() { 26 | HexEdit.Buffer.Delete(_data.Range); 27 | Invalidate(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /HexEditControl/Commands/AddByteCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Zodiacon.WPF; 8 | 9 | namespace Zodiacon.HexEditControl.Commands { 10 | class AddByteCommand : HexEditCommandBase { 11 | public bool Overwrite { get; } 12 | byte _data; 13 | byte[] _oldData = new byte[1]; 14 | int _inputIndex, _wordIndex, _wordSize; 15 | long _offset; 16 | 17 | 18 | public AddByteCommand(HexEdit hexEdit, long offset, byte data, bool overwrite, int inputIndex, int wordIndex, int wordSize) : base(hexEdit) { 19 | Overwrite = overwrite; 20 | _offset = offset; 21 | _data = data; 22 | hexEdit.Buffer.GetBytes(offset, 1, _oldData); 23 | _inputIndex = inputIndex; 24 | _wordIndex = wordIndex; 25 | _wordSize = wordSize; 26 | } 27 | 28 | public override void Execute() { 29 | var br = new ByteRange(_offset, _data); 30 | if (Overwrite) 31 | HexEdit.Buffer.Overwrite(br); 32 | else 33 | HexEdit.Buffer.Insert(br); 34 | 35 | HexEdit.InputIndex = _inputIndex; 36 | HexEdit.WordIndex = _wordIndex; 37 | if (_inputIndex == 1 && _wordIndex == _wordSize - 1) { 38 | HexEdit.CaretOffset = _offset + _wordSize; 39 | } 40 | 41 | Invalidate(); 42 | } 43 | 44 | public override void Undo() { 45 | var br = new ByteRange(_offset, _oldData[0]); 46 | if (Overwrite) 47 | HexEdit.Buffer.Overwrite(br); 48 | else { 49 | HexEdit.Buffer.Delete(br.Range); 50 | } 51 | 52 | HexEdit.InputIndex = _inputIndex; 53 | HexEdit.WordIndex = _wordIndex; 54 | if (_inputIndex == 1 && _wordIndex == _wordSize - 1) 55 | HexEdit.CaretOffset = _offset; 56 | 57 | Invalidate(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /HexEditControl/Commands/DeleteBulkTextCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Zodiacon.HexEditControl.Commands { 8 | class DeleteBulkTextCommand : HexEditCommandBase { 9 | ByteRange _byteRange; 10 | 11 | public DeleteBulkTextCommand(HexEdit hexEdit, Range range) : base(hexEdit) { 12 | var data = new byte[range.Count]; 13 | hexEdit.Buffer.GetBytes(range.Start, (int)range.Count, data); 14 | _byteRange = new ByteRange(range.Start, data); 15 | } 16 | 17 | public override void Execute() { 18 | HexEdit.Buffer.Delete(_byteRange.Range); 19 | HexEdit.CaretOffset = _byteRange.Start; 20 | Invalidate(); 21 | } 22 | 23 | public override void Undo() { 24 | HexEdit.Buffer.Insert(_byteRange); 25 | HexEdit.CaretOffset = _byteRange.End + 1; 26 | Invalidate(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /HexEditControl/Commands/HexEditCommandBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Zodiacon.WPF; 7 | 8 | namespace Zodiacon.HexEditControl.Commands { 9 | abstract class HexEditCommandBase : IAppCommand { 10 | public string Description { get; set; } 11 | 12 | public string Name { get; set; } 13 | public HexEdit HexEdit { get; } 14 | 15 | protected HexEditCommandBase(HexEdit hexEdit) { 16 | HexEdit = hexEdit; 17 | } 18 | 19 | protected virtual void Invalidate(bool modified = true) { 20 | if(modified) 21 | HexEdit.IsModified = true; 22 | HexEdit.InvalidateVisual(); 23 | } 24 | 25 | public abstract void Execute(); 26 | 27 | public abstract void Undo(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /HexEditControl/DataRange.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO.MemoryMappedFiles; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Zodiacon.HexEditControl { 10 | public abstract class DataRange { 11 | public Range Range { get; protected set; } 12 | 13 | public long Start => Range.Start; 14 | public long End => Range.End; 15 | public long Count => Range.Count; 16 | 17 | public abstract void GetData(int srcIndex, byte[] buffer, int dstIndex, int count); 18 | public abstract DataRange GetSubRange(Range range); 19 | 20 | public abstract void Shift(long offset); 21 | 22 | public abstract void WriteData(long position, MemoryMappedViewAccessor accessor); 23 | 24 | public bool IsEmpty => this == EmptyDataRange.Instance; 25 | 26 | protected DataRange(Range range) { 27 | Range = range; 28 | } 29 | } 30 | 31 | sealed class EmptyDataRange : DataRange { 32 | public static readonly DataRange Instance = new EmptyDataRange(); 33 | 34 | private EmptyDataRange() : base(Range.FromStartToEnd(-1, -2)) { 35 | } 36 | 37 | public override void GetData(int srcIndex, byte[] buffer, int dstIndex, int count) { 38 | throw new NotImplementedException(); 39 | } 40 | 41 | public override DataRange GetSubRange(Range range) { 42 | throw new NotImplementedException(); 43 | } 44 | 45 | public override void Shift(long offset) { 46 | throw new NotImplementedException(); 47 | } 48 | 49 | public override void WriteData(long position, MemoryMappedViewAccessor accessor) { 50 | throw new NotImplementedException(); 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /HexEditControl/FileRange.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO.MemoryMappedFiles; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Zodiacon.HexEditControl { 10 | [DebuggerDisplay("{Range} (File)")] 11 | public class FileRange : DataRange { 12 | MemoryMappedViewAccessor _accessor; 13 | 14 | public long Offset { get; } 15 | 16 | public long Size { get; } 17 | 18 | public long FileOffset { get; } 19 | 20 | public FileRange(Range range, long fileOffset, MemoryMappedViewAccessor accessor) : base(range) { 21 | FileOffset = fileOffset; 22 | _accessor = accessor; 23 | } 24 | 25 | public override DataRange GetSubRange(Range range) { 26 | if (range.IsEmpty) 27 | return EmptyDataRange.Instance; 28 | 29 | return new FileRange(range, FileOffset + (range.Start - Range.Start), _accessor); 30 | } 31 | 32 | public override string ToString() { 33 | return $"{{{Range}}} ({Count}) (File offset={FileOffset})"; 34 | } 35 | 36 | public override void Shift(long offset) { 37 | Range = Range.Offset(offset); 38 | } 39 | 40 | public override void GetData(int srcIndex, byte[] buffer, int dstIndex, int count) { 41 | _accessor.ReadArray(FileOffset + srcIndex, buffer, dstIndex, count); 42 | } 43 | 44 | static byte[] _moveBuffer; 45 | 46 | public override void WriteData(long position, MemoryMappedViewAccessor accessor) { 47 | if (position != FileOffset) { 48 | var count = Count; 49 | const int buffseSize = 1 << 21; 50 | if (_moveBuffer == null) 51 | _moveBuffer = new byte[buffseSize]; 52 | var start = FileOffset; 53 | while (count > 0) { 54 | int read = accessor.ReadArray(start, _moveBuffer, 0, (int)Math.Min(count, buffseSize)); 55 | accessor.WriteArray(position, _moveBuffer, 0, read); 56 | count -= read; 57 | position += read; 58 | start += read; 59 | } 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /HexEditControl/HexEdit.Commands.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Input; 4 | using Zodiacon.HexEditControl.Commands; 5 | using Zodiacon.WPF; 6 | 7 | namespace Zodiacon.HexEditControl { 8 | partial class HexEdit { 9 | readonly AppCommandManager _commandManager = new AppCommandManager(); 10 | 11 | static void RegisterCommands() { 12 | CommandManager.RegisterClassCommandBinding(typeof(HexEdit), new CommandBinding(ApplicationCommands.SelectAll, 13 | (s, e) => ((HexEdit)s).ExecuteSelectAll(e))); 14 | CommandManager.RegisterClassCommandBinding(typeof(HexEdit), new CommandBinding(ApplicationCommands.Copy, 15 | (s, e) => ((HexEdit)s).ExecuteCopy(e), (s, e) => ((HexEdit)s).CanExecuteCopy(e))); 16 | CommandManager.RegisterClassCommandBinding(typeof(HexEdit), new CommandBinding(ApplicationCommands.Paste, 17 | (s, e) => ((HexEdit)s).ExecutePaste(e), (s, e) => ((HexEdit)s).CanExecutePaste(e))); 18 | CommandManager.RegisterClassCommandBinding(typeof(HexEdit), new CommandBinding(ApplicationCommands.Cut, 19 | (s, e) => ((HexEdit)s).ExecuteCut(e), (s, e) => ((HexEdit)s).CanExecuteCut(e))); 20 | CommandManager.RegisterClassCommandBinding(typeof(HexEdit), new CommandBinding(ApplicationCommands.Undo, 21 | (s, e) => ((HexEdit)s).ExecuteUndo(e), (s, e) => ((HexEdit)s).CanExecuteUndo(e))); 22 | CommandManager.RegisterClassCommandBinding(typeof(HexEdit), new CommandBinding(ApplicationCommands.Redo, 23 | (s, e) => ((HexEdit)s).ExecuteRedo(e), (s, e) => ((HexEdit)s).CanExecuteRedo(e))); 24 | CommandManager.RegisterClassCommandBinding(typeof(HexEdit), new CommandBinding(ApplicationCommands.Delete, 25 | (s, e) => ((HexEdit)s).ExecuteDelete(e), (s, e) => ((HexEdit)s).CanExecuteDelete(e))); 26 | } 27 | 28 | private void CanExecutePaste(CanExecuteRoutedEventArgs e) { 29 | e.CanExecute = Clipboard.ContainsData(DataFormats.Serializable); 30 | } 31 | 32 | private void CanExecuteDelete(CanExecuteRoutedEventArgs e) { 33 | e.CanExecute = !IsReadOnly && (SelectionLength > 0 || CaretOffset < _hexBuffer.Size - WordSize); 34 | } 35 | 36 | private void ExecuteDelete(ExecutedRoutedEventArgs e) { 37 | if (SelectionLength > 0) { 38 | AddCommand(new DeleteBulkTextCommand(this, Range.FromStartToEnd(SelectionStart, SelectionEnd))); 39 | ClearSelection(); 40 | } 41 | else { 42 | AddCommand(new DeleteBulkTextCommand(this, Range.FromStartAndCount(CaretOffset, WordSize))); 43 | } 44 | } 45 | 46 | void AddCommand(IAppCommand cmd, bool execute = true) { 47 | _commandManager.AddCommand(cmd); 48 | IsModified = _commandManager.CanUndo; 49 | InvalidateVisual(); 50 | } 51 | 52 | private void CanExecuteUndo(CanExecuteRoutedEventArgs e) { 53 | e.CanExecute = _commandManager.CanUndo; 54 | } 55 | 56 | private void ExecuteUndo(ExecutedRoutedEventArgs e) { 57 | _commandManager.Undo(); 58 | IsModified = _commandManager.CanUndo; 59 | } 60 | 61 | private void ExecuteRedo(ExecutedRoutedEventArgs e) { 62 | _commandManager.Redo(); 63 | IsModified = _commandManager.CanUndo; 64 | } 65 | 66 | private void CanExecuteRedo(CanExecuteRoutedEventArgs e) { 67 | e.CanExecute = _commandManager.CanRedo; 68 | } 69 | 70 | private void ExecutePaste(ExecutedRoutedEventArgs e) { 71 | var bytes = (byte[])Clipboard.GetData(DataFormats.Serializable); 72 | var br = new ByteRange(CaretOffset, bytes); 73 | 74 | var cmd = new AddBulkTextCommand(this, br, OverwriteMode); 75 | _commandManager.AddCommand(cmd); 76 | } 77 | 78 | private void CanExecuteCut(CanExecuteRoutedEventArgs e) { 79 | e.CanExecute = !IsReadOnly && SelectionLength > 0 && SelectionLength < 1 << 30; 80 | } 81 | 82 | private void CanExecuteCopy(CanExecuteRoutedEventArgs e) { 83 | e.CanExecute = SelectionLength > 0 && SelectionLength < 1 << 30; 84 | } 85 | 86 | private void ExecuteCopy(ExecutedRoutedEventArgs e) { 87 | try { 88 | var count = SelectionLength; 89 | var bytes = new byte[count]; 90 | _hexBuffer.GetBytes(SelectionStart, (int)count, bytes); 91 | var data = new DataObject(DataFormats.Serializable, bytes); 92 | data.SetText(FormatBytes(bytes, WordSize)); 93 | Clipboard.SetDataObject(data, true); 94 | } 95 | catch (OutOfMemoryException) { 96 | 97 | } 98 | } 99 | private void ExecuteCut(ExecutedRoutedEventArgs e) { 100 | ExecuteCopy(e); 101 | ExecuteDelete(e); 102 | } 103 | } 104 | } 105 | 106 | -------------------------------------------------------------------------------- /HexEditControl/HexEdit.Prperties.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using System.Windows; 5 | using System.Windows.Input; 6 | using System.Windows.Media; 7 | 8 | namespace Zodiacon.HexEditControl { 9 | partial class HexEdit { 10 | public bool OverwriteMode { 11 | get { return (bool)GetValue(OverwriteModeProperty); } 12 | set { SetValue(OverwriteModeProperty, value); } 13 | } 14 | 15 | public static readonly DependencyProperty OverwriteModeProperty = 16 | DependencyProperty.Register(nameof(OverwriteMode), typeof(bool), typeof(HexEdit), 17 | new PropertyMetadata(true, (s, e) => ((HexEdit)s).OnOverwriteModeChanged(e))); 18 | 19 | private void OnOverwriteModeChanged(DependencyPropertyChangedEventArgs e) { 20 | ClearChange(); 21 | UpdateCaretWidth(); 22 | } 23 | 24 | public static readonly RoutedEvent CaretPositionChangedEvent = 25 | EventManager.RegisterRoutedEvent(nameof(CaretPositionChanged), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(HexEdit)); 26 | 27 | public event RoutedEventHandler CaretPositionChanged { 28 | add { AddHandler(CaretPositionChangedEvent, value); } 29 | remove { RemoveHandler(CaretPositionChangedEvent, value); } 30 | } 31 | 32 | void UpdateCaretWidth() { 33 | _caret.Width = OverwriteMode ? (_charWidth < 1 ? 8 : _charWidth) : SystemParameters.CaretWidth; 34 | } 35 | 36 | public long SelectionLength => SelectionStart < 0 ? 0 : SelectionEnd - SelectionStart + WordSize; 37 | 38 | public Brush EditForeground { 39 | get { return (Brush)GetValue(EditForegroundProperty); } 40 | set { SetValue(EditForegroundProperty, value); } 41 | } 42 | 43 | public static readonly DependencyProperty EditForegroundProperty = 44 | DependencyProperty.Register(nameof(EditForeground), typeof(Brush), typeof(HexEdit), 45 | new FrameworkPropertyMetadata(Brushes.Red, FrameworkPropertyMetadataOptions.AffectsRender)); 46 | 47 | public long CaretOffset { 48 | get { return (long)GetValue(CaretOffsetProperty); } 49 | set { SetValue(CaretOffsetProperty, value); } 50 | } 51 | 52 | public static readonly DependencyProperty CaretOffsetProperty = 53 | DependencyProperty.Register(nameof(CaretOffset), typeof(long), typeof(HexEdit), 54 | new PropertyMetadata(0L, (s, e) => ((HexEdit)s).OnCaretOffsetChanged(e), (s, e) => ((HexEdit)s).CoerceCaretOffset(e))); 55 | 56 | private object CoerceCaretOffset(object value) { 57 | var offset = (long)value; 58 | if (offset < 0) 59 | offset = 0; 60 | else if (_sizeLimit > 0 && offset >= _sizeLimit) 61 | offset = _sizeLimit - 1; 62 | else if (_hexBuffer != null && offset > _hexBuffer.Size) 63 | offset = _hexBuffer.Size; 64 | return offset; 65 | } 66 | 67 | private void OnCaretOffsetChanged(DependencyPropertyChangedEventArgs e) { 68 | SetCaretPosition(CaretOffset); 69 | MakeVisible(CaretOffset); 70 | RaiseEvent(new RoutedEventArgs(CaretPositionChangedEvent)); 71 | } 72 | 73 | private void SetCaretPosition(long caretOffset) { 74 | var pt = GetPositionByOffset(caretOffset); 75 | _caretPosition.X = pt.X; 76 | _caretPosition.Y = pt.Y; 77 | } 78 | 79 | public long SelectionStart { 80 | get { return (long)GetValue(SelectionStartProperty); } 81 | set { SetValue(SelectionStartProperty, value); } 82 | } 83 | 84 | public static readonly DependencyProperty SelectionStartProperty = 85 | DependencyProperty.Register(nameof(SelectionStart), typeof(long), typeof(HexEdit), new PropertyMetadata(-1L)); 86 | 87 | 88 | public long SelectionEnd { 89 | get { return (long)GetValue(SelectionEndProperty); } 90 | set { SetValue(SelectionEndProperty, value); } 91 | } 92 | 93 | public static readonly DependencyProperty SelectionEndProperty = 94 | DependencyProperty.Register(nameof(SelectionEnd), typeof(long), typeof(HexEdit), new PropertyMetadata(-1L)); 95 | 96 | public int BytesPerLine { 97 | get { return (int)GetValue(BytesPerLineProperty); } 98 | set { SetValue(BytesPerLineProperty, value); } 99 | } 100 | 101 | public static readonly DependencyProperty BytesPerLineProperty = 102 | DependencyProperty.Register(nameof(BytesPerLine), typeof(int), typeof(HexEdit), 103 | new PropertyMetadata(32, (s, e) => ((HexEdit)s).Refresh()), ValidateBytesPerLine); 104 | 105 | private static bool ValidateBytesPerLine(object value) { 106 | var bytes = (int)value; 107 | if (bytes < 8 || bytes > 128) 108 | return false; 109 | return bytes % 8 == 0; 110 | } 111 | 112 | public double VerticalSpace { 113 | get { return (double)GetValue(VerticalSpaceProperty); } 114 | set { SetValue(VerticalSpaceProperty, value); } 115 | } 116 | 117 | public static readonly DependencyProperty VerticalSpaceProperty = 118 | DependencyProperty.Register(nameof(VerticalSpace), typeof(double), typeof(HexEdit), new PropertyMetadata(2.0, (s, e) => ((HexEdit)s).Refresh())); 119 | 120 | public int WordSize { 121 | get { return (int)GetValue(WordSizeProperty); } 122 | set { SetValue(WordSizeProperty, value); } 123 | } 124 | 125 | public static readonly DependencyProperty WordSizeProperty = 126 | DependencyProperty.Register(nameof(WordSize), typeof(int), typeof(HexEdit), new PropertyMetadata(1, 127 | (s, e) => ((HexEdit)s).Refresh()), ValidateWordSize); 128 | 129 | public Brush SelectionBackground { 130 | get { return (Brush)GetValue(SelectionBackgroundProperty); } 131 | set { SetValue(SelectionBackgroundProperty, value); } 132 | } 133 | 134 | public static readonly DependencyProperty SelectionBackgroundProperty = 135 | DependencyProperty.Register("SelectionBackground", typeof(Brush), typeof(HexEdit), 136 | new FrameworkPropertyMetadata(Brushes.Yellow, FrameworkPropertyMetadataOptions.AffectsRender)); 137 | 138 | 139 | public Brush SelectionForeground { 140 | get { return (Brush)GetValue(SelectionForegroundProperty); } 141 | set { SetValue(SelectionForegroundProperty, value); } 142 | } 143 | 144 | public static readonly DependencyProperty SelectionForegroundProperty = 145 | DependencyProperty.Register("SelectionForeground", typeof(Brush), typeof(HexEdit), 146 | new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender)); 147 | 148 | public bool IsReadOnly { 149 | get { return (bool)GetValue(IsReadOnlyProperty); } 150 | set { SetValue(IsReadOnlyProperty, value); } 151 | } 152 | 153 | public static readonly DependencyProperty IsReadOnlyProperty = 154 | DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(HexEdit), new PropertyMetadata(false)); 155 | 156 | public bool IsModified { 157 | get { return (bool)GetValue(IsModifiedProperty); } 158 | set { SetValue(IsModifiedProperty, value); } 159 | } 160 | 161 | public static readonly DependencyProperty IsModifiedProperty = 162 | DependencyProperty.Register(nameof(IsModified), typeof(bool), typeof(HexEdit), new PropertyMetadata(false)); 163 | 164 | public bool ShowOffset { 165 | get { return (bool)GetValue(ShowOffsetProperty); } 166 | set { SetValue(ShowOffsetProperty, value); } 167 | } 168 | 169 | public static readonly DependencyProperty ShowOffsetProperty = 170 | DependencyProperty.Register(nameof(ShowOffset), typeof(bool), typeof(HexEdit), 171 | new PropertyMetadata(true, (s, e) => ((HexEdit)s).Refresh())); 172 | 173 | 174 | private static bool ValidateWordSize(object value) { 175 | var wordSize = (int)value; 176 | return wordSize == 1 || wordSize == 2 || wordSize == 4 || wordSize == 8; 177 | } 178 | 179 | private string FormatBytes(byte[] bytes, int wordSize) { 180 | var sb = new StringBuilder((wordSize + 1) * bytes.Length); 181 | for (int i = 0; i < bytes.Length; i += wordSize) 182 | sb.Append(_bitConverters[_bitConverterIndex[wordSize]](bytes, i)).Append(" "); 183 | return sb.ToString(); 184 | } 185 | 186 | private void ExecuteSelectAll(ExecutedRoutedEventArgs e) { 187 | SelectionStart = 0; 188 | SelectionEnd = _hexBuffer.Size; 189 | InvalidateVisual(); 190 | } 191 | 192 | public static readonly DependencyProperty DataProperty = 193 | DependencyProperty.Register(nameof(Data), typeof(IHexEdit), typeof(HexEdit), new FrameworkPropertyMetadata(null)); 194 | 195 | public IHexEdit Data { 196 | get { return (IHexEdit)GetValue(DataProperty); } 197 | set { throw new InvalidOperationException(); } 198 | } 199 | 200 | public event Action BufferSizeChanged { 201 | add { Buffer.SizeChanged += value; } 202 | remove { Buffer.SizeChanged -= value; } 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /HexEditControl/HexEdit.xaml: -------------------------------------------------------------------------------- 1 |  9 | 12 | 13 | 22 | 23 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /HexEditControl/HexEdit.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Globalization; 5 | using System.IO; 6 | using System.IO.MemoryMappedFiles; 7 | using System.Linq; 8 | using System.Runtime.InteropServices; 9 | using System.Text; 10 | using System.Windows; 11 | using System.Windows.Data; 12 | using System.Windows.Input; 13 | using System.Windows.Media; 14 | using System.Windows.Threading; 15 | using Zodiacon.HexEditControl.Commands; 16 | using Zodiacon.WPF; 17 | 18 | namespace Zodiacon.HexEditControl { 19 | /// 20 | /// Interaction logic for HexEdit.xaml 21 | /// 22 | public partial class HexEdit : IHexEdit, IDisposable { 23 | long _sizeLimit; 24 | readonly DispatcherTimer _timer; 25 | double _hexDataXPos, _hexDataWidth; 26 | long _startOffset = -1, _endOffset = -1; 27 | double _charWidth; 28 | int _viewLines; 29 | 30 | ByteBuffer _hexBuffer; 31 | readonly Dictionary _offsetsPositions = new Dictionary(128); 32 | readonly Dictionary _verticalPositions = new Dictionary(128); 33 | 34 | public ByteBuffer Buffer => _hexBuffer; 35 | public long Size => Buffer.Size; 36 | 37 | public HexEdit() { 38 | InitializeComponent(); 39 | 40 | _timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(.5) }; 41 | 42 | // create new document by default 43 | CreateNew(); 44 | 45 | Loaded += delegate { 46 | SetValue(DataProperty, this); 47 | _root.Focus(); 48 | 49 | _timer.Tick += _timer_Tick; 50 | _timer.Start(); 51 | SetCaretPosition(CaretOffset); 52 | UpdateCaretWidth(); 53 | }; 54 | } 55 | 56 | static HexEdit() { 57 | RegisterCommands(); 58 | } 59 | 60 | private void _timer_Tick(object sender, EventArgs e) { 61 | if (CaretOffset < 0 && _hexBuffer.Size > 0) 62 | _caret.Visibility = Visibility.Collapsed; 63 | else 64 | _caret.Visibility = _caret.Visibility == Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed; 65 | } 66 | 67 | Func[] _bitConverters = { 68 | (bytes, i) => bytes[i].ToString("X2"), 69 | (bytes, i) => BitConverter.ToUInt16(bytes, i).ToString("X4"), 70 | (bytes, i) => BitConverter.ToUInt32(bytes, i).ToString("X8"), 71 | (bytes, i) => BitConverter.ToUInt64(bytes, i).ToString("X16"), 72 | }; 73 | 74 | static int[] _bitConverterIndex = { 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3 }; 75 | 76 | private void Refresh() { 77 | Recalculate(); 78 | if (CaretOffset >= 0) { 79 | ClearChange(); 80 | CaretOffset -= CaretOffset % WordSize; 81 | SetCaretPosition(CaretOffset); 82 | MakeVisible(CaretOffset); 83 | } 84 | InvalidateVisual(); 85 | } 86 | 87 | public void CreateNew(long sizeLimit = 1 << 20) { 88 | Dispose(); 89 | if (_hexBuffer != null) 90 | _hexBuffer.SizeChanged -= _hexBuffer_SizeChanged; 91 | 92 | _hexBuffer = new ByteBuffer(0, _sizeLimit = sizeLimit); 93 | _hexBuffer.SizeChanged += _hexBuffer_SizeChanged; 94 | CaretOffset = 0; 95 | } 96 | 97 | private void _hexBuffer_SizeChanged(long oldSize, long newSize) { 98 | if (Math.Abs(oldSize - newSize) > BytesPerLine || newSize % BytesPerLine == 0) { 99 | Recalculate(); 100 | } 101 | } 102 | 103 | public void OpenFile(string filename) { 104 | if (string.IsNullOrWhiteSpace(filename)) 105 | throw new ArgumentException("Filename is empty or null", nameof(filename)); 106 | 107 | Dispose(); 108 | 109 | if (_hexBuffer != null) 110 | _hexBuffer.SizeChanged -= _hexBuffer_SizeChanged; 111 | 112 | _sizeLimit = 0; 113 | _hexBuffer = new ByteBuffer(filename); 114 | IsReadOnly = _hexBuffer.IsReadOnly; 115 | _hexBuffer.SizeChanged += _hexBuffer_SizeChanged; 116 | Refresh(); 117 | } 118 | 119 | private void Recalculate() { 120 | _scroll.ViewportSize = ActualHeight; 121 | _scroll.Maximum = (_hexBuffer.Size / BytesPerLine + 1) * (FontSize + VerticalSpace) - ActualHeight + VerticalSpace * 2; 122 | } 123 | 124 | private void _scroll_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) { 125 | InvalidateVisual(); 126 | } 127 | 128 | byte[] _readBuffer = new byte[1 << 16]; 129 | StringBuilder _hexString = new StringBuilder(256); // hex string 130 | StringBuilder _hexChangesString = new StringBuilder(256); // changes string 131 | 132 | protected override void OnRender(DrawingContext dc) { 133 | if (_hexBuffer == null || ActualHeight < 5) return; 134 | 135 | dc.PushClip(new RectangleGeometry(new Rect(0, 0, ActualWidth, ActualHeight))); 136 | 137 | var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch); 138 | int lineHeight = (int)(FontSize + VerticalSpace); 139 | var y = -_scroll.Value; 140 | var viewport = _scroll.ViewportSize; 141 | long start = (long)(BytesPerLine * (0 - VerticalSpace - y) / lineHeight); 142 | 143 | if (start < 0) 144 | start = 0; 145 | else 146 | start = start / BytesPerLine * BytesPerLine; 147 | long end = start + BytesPerLine * ((long)viewport / lineHeight + 1); 148 | if (end > _hexBuffer.Size) 149 | end = _hexBuffer.Size; 150 | 151 | var maxWidth = 0.0; 152 | bool empty = _hexBuffer.Size == 0; 153 | 154 | if (ShowOffset) { 155 | for (long i = start; i < end || empty; i += BytesPerLine) { 156 | var pos = 2 + (i / BytesPerLine) * lineHeight + y; 157 | var text = new FormattedText(i.ToString("X8") + ": ", CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground); 158 | if (text.Width > maxWidth) 159 | maxWidth = text.Width; 160 | dc.DrawText(text, new Point(2, pos)); 161 | if (empty) 162 | break; 163 | } 164 | } 165 | 166 | var x = maxWidth + 8; 167 | 168 | int readSize = (int)(end - start + 1 + 7); 169 | 170 | List changes = new List(); 171 | 172 | var read = _hexBuffer.GetBytes(start, readSize, _readBuffer, changes); 173 | if (start + read < end) 174 | end = start + read; 175 | 176 | _hexDataXPos = x; 177 | 178 | var singleChar = new FormattedText("8", CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground); 179 | 180 | maxWidth = 0; 181 | _offsetsPositions.Clear(); 182 | _verticalPositions.Clear(); 183 | 184 | var bitConverter = _bitConverters[_bitConverterIndex[WordSize]]; 185 | _viewLines = 0; 186 | var space = new string(' ', WordSize * 2 + 1); 187 | int len = 0; 188 | 189 | int readIndex = 0; 190 | for (long i = start; i < end; i += BytesPerLine) { 191 | var pos = 2 + (i / BytesPerLine) * lineHeight + y; 192 | _hexString.Clear(); 193 | _hexChangesString.Clear(); 194 | 195 | for (var j = i; j < i + BytesPerLine && j < end; j += WordSize) { 196 | int bufIndex = (int)(j - start); 197 | 198 | if (j >= SelectionStart && j <= SelectionEnd) { 199 | // j is selected 200 | dc.DrawRectangle(SelectionBackground, null, new Rect(x + (j - i) / WordSize * (2 * WordSize + 1) * _charWidth, pos, _charWidth * WordSize * 2, FontSize)); 201 | } 202 | 203 | var changed = changes.Any(change => j >= change.Offset && j < change.Offset + change.Count); 204 | if (changed) { 205 | _hexChangesString.Append(bitConverter(_readBuffer, bufIndex)).Append(" "); 206 | _hexString.Append(space); 207 | } 208 | else { 209 | _hexString.Append(bitConverter(_readBuffer, bufIndex + readIndex)).Append(" "); 210 | _hexChangesString.Append(space); 211 | } 212 | } 213 | 214 | var pt = new Point(x, pos); 215 | 216 | var text = new FormattedText(_hexString.ToString(), CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground); 217 | if (len == 0) 218 | len = _hexString.Length; 219 | 220 | dc.DrawText(text, pt); 221 | if (text.WidthIncludingTrailingWhitespace > maxWidth) { 222 | maxWidth = text.WidthIncludingTrailingWhitespace; 223 | if (_charWidth < 1) { 224 | _charWidth = maxWidth / len; 225 | } 226 | } 227 | 228 | text = new FormattedText(_hexChangesString.ToString(), CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Brushes.Red); 229 | dc.DrawText(text, pt); 230 | if (text.Width > maxWidth) 231 | maxWidth = text.Width; 232 | 233 | _offsetsPositions[i] = pt; 234 | _verticalPositions[(int)pt.Y] = i; 235 | 236 | _viewLines++; 237 | } 238 | 239 | if (empty) { 240 | _offsetsPositions[0] = new Point(x, 2 + y); 241 | } 242 | _hexDataWidth = maxWidth; 243 | 244 | x = _hexDataXPos + _hexDataWidth + 10; 245 | maxWidth = 0; 246 | char ch; 247 | 248 | for (long i = start; i < end; i += BytesPerLine) { 249 | var pos = 2 + (i / BytesPerLine) * lineHeight + y; 250 | _hexString.Clear(); 251 | _hexChangesString.Clear(); 252 | 253 | for (var j = i; j < i + BytesPerLine && j < end; j++) { 254 | if (SelectionStart <= j && j <= SelectionEnd) { 255 | // j is selected 256 | dc.DrawRectangle(SelectionBackground, null, new Rect(x + _charWidth * (j - i), pos, _charWidth, FontSize)); 257 | } 258 | 259 | var changed = changes.Any(change => j >= change.Offset && j < change.Offset + change.Count); 260 | 261 | ch = (char)_readBuffer[j - start]; 262 | if (changed) { 263 | _hexChangesString.Append(char.IsControl(ch) ? '.' : ch); 264 | _hexString.Append(' '); 265 | } 266 | else { 267 | _hexString.Append(char.IsControl(ch) ? '.' : ch); 268 | _hexChangesString.Append(' '); 269 | } 270 | } 271 | 272 | var pt = new Point(x, pos); 273 | 274 | var text = new FormattedText(_hexString.ToString(), CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground); 275 | dc.DrawText(text, pt); 276 | 277 | text = new FormattedText(_hexChangesString.ToString(), CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, EditForeground); 278 | dc.DrawText(text, pt); 279 | } 280 | 281 | _endOffset = end; 282 | _startOffset = start; 283 | 284 | dc.Pop(); 285 | 286 | } 287 | 288 | Point GetPositionByOffset(long offset) { 289 | if (offset < 0 && _hexBuffer.Size == 0) 290 | offset = 0; 291 | if (offset >= 0) { 292 | var offset2 = offset / BytesPerLine * BytesPerLine; 293 | Point pt; 294 | if (_offsetsPositions.TryGetValue(offset2, out pt)) { 295 | return new Point(pt.X + (offset - offset2) * _charWidth * (WordSize * 2 + 1) / WordSize, pt.Y); 296 | } 297 | } 298 | return new Point(-100, -100); 299 | } 300 | 301 | long GetOffsetByCursorPosition(Point pt) { 302 | var xp = pt.X - _hexDataXPos; 303 | long offset = -1; 304 | var y = (int)pt.Y; 305 | while (!_verticalPositions.TryGetValue(y, out offset) && y > -5) 306 | y--; 307 | if (offset < 0) 308 | return offset; 309 | 310 | int x = (int)(xp / (_charWidth * (WordSize * 2 + 1))) * WordSize; 311 | return offset + x; 312 | } 313 | 314 | private void This_SizeChanged(object sender, SizeChangedEventArgs e) { 315 | Refresh(); 316 | } 317 | 318 | private void Grid_MouseWheel(object sender, MouseWheelEventArgs e) { 319 | _scroll.Value -= e.Delta; 320 | } 321 | 322 | static byte[] _zeros = new byte[8]; 323 | bool _selecting; 324 | private void Grid_KeyDown(object sender, KeyEventArgs e) { 325 | var modifiers = e.KeyboardDevice.Modifiers; 326 | bool shiftDown = modifiers == ModifierKeys.Shift; 327 | if (shiftDown && !_selecting) { 328 | SelectionStart = SelectionEnd = CaretOffset; 329 | _selecting = true; 330 | } 331 | e.Handled = true; 332 | bool arrowKey = true; 333 | var offset = CaretOffset; 334 | 335 | switch (e.Key) { 336 | case Key.Escape: 337 | case Key.Return: 338 | e.Handled = false; 339 | return; 340 | 341 | case Key.Insert: 342 | OverwriteMode = !OverwriteMode; 343 | break; 344 | 345 | case Key.Down: 346 | CaretOffset += BytesPerLine; 347 | break; 348 | 349 | case Key.Up: 350 | CaretOffset -= BytesPerLine; 351 | break; 352 | 353 | case Key.Right: 354 | CaretOffset += WordSize; 355 | break; 356 | 357 | case Key.Left: 358 | CaretOffset -= WordSize; 359 | break; 360 | 361 | case Key.PageDown: 362 | if ((modifiers & ModifierKeys.Control) == ModifierKeys.Control) { 363 | CaretOffset = _hexBuffer.Size - _hexBuffer.Size % WordSize; 364 | MakeVisible(CaretOffset); 365 | } 366 | else 367 | CaretOffset += BytesPerLine * _viewLines; 368 | break; 369 | 370 | case Key.PageUp: 371 | if ((modifiers & ModifierKeys.Control) == ModifierKeys.Control) { 372 | CaretOffset = 0; 373 | MakeVisible(0); 374 | } 375 | else 376 | CaretOffset -= BytesPerLine * _viewLines; 377 | break; 378 | 379 | case Key.Back: 380 | if (IsReadOnly) 381 | break; 382 | 383 | if (CaretOffset < WordSize) 384 | break; 385 | CaretOffset -= WordSize; 386 | ClearChange(); 387 | 388 | if (SelectionLength > 0) { 389 | var cmd = new DeleteBulkTextCommand(this, Range.FromStartToEnd(SelectionStart, SelectionEnd)); 390 | AddCommand(cmd); 391 | ClearSelection(); 392 | } 393 | else { 394 | // delete 395 | AddCommand(new DeleteBulkTextCommand(this, Range.FromStartAndCount(CaretOffset, WordSize))); 396 | } 397 | InvalidateVisual(); 398 | break; 399 | 400 | default: 401 | arrowKey = false; 402 | if (modifiers == ModifierKeys.None) 403 | HandleTextEdit(e); 404 | else 405 | e.Handled = false; 406 | break; 407 | } 408 | if (shiftDown && arrowKey) { 409 | bool expanding = CaretOffset > offset; // higher addresses 410 | UpdateSelection(expanding); 411 | } 412 | else { 413 | _selecting = false; 414 | } 415 | if (arrowKey) { 416 | ClearChange(); 417 | } 418 | _root.Focus(); 419 | Keyboard.Focus(_root); 420 | } 421 | 422 | void ClearChange() { 423 | InputIndex = WordIndex = 0; 424 | _lastValue = 0; 425 | CaretOffset -= CaretOffset % WordSize; 426 | } 427 | 428 | internal int InputIndex, WordIndex; 429 | byte _lastValue = 0; 430 | 431 | private void HandleTextEdit(KeyEventArgs e) { 432 | if (IsReadOnly) return; 433 | 434 | if ((e.Key < Key.D0 || e.Key > Key.D9) && (e.Key < Key.A || e.Key > Key.F)) 435 | return; 436 | 437 | // make a change 438 | byte value = e.Key >= Key.A ? (byte)(e.Key - Key.A + 10) : (byte)(e.Key - Key.D0); 439 | _lastValue = (byte)(_lastValue * 16 + value); 440 | 441 | var overwrite = OverwriteMode; 442 | // create a new change set 443 | 444 | if (SelectionLength > 0) { 445 | CaretOffset = SelectionStart; 446 | AddCommand(new DeleteBulkTextCommand(this, Range.FromStartToEnd(SelectionStart, SelectionEnd))); 447 | ClearSelection(); 448 | } 449 | if (CaretOffset == _hexBuffer.Size) 450 | overwrite = false; 451 | 452 | Debug.Assert(InputIndex <= 1); 453 | 454 | if (InputIndex == 0) { 455 | AddCommand(new AddByteCommand(this, CaretOffset, _lastValue, overwrite, InputIndex, WordIndex, WordSize)); 456 | } 457 | else { 458 | AddCommand(new AddByteCommand(this, CaretOffset, _lastValue, true, InputIndex, WordIndex, WordSize)); 459 | _lastValue = 0; 460 | } 461 | if (++InputIndex == 2) { 462 | InputIndex = 0; 463 | if (++WordIndex == WordSize) { 464 | WordIndex = 0; 465 | } 466 | } 467 | } 468 | 469 | private void ClearSelection() { 470 | SelectionStart = SelectionEnd = -1; 471 | } 472 | 473 | public void MakeVisible(long offset) { 474 | if (offset >= _startOffset + BytesPerLine && offset <= _endOffset - BytesPerLine) 475 | return; 476 | 477 | var start = offset - _viewLines * BytesPerLine / 2; 478 | if (start < 0) 479 | start = 0; 480 | 481 | _scroll.Value = VerticalSpace + start * (FontSize + VerticalSpace) / BytesPerLine; 482 | InvalidateVisual(); 483 | } 484 | 485 | bool _mouseLeftButtonDown; 486 | private void _scroll_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { 487 | var pt = e.GetPosition(_root); 488 | CaretOffset = GetOffsetByCursorPosition(pt); 489 | _root.Focus(); 490 | _root.CaptureMouse(); 491 | _mouseLeftButtonDown = true; 492 | ClearSelection(); 493 | } 494 | 495 | private void Grid_MouseMove(object sender, MouseEventArgs e) { 496 | // change cursor 497 | var pt = e.GetPosition(_root); 498 | if (pt.X >= _hexDataXPos - 2 && pt.X < _hexDataXPos + _hexDataWidth) { 499 | Cursor = Cursors.IBeam; 500 | if (_mouseLeftButtonDown) { 501 | var offset = CaretOffset; 502 | CaretOffset = GetOffsetByCursorPosition(pt); 503 | if (offset != CaretOffset && !_selecting) { 504 | _selecting = true; 505 | SelectionStart = SelectionEnd = offset; 506 | } 507 | else if (_selecting) { 508 | UpdateSelection(CaretOffset >= offset); 509 | } 510 | } 511 | } 512 | else 513 | Cursor = null; 514 | } 515 | 516 | private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { 517 | _selecting = _mouseLeftButtonDown = false; 518 | _root.ReleaseMouseCapture(); 519 | InvalidateVisual(); 520 | } 521 | 522 | private void UpdateSelection(bool expanding) { 523 | if (expanding) { 524 | if (CaretOffset >= SelectionStart && CaretOffset > SelectionEnd) 525 | SelectionEnd = CaretOffset - WordSize; 526 | else 527 | SelectionStart = CaretOffset; 528 | } 529 | else { 530 | if (CaretOffset > SelectionStart && CaretOffset <= SelectionEnd) 531 | SelectionEnd = CaretOffset - WordSize; 532 | else 533 | SelectionStart = CaretOffset; 534 | } 535 | 536 | InvalidateVisual(); 537 | } 538 | 539 | public void SaveChanges() { 540 | _hexBuffer.ApplyChanges(); 541 | ClearChange(); 542 | _commandManager.Clear(); 543 | IsModified = false; 544 | InvalidateVisual(); 545 | } 546 | 547 | private void _root_MouseRightButtonDown(object sender, MouseButtonEventArgs e) { 548 | var pt = e.GetPosition(this); 549 | CaretOffset = GetOffsetByCursorPosition(pt); 550 | _root.Focus(); 551 | } 552 | 553 | private void _root_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) { 554 | _caret.Visibility = Visibility.Visible; 555 | } 556 | 557 | public void SaveChangesAs(string newfilename) { 558 | _hexBuffer.SaveToFile(newfilename); 559 | ClearChange(); 560 | _commandManager.Clear(); 561 | IsModified = false; 562 | InvalidateVisual(); 563 | } 564 | 565 | public void DiscardChanges() { 566 | _hexBuffer.DiscardChanges(); 567 | _commandManager.Clear(); 568 | IsModified = false; 569 | InvalidateVisual(); 570 | } 571 | 572 | public void Dispose() { 573 | if (_hexBuffer != null) { 574 | _hexBuffer.Dispose(); 575 | _hexBuffer = null; 576 | } 577 | } 578 | 579 | public byte[] GetBytes(long offset, int count) { 580 | if (Buffer == null) 581 | return null; 582 | var bytes = new byte[count]; 583 | int returned = Buffer.GetBytes(offset, count, bytes); 584 | if (returned < count) 585 | Array.Resize(ref bytes, returned); 586 | return bytes; 587 | } 588 | 589 | public long FindNext(long offset, byte[] data) { 590 | var buffer = new byte[Math.Max(1 << 21, data.Length * 2)]; 591 | 592 | int iSearch = 0, iData = 0; 593 | for (;;) { 594 | var read = Buffer.GetBytes(offset, buffer.Length, buffer); 595 | if (read < data.Length) 596 | break; 597 | 598 | do { 599 | while (iSearch < buffer.Length && buffer[iSearch++] == data[iData]) { 600 | if (++iData == data.Length) 601 | return offset + iSearch - data.Length; 602 | } 603 | iData = 0; 604 | } while (iSearch < buffer.Length); 605 | 606 | offset += iSearch - data.Length; 607 | iSearch = 0; 608 | } 609 | return -1; 610 | } 611 | 612 | public void SetData(byte[] data) { 613 | Buffer.SetData(data); 614 | DiscardChanges(); 615 | InvalidateVisual(); 616 | } 617 | 618 | public long FindNext(long offset, string text, Encoding encoding) { 619 | var bytes = encoding.GetBytes(text); 620 | return FindNext(offset, bytes); 621 | } 622 | } 623 | } 624 | -------------------------------------------------------------------------------- /HexEditControl/IHexEdit.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Zodiacon.HexEditControl { 8 | public interface IHexEdit { 9 | byte[] GetBytes(long offset, int count); 10 | long Size { get; } 11 | 12 | event Action BufferSizeChanged; 13 | 14 | long CaretOffset { get; set; } 15 | 16 | long FindNext(long offset, byte[] data); 17 | void SetData(byte[] data); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /HexEditControl/OffsetAndHeight.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Zodiacon.HexEditControl { 8 | struct OffsetAndHeight : IComparable { 9 | public readonly long Offset; 10 | public readonly int Height; 11 | 12 | public OffsetAndHeight(long offset, int height) { 13 | Offset = offset; 14 | Height = height; 15 | } 16 | 17 | public int CompareTo(OffsetAndHeight other) { 18 | int compareOffsets = Offset.CompareTo(other.Offset); 19 | if (compareOffsets != 0) 20 | return compareOffsets; 21 | return -Height.CompareTo(other.Height); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /HexEditControl/OffsetRange.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Zodiacon.HexEditControl { 8 | public struct OffsetRange { 9 | public readonly long Offset; 10 | public readonly int Count; 11 | 12 | public OffsetRange(long start, int count) { 13 | Offset = start; 14 | Count = count; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /HexEditControl/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("HexEditControl")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("HexEditControl")] 15 | [assembly: AssemblyCopyright("Copyright © 2016")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly:ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /HexEditControl/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Zodiacon.HexEditControl.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Zodiacon.HexEditControl.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /HexEditControl/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /HexEditControl/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Zodiacon.HexEditControl.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /HexEditControl/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /HexEditControl/Range.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Zodiacon.HexEditControl { 4 | public struct Range : IEquatable { 5 | public bool IsEmpty => Start > End; 6 | 7 | public long Start { get; } 8 | public long End { get; } 9 | public long Count => End - Start + 1; 10 | 11 | private Range(long start, long end) 12 | : this() { 13 | Start = start; 14 | End = end; 15 | } 16 | 17 | public Range Offset(long offset) { 18 | return new Range(Start + offset, End + offset); 19 | } 20 | 21 | public static Range FromStartToEnd(long start, long end) { 22 | return new Range(start, end); 23 | } 24 | 25 | public static Range FromStartAndCount(long start, long count) { 26 | return FromStartToEnd(start, start + count - 1); 27 | } 28 | 29 | public bool ContainedEntirelyWithin(Range other) { 30 | return GetIntersection(other) == this; 31 | } 32 | 33 | public bool ContainsEntirely(Range other) { 34 | return GetIntersection(other) == other; 35 | } 36 | 37 | public bool Contains(long value) { 38 | var result = true; 39 | result &= Start.CompareTo(value) <= 0; 40 | result &= End.CompareTo(value) >= 0; 41 | 42 | return result; 43 | } 44 | 45 | public Range GetIntersection(Range other) { 46 | if (!Intersects(other)) 47 | return new Range(0, -1); 48 | 49 | long start, end; 50 | 51 | start = Start.CompareTo(other.Start) >= 0 ? Start : other.Start; 52 | 53 | end = End.CompareTo(other.End) < 0 ? End : other.End; 54 | 55 | return new Range(start, end); 56 | } 57 | 58 | public bool Intersects(Range other) { 59 | var start = Math.Max(Start, other.Start); 60 | var end = Math.Min(End, other.End); 61 | return start <= end; 62 | } 63 | 64 | public override string ToString() { 65 | return $"{Start.ToString()}..{End.ToString()}"; 66 | } 67 | 68 | public override int GetHashCode() { 69 | return Start.GetHashCode() ^ End.GetHashCode(); 70 | } 71 | 72 | public static bool operator ==(Range a, Range b) { 73 | return a.Start.Equals(b.Start) && a.End.Equals(b.End); 74 | } 75 | 76 | public static bool operator !=(Range a, Range b) { 77 | return !(a == b); 78 | } 79 | 80 | public override bool Equals(object obj) { 81 | if (!(obj is Range)) 82 | return false; 83 | 84 | return (this == (Range)obj); 85 | } 86 | 87 | public bool Equals(Range other) { 88 | return this == other; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /HexEditControl/Zodiacon.HexEditControl.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {2B53AB2B-DAD7-4794-9F36-50E6004B5806} 8 | library 9 | Properties 10 | Zodiacon.HexEditControl 11 | HexEditControl 12 | v4.5.2 13 | 512 14 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 4 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | ..\packages\Prism.Core.6.2.0\lib\net45\Prism.dll 37 | True 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 4.0 50 | 51 | 52 | 53 | 54 | 55 | ..\packages\Zodiacon.WPF.1.1.13\lib\net45\Zodiacon.WPF.dll 56 | True 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | HexEdit.xaml 70 | 71 | 72 | 73 | 74 | 75 | Code 76 | 77 | 78 | True 79 | True 80 | Resources.resx 81 | 82 | 83 | True 84 | Settings.settings 85 | True 86 | 87 | 88 | 89 | ResXFileCodeGenerator 90 | Resources.Designer.cs 91 | 92 | 93 | 94 | 95 | 96 | SettingsSingleFileGenerator 97 | Settings.Designer.cs 98 | 99 | 100 | 101 | 102 | 103 | MSBuild:Compile 104 | Designer 105 | 106 | 107 | 108 | 109 | 116 | -------------------------------------------------------------------------------- /HexEditControl/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /HexEditControl/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /HexStudio.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HexStudio", "HexStudio\HexStudio.csproj", "{D94920FD-45FB-4992-8F87-F267BBC0E6CE}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Zodiacon.HexEditControl", "HexEditControl\Zodiacon.HexEditControl.csproj", "{2B53AB2B-DAD7-4794-9F36-50E6004B5806}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HexEditControl.Tests", "HexEditControl.Tests\HexEditControl.Tests.csproj", "{5E43DD3C-BF88-4EA5-B5D7-E9D212D11DA6}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {D94920FD-45FB-4992-8F87-F267BBC0E6CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {D94920FD-45FB-4992-8F87-F267BBC0E6CE}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {D94920FD-45FB-4992-8F87-F267BBC0E6CE}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {D94920FD-45FB-4992-8F87-F267BBC0E6CE}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {2B53AB2B-DAD7-4794-9F36-50E6004B5806}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {2B53AB2B-DAD7-4794-9F36-50E6004B5806}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {2B53AB2B-DAD7-4794-9F36-50E6004B5806}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {2B53AB2B-DAD7-4794-9F36-50E6004B5806}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {5E43DD3C-BF88-4EA5-B5D7-E9D212D11DA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {5E43DD3C-BF88-4EA5-B5D7-E9D212D11DA6}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {5E43DD3C-BF88-4EA5-B5D7-E9D212D11DA6}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {5E43DD3C-BF88-4EA5-B5D7-E9D212D11DA6}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /HexStudio/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /HexStudio/App.xaml: -------------------------------------------------------------------------------- 1 |  8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /HexStudio/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using HexStudio.ViewModels; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel.Composition.Hosting; 5 | using System.Configuration; 6 | using System.Data; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Threading.Tasks; 10 | using System.Windows; 11 | using Zodiacon.WPF; 12 | 13 | namespace HexStudio { 14 | /// 15 | /// Interaction logic for App.xaml 16 | /// 17 | public partial class App : Application { 18 | MainViewModel _mainViewModel; 19 | 20 | protected override void OnStartup(StartupEventArgs e) { 21 | var catalog = new AggregateCatalog( 22 | new AssemblyCatalog(Assembly.GetExecutingAssembly()), 23 | new AssemblyCatalog(typeof(IDialogService).Assembly)); 24 | var container = new CompositionContainer(catalog); 25 | 26 | var vm = container.GetExportedValue(); 27 | _mainViewModel = vm; 28 | var win = new MainWindow { DataContext = vm }; 29 | vm.MessageBoxService.SetOwner(win); 30 | win.Show(); 31 | } 32 | 33 | private void Application_Exit(object sender, ExitEventArgs e) { 34 | _mainViewModel.SaveSettings(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /HexStudio/ByteFinder.cs: -------------------------------------------------------------------------------- 1 | using HexStudio.ViewModels; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Zodiacon.HexEditControl; 8 | 9 | namespace HexStudio { 10 | class FindResultViewModel { 11 | public OpenFileViewModel OpenFile { get; } 12 | public long Offset { get; set; } 13 | public IHexEdit Editor { get; set; } 14 | public FindResultViewModel(OpenFileViewModel openFile) { 15 | OpenFile = openFile; 16 | } 17 | } 18 | 19 | enum ByteFinderOptions { 20 | FromStart = 0, 21 | FromCurrentPosition = 1, 22 | } 23 | 24 | class ByteFinder { 25 | IEnumerable _files; 26 | byte[] _data; 27 | ByteFinderOptions _options; 28 | 29 | public ByteFinder(IEnumerable files, byte[] data, ByteFinderOptions options) { 30 | _files = files; 31 | _data = data; 32 | _options = options; 33 | } 34 | 35 | public IEnumerable Find() { 36 | foreach (var file in _files) { 37 | var editor = file.HexEditor; 38 | var start = _options.HasFlag(ByteFinderOptions.FromCurrentPosition) ? editor.CaretOffset : 0; 39 | do { 40 | var find = editor.FindNext(start, _data); 41 | if (find < 0) 42 | break; 43 | 44 | yield return new FindResultViewModel(file) { 45 | Editor = editor, 46 | Offset = find 47 | }; 48 | start = find + _data.Length; 49 | } while (true); 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /HexStudio/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace HexStudio { 8 | class Constants { 9 | public const string AppTitle = "Hex Studio"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /HexStudio/Controls/EditChange.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace HexStudio.Controls { 8 | class EditChange : IEqualityComparer, IComparable { 9 | public long Offset { get; set; } 10 | public byte Value { get; set; } 11 | 12 | public int CompareTo(EditChange other) { 13 | return Offset.CompareTo(other.Offset); 14 | } 15 | 16 | public bool Equals(EditChange x, EditChange y) { 17 | return x.Offset == y.Offset; 18 | } 19 | 20 | public int GetHashCode(EditChange obj) { 21 | return obj.Offset.GetHashCode(); 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /HexStudio/Controls/HexEdit.xaml: -------------------------------------------------------------------------------- 1 |  10 | 12 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /HexStudio/Controls/HexEdit.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.IO.MemoryMappedFiles; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows; 10 | using System.Windows.Controls; 11 | using System.Windows.Data; 12 | using System.Windows.Documents; 13 | using System.Windows.Input; 14 | using System.Windows.Media; 15 | using System.Windows.Media.Imaging; 16 | using System.Windows.Navigation; 17 | using System.Windows.Shapes; 18 | using System.Windows.Threading; 19 | 20 | namespace HexStudio.Controls { 21 | /// 22 | /// Interaction logic for HexEdit.xaml 23 | /// 24 | public partial class HexEdit { 25 | MemoryMappedFile _memFile; 26 | MemoryMappedViewAccessor _accessor; 27 | long _size; 28 | DispatcherTimer _timer; 29 | double _hexDataXPos, _hexDataWidth; 30 | long _startOffset, _endOffset; 31 | Dictionary _offsetsPositions = new Dictionary(128); 32 | Dictionary _verticalPositions = new Dictionary(128); 33 | Dictionary _changes = new Dictionary(128); 34 | double _charWidth; 35 | int _viewLines; 36 | //long _totalLines; 37 | 38 | public HexEdit() { 39 | InitializeComponent(); 40 | 41 | _timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(.5) }; 42 | _timer.Tick += _timer_Tick; 43 | _timer.Start(); 44 | } 45 | 46 | private void _timer_Tick(object sender, EventArgs e) { 47 | if (CaretOffset < 0) 48 | _caret.Visibility = Visibility.Collapsed; 49 | else 50 | _caret.Visibility = _caret.Visibility == Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed; 51 | } 52 | 53 | 54 | public string Filename { 55 | get { return (string)GetValue(FilenameProperty); } 56 | set { SetValue(FilenameProperty, value); } 57 | } 58 | 59 | public static readonly DependencyProperty FilenameProperty = 60 | DependencyProperty.Register(nameof(Filename), typeof(string), typeof(HexEdit), new PropertyMetadata(null, (s, e) => ((HexEdit)s).OnFilenameChanged(e))); 61 | 62 | 63 | public long CaretOffset { 64 | get { return (long)GetValue(CaretOffsetProperty); } 65 | set { SetValue(CaretOffsetProperty, value); } 66 | } 67 | 68 | public static readonly DependencyProperty CaretOffsetProperty = 69 | DependencyProperty.Register(nameof(CaretOffset), typeof(long), typeof(HexEdit), new PropertyMetadata(-1L, (s, e) => ((HexEdit)s).OnCaretOffsetChanged(e), (s, e) => ((HexEdit)s).CoerceCaretOffset(e))); 70 | 71 | private object CoerceCaretOffset(object value) { 72 | var offset = (long)value; 73 | if (offset < 0) 74 | offset = 0; 75 | else if (offset >= _size) 76 | offset = _size - 1; 77 | return offset; 78 | } 79 | 80 | private void OnCaretOffsetChanged(DependencyPropertyChangedEventArgs e) { 81 | SetCaretPosition(CaretOffset); 82 | MakeVisible(CaretOffset); 83 | _inputIndex = _wordIndex = 0; 84 | _lastValue = 0; 85 | } 86 | 87 | private void SetCaretPosition(long caretOffset) { 88 | var pt = GetPositionByOffset(caretOffset); 89 | _caretPosition.X = pt.X; 90 | _caretPosition.Y = pt.Y; 91 | } 92 | 93 | public long SelectionStart { 94 | get { return (long)GetValue(SelectionStartProperty); } 95 | set { SetValue(SelectionStartProperty, value); } 96 | } 97 | 98 | public static readonly DependencyProperty SelectionStartProperty = 99 | DependencyProperty.Register(nameof(SelectionStart), typeof(long), typeof(HexEdit), new PropertyMetadata(-1L)); 100 | 101 | 102 | public long SelectionEnd { 103 | get { return (long)GetValue(SelectionEndProperty); } 104 | set { SetValue(SelectionEndProperty, value); } 105 | } 106 | 107 | public static readonly DependencyProperty SelectionEndProperty = 108 | DependencyProperty.Register(nameof(SelectionEnd), typeof(long), typeof(HexEdit), new PropertyMetadata(-1L)); 109 | 110 | public int BytesPerLine { 111 | get { return (int)GetValue(BytesPerLineProperty); } 112 | set { SetValue(BytesPerLineProperty, value); } 113 | } 114 | 115 | public static readonly DependencyProperty BytesPerLineProperty = 116 | DependencyProperty.Register(nameof(BytesPerLine), typeof(int), typeof(HexEdit), new PropertyMetadata(32, (s, e) => ((HexEdit)s).Refresh()), ValidateBytesPerLine); 117 | 118 | private static bool ValidateBytesPerLine(object value) { 119 | var bytes = (int)value; 120 | if (bytes < 8 || bytes > 128) 121 | return false; 122 | return bytes % 8 == 0; 123 | } 124 | 125 | public double VerticalSpace { 126 | get { return (double)GetValue(VerticalSpaceProperty); } 127 | set { SetValue(VerticalSpaceProperty, value); } 128 | } 129 | 130 | public static readonly DependencyProperty VerticalSpaceProperty = 131 | DependencyProperty.Register("VerticalSpace", typeof(double), typeof(HexEdit), new PropertyMetadata(2.0, (s, e) => ((HexEdit)s).Refresh())); 132 | 133 | public int WordSize { 134 | get { return (int)GetValue(WordSizeProperty); } 135 | set { SetValue(WordSizeProperty, value); } 136 | } 137 | 138 | public static readonly DependencyProperty WordSizeProperty = 139 | DependencyProperty.Register("WordSize", typeof(int), typeof(HexEdit), new PropertyMetadata(1, (s, e) => ((HexEdit)s).Refresh()), ValidateWordSize); 140 | 141 | public Brush SelectionBackground { 142 | get { return (Brush)GetValue(SelectionBackgroundProperty); } 143 | set { SetValue(SelectionBackgroundProperty, value); } 144 | } 145 | 146 | public static readonly DependencyProperty SelectionBackgroundProperty = 147 | DependencyProperty.Register("SelectionBackground", typeof(Brush), typeof(HexEdit), 148 | new FrameworkPropertyMetadata(Brushes.Yellow, FrameworkPropertyMetadataOptions.AffectsRender)); 149 | 150 | 151 | public Brush SelectionForeground { 152 | get { return (Brush)GetValue(SelectionForegroundProperty); } 153 | set { SetValue(SelectionForegroundProperty, value); } 154 | } 155 | 156 | public static readonly DependencyProperty SelectionForegroundProperty = 157 | DependencyProperty.Register("SelectionForeground", typeof(Brush), typeof(HexEdit), 158 | new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender)); 159 | 160 | public bool IsReadOnly { 161 | get { return (bool)GetValue(IsReadOnlyProperty); } 162 | set { SetValue(IsReadOnlyProperty, value); } 163 | } 164 | 165 | public static readonly DependencyProperty IsReadOnlyProperty = 166 | DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(HexEdit), new PropertyMetadata(false)); 167 | 168 | 169 | 170 | public bool IsModified { 171 | get { return (bool)GetValue(IsModifiedProperty); } 172 | set { SetValue(IsModifiedProperty, value); } 173 | } 174 | 175 | public static readonly DependencyProperty IsModifiedProperty = 176 | DependencyProperty.Register(nameof(IsModified), typeof(bool), typeof(HexEdit), new PropertyMetadata(false)); 177 | 178 | 179 | private static bool ValidateWordSize(object value) { 180 | var wordSize = (int)value; 181 | return wordSize == 1 || wordSize == 2 || wordSize == 4 || wordSize == 8; 182 | } 183 | 184 | Func[] _bitConverters = { 185 | (bytes, i) => bytes[i].ToString("X2"), 186 | (bytes, i) => BitConverter.ToUInt16(bytes, i).ToString("X4"), 187 | (bytes, i) => BitConverter.ToUInt32(bytes, i).ToString("X8"), 188 | (bytes, i) => BitConverter.ToUInt64(bytes, i).ToString("X16"), 189 | }; 190 | 191 | int[] _bitConverterIndex = { 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3 }; 192 | 193 | private void Refresh() { 194 | Recalculate(); 195 | InvalidateVisual(); 196 | if (CaretOffset >= 0) { 197 | CaretOffset = CaretOffset - CaretOffset % WordSize; 198 | SetCaretPosition(CaretOffset); 199 | } 200 | } 201 | 202 | private void OnFilenameChanged(DependencyPropertyChangedEventArgs e) { 203 | if (_accessor != null) 204 | _accessor.Dispose(); 205 | if (_memFile != null) 206 | _memFile.Dispose(); 207 | 208 | var filename = (string)e.NewValue; 209 | _size = 0; 210 | if (filename != null) { 211 | _size = new FileInfo(filename).Length; 212 | _memFile = MemoryMappedFile.CreateFromFile(filename, FileMode.Open); 213 | _accessor = _memFile.CreateViewAccessor(); 214 | } 215 | Refresh(); 216 | } 217 | 218 | private void Recalculate() { 219 | _scroll.ViewportSize = ActualHeight; 220 | _scroll.Maximum = _size / BytesPerLine * (FontSize + VerticalSpace) - ActualHeight + VerticalSpace * 2; 221 | } 222 | 223 | private void _scroll_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) { 224 | InvalidateVisual(); 225 | } 226 | 227 | protected override void OnRender(DrawingContext dc) { 228 | if (_accessor == null || _size == 0 || ActualHeight < 1) return; 229 | 230 | dc.PushClip(new RectangleGeometry(new Rect(0, 0, ActualWidth, ActualHeight))); 231 | 232 | var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch); 233 | int lineHeight = (int)(FontSize + VerticalSpace); 234 | var y = -_scroll.Value; 235 | var viewport = _scroll.ViewportSize; 236 | long start = (long)(BytesPerLine * (0 - VerticalSpace - y) / lineHeight); 237 | 238 | if (start < 0) 239 | start = 0; 240 | else 241 | start = start / BytesPerLine * BytesPerLine; 242 | long end = start + BytesPerLine * ((long)viewport / lineHeight + 1); 243 | if (end > _size) 244 | end = _size; 245 | 246 | var maxWidth = 0.0; 247 | for (long i = start; i < end; i += BytesPerLine) { 248 | var pos = 2 + (i / BytesPerLine) * lineHeight + y; 249 | var text = new FormattedText(i.ToString("X8") + ": ", CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground); 250 | if (text.Width > maxWidth) 251 | maxWidth = text.Width; 252 | dc.DrawText(text, new Point(2, pos)); 253 | } 254 | 255 | var x = maxWidth + 8; 256 | 257 | var buf = new byte[end - start + 1]; 258 | var read = _accessor.ReadArray(start, buf, 0, buf.Length); 259 | if (start + read < end) 260 | end = start + read; 261 | 262 | var sb = new StringBuilder(256); // entire row 263 | var changed_sb = new StringBuilder(256); // changes 264 | 265 | _hexDataXPos = x; 266 | 267 | var singleChar = new FormattedText("8", CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground); 268 | 269 | maxWidth = 0; 270 | _offsetsPositions.Clear(); 271 | _verticalPositions.Clear(); 272 | 273 | var bitConverter = _bitConverters[_bitConverterIndex[WordSize]]; 274 | _viewLines = 0; 275 | var space = new string(' ', WordSize * 2 + 1); 276 | EditChange change; 277 | int len = 0; 278 | 279 | for (long i = start; i < end; i += BytesPerLine) { 280 | var pos = 2 + (i / BytesPerLine) * lineHeight + y; 281 | sb.Clear(); 282 | changed_sb.Clear(); 283 | 284 | if (SelectionStart <= i && i <= SelectionEnd) { 285 | // j is selected 286 | dc.DrawRectangle(SelectionBackground, null, new Rect(x, pos, _charWidth * WordSize * 2, FontSize)); 287 | } 288 | for (var j = i; j < i + BytesPerLine && j < end; j += WordSize) { 289 | int bufIndex = (int)(j - start); 290 | bool changed = false; 291 | for(int k = 0; k < WordSize; k++) { 292 | if (_changes.TryGetValue(j + k, out change)) { 293 | buf[bufIndex + k] = change.Value; 294 | changed = true; 295 | } 296 | } 297 | if (changed) { 298 | changed_sb.Append(bitConverter(buf, bufIndex)).Append(" "); 299 | sb.Append(space); 300 | } 301 | else { 302 | sb.Append(bitConverter(buf, bufIndex)).Append(" "); 303 | changed_sb.Append(space); 304 | } 305 | } 306 | 307 | var pt = new Point(x, pos); 308 | 309 | var text = new FormattedText(sb.ToString(), CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground); 310 | if (len == 0) 311 | len = sb.Length; 312 | 313 | dc.DrawText(text, pt); 314 | if (text.WidthIncludingTrailingWhitespace > maxWidth) { 315 | maxWidth = text.WidthIncludingTrailingWhitespace; 316 | if (_charWidth < 1) { 317 | _charWidth = maxWidth / len; 318 | } 319 | } 320 | 321 | text = new FormattedText(changed_sb.ToString(), CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Brushes.Red); 322 | dc.DrawText(text, pt); 323 | if (text.Width > maxWidth) 324 | maxWidth = text.Width; 325 | 326 | _offsetsPositions[i] = pt; 327 | _verticalPositions[(int)pt.Y] = i; 328 | 329 | _viewLines++; 330 | } 331 | 332 | _hexDataWidth = maxWidth; 333 | 334 | x = _hexDataXPos + _hexDataWidth + 10; 335 | maxWidth = 0; 336 | char ch; 337 | 338 | for (long i = start; i < end; i += BytesPerLine) { 339 | var pos = 2 + (i / BytesPerLine) * lineHeight + y; 340 | sb.Clear(); 341 | changed_sb.Clear(); 342 | 343 | if (SelectionStart <= i && i <= SelectionEnd) { 344 | // j is selected 345 | dc.DrawRectangle(SelectionBackground, null, new Rect(x, pos, _charWidth * WordSize, FontSize)); 346 | } 347 | for (var j = i; j < i + BytesPerLine && j < end; j++) { 348 | 349 | if (_changes.TryGetValue(j, out change)) { 350 | ch = (char)change.Value; 351 | changed_sb.Append(char.IsControl(ch) ? '.' : ch); 352 | sb.Append(' '); 353 | } 354 | else { 355 | ch = Encoding.ASCII.GetChars(buf, (int)(j - start), 1)[0]; 356 | sb.Append(char.IsControl(ch) ? '.' : ch); 357 | changed_sb.Append(' '); 358 | } 359 | } 360 | 361 | var pt = new Point(x, pos); 362 | 363 | var text = new FormattedText(sb.ToString(), CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Foreground); 364 | dc.DrawText(text, pt); 365 | 366 | text = new FormattedText(changed_sb.ToString(), CultureInfo.InvariantCulture, FlowDirection.LeftToRight, typeface, FontSize, Brushes.Red); 367 | dc.DrawText(text, pt); 368 | } 369 | 370 | _endOffset = end; 371 | _startOffset = start; 372 | 373 | dc.Pop(); 374 | 375 | } 376 | 377 | Point GetPositionByOffset(long offset) { 378 | if (offset >= 0) { 379 | var offset2 = offset / BytesPerLine * BytesPerLine; 380 | Point pt; 381 | if (_offsetsPositions.TryGetValue(offset2, out pt)) { 382 | return new Point(pt.X + (offset - offset2) * _charWidth * (WordSize * 2 + 1) / WordSize, pt.Y); 383 | } 384 | } 385 | return new Point(-100, -100); 386 | } 387 | 388 | long GetOffsetByCursorPosition(Point pt) { 389 | var xp = pt.X - _hexDataXPos; 390 | long offset; 391 | var y = (int)pt.Y; 392 | while (!_verticalPositions.TryGetValue(y, out offset)) 393 | y--; 394 | int x = (int)(xp / (_charWidth * (WordSize * 2 + 1))) * WordSize; 395 | return offset + x; 396 | } 397 | 398 | private void This_SizeChanged(object sender, SizeChangedEventArgs e) { 399 | Refresh(); 400 | } 401 | 402 | private void Grid_MouseWheel(object sender, MouseWheelEventArgs e) { 403 | _scroll.Value -= e.Delta; 404 | } 405 | 406 | private void _scroll_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { 407 | var pt = e.GetPosition(this); 408 | CaretOffset = GetOffsetByCursorPosition(pt); 409 | Focus(); 410 | } 411 | 412 | private void Grid_KeyDown(object sender, KeyEventArgs e) { 413 | if (CaretOffset < 0) { 414 | } 415 | else { 416 | bool shiftDown = e.KeyboardDevice.Modifiers == ModifierKeys.Shift; 417 | if (shiftDown) { 418 | if (SelectionStart < 0) 419 | SelectionStart = SelectionEnd = CaretOffset; 420 | } 421 | switch (e.Key) { 422 | case Key.Down: 423 | CaretOffset += BytesPerLine; 424 | break; 425 | case Key.Up: 426 | CaretOffset -= BytesPerLine; 427 | break; 428 | case Key.Right: 429 | CaretOffset++; 430 | break; 431 | case Key.Left: 432 | CaretOffset--; 433 | break; 434 | case Key.PageDown: 435 | CaretOffset += BytesPerLine * _viewLines; 436 | break; 437 | case Key.PageUp: 438 | CaretOffset -= BytesPerLine * _viewLines; 439 | break; 440 | 441 | default: 442 | if(e.KeyboardDevice.Modifiers == ModifierKeys.None) 443 | HandleTextEdit(e); 444 | break; 445 | } 446 | if (shiftDown) { 447 | SelectionEnd = CaretOffset; 448 | InvalidateVisual(); 449 | } 450 | } 451 | e.Handled = true; 452 | } 453 | 454 | int _inputIndex = 0, _wordIndex = 0; 455 | byte _lastValue = 0; 456 | EditChange _currentChange; 457 | 458 | private void HandleTextEdit(KeyEventArgs e) { 459 | if (IsReadOnly) return; 460 | 461 | if ((e.Key < Key.D0 || e.Key > Key.D9) && (e.Key < Key.A || e.Key > Key.F)) 462 | return; 463 | 464 | // make a change 465 | byte value = e.Key >= Key.A ? (byte)(e.Key - Key.A + 10) : (byte)(e.Key - Key.D0); 466 | _lastValue = (byte)(_lastValue * 16 + value); 467 | 468 | if (!IsModified) 469 | IsModified = true; 470 | 471 | if (_inputIndex == 0) { 472 | _currentChange = new EditChange { Offset = CaretOffset + _wordIndex }; 473 | _changes[CaretOffset + _wordIndex] = _currentChange; 474 | } 475 | _currentChange.Value = _lastValue; 476 | if (++_inputIndex == 2) { 477 | _currentChange = null; 478 | _inputIndex = 0; 479 | if (++_wordIndex == WordSize) { 480 | CaretOffset += WordSize; 481 | _wordIndex = 0; 482 | } 483 | } 484 | InvalidateVisual(); 485 | } 486 | 487 | public void MakeVisible(long offset) { 488 | if (offset >= _startOffset + BytesPerLine && offset <= _endOffset - BytesPerLine) 489 | return; 490 | 491 | var start = offset - _viewLines * BytesPerLine / 2; 492 | if (start < 0) 493 | start = 0; 494 | 495 | _scroll.Value = VerticalSpace + start * (FontSize + VerticalSpace) / BytesPerLine; 496 | InvalidateVisual(); 497 | } 498 | 499 | private void Grid_MouseMove(object sender, MouseEventArgs e) { 500 | // change cursor 501 | var pt = e.GetPosition(this); 502 | if (pt.X >= _hexDataXPos && pt.X < _hexDataXPos + _hexDataWidth) 503 | Cursor = Cursors.IBeam; 504 | else 505 | Cursor = null; 506 | } 507 | 508 | public void SaveChanges() { 509 | foreach (var change in _changes.Values) { 510 | _accessor.Write(change.Offset, change.Value); 511 | } 512 | _changes.Clear(); 513 | IsModified = false; 514 | InvalidateVisual(); 515 | } 516 | 517 | public void DiscardChanges() { 518 | _changes.Clear(); 519 | IsModified = false; 520 | InvalidateVisual(); 521 | } 522 | } 523 | } 524 | -------------------------------------------------------------------------------- /HexStudio/Converters/OverwriteToTextConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Data; 8 | 9 | namespace HexStudio.Converters { 10 | class OverwriteToTextConverter : IValueConverter { 11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { 12 | return (bool)value ? "OVR" : "INS"; 13 | } 14 | 15 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { 16 | throw new NotImplementedException(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /HexStudio/Converters/RgbColorToBrushConverter.cs: -------------------------------------------------------------------------------- 1 | using HexStudio.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows.Data; 9 | using System.Windows.Media; 10 | 11 | namespace HexStudio.Converters { 12 | class RgbColorToBrushConverter : IValueConverter { 13 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { 14 | var color = (RgbColor)value; 15 | return new SolidColorBrush(Color.FromRgb(color.R, color.G, color.B)); 16 | } 17 | 18 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { 19 | throw new NotImplementedException(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /HexStudio/HexStudio.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {D94920FD-45FB-4992-8F87-F267BBC0E6CE} 8 | WinExe 9 | Properties 10 | HexStudio 11 | HexStudio 12 | v4.5.2 13 | 512 14 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 4 16 | true 17 | 18 | 19 | 20 | AnyCPU 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | false 29 | 30 | 31 | AnyCPU 32 | pdbonly 33 | true 34 | bin\Release\ 35 | TRACE 36 | prompt 37 | 4 38 | false 39 | 40 | 41 | Icons\app.ico 42 | 43 | 44 | 45 | ..\packages\MahApps.Metro.1.6.0-alpha002\lib\net45\MahApps.Metro.dll 46 | 47 | 48 | ..\packages\Prism.Core.6.3.0\lib\net45\Prism.dll 49 | True 50 | 51 | 52 | 53 | 54 | 55 | 56 | ..\packages\MahApps.Metro.1.6.0-alpha002\lib\net45\System.Windows.Interactivity.dll 57 | True 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 4.0 67 | 68 | 69 | 70 | 71 | 72 | ..\packages\Zodiacon.WPF.1.2.3\lib\net45\Zodiacon.WPF.dll 73 | 74 | 75 | 76 | 77 | MSBuild:Compile 78 | Designer 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | FindDialogView.xaml 92 | 93 | 94 | GenericWindow.xaml 95 | 96 | 97 | MainView.xaml 98 | 99 | 100 | OpenFileView.xaml 101 | 102 | 103 | MSBuild:Compile 104 | Designer 105 | 106 | 107 | App.xaml 108 | Code 109 | 110 | 111 | MainWindow.xaml 112 | Code 113 | 114 | 115 | Designer 116 | MSBuild:Compile 117 | 118 | 119 | Designer 120 | MSBuild:Compile 121 | 122 | 123 | Designer 124 | MSBuild:Compile 125 | 126 | 127 | Designer 128 | MSBuild:Compile 129 | 130 | 131 | Designer 132 | MSBuild:Compile 133 | 134 | 135 | 136 | 137 | Code 138 | 139 | 140 | True 141 | True 142 | Resources.resx 143 | 144 | 145 | True 146 | Settings.settings 147 | True 148 | 149 | 150 | ResXFileCodeGenerator 151 | Resources.Designer.cs 152 | 153 | 154 | 155 | SettingsSingleFileGenerator 156 | Settings.Designer.cs 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | {2b53ab2b-dad7-4794-9f36-50e6004b5806} 166 | Zodiacon.HexEditControl 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 228 | -------------------------------------------------------------------------------- /HexStudio/Icons/app.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zodiacon/HexStudio/079a3aca3ccb3cff02ac34602b323a2cbb6d9bdf/HexStudio/Icons/app.ico -------------------------------------------------------------------------------- /HexStudio/Icons/close.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zodiacon/HexStudio/079a3aca3ccb3cff02ac34602b323a2cbb6d9bdf/HexStudio/Icons/close.ico -------------------------------------------------------------------------------- /HexStudio/Icons/copy.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zodiacon/HexStudio/079a3aca3ccb3cff02ac34602b323a2cbb6d9bdf/HexStudio/Icons/copy.ico -------------------------------------------------------------------------------- /HexStudio/Icons/cut.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zodiacon/HexStudio/079a3aca3ccb3cff02ac34602b323a2cbb6d9bdf/HexStudio/Icons/cut.ico -------------------------------------------------------------------------------- /HexStudio/Icons/delete.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zodiacon/HexStudio/079a3aca3ccb3cff02ac34602b323a2cbb6d9bdf/HexStudio/Icons/delete.ico -------------------------------------------------------------------------------- /HexStudio/Icons/file.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zodiacon/HexStudio/079a3aca3ccb3cff02ac34602b323a2cbb6d9bdf/HexStudio/Icons/file.ico -------------------------------------------------------------------------------- /HexStudio/Icons/file_new.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zodiacon/HexStudio/079a3aca3ccb3cff02ac34602b323a2cbb6d9bdf/HexStudio/Icons/file_new.ico -------------------------------------------------------------------------------- /HexStudio/Icons/find.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zodiacon/HexStudio/079a3aca3ccb3cff02ac34602b323a2cbb6d9bdf/HexStudio/Icons/find.ico -------------------------------------------------------------------------------- /HexStudio/Icons/open.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zodiacon/HexStudio/079a3aca3ccb3cff02ac34602b323a2cbb6d9bdf/HexStudio/Icons/open.ico -------------------------------------------------------------------------------- /HexStudio/Icons/paste.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zodiacon/HexStudio/079a3aca3ccb3cff02ac34602b323a2cbb6d9bdf/HexStudio/Icons/paste.ico -------------------------------------------------------------------------------- /HexStudio/Icons/recycle.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zodiacon/HexStudio/079a3aca3ccb3cff02ac34602b323a2cbb6d9bdf/HexStudio/Icons/recycle.ico -------------------------------------------------------------------------------- /HexStudio/Icons/redo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zodiacon/HexStudio/079a3aca3ccb3cff02ac34602b323a2cbb6d9bdf/HexStudio/Icons/redo.ico -------------------------------------------------------------------------------- /HexStudio/Icons/save.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zodiacon/HexStudio/079a3aca3ccb3cff02ac34602b323a2cbb6d9bdf/HexStudio/Icons/save.ico -------------------------------------------------------------------------------- /HexStudio/Icons/save_as.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zodiacon/HexStudio/079a3aca3ccb3cff02ac34602b323a2cbb6d9bdf/HexStudio/Icons/save_as.ico -------------------------------------------------------------------------------- /HexStudio/Icons/selection_delete.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zodiacon/HexStudio/079a3aca3ccb3cff02ac34602b323a2cbb6d9bdf/HexStudio/Icons/selection_delete.ico -------------------------------------------------------------------------------- /HexStudio/Icons/undo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zodiacon/HexStudio/079a3aca3ccb3cff02ac34602b323a2cbb6d9bdf/HexStudio/Icons/undo.ico -------------------------------------------------------------------------------- /HexStudio/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  13 | 14 | 15 | -------------------------------------------------------------------------------- /HexStudio/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using HexStudio.ViewModels; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Data; 10 | using System.Windows.Documents; 11 | using System.Windows.Input; 12 | using System.Windows.Media; 13 | using System.Windows.Media.Imaging; 14 | using System.Windows.Navigation; 15 | using System.Windows.Shapes; 16 | 17 | namespace HexStudio { 18 | /// 19 | /// Interaction logic for MainWindow.xaml 20 | /// 21 | public partial class MainWindow { 22 | public MainWindow() { 23 | InitializeComponent(); 24 | } 25 | 26 | private void MetroWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) { 27 | if (!((MainViewModel)DataContext).QueryCloseAll()) 28 | e.Cancel = true; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /HexStudio/Models/RgbColor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace HexStudio.Models { 8 | public class RgbColor { 9 | public byte R { get; set; } 10 | public byte G { get; set; } 11 | public byte B { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /HexStudio/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("HexStudio")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("HexStudio")] 15 | [assembly: AssemblyCopyright("Copyright © 2016")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /HexStudio/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace HexStudio.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HexStudio.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /HexStudio/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /HexStudio/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace HexStudio.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /HexStudio/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /HexStudio/Resources/Templates.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /HexStudio/Settings.cs: -------------------------------------------------------------------------------- 1 | using HexStudio.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace HexStudio { 9 | public class Settings { 10 | public List RecentFiles { get; set; } 11 | public RgbColor TextForeground { get; set; } 12 | public double FontSize { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /HexStudio/TabControlProperties.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Data; 10 | using System.Windows.Markup; 11 | 12 | namespace HexStudio { 13 | public static class TabControlProperties { 14 | public static bool GetIsCached(DependencyObject obj) { 15 | return (bool)obj.GetValue(IsCachedProperty); 16 | } 17 | 18 | public static void SetIsCached(DependencyObject obj, bool value) { 19 | obj.SetValue(IsCachedProperty, value); 20 | } 21 | 22 | /// 23 | /// Controls whether tab content is cached or not 24 | /// 25 | /// When TabContent.IsCached is true, visual state of each tab is preserved (cached), even when the tab is hidden 26 | public static readonly DependencyProperty IsCachedProperty = 27 | DependencyProperty.RegisterAttached("IsCached", typeof(bool), typeof(TabControlProperties), new UIPropertyMetadata(false, OnIsCachedChanged)); 28 | 29 | 30 | public static DataTemplate GetTemplate(DependencyObject obj) { 31 | return (DataTemplate)obj.GetValue(TemplateProperty); 32 | } 33 | 34 | public static void SetTemplate(DependencyObject obj, DataTemplate value) { 35 | obj.SetValue(TemplateProperty, value); 36 | } 37 | 38 | /// 39 | /// Used instead of TabControl.ContentTemplate for cached tabs 40 | /// 41 | public static readonly DependencyProperty TemplateProperty = 42 | DependencyProperty.RegisterAttached("Template", typeof(DataTemplate), typeof(TabControlProperties), new UIPropertyMetadata(null)); 43 | 44 | 45 | public static DataTemplateSelector GetTemplateSelector(DependencyObject obj) { 46 | return (DataTemplateSelector)obj.GetValue(TemplateSelectorProperty); 47 | } 48 | 49 | public static void SetTemplateSelector(DependencyObject obj, DataTemplateSelector value) { 50 | obj.SetValue(TemplateSelectorProperty, value); 51 | } 52 | 53 | /// 54 | /// Used instead of TabControl.ContentTemplateSelector for cached tabs 55 | /// 56 | public static readonly DependencyProperty TemplateSelectorProperty = 57 | DependencyProperty.RegisterAttached("TemplateSelector", typeof(DataTemplateSelector), typeof(TabControlProperties), new UIPropertyMetadata(null)); 58 | 59 | [EditorBrowsable(EditorBrowsableState.Never)] 60 | public static TabControl GetInternalTabControl(DependencyObject obj) { 61 | return (TabControl)obj.GetValue(InternalTabControlProperty); 62 | } 63 | 64 | [EditorBrowsable(EditorBrowsableState.Never)] 65 | public static void SetInternalTabControl(DependencyObject obj, TabControl value) { 66 | obj.SetValue(InternalTabControlProperty, value); 67 | } 68 | 69 | // Using a DependencyProperty as the backing store for InternalTabControl. This enables animation, styling, binding, etc... 70 | [EditorBrowsable(EditorBrowsableState.Never)] 71 | public static readonly DependencyProperty InternalTabControlProperty = 72 | DependencyProperty.RegisterAttached("InternalTabControl", typeof(TabControl), typeof(TabControlProperties), new UIPropertyMetadata(null, OnInternalTabControlChanged)); 73 | 74 | 75 | [EditorBrowsable(EditorBrowsableState.Never)] 76 | public static ContentControl GetInternalCachedContent(DependencyObject obj) { 77 | return (ContentControl)obj.GetValue(InternalCachedContentProperty); 78 | } 79 | 80 | [EditorBrowsable(EditorBrowsableState.Never)] 81 | public static void SetInternalCachedContent(DependencyObject obj, ContentControl value) { 82 | obj.SetValue(InternalCachedContentProperty, value); 83 | } 84 | 85 | // Using a DependencyProperty as the backing store for InternalCachedContent. This enables animation, styling, binding, etc... 86 | [EditorBrowsable(EditorBrowsableState.Never)] 87 | public static readonly DependencyProperty InternalCachedContentProperty = 88 | DependencyProperty.RegisterAttached("InternalCachedContent", typeof(ContentControl), typeof(TabControlProperties), new UIPropertyMetadata(null)); 89 | 90 | [EditorBrowsable(EditorBrowsableState.Never)] 91 | public static object GetInternalContentManager(DependencyObject obj) { 92 | return (object)obj.GetValue(InternalContentManagerProperty); 93 | } 94 | 95 | [EditorBrowsable(EditorBrowsableState.Never)] 96 | public static void SetInternalContentManager(DependencyObject obj, object value) { 97 | obj.SetValue(InternalContentManagerProperty, value); 98 | } 99 | 100 | // Using a DependencyProperty as the backing store for InternalContentManager. This enables animation, styling, binding, etc... 101 | public static readonly DependencyProperty InternalContentManagerProperty = 102 | DependencyProperty.RegisterAttached("InternalContentManager", typeof(object), typeof(TabControlProperties), new UIPropertyMetadata(null)); 103 | 104 | private static void OnIsCachedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { 105 | if (obj == null) return; 106 | 107 | var tabControl = obj as TabControl; 108 | if (tabControl == null) { 109 | throw new InvalidOperationException("Cannot set TabContent.IsCached on object of type " + args.NewValue.GetType().Name + 110 | ". Only objects of type TabControl can have TabContent.IsCached property."); 111 | } 112 | 113 | bool newValue = (bool)args.NewValue; 114 | 115 | if (!newValue) { 116 | if (args.OldValue != null && ((bool)args.OldValue)) { 117 | throw new NotImplementedException("Cannot change TabContent.IsCached from True to False. Turning tab caching off is not implemented"); 118 | } 119 | 120 | return; 121 | } 122 | 123 | EnsureContentTemplateIsNull(tabControl); 124 | tabControl.ContentTemplate = CreateContentTemplate(); 125 | EnsureContentTemplateIsNotModified(tabControl); 126 | } 127 | 128 | private static DataTemplate CreateContentTemplate() { 129 | const string xaml = 130 | ""; 131 | 132 | var context = new ParserContext(); 133 | 134 | context.XamlTypeMapper = new XamlTypeMapper(new string[0]); 135 | context.XamlTypeMapper.AddMappingProcessingInstruction("b", typeof(TabControlProperties).Namespace, typeof(TabControlProperties).Assembly.FullName); 136 | 137 | context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation"); 138 | context.XmlnsDictionary.Add("b", "b"); 139 | 140 | var template = (DataTemplate)XamlReader.Parse(xaml, context); 141 | return template; 142 | } 143 | 144 | private static void OnInternalTabControlChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { 145 | if (obj == null) return; 146 | var container = obj as Decorator; 147 | 148 | if (container == null) { 149 | var message = "Cannot set TabContent.InternalTabControl on object of type " + obj.GetType().Name + 150 | ". Only controls that derive from Decorator, such as Border can have a TabContent.InternalTabControl."; 151 | throw new InvalidOperationException(message); 152 | } 153 | 154 | if (args.NewValue == null) return; 155 | if (!(args.NewValue is TabControl)) { 156 | throw new InvalidOperationException("Value of TabContent.InternalTabControl cannot be of type " + args.NewValue.GetType().Name + ", it must be of type TabControl"); 157 | } 158 | 159 | var tabControl = (TabControl)args.NewValue; 160 | var contentManager = GetContentManager(tabControl, container); 161 | contentManager.UpdateSelectedTab(); 162 | } 163 | 164 | private static ContentManager GetContentManager(TabControl tabControl, Decorator container) { 165 | var contentManager = (ContentManager)GetInternalContentManager(tabControl); 166 | if (contentManager != null) { 167 | /* 168 | * Content manager already exists for the tab control. This means that tab content template is applied 169 | * again, and new instance of the Border control (container) has been created. The old container 170 | * referenced by the content manager is no longer visible and needs to be replaced 171 | */ 172 | contentManager.ReplaceContainer(container); 173 | } 174 | else { 175 | // create content manager for the first time 176 | contentManager = new ContentManager(tabControl, container); 177 | SetInternalContentManager(tabControl, contentManager); 178 | } 179 | 180 | return contentManager; 181 | } 182 | 183 | private static void EnsureContentTemplateIsNull(TabControl tabControl) { 184 | if (tabControl.ContentTemplate != null) { 185 | throw new InvalidOperationException("TabControl.ContentTemplate value is not null. If TabContent.IsCached is True, use TabContent.Template instead of ContentTemplate"); 186 | } 187 | } 188 | 189 | private static void EnsureContentTemplateIsNotModified(TabControl tabControl) { 190 | var descriptor = DependencyPropertyDescriptor.FromProperty(TabControl.ContentTemplateProperty, typeof(TabControl)); 191 | descriptor.AddValueChanged(tabControl, (sender, args) => 192 | { 193 | throw new InvalidOperationException("Cannot assign to TabControl.ContentTemplate when TabContent.IsCached is True. Use TabContent.Template instead"); 194 | }); 195 | } 196 | 197 | public class ContentManager { 198 | TabControl _tabControl; 199 | Decorator _border; 200 | 201 | public ContentManager(TabControl tabControl, Decorator border) { 202 | _tabControl = tabControl; 203 | _border = border; 204 | _tabControl.SelectionChanged += (sender, args) => { UpdateSelectedTab(); }; 205 | } 206 | 207 | public void ReplaceContainer(Decorator newBorder) { 208 | if (Object.ReferenceEquals(_border, newBorder)) return; 209 | 210 | _border.Child = null; // detach any tab content that old border may hold 211 | _border = newBorder; 212 | } 213 | 214 | public void UpdateSelectedTab() { 215 | _border.Child = GetCurrentContent(); 216 | } 217 | 218 | private ContentControl GetCurrentContent() { 219 | var item = _tabControl.SelectedItem; 220 | if (item == null) return null; 221 | 222 | var tabItem = _tabControl.ItemContainerGenerator.ContainerFromItem(item); 223 | if (tabItem == null) return null; 224 | 225 | var cachedContent = TabControlProperties.GetInternalCachedContent(tabItem); 226 | if (cachedContent == null) { 227 | cachedContent = new ContentControl { 228 | DataContext = item, 229 | ContentTemplate = TabControlProperties.GetTemplate(_tabControl), 230 | ContentTemplateSelector = TabControlProperties.GetTemplateSelector(_tabControl) 231 | }; 232 | 233 | cachedContent.SetBinding(ContentControl.ContentProperty, new Binding()); 234 | TabControlProperties.SetInternalCachedContent(tabItem, cachedContent); 235 | } 236 | 237 | return cachedContent; 238 | } 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /HexStudio/ViewModels/FindDialogViewModel.cs: -------------------------------------------------------------------------------- 1 | using Prism.Commands; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows; 9 | using System.Windows.Input; 10 | using Zodiacon.HexEditControl; 11 | using Zodiacon.WPF; 12 | 13 | namespace HexStudio.ViewModels { 14 | sealed class FindDialogViewModel : DialogViewModelBase { 15 | MainViewModel _mainViewModel; 16 | ByteFinder _finder; 17 | 18 | public FindDialogViewModel(Window dialog, MainViewModel mainViewModel) : base(dialog) { 19 | _mainViewModel = mainViewModel; 20 | 21 | SearchCommand = new DelegateCommand(() => { 22 | byte[] bytes; 23 | if (IsBytesSearch) 24 | bytes = HexEdit.GetBytes(0, (int)HexEdit.Size); 25 | else { 26 | Encoding encoding; 27 | if (IsAscii) 28 | encoding = Encoding.ASCII; 29 | else if (IsUTF8) 30 | encoding = Encoding.UTF8; 31 | else 32 | encoding = Encoding.Unicode; 33 | bytes = encoding.GetBytes(SearchString); 34 | } 35 | 36 | // initiate search 37 | 38 | var files = IsSearchFile ? Enumerable.Range(0, 1).Select(_ => _mainViewModel.SelectedFile) : _mainViewModel.OpenFiles; 39 | _finder = new ByteFinder(files, bytes, ByteFinderOptions.FromStart); 40 | RaisePropertyChanged(nameof(FindResults)); 41 | 42 | }, () => IsStringSearch && !string.IsNullOrEmpty(SearchString) || IsBytesSearch && HexEdit?.Size > 0) 43 | .ObservesProperty(() => IsStringSearch).ObservesProperty(() => IsBytesSearch).ObservesProperty(() => SearchString); 44 | 45 | GoToFindLocationCommand = new DelegateCommand(result => { 46 | _mainViewModel.SelectedFile = result.OpenFile; 47 | result.Editor.CaretOffset = result.Offset; 48 | }, result => result != null); 49 | } 50 | 51 | public IEnumerable FindResults => _finder?.Find(); 52 | 53 | public double Width => 650.0; 54 | public SizeToContent SizeToContent => SizeToContent.WidthAndHeight; 55 | public string Title => "Find"; 56 | public string Icon => "/icons/find.ico"; 57 | public ResizeMode ResizeMode => ResizeMode.CanMinimize; 58 | 59 | public DelegateCommandBase SearchCommand { get; } 60 | public DelegateCommandBase GoToFindLocationCommand { get; } 61 | 62 | private bool _isBytesSearch = true; 63 | 64 | public bool IsBytesSearch { 65 | get { return _isBytesSearch; } 66 | set { SetProperty(ref _isBytesSearch, value); } 67 | } 68 | 69 | private bool _isStringSearch; 70 | 71 | public bool IsStringSearch { 72 | get { return _isStringSearch; } 73 | set { SetProperty(ref _isStringSearch, value); } 74 | } 75 | 76 | private bool _isAscii = true; 77 | 78 | public bool IsAscii { 79 | get { return _isAscii; } 80 | set { SetProperty(ref _isAscii, value); } 81 | } 82 | 83 | private bool _isUtf8; 84 | 85 | public bool IsUTF8 { 86 | get { return _isUtf8; } 87 | set { SetProperty(ref _isUtf8, value); } 88 | } 89 | 90 | private bool _isUtf16; 91 | 92 | public bool IsUTF16 { 93 | get { return _isUtf16; } 94 | set { SetProperty(ref _isUtf16, value); } 95 | } 96 | 97 | private string _searchString; 98 | 99 | public string SearchString { 100 | get { return _searchString; } 101 | set { SetProperty(ref _searchString, value); } 102 | } 103 | 104 | IHexEdit _hexEdit; 105 | public IHexEdit HexEdit { 106 | get { return _hexEdit; } 107 | set { 108 | _hexEdit = value; 109 | _hexEdit.BufferSizeChanged += delegate { SearchCommand.RaiseCanExecuteChanged(); }; 110 | if (_data != null) 111 | _hexEdit.SetData(_data); 112 | } 113 | } 114 | 115 | private bool _isSearchSelection; 116 | 117 | public bool IsSearchSelection { 118 | get { return _isSearchSelection; } 119 | set { SetProperty(ref _isSearchSelection, value); } 120 | } 121 | 122 | private bool _isSearchFile = true; 123 | 124 | public bool IsSearchFile { 125 | get { return _isSearchFile; } 126 | set { SetProperty(ref _isSearchFile, value); } 127 | } 128 | 129 | private bool _isSearchAllFiles; 130 | 131 | public bool IsSearchAllFiles { 132 | get { return _isSearchAllFiles; } 133 | set { SetProperty(ref _isSearchAllFiles, value); } 134 | } 135 | 136 | byte[] _data; 137 | protected override void OnClose(bool? result) { 138 | _data = HexEdit.GetBytes(0, (int)HexEdit.Size); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /HexStudio/ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | using HexStudio.Views; 2 | using Prism.Commands; 3 | using Prism.Mvvm; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Collections.ObjectModel; 7 | using System.ComponentModel.Composition; 8 | using System.Diagnostics; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Runtime.Serialization; 12 | using System.Text; 13 | using System.Threading.Tasks; 14 | using System.Windows; 15 | using System.Windows.Input; 16 | using System.Windows.Threading; 17 | using Zodiacon.WPF; 18 | 19 | namespace HexStudio.ViewModels { 20 | [Export] 21 | class MainViewModel : BindableBase { 22 | ObservableCollection _openFiles = new ObservableCollection(); 23 | ObservableCollection _recentFiles = new ObservableCollection(); 24 | Settings _settings; 25 | FindDialogViewModel _findDialogViewModel; 26 | 27 | public Settings Settings => _settings; 28 | 29 | #pragma warning disable 649 30 | [Import] 31 | public UIServicesDefaults UIServices; 32 | #pragma warning restore 649 33 | 34 | public IList RecentFiles => _recentFiles; 35 | 36 | public IFileDialogService FileDialogService => UIServices.FileDialogService; 37 | public IMessageBoxService MessageBoxService => UIServices.MessageBoxService; 38 | public IDialogService DialogService => UIServices.DialogService; 39 | 40 | public MainViewModel() { 41 | LoadSettings(); 42 | 43 | FindCommand = new DelegateCommand(() => { 44 | if (_findDialogViewModel != null) 45 | _findDialogViewModel.Show(); 46 | else { 47 | // find dialog 48 | _findDialogViewModel = DialogService.CreateDialog(this); 49 | _findDialogViewModel.Show(); 50 | } 51 | }, () => SelectedFile != null).ObservesProperty(() => SelectedFile); 52 | } 53 | 54 | public bool QueryCloseAll() { 55 | if (!OpenFiles.Any(file => file.IsModified)) 56 | return true; 57 | 58 | var result = MessageBoxService.ShowMessage("Save modified files before exit?", 59 | Constants.AppTitle, MessageBoxButton.YesNoCancel, MessageBoxImage.Warning); 60 | if (result == MessageBoxResult.Yes) { 61 | foreach (var file in OpenFiles) 62 | if (file.IsModified) 63 | file.SaveInternal(); 64 | return true; 65 | } 66 | 67 | return result == MessageBoxResult.No; 68 | } 69 | 70 | public static ICommand EmptyCommand = new DelegateCommand(() => { }, () => false); 71 | 72 | public ICommand ExitCommand => new DelegateCommand(() => Application.Current.Shutdown()); 73 | 74 | public ICommand NewFileCommand => new DelegateCommand(() => { 75 | var file = new OpenFileViewModel(this); 76 | OpenFiles.Add(file); 77 | SelectedFile = file; 78 | }); 79 | 80 | public IList OpenFiles => _openFiles; 81 | 82 | private OpenFileViewModel _selecetdFile; 83 | 84 | public OpenFileViewModel SelectedFile { 85 | get { return _selecetdFile; } 86 | set { 87 | if (SetProperty(ref _selecetdFile, value)) { 88 | RaisePropertyChanged(nameof(IsSelectedFile)); 89 | } 90 | } 91 | } 92 | 93 | public ICommand CloseAllCommand => new DelegateCommand(() => { 94 | var toClose = new List(4); 95 | 96 | foreach (var file in OpenFiles) 97 | if (!file.IsModified || file.QueryCloseFile()) 98 | toClose.Add(file); 99 | 100 | foreach (var file in toClose) 101 | OpenFiles.Remove(file); 102 | }, () => SelectedFile != null). 103 | ObservesProperty(() => SelectedFile); 104 | 105 | public ICommand OpenFileCommand => new DelegateCommand(() => { 106 | var filename = FileDialogService.GetFileForOpen(); 107 | if (filename == null) return; 108 | 109 | if (!File.Exists(filename)) { 110 | MessageBoxService.ShowMessage("File not found.", Constants.AppTitle); 111 | return; 112 | } 113 | 114 | OpenFileInternal(filename); 115 | }); 116 | 117 | public ICommand SaveFileCommand => new DelegateCommand(file => file.SaveFile()).ObservesProperty(() => SelectedFile); 118 | 119 | public void CloseFile(OpenFileViewModel file) { 120 | int index = OpenFiles.IndexOf(file); 121 | Debug.Assert(index >= 0); 122 | file.Dispose(); 123 | OpenFiles.RemoveAt(index); 124 | if (--index < 0) 125 | index = 0; 126 | if (OpenFiles.Count > 0) 127 | SelectedFile = OpenFiles[index]; 128 | } 129 | 130 | public ICommand OpenRecentFileCommand => new DelegateCommand(filename => OpenFileInternal(filename)); 131 | 132 | private void OpenFileInternal(string filename) { 133 | var exitingFile = _openFiles.FirstOrDefault(openfile => openfile.FileName == filename); 134 | if (exitingFile != null) { 135 | SelectedFile = exitingFile; 136 | return; 137 | } 138 | 139 | var file = new OpenFileViewModel(this); 140 | file.Ready += delegate { 141 | Dispatcher.CurrentDispatcher.InvokeAsync(() => { 142 | try { 143 | file.OpenFile(filename); 144 | } 145 | catch (Exception ex) { 146 | OpenFiles.Remove(file); 147 | MessageBoxService.ShowMessage($"Error: {ex.Message}", Constants.AppTitle); 148 | } 149 | }); 150 | }; 151 | OpenFiles.Add(file); 152 | _recentFiles.Remove(filename); 153 | _recentFiles.Insert(0, filename); 154 | if (_recentFiles.Count > 10) 155 | _recentFiles.RemoveAt(9); 156 | SelectedFile = file; 157 | } 158 | 159 | string GetSettingsFilename() { 160 | return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\HexStudio.settings"; 161 | } 162 | 163 | public void SaveSettings() { 164 | using (var stm = File.Open(GetSettingsFilename(), FileMode.Create)) { 165 | _settings.RecentFiles = _recentFiles.ToList(); 166 | var serializer = new DataContractSerializer(typeof(Settings)); 167 | serializer.WriteObject(stm, _settings); 168 | } 169 | } 170 | 171 | public void LoadSettings() { 172 | try { 173 | using (var stm = File.Open(GetSettingsFilename(), FileMode.Open)) { 174 | var serializer = new DataContractSerializer(typeof(Settings)); 175 | _settings = (Settings)serializer.ReadObject(stm); 176 | _recentFiles = new ObservableCollection(_settings.RecentFiles); 177 | } 178 | } 179 | catch { } 180 | finally { 181 | if (_settings == null) 182 | _settings = new Settings(); 183 | } 184 | } 185 | 186 | public bool IsSelectedFile => SelectedFile != null; 187 | 188 | public ICommand FindCommand { get; } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /HexStudio/ViewModels/OpenFileViewModel.cs: -------------------------------------------------------------------------------- 1 | using Prism.Commands; 2 | using Prism.Mvvm; 3 | using System.IO; 4 | using System; 5 | using System.Windows; 6 | using Zodiacon.HexEditControl; 7 | 8 | namespace HexStudio.ViewModels { 9 | class OpenFileViewModel : BindableBase, IDisposable { 10 | public DelegateCommandBase SaveFileCommand { get; } 11 | public DelegateCommandBase SaveAsFileCommand { get; } 12 | public DelegateCommandBase RevertFileCommand { get; } 13 | public DelegateCommandBase CloseCommand { get; } 14 | 15 | MainViewModel _mainViewModel; 16 | 17 | public event EventHandler Ready; 18 | 19 | public long? Size => _editor?.Buffer.Size; 20 | 21 | private long _offset; 22 | 23 | static int[] _bytesPerLines = { 8, 16, 24, 32, 48, 64, 96, 128 }; 24 | 25 | public int[] BytesPerLines => _bytesPerLines; 26 | 27 | private int _bytesPerLine = 32; 28 | 29 | public int BytesPerLine { 30 | get { return _bytesPerLine; } 31 | set { 32 | if (SetProperty(ref _bytesPerLine, value)) { 33 | _editor.BytesPerLine = value; 34 | } 35 | } 36 | } 37 | 38 | public long Offset { 39 | get { return _offset; } 40 | set { SetProperty(ref _offset, value); } 41 | } 42 | 43 | public OpenFileViewModel(MainViewModel mainViewModel) { 44 | _mainViewModel = mainViewModel; 45 | 46 | SaveFileCommand = new DelegateCommand(SaveInternal, () => IsModified).ObservesProperty(() => IsModified); 47 | SaveAsFileCommand = new DelegateCommand(SaveAsInternal); 48 | 49 | RevertFileCommand = new DelegateCommand(() => { 50 | _editor.DiscardChanges(); 51 | }, () => IsModified && FileName != null).ObservesProperty(() => IsModified).ObservesProperty(() => FileName); 52 | 53 | CloseCommand = new DelegateCommand(() => { 54 | if (IsModified) { 55 | MessageBoxResult reply = QuerySaveFile(); 56 | if (reply == MessageBoxResult.Cancel) 57 | return; 58 | if (reply == MessageBoxResult.Yes) 59 | SaveInternal(); 60 | } 61 | _mainViewModel.CloseFile(this); 62 | }); 63 | } 64 | 65 | private MessageBoxResult QuerySaveFile() { 66 | return _mainViewModel.MessageBoxService.ShowMessage("File modified. Save before close?", 67 | Constants.AppTitle, MessageBoxButton.YesNoCancel, MessageBoxImage.Question); 68 | } 69 | 70 | public void OpenFile(string filename) { 71 | _editor.OpenFile(filename); 72 | FileName = filename; 73 | _editor.Buffer.SizeChanged += _editor_SizeChanged; 74 | RaisePropertyChanged(nameof(Size)); 75 | } 76 | 77 | private string _filename; 78 | 79 | public string FileName { 80 | get { return _filename; } 81 | set { 82 | if (SetProperty(ref _filename, value)) { 83 | RaisePropertyChanged(nameof(Title)); 84 | } 85 | } 86 | } 87 | 88 | public void SaveInternal() { 89 | if (FileName == null) 90 | SaveAsInternal(); 91 | else 92 | _editor.SaveChanges(); 93 | } 94 | 95 | private void SaveAsInternal() { 96 | var filename = _mainViewModel.FileDialogService.GetFileForSave(); 97 | if (filename == null) return; 98 | 99 | _editor.SaveChangesAs(filename); 100 | FileName = filename; 101 | RaisePropertyChanged(nameof(Title)); 102 | } 103 | 104 | public string Title => (FileName == null ? "Untitled" : Path.GetFileName(FileName)) + (IsModified ? " *" : string.Empty); 105 | 106 | private bool _isReadOnly; 107 | 108 | public bool IsReadOnly { 109 | get { return _isReadOnly; } 110 | set { SetProperty(ref _isReadOnly, value); } 111 | } 112 | 113 | public bool QueryCloseFile() { 114 | var reply = QuerySaveFile(); 115 | if (reply == MessageBoxResult.Yes) { 116 | SaveInternal(); 117 | return true; 118 | } 119 | return reply == MessageBoxResult.No; 120 | } 121 | 122 | private int _wordSize = 1; 123 | 124 | public int WordSize { 125 | get { return _wordSize; } 126 | set { SetProperty(ref _wordSize, value); } 127 | } 128 | 129 | private bool _isModified; 130 | 131 | public void SaveFile() { 132 | SaveInternal(); 133 | } 134 | 135 | public bool IsModified { 136 | get { return _isModified; } 137 | set { 138 | if (SetProperty(ref _isModified, value)) { 139 | RaisePropertyChanged(nameof(Title)); 140 | } 141 | } 142 | } 143 | 144 | public IHexEdit HexEditor => _editor; 145 | 146 | HexEdit _editor; 147 | internal void SetHexEdit(HexEdit hexEdit) { 148 | _editor = hexEdit; 149 | _editor.BytesPerLine = BytesPerLine; 150 | _editor.Buffer.SizeChanged += _editor_SizeChanged; 151 | Ready?.Invoke(this, EventArgs.Empty); 152 | _editor.CaretPositionChanged += _editor_CaretPositionChanged; 153 | } 154 | 155 | private void _editor_CaretPositionChanged(object sender, RoutedEventArgs e) { 156 | Offset = _editor.CaretOffset; 157 | } 158 | 159 | private void _editor_SizeChanged(long oldSize, long newSize) { 160 | RaisePropertyChanged(nameof(Size)); 161 | } 162 | 163 | public void Dispose() { 164 | _editor.Dispose(); 165 | } 166 | 167 | private bool _is1Byte = true; 168 | 169 | public bool Is1Byte { 170 | get { return _is1Byte; } 171 | set { 172 | if (SetProperty(ref _is1Byte, value) && value) { 173 | WordSize = 1; 174 | } 175 | } 176 | } 177 | 178 | private bool _is2Byte; 179 | 180 | public bool Is2Byte { 181 | get { return _is2Byte; } 182 | set { 183 | if (SetProperty(ref _is2Byte, value) && value) { 184 | WordSize = 2; 185 | } 186 | } 187 | } 188 | 189 | private bool _is4Byte; 190 | 191 | public bool Is4Byte { 192 | get { return _is4Byte; } 193 | set { 194 | if (SetProperty(ref _is4Byte, value) && value) { 195 | WordSize = 4; 196 | } 197 | } 198 | } 199 | 200 | private bool _is8Byte; 201 | 202 | public bool Is8Byte { 203 | get { return _is8Byte; } 204 | set { 205 | if (SetProperty(ref _is8Byte, value) && value) { 206 | WordSize = 8; 207 | } 208 | } 209 | } 210 | 211 | private bool _overwriteMode; 212 | 213 | public bool OverwriteMode { 214 | get { return _overwriteMode; } 215 | set { SetProperty(ref _overwriteMode, value); } 216 | } 217 | 218 | } 219 | } -------------------------------------------------------------------------------- /HexStudio/Views/FindDialogView.xaml: -------------------------------------------------------------------------------- 1 |  12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 85 | 88 | 91 | 94 | 97 | 98 | 99 | 102 | 103 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /HexStudio/Views/MainView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace HexStudio.Views { 17 | /// 18 | /// Interaction logic for MainView.xaml 19 | /// 20 | public partial class MainView : UserControl { 21 | public MainView() { 22 | InitializeComponent(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /HexStudio/Views/OpenFileView.xaml: -------------------------------------------------------------------------------- 1 |  12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /HexStudio/Views/OpenFileView.xaml.cs: -------------------------------------------------------------------------------- 1 | using HexStudio.ViewModels; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Data; 10 | using System.Windows.Documents; 11 | using System.Windows.Input; 12 | using System.Windows.Media; 13 | using System.Windows.Media.Imaging; 14 | using System.Windows.Navigation; 15 | using System.Windows.Shapes; 16 | 17 | namespace HexStudio.Views { 18 | /// 19 | /// Interaction logic for OpenFileView.xaml 20 | /// 21 | public partial class OpenFileView : UserControl { 22 | public OpenFileView() { 23 | InitializeComponent(); 24 | 25 | DataContextChanged += OpenFileView_DataContextChanged; 26 | } 27 | 28 | private void OpenFileView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) { 29 | ((OpenFileViewModel)DataContext).SetHexEdit(_hexEdit); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /HexStudio/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HexStudio 2 | Hex Studio is a work in progress Hex viewer and editor. 3 | --------------------------------------------------------------------------------