├── .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 |
48 |
49 |
50 |
51 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/HexStudio/Views/FindDialogView.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.Shapes;
14 |
15 | namespace HexStudio.Views {
16 | ///
17 | /// Interaction logic for FindDialog.xaml
18 | ///
19 | public partial class FindDialogView {
20 | public FindDialogView() {
21 | InitializeComponent();
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/HexStudio/Views/GenericWindow.xaml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
--------------------------------------------------------------------------------
/HexStudio/Views/GenericWindow.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.Shapes;
14 |
15 | namespace HexStudio.Views {
16 | ///
17 | /// Interaction logic for GenericWindow.xaml
18 | ///
19 | public partial class GenericWindow {
20 | public GenericWindow() {
21 | InitializeComponent();
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/HexStudio/Views/MainView.xaml:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
76 |
77 |
78 |
81 |
82 |
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 |
--------------------------------------------------------------------------------