├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml ├── stale.yml └── workflows │ └── build.yml ├── .gitignore ├── Directory.Build.props ├── HexView.Avalonia ├── Controls │ └── HexViewControl.cs ├── HexView.Avalonia.csproj ├── Model │ ├── IHexFormatter.cs │ └── ILineReader.cs ├── Properties │ └── AssemblyInfo.cs └── Services │ ├── HexFormatter.cs │ └── MemoryMappedLineReader.cs ├── HexView.Desktop ├── HexView.Desktop.csproj └── Program.cs ├── HexView.sln ├── HexView ├── App.axaml ├── App.axaml.cs ├── Assets │ └── RobotoMono-Regular.ttf ├── HexView.csproj └── Views │ ├── DiffView.axaml │ ├── DiffView.axaml.cs │ ├── MainView.axaml │ ├── MainView.axaml.cs │ ├── MainWindow.axaml │ ├── MainWindow.axaml.cs │ ├── SingleView.axaml │ └── SingleView.axaml.cs ├── LICENSE.md ├── NuGet.Config ├── README.md └── global.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Default settings: 7 | # A newline ending every file 8 | # Use 4 spaces as indentation 9 | [*] 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 4 13 | 14 | # C# files 15 | [*.cs] 16 | # New line preferences 17 | csharp_new_line_before_open_brace = all 18 | csharp_new_line_before_else = true 19 | csharp_new_line_before_catch = true 20 | csharp_new_line_before_finally = true 21 | csharp_new_line_before_members_in_object_initializers = true 22 | csharp_new_line_before_members_in_anonymous_types = true 23 | csharp_new_line_between_query_expression_clauses = true 24 | 25 | # Indentation preferences 26 | csharp_indent_block_contents = true 27 | csharp_indent_braces = false 28 | csharp_indent_case_contents = true 29 | csharp_indent_switch_labels = true 30 | csharp_indent_labels = one_less_than_current 31 | 32 | # avoid this. unless absolutely necessary 33 | dotnet_style_qualification_for_field = false:suggestion 34 | dotnet_style_qualification_for_property = false:suggestion 35 | dotnet_style_qualification_for_method = false:suggestion 36 | dotnet_style_qualification_for_event = false:suggestion 37 | 38 | # prefer var 39 | csharp_style_var_for_built_in_types = true 40 | csharp_style_var_when_type_is_apparent = true 41 | csharp_style_var_elsewhere = true:suggestion 42 | 43 | # use language keywords instead of BCL types 44 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 45 | dotnet_style_predefined_type_for_member_access = true:suggestion 46 | 47 | # name all constant fields using PascalCase 48 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion 49 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields 50 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style 51 | 52 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 53 | dotnet_naming_symbols.constant_fields.required_modifiers = const 54 | 55 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 56 | 57 | # static fields should have s_ prefix 58 | dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion 59 | dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields 60 | dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style 61 | 62 | dotnet_naming_symbols.static_fields.applicable_kinds = field 63 | dotnet_naming_symbols.static_fields.required_modifiers = static 64 | 65 | dotnet_naming_style.static_prefix_style.required_prefix = s_ 66 | dotnet_naming_style.static_prefix_style.capitalization = camel_case 67 | 68 | # internal and private fields should be _camelCase 69 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion 70 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields 71 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style 72 | 73 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field 74 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal 75 | 76 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _ 77 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case 78 | 79 | # use accessibility modifiers 80 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 81 | 82 | # Code style defaults 83 | dotnet_sort_system_directives_first = true 84 | csharp_preserve_single_line_blocks = true 85 | csharp_preserve_single_line_statements = false 86 | 87 | # Expression-level preferences 88 | dotnet_style_object_initializer = true:suggestion 89 | dotnet_style_collection_initializer = true:suggestion 90 | dotnet_style_explicit_tuple_names = true:suggestion 91 | dotnet_style_coalesce_expression = true:suggestion 92 | dotnet_style_null_propagation = true:suggestion 93 | 94 | # Expression-bodied members 95 | csharp_style_expression_bodied_methods = false:none 96 | csharp_style_expression_bodied_constructors = false:none 97 | csharp_style_expression_bodied_operators = false:none 98 | csharp_style_expression_bodied_properties = true:none 99 | csharp_style_expression_bodied_indexers = true:none 100 | csharp_style_expression_bodied_accessors = true:none 101 | 102 | # Pattern matching 103 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 104 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 105 | csharp_style_inlined_variable_declaration = true:suggestion 106 | 107 | # Null checking preferences 108 | csharp_style_throw_expression = true:suggestion 109 | csharp_style_conditional_delegate_call = true:suggestion 110 | 111 | # Space preferences 112 | csharp_space_after_cast = false 113 | csharp_space_after_colon_in_inheritance_clause = true 114 | csharp_space_after_comma = true 115 | csharp_space_after_dot = false 116 | csharp_space_after_keywords_in_control_flow_statements = true 117 | csharp_space_after_semicolon_in_for_statement = true 118 | csharp_space_around_binary_operators = before_and_after 119 | csharp_space_around_declaration_statements = do_not_ignore 120 | csharp_space_before_colon_in_inheritance_clause = true 121 | csharp_space_before_comma = false 122 | csharp_space_before_dot = false 123 | csharp_space_before_open_square_brackets = false 124 | csharp_space_before_semicolon_in_for_statement = false 125 | csharp_space_between_empty_square_brackets = false 126 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 127 | csharp_space_between_method_call_name_and_opening_parenthesis = false 128 | csharp_space_between_method_call_parameter_list_parentheses = false 129 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 130 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 131 | csharp_space_between_method_declaration_parameter_list_parentheses = false 132 | csharp_space_between_parentheses = false 133 | csharp_space_between_square_brackets = false 134 | 135 | # Xaml files 136 | [*.{xaml,axaml}] 137 | indent_style = space 138 | indent_size = 2 139 | 140 | # Xml project files 141 | [*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] 142 | indent_size = 2 143 | 144 | # Xml build files 145 | [*.builds] 146 | indent_size = 2 147 | 148 | # Xml files 149 | [*.{xml,stylecop,resx,ruleset}] 150 | indent_size = 2 151 | 152 | # Xml config files 153 | [*.{props,targets,config,nuspec}] 154 | indent_size = 2 155 | 156 | # Shell scripts 157 | [*.sh] 158 | end_of_line = lf 159 | [*.{cmd, bat}] 160 | end_of_line = crlf 161 | 162 | # Yaml files 163 | [*.yml] 164 | indent_style = space 165 | indent_size = 2 166 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [wieslawsoltes] 2 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # https://probot.github.io/apps/stale/ 2 | 3 | # Number of days of inactivity before an issue becomes stale 4 | daysUntilStale: 60 5 | 6 | # Number of days of inactivity before a stale issue is closed 7 | daysUntilClose: 7 8 | 9 | # Issues with these labels will never be considered stale 10 | exemptLabels: 11 | - pinned 12 | - security 13 | 14 | # Label to use when marking an issue as stale 15 | staleLabel: stale 16 | 17 | # Comment to post when marking an issue as stale. Set to `false` to disable 18 | markComment: > 19 | This issue has been automatically marked as stale because it has not had 20 | recent activity. It will be closed if no further activity occurs. Thank you 21 | for your contributions. 22 | 23 | # Comment to post when closing a stale issue. Set to `false` to disable 24 | closeComment: true 25 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - release/* 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | build: 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, windows-latest, macos-latest] 17 | name: Build ${{ matrix.os }} 18 | runs-on: ${{ matrix.os }} 19 | 20 | steps: 21 | - uses: actions/checkout@v1 22 | - name: Setup .NET Core 23 | uses: actions/setup-dotnet@v1 24 | - name: Build Release 25 | run: dotnet build --configuration Release 26 | - name: Test Release 27 | run: dotnet test --configuration Release 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | 290 | # macOS 291 | .DS_Store 292 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 11.0.0 4 | 5 | 6 | -------------------------------------------------------------------------------- /HexView.Avalonia/Controls/HexViewControl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text; 4 | using Avalonia; 5 | using Avalonia.Controls; 6 | using Avalonia.Controls.Documents; 7 | using Avalonia.Controls.Primitives; 8 | using Avalonia.Input; 9 | using Avalonia.Interactivity; 10 | using Avalonia.Media; 11 | using HexView.Avalonia.Model; 12 | 13 | namespace HexView.Avalonia.Controls; 14 | 15 | public class HexViewControl : Control, ILogicalScrollable 16 | { 17 | public static readonly StyledProperty ToBaseProperty = 18 | AvaloniaProperty.Register(nameof(ToBase), defaultValue: 16); 19 | 20 | public static readonly StyledProperty BytesWidthProperty = 21 | AvaloniaProperty.Register(nameof(BytesWidth), defaultValue: 8); 22 | 23 | private volatile bool _updating; 24 | private Size _extent; 25 | private Size _viewport; 26 | private Vector _offset; 27 | private bool _canHorizontallyScroll; 28 | private bool _canVerticallyScroll; 29 | private EventHandler? _scrollInvalidated; 30 | private Typeface _typeface; 31 | private double _lineHeight; 32 | private FontFamily? _fontFamily; 33 | private double _fontSize; 34 | private IBrush? _foreground; 35 | private Size _scrollSize = new(1, 1); 36 | private Size _pageScrollSize = new(10, 10); 37 | 38 | public int ToBase 39 | { 40 | get => GetValue(ToBaseProperty); 41 | set => SetValue(ToBaseProperty, value); 42 | } 43 | 44 | public int BytesWidth 45 | { 46 | get => GetValue(BytesWidthProperty); 47 | set => SetValue(BytesWidthProperty, value); 48 | } 49 | 50 | public IHexFormatter? HexFormatter { get; set; } 51 | 52 | public ILineReader? LineReader { get; set; } 53 | 54 | Size IScrollable.Extent => _extent; 55 | 56 | Vector IScrollable.Offset 57 | { 58 | get => _offset; 59 | set 60 | { 61 | if (_updating) 62 | { 63 | return; 64 | } 65 | _updating = true; 66 | _offset = CoerceOffset(value); 67 | InvalidateScrollable(); 68 | _updating = false; 69 | } 70 | } 71 | 72 | Size IScrollable.Viewport => _viewport; 73 | 74 | bool ILogicalScrollable.CanHorizontallyScroll 75 | { 76 | get => _canHorizontallyScroll; 77 | set 78 | { 79 | _canHorizontallyScroll = value; 80 | InvalidateMeasure(); 81 | } 82 | } 83 | 84 | bool ILogicalScrollable.CanVerticallyScroll 85 | { 86 | get => _canVerticallyScroll; 87 | set 88 | { 89 | _canVerticallyScroll = value; 90 | InvalidateMeasure(); 91 | } 92 | } 93 | 94 | bool ILogicalScrollable.IsLogicalScrollEnabled => true; 95 | 96 | event EventHandler? ILogicalScrollable.ScrollInvalidated 97 | { 98 | add => _scrollInvalidated += value; 99 | remove => _scrollInvalidated -= value; 100 | } 101 | 102 | Size ILogicalScrollable.ScrollSize => _scrollSize; 103 | 104 | Size ILogicalScrollable.PageScrollSize => _pageScrollSize; 105 | 106 | bool ILogicalScrollable.BringIntoView(Control target, Rect targetRect) 107 | { 108 | return false; 109 | } 110 | 111 | Control? ILogicalScrollable.GetControlInDirection(NavigationDirection direction, Control? from) 112 | { 113 | return null; 114 | } 115 | 116 | void ILogicalScrollable.RaiseScrollInvalidated(EventArgs e) 117 | { 118 | _scrollInvalidated?.Invoke(this, e); 119 | } 120 | 121 | private Vector CoerceOffset(Vector value) 122 | { 123 | var scrollable = (ILogicalScrollable)this; 124 | var maxX = Math.Max(scrollable.Extent.Width - scrollable.Viewport.Width, 0); 125 | var maxY = Math.Max(scrollable.Extent.Height - scrollable.Viewport.Height, 0); 126 | return new Vector(Clamp(value.X, 0, maxX), Clamp(value.Y, 0, maxY)); 127 | static double Clamp(double val, double min, double max) => val < min ? min : val > max ? max : val; 128 | } 129 | 130 | private FormattedText CreateFormattedText(string text) 131 | { 132 | return new FormattedText(text, 133 | CultureInfo.CurrentCulture, 134 | FlowDirection.LeftToRight, 135 | _typeface, 136 | _fontSize, 137 | _foreground); 138 | } 139 | 140 | protected override void OnLoaded(RoutedEventArgs routedEventArgs) 141 | { 142 | base.OnLoaded(routedEventArgs); 143 | 144 | Invalidate(); 145 | InvalidateScrollable(); 146 | } 147 | 148 | protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) 149 | { 150 | base.OnPropertyChanged(change); 151 | 152 | if (change.Property == BoundsProperty) 153 | { 154 | InvalidateScrollable(); 155 | } 156 | 157 | if (change.Property == TextElement.FontFamilyProperty 158 | || change.Property == TextElement.FontSizeProperty 159 | || change.Property == TextElement.ForegroundProperty) 160 | { 161 | Invalidate(); 162 | InvalidateScrollable(); 163 | } 164 | 165 | if (change.Property == ToBaseProperty) 166 | { 167 | InvalidateVisual(); 168 | } 169 | 170 | if (change.Property == BytesWidthProperty) 171 | { 172 | InvalidateScrollable(); 173 | } 174 | } 175 | 176 | private void Invalidate() 177 | { 178 | _fontFamily = TextElement.GetFontFamily(this); 179 | _fontSize = TextElement.GetFontSize(this); 180 | _foreground = TextElement.GetForeground(this); 181 | _typeface = new Typeface(_fontFamily); 182 | _lineHeight = CreateFormattedText("0").Height; 183 | } 184 | 185 | public void InvalidateScrollable() 186 | { 187 | if (this is not ILogicalScrollable scrollable) 188 | { 189 | return; 190 | } 191 | 192 | var lines = HexFormatter?.Lines ?? 0; 193 | var width = Bounds.Width; 194 | var height = Bounds.Height; 195 | 196 | _scrollSize = new Size(1, _lineHeight); 197 | _pageScrollSize = new Size(_viewport.Width, _viewport.Height); 198 | _extent = new Size(width, lines * _lineHeight); 199 | _viewport = new Size(width, height); 200 | 201 | scrollable.RaiseScrollInvalidated(EventArgs.Empty); 202 | 203 | InvalidateVisual(); 204 | } 205 | 206 | public override void Render(DrawingContext context) 207 | { 208 | base.Render(context); 209 | 210 | if (HexFormatter is null || LineReader is null) 211 | { 212 | context.DrawRectangle(Brushes.Transparent, null, Bounds); 213 | 214 | return; 215 | } 216 | 217 | var toBase = ToBase; 218 | var bytesWidth = BytesWidth; 219 | 220 | if (bytesWidth != HexFormatter.Width) 221 | { 222 | HexFormatter.Width = bytesWidth; 223 | } 224 | 225 | var startLine = (long)Math.Ceiling(_offset.Y / _lineHeight); 226 | var lines = _viewport.Height / _lineHeight; 227 | var endLine = (long)Math.Min(Math.Floor(startLine + lines), HexFormatter.Lines - 1); 228 | 229 | var sb = new StringBuilder(); 230 | for (var i = startLine; i <= endLine; i++) 231 | { 232 | var bytes = LineReader.GetLine(i, HexFormatter.Width); 233 | HexFormatter.AddLine(bytes, i, sb, toBase); 234 | sb.AppendLine(); 235 | } 236 | 237 | var text = sb.ToString(); 238 | var ft = CreateFormattedText(text); 239 | var origin = new Point(); 240 | 241 | // TODO: Format diff. 242 | // ft.SetForegroundBrush(Brushes.Red, 8, 2); 243 | // ft.SetForegroundBrush(Brushes.Red, 11, 2); 244 | 245 | context.DrawText(ft, origin); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /HexView.Avalonia/HexView.Avalonia.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Library 5 | netstandard2.0 6 | 13 7 | enable 8 | true 9 | 10 | 11 | 12 | 11.0.0 13 | beta.1 14 | Wiesław Šoltés 15 | Wiesław Šoltés 16 | A HexView control for Avalonia. 17 | Copyright © Wiesław Šoltés 2025 18 | https://github.com/wieslawsoltes/HexView 19 | HexView.Avalonia 20 | MIT 21 | hexview;control;avalonia;generator 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /HexView.Avalonia/Model/IHexFormatter.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace HexView.Avalonia.Model; 4 | 5 | public interface IHexFormatter 6 | { 7 | long Lines { get; } 8 | int Width { get; set; } 9 | void AddLine(byte[] bytes, long lineNumber, StringBuilder sb, int toBase); 10 | } 11 | -------------------------------------------------------------------------------- /HexView.Avalonia/Model/ILineReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace HexView.Avalonia.Model; 4 | 5 | public interface ILineReader : IDisposable 6 | { 7 | byte[] GetLine(long lineNumber, int width); 8 | } 9 | -------------------------------------------------------------------------------- /HexView.Avalonia/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Metadata; 2 | 3 | [assembly: XmlnsDefinition("https://github.com/avaloniaui", "HexView.Avalonia.Controls")] 4 | -------------------------------------------------------------------------------- /HexView.Avalonia/Services/HexFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using HexView.Avalonia.Model; 4 | 5 | namespace HexView.Avalonia.Services; 6 | 7 | public class HexFormatter : IHexFormatter 8 | { 9 | private readonly long _length; 10 | private long _lines; 11 | private int _width; 12 | private readonly int _offsetPadding; 13 | 14 | public HexFormatter(long length) 15 | { 16 | _length = length; 17 | _width = 8; 18 | _lines = (long)Math.Ceiling((decimal)_length / _width); 19 | _offsetPadding = _length.ToString("X").Length; 20 | } 21 | 22 | public long Lines => _lines; 23 | 24 | public int Width 25 | { 26 | get => _width; 27 | set 28 | { 29 | _width = value; 30 | _lines = (long)Math.Ceiling((decimal)_length / _width); 31 | } 32 | } 33 | 34 | public void AddLine(byte[] bytes, long lineNumber, StringBuilder sb, int toBase) 35 | { 36 | if (toBase != 2 && toBase != 8 && toBase != 10 && toBase != 16) 37 | { 38 | throw new ArgumentException("Invalid base"); 39 | } 40 | 41 | var width = _width; 42 | var offset = lineNumber * width; 43 | 44 | sb.Append($"{offset.ToString($"X{_offsetPadding}")}: "); 45 | 46 | var toBasePadding = toBase switch 47 | { 48 | 2 => 8, 49 | 8 => 3, 50 | 10 => 3, 51 | 16 => 2, 52 | _ => throw new ArgumentOutOfRangeException(nameof(toBase), toBase, null) 53 | }; 54 | 55 | var paddingChar = toBase switch 56 | { 57 | 2 => '0', 58 | 8 => ' ', 59 | 10 => ' ', 60 | 16 => '0', 61 | _ => throw new ArgumentOutOfRangeException(nameof(toBase), toBase, null) 62 | }; 63 | 64 | for (var j = 0; j < width; j++) 65 | { 66 | var position = offset + j; 67 | 68 | var isSplit = j > 0 && j % 8 == 0; 69 | if (isSplit) 70 | { 71 | sb.Append("| "); 72 | } 73 | 74 | if (position < _length) 75 | { 76 | if (toBase == 16) 77 | { 78 | var value = $"{bytes[j]:X2}"; 79 | sb.Append(value); 80 | } 81 | else 82 | { 83 | var value = Convert.ToString(bytes[j], toBase).PadLeft(toBasePadding, paddingChar); 84 | sb.Append(value); 85 | } 86 | } 87 | else 88 | { 89 | var value = new string(' ', toBasePadding); 90 | sb.Append(value); 91 | } 92 | 93 | sb.Append(' '); 94 | } 95 | 96 | sb.Append(" | "); 97 | 98 | for (var j = 0; j < width; j++) 99 | { 100 | var c = (char)bytes[j]; 101 | 102 | sb.Append(char.IsControl(c) ? ' ' : c); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /HexView.Avalonia/Services/MemoryMappedLineReader.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.IO.MemoryMappedFiles; 3 | using HexView.Avalonia.Model; 4 | 5 | namespace HexView.Avalonia.Services; 6 | 7 | public class MemoryMappedLineReader : ILineReader 8 | { 9 | private readonly FileStream _stream; 10 | private readonly MemoryMappedFile _file; 11 | private readonly MemoryMappedViewAccessor _accessor; 12 | 13 | public MemoryMappedLineReader(string path) : 14 | this(File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read)) 15 | { 16 | } 17 | 18 | public MemoryMappedLineReader(FileStream stream) 19 | { 20 | _stream = stream; 21 | _file = MemoryMappedFile.CreateFromFile( 22 | _stream, 23 | null, 24 | 0, 25 | MemoryMappedFileAccess.Read, 26 | HandleInheritability.None, 27 | false); 28 | _accessor = _file.CreateViewAccessor(0, _stream.Length, MemoryMappedFileAccess.Read); 29 | 30 | } 31 | 32 | public byte[] GetLine(long lineNumber, int width) 33 | { 34 | var bytes = new byte[width]; 35 | var offset = lineNumber * width; 36 | 37 | for (var j = 0; j < width; j++) 38 | { 39 | var position = offset + j; 40 | if (position < _stream.Length) 41 | { 42 | bytes[j] = _accessor.ReadByte(position); 43 | } 44 | else 45 | { 46 | break; 47 | } 48 | } 49 | 50 | return bytes; 51 | } 52 | 53 | public void Dispose() 54 | { 55 | _accessor.Dispose(); 56 | _file.Dispose(); 57 | _stream.Dispose(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /HexView.Desktop/HexView.Desktop.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | 5 | 6 | WinExe 7 | 8 | 9 | net9.0 10 | enable 11 | full 12 | true 13 | false 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /HexView.Desktop/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia; 3 | 4 | namespace HexView.Desktop; 5 | 6 | class Program 7 | { 8 | // Initialization code. Don't use any Avalonia, third-party APIs or any 9 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized 10 | // yet and stuff might break. 11 | [STAThread] 12 | public static void Main(string[] args) => BuildAvaloniaApp() 13 | .StartWithClassicDesktopLifetime(args); 14 | 15 | // Avalonia configuration, don't remove; also used by visual designer. 16 | public static AppBuilder BuildAvaloniaApp() 17 | => AppBuilder.Configure() 18 | .UsePlatformDetect() 19 | .LogToTrace(); 20 | } 21 | -------------------------------------------------------------------------------- /HexView.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HexView", "HexView\HexView.csproj", "{DE055189-83CD-4AE3-BD7A-7A38E6C90D92}" 4 | EndProject 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{57B5C431-566C-4FE4-80DF-23B304C9B554}" 6 | ProjectSection(SolutionItems) = preProject 7 | .editorconfig = .editorconfig 8 | .gitattributes = .gitattributes 9 | .gitignore = .gitignore 10 | global.json = global.json 11 | NuGet.Config = NuGet.Config 12 | Directory.Build.props = Directory.Build.props 13 | EndProjectSection 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3CA4B5EA-750D-4724-B696-D1EE0AE93963}" 16 | EndProject 17 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HexView.Desktop", "HexView.Desktop\HexView.Desktop.csproj", "{8F934DA2-8B62-42EE-BCC7-337E23C586AD}" 18 | EndProject 19 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HexView.Avalonia", "HexView.Avalonia\HexView.Avalonia.csproj", "{8012D0A8-4793-40D7-983C-017D6BE4C99A}" 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{68E8B901-9040-4EDA-83C9-F35F46502DB3}" 22 | EndProject 23 | Global 24 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 25 | Debug|Any CPU = Debug|Any CPU 26 | Release|Any CPU = Release|Any CPU 27 | EndGlobalSection 28 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 29 | {DE055189-83CD-4AE3-BD7A-7A38E6C90D92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {DE055189-83CD-4AE3-BD7A-7A38E6C90D92}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {DE055189-83CD-4AE3-BD7A-7A38E6C90D92}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {DE055189-83CD-4AE3-BD7A-7A38E6C90D92}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {8F934DA2-8B62-42EE-BCC7-337E23C586AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {8F934DA2-8B62-42EE-BCC7-337E23C586AD}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {8F934DA2-8B62-42EE-BCC7-337E23C586AD}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {8F934DA2-8B62-42EE-BCC7-337E23C586AD}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {8012D0A8-4793-40D7-983C-017D6BE4C99A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {8012D0A8-4793-40D7-983C-017D6BE4C99A}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {8012D0A8-4793-40D7-983C-017D6BE4C99A}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {8012D0A8-4793-40D7-983C-017D6BE4C99A}.Release|Any CPU.Build.0 = Release|Any CPU 41 | EndGlobalSection 42 | GlobalSection(NestedProjects) = preSolution 43 | {8012D0A8-4793-40D7-983C-017D6BE4C99A} = {3CA4B5EA-750D-4724-B696-D1EE0AE93963} 44 | {8F934DA2-8B62-42EE-BCC7-337E23C586AD} = {68E8B901-9040-4EDA-83C9-F35F46502DB3} 45 | {DE055189-83CD-4AE3-BD7A-7A38E6C90D92} = {68E8B901-9040-4EDA-83C9-F35F46502DB3} 46 | EndGlobalSection 47 | EndGlobal 48 | -------------------------------------------------------------------------------- /HexView/App.axaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | avares://HexView/Assets#Roboto Mono 8 | Consolas 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /HexView/App.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls.ApplicationLifetimes; 3 | using Avalonia.Markup.Xaml; 4 | using HexView.Views; 5 | 6 | namespace HexView; 7 | 8 | public partial class App : Application 9 | { 10 | public override void Initialize() 11 | { 12 | AvaloniaXamlLoader.Load(this); 13 | } 14 | 15 | public override void OnFrameworkInitializationCompleted() 16 | { 17 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) 18 | { 19 | desktop.MainWindow = new MainWindow(); 20 | } 21 | else if (ApplicationLifetime is ISingleViewApplicationLifetime single) 22 | { 23 | single.MainView = new MainView(); 24 | } 25 | 26 | base.OnFrameworkInitializationCompleted(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /HexView/Assets/RobotoMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wieslawsoltes/HexView/95e000aa2ab45ffb67ce96d9c7b221a61f291023/HexView/Assets/RobotoMono-Regular.ttf -------------------------------------------------------------------------------- /HexView/HexView.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Library 4 | net9.0 5 | enable 6 | true 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /HexView/Views/DiffView.axaml: -------------------------------------------------------------------------------- 1 |  9 | 11 | 14 | 16 | 17 | 18 | 2 19 | 8 20 | 10 21 | 16 22 | 23 | 24 | 26 | 27 | 28 | 8 29 | 16 30 | 24 31 | 32 32 | 33 | 34 | 36 | 37 | 38 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 59 | 64 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /HexView/Views/DiffView.axaml.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using Avalonia.Controls; 4 | using Avalonia.Input; 5 | using Avalonia.Interactivity; 6 | using HexView.Avalonia.Model; 7 | using HexView.Avalonia.Services; 8 | 9 | namespace HexView.Views; 10 | 11 | public partial class DiffView : UserControl 12 | { 13 | private ILineReader? _lineReader1; 14 | private IHexFormatter? _hexFormatter1; 15 | private ILineReader? _lineReader2; 16 | private IHexFormatter? _hexFormatter2; 17 | private bool _updating; 18 | 19 | public DiffView() 20 | { 21 | InitializeComponent(); 22 | 23 | HexViewControl1.AddHandler(DragDrop.DropEvent, Drop); 24 | HexViewControl1.AddHandler(DragDrop.DragOverEvent, DragOver); 25 | 26 | HexViewControl2.AddHandler(DragDrop.DropEvent, Drop); 27 | HexViewControl2.AddHandler(DragDrop.DragOverEvent, DragOver); 28 | } 29 | 30 | private void OpenFile1(FileStream stream, string path) 31 | { 32 | _lineReader1?.Dispose(); 33 | _lineReader1 = new MemoryMappedLineReader(stream); 34 | _hexFormatter1 = new HexFormatter(stream.Length); 35 | HexViewControl1.LineReader = _lineReader1; 36 | HexViewControl1.HexFormatter = _hexFormatter1; 37 | HexViewControl1.InvalidateScrollable(); 38 | // TODO: path 39 | } 40 | 41 | private void OpenFile2(FileStream stream, string path) 42 | { 43 | _lineReader2?.Dispose(); 44 | _lineReader2 = new MemoryMappedLineReader(stream); 45 | _hexFormatter2 = new HexFormatter(stream.Length); 46 | HexViewControl2.LineReader = _lineReader2; 47 | HexViewControl2.HexFormatter = _hexFormatter2; 48 | HexViewControl2.InvalidateScrollable(); 49 | // TODO: path 50 | } 51 | 52 | private void DragOver(object? sender, DragEventArgs e) 53 | { 54 | e.DragEffects &= (DragDropEffects.Copy | DragDropEffects.Link); 55 | 56 | if (!e.Data.Contains(DataFormats.Files)) 57 | { 58 | e.DragEffects = DragDropEffects.None; 59 | } 60 | } 61 | 62 | private void Drop(object? sender, DragEventArgs e) 63 | { 64 | if (e.Data.Contains(DataFormats.Files)) 65 | { 66 | var path = e.Data.GetFileNames()?.FirstOrDefault(); 67 | if (path is { }) 68 | { 69 | if (Equals(sender, HexViewControl1)) 70 | { 71 | var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read); 72 | OpenFile1(stream, path); 73 | } 74 | 75 | if (Equals(sender, HexViewControl2)) 76 | { 77 | var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read); 78 | OpenFile2(stream, path); 79 | } 80 | } 81 | } 82 | } 83 | 84 | protected override void OnLoaded(RoutedEventArgs routedEventArgs) 85 | { 86 | base.OnLoaded(routedEventArgs); 87 | #if DEBUG 88 | //var path = @"/Users/wieslawsoltes/Documents/GitHub/Acdparser/clippitMS/CLIPPIT.ACS"; 89 | var path = @"c:\Users\Administrator\Documents\GitHub\Acdparser\clippitMS\CLIPPIT.ACS"; 90 | 91 | var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read); 92 | OpenFile1(stream, path); 93 | 94 | OpenFile2(stream, path); 95 | 96 | ScrollViewer1.ScrollChanged += ScrollViewer1OnScrollChanged; 97 | ScrollViewer2.ScrollChanged += ScrollViewer2OnScrollChanged; 98 | #endif 99 | } 100 | 101 | private void ScrollViewer1OnScrollChanged(object? sender, ScrollChangedEventArgs e) 102 | { 103 | if (_updating) 104 | { 105 | return; 106 | } 107 | 108 | _updating = true; 109 | ScrollViewer2.Offset = ScrollViewer1.Offset; 110 | _updating = false; 111 | } 112 | 113 | private void ScrollViewer2OnScrollChanged(object? sender, ScrollChangedEventArgs e) 114 | { 115 | if (_updating) 116 | { 117 | return; 118 | } 119 | 120 | _updating = true; 121 | ScrollViewer1.Offset = ScrollViewer2.Offset; 122 | _updating = false; 123 | } 124 | 125 | protected override void OnUnloaded(RoutedEventArgs routedEventArgs) 126 | { 127 | base.OnUnloaded(routedEventArgs); 128 | 129 | _lineReader1?.Dispose(); 130 | _lineReader2?.Dispose(); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /HexView/Views/MainView.axaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /HexView/Views/MainView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | 3 | namespace HexView.Views; 4 | 5 | public partial class MainView : UserControl 6 | { 7 | public MainView() 8 | { 9 | InitializeComponent(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /HexView/Views/MainWindow.axaml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /HexView/Views/MainWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | 3 | namespace HexView.Views; 4 | 5 | public partial class MainWindow : Window 6 | { 7 | public MainWindow() 8 | { 9 | InitializeComponent(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /HexView/Views/SingleView.axaml: -------------------------------------------------------------------------------- 1 |  9 | 11 | 14 | 17 |