Mesen2/UI/Debugger/ViewModels/MemoryViewerFindViewModel.cs

200 lines
5.8 KiB
C#

using Avalonia.Controls;
using Avalonia.Threading;
using Mesen.Config;
using Mesen.Debugger.Utilities;
using Mesen.ViewModels;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace Mesen.Debugger.ViewModels;
public class MemoryViewerFindViewModel : DisposableViewModel
{
[Reactive] public SearchDataType DataType { get; set; }
[Reactive] public SearchIntType IntType { get; set; }
[Reactive] public bool CaseSensitive { get; set; }
[Reactive] public bool UseTblMappings { get; set; }
[Reactive] public bool FilterNotAccessed { get; set; }
[Reactive] public bool FilterRead { get; set; }
[Reactive] public bool FilterWrite { get; set; }
[Reactive] public bool FilterExec { get; set; }
[Reactive] public bool FilterTimeSpanEnabled { get; set; }
[Reactive] public int FilterTimeSpan { get; set; }
[Reactive] public bool FilterCode { get; set; }
[Reactive] public bool FilterData { get; set; }
[Reactive] public bool FilterUnidentified { get; set; }
[Reactive] public bool IsInteger { get; private set; }
[Reactive] public bool IsString { get; private set; }
[Reactive] public bool IsValid { get; private set; } = false;
[Reactive] public bool ShowNotFoundError { get; set; }
[Reactive] public string SearchString { get; set; } = "";
private MemoryToolsViewModel _memToolsModel;
[Obsolete("For designer only")]
public MemoryViewerFindViewModel() : this(new(new())) { }
public MemoryViewerFindViewModel(MemoryToolsViewModel memToolsModel)
{
_memToolsModel = memToolsModel;
AddDisposable(this.WhenAnyValue(x => x.DataType).Subscribe(x => {
IsInteger = DataType == SearchDataType.Integer;
IsString = DataType == SearchDataType.String;
}));
AddDisposable(this.WhenAnyValue(x => x.SearchString).Subscribe(x => {
if(SearchString.Contains(Environment.NewLine)) {
//Run asynchronously to allow the textbox to update its content correctly
Dispatcher.UIThread.Post(() => {
SearchString = SearchString.Replace(Environment.NewLine, " ");
});
}
}));
AddDisposable(this.WhenAnyValue(x => x.DataType, x => x.IntType, x => x.SearchString).Subscribe(x => {
SearchData? searchData = GetSearchData();
IsValid = searchData != null && searchData.Data.Length > 0;
}));
}
public SearchData? GetSearchData()
{
switch(DataType) {
case SearchDataType.Hex:
if(Regex.IsMatch(SearchString, "^[ a-f0-9?]+$", RegexOptions.IgnoreCase)) {
return new SearchData(HexUtilities.HexToArrayWithWildcards(SearchString));
}
break;
case SearchDataType.String:
if(UseTblMappings && _memToolsModel.TblConverter != null) {
if(CaseSensitive) {
return new SearchData(_memToolsModel.TblConverter.GetBytes(SearchString));
} else {
byte[] lcData = _memToolsModel.TblConverter.GetBytes(SearchString.ToLower());
byte[] ucData = _memToolsModel.TblConverter.GetBytes(SearchString.ToUpper());
if(lcData.Length != ucData.Length) {
return null;
}
return new SearchData(lcData, ucData);
}
} else {
if(CaseSensitive) {
return new SearchData(Encoding.UTF8.GetBytes(SearchString));
} else {
byte[] lcData = Encoding.UTF8.GetBytes(SearchString.ToLower());
byte[] ucData = Encoding.UTF8.GetBytes(SearchString.ToUpper());
if(lcData.Length != ucData.Length) {
return null;
}
return new SearchData(lcData, ucData);
}
}
case SearchDataType.Integer:
if(long.TryParse(SearchString, out long value)) {
switch(IntType) {
case SearchIntType.IntAuto:
if(value >= Int32.MinValue && value <= UInt32.MaxValue) {
return new SearchData(new byte[] { (byte)(value & 0xFF), (byte)((value >> 8) & 0xFF), (byte)((value >> 16) & 0xFF), (byte)((value >> 24) & 0xFF) });
} else if(value >= Int16.MinValue && value <= UInt16.MaxValue) {
return new SearchData(new byte[] { (byte)(value & 0xFF), (byte)((value >> 8) & 0xFF) });
} else if(value >= sbyte.MinValue && value <= byte.MaxValue) {
return new SearchData(new byte[] { (byte)(value & 0xFF) });
}
break;
case SearchIntType.Int32:
if(value >= Int32.MinValue && value <= UInt32.MaxValue) {
return new SearchData(new byte[] { (byte)(value & 0xFF), (byte)((value >> 8) & 0xFF), (byte)((value >> 16) & 0xFF), (byte)((value >> 24) & 0xFF) });
}
break;
case SearchIntType.Int16:
if(value >= Int16.MinValue && value <= UInt16.MaxValue) {
return new SearchData(new byte[] { (byte)(value & 0xFF), (byte)((value >> 8) & 0xFF) });
}
break;
case SearchIntType.Int8:
if(value >= sbyte.MinValue && value <= byte.MaxValue) {
return new SearchData(new byte[] { (byte)(value & 0xFF) });
}
break;
}
}
break;
}
return null;
}
public bool IsDataTypeFiltered
{
get
{
int count = (FilterCode ? 1 : 0) + (FilterData ? 1 : 0) + (FilterUnidentified ? 1 : 0);
return count > 0 && count < 3;
}
}
public bool IsAccessFiltered
{
get
{
int count = (FilterNotAccessed ? 1 : 0) + (FilterRead ? 1 : 0) + (FilterWrite ? 1 : 0) + (FilterExec ? 1 : 0);
return count > 0 && count < 4;
}
}
}
public class SearchData
{
public short[] Data;
public short[]? DataAlt; //used for case insensitive searches
public SearchData(byte[] data, byte[]? dataAlt = null)
{
Data = new short[data.Length];
Array.Copy(data, 0, Data, 0, data.Length);
if(dataAlt != null) {
DataAlt = new short[dataAlt.Length];
Array.Copy(dataAlt, 0, DataAlt, 0, dataAlt.Length);
}
}
public SearchData(short[] data)
{
Data = data;
}
}
public enum SearchDataType
{
Hex,
String,
Integer,
}
public enum SearchIntType
{
IntAuto,
Int8,
Int16,
Int32
}
public enum SearchDirection
{
Forward,
Backward
}