// /*************************************************************************** // Aaru Data Preservation Suite // ---------------------------------------------------------------------------- // // Author(s) : Natalia Portillo // // Component : Text User Interface. // // --[ License ] -------------------------------------------------------------- // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // ---------------------------------------------------------------------------- // Copyright © 2011-2025 Natalia Portillo // ****************************************************************************/ using System.Collections.ObjectModel; using System.Windows.Input; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; using Aaru.Tui.ViewModels.Dialogs; using Aaru.Tui.Views.Dialogs; using Aaru.Tui.Views.Windows; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Prism.DryIoc; using GoToSectorDialog = Aaru.Tui.Views.Dialogs.GoToSectorDialog; namespace Aaru.Tui.ViewModels.Windows; public sealed partial class HexViewWindowViewModel : ViewModelBase { private const int BYTES_PER_LINE = 16; readonly IRegionManager _regionManager; [ObservableProperty] ulong _currentSector; [ObservableProperty] string _filePath; [ObservableProperty] long _fileSize; IMediaImage _imageFormat; [ObservableProperty] ObservableCollection _lines = []; bool _longMode; internal HexViewWindowViewModel(IRegionManager regionManager) { _regionManager = regionManager; ExitCommand = new RelayCommand(Exit); BackCommand = new RelayCommand(Back); NextSectorCommand = new RelayCommand(NextSector); PreviousSectorCommand = new RelayCommand(PreviousSector); GoToCommand = new AsyncRelayCommand(GoToAsync); HelpCommand = new AsyncRelayCommand(HelpAsync); ToggleLongCommand = new RelayCommand(ToggleLong); } public ICommand BackCommand { get; } public ICommand ExitCommand { get; } public ICommand NextSectorCommand { get; } public ICommand PreviousSectorCommand { get; } public ICommand GoToCommand { get; } public ICommand HelpCommand { get; } public ICommand ToggleLongCommand { get; } /// public override void OnNavigatedTo(NavigationContext navigationContext) { base.OnNavigatedTo(navigationContext); _imageFormat = navigationContext.Parameters.GetValue("imageFormat"); FilePath = navigationContext.Parameters.GetValue("filePath"); LoadSector(); } void ToggleLong() { _longMode = !_longMode; if(_longMode) FilePath += Localization.Resources.Long_Mode; else FilePath = FilePath.Replace(Localization.Resources.Long_Mode, string.Empty); LoadSector(); } Task HelpAsync() { AvaloniaObject? view = (Application.Current as PrismApplication)?.MainWindow; if(view is null) return Task.CompletedTask; var dialog = new HexViewHelpDialog { DataContext = new HexViewHelpDialogViewModel(null!) }; // Set the dialog reference after creation ((HexViewHelpDialogViewModel)dialog.DataContext!)._dialog = dialog; return dialog.ShowDialog(view as Window); } async Task GoToAsync() { AvaloniaObject? view = (Application.Current as PrismApplication)?.MainWindow; if(view is null) return; var dialog = new GoToSectorDialog { DataContext = new GoToSectorDialogViewModel(null!, _imageFormat.Info.Sectors - 1) }; // Set the dialog reference after creation ((GoToSectorDialogViewModel)dialog.DataContext!)._dialog = dialog; bool? result = await dialog.ShowDialog(view as Window); if(result == true) { var viewModel = (GoToSectorDialogViewModel)dialog.DataContext; if(viewModel.Result.HasValue) { CurrentSector = viewModel.Result.Value; LoadSector(); } } } void PreviousSector() { if(CurrentSector <= 0) return; CurrentSector--; LoadSector(); } void NextSector() { if(CurrentSector >= _imageFormat.Info.Sectors - 1) return; CurrentSector++; LoadSector(); } void Back() { IRegion? region = _regionManager.Regions["ContentRegion"]; IRegionNavigationService? navigationService = region.NavigationService; if(navigationService?.Journal.CanGoBack == true) navigationService.Journal.GoBack(); else { // No history - navigate directly to FileView _regionManager.RequestNavigate("ContentRegion", nameof(FileView)); } } void Exit() { var lifetime = Application.Current!.ApplicationLifetime as IControlledApplicationLifetime; lifetime!.Shutdown(); } // TODO: Show message when sector was not dumped void LoadSector() { Lines.Clear(); byte[]? sector; if(_longMode) { ErrorNumber errno = _imageFormat.ReadSectorLong(CurrentSector, false, out sector, out _); if(errno != ErrorNumber.NoError) { ToggleLong(); return; } } else _imageFormat.ReadSector(CurrentSector, false, out sector, out _); using var stream = new MemoryStream(sector); var buffer = new byte[BYTES_PER_LINE]; long offset = 0; const int maxLines = 1000; for(var lineCount = 0; stream.Position < stream.Length && lineCount < maxLines; lineCount++) { int bytesRead = stream.Read(buffer, 0, BYTES_PER_LINE); if(bytesRead == 0) break; var line = new HexViewLine { Offset = offset, Bytes = buffer.Take(bytesRead).ToArray() }; Lines.Add(line); offset += bytesRead; } } } public sealed class HexViewLine { internal long Offset { get; init; } internal byte[] Bytes { get; init; } public string OffsetString => $"{Offset:X8}"; public string HexString { get { var hex = string.Join(" ", Bytes.Select(static b => $"{b:X2}")); // Pad to 16 bytes worth of hex (16 * 3 - 1 = 47 chars) return hex.PadRight(47); } } public string AsciiString { get { var ascii = new char[Bytes.Length]; for(var i = 0; i < Bytes.Length; i++) ascii[i] = Bytes[i] >= 32 && Bytes[i] < 127 ? (char)Bytes[i] : '.'; return new string(ascii).PadRight(16); } } }