mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
264 lines
9.1 KiB
C#
264 lines
9.1 KiB
C#
using Avalonia.Controls;
|
|
using Mesen.Config;
|
|
using Mesen.Debugger.Utilities;
|
|
using Mesen.Debugger.Windows;
|
|
using Mesen.Interop;
|
|
using Mesen.Localization;
|
|
using Mesen.Utilities;
|
|
using Mesen.ViewModels;
|
|
using Mesen.Windows;
|
|
using ReactiveUI;
|
|
using ReactiveUI.Fody.Helpers;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reactive.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Mesen.Debugger.ViewModels
|
|
{
|
|
public class AssemblerWindowViewModel : DisposableViewModel
|
|
{
|
|
public AssemblerConfig Config { get; }
|
|
|
|
[Reactive] public string Code { get; set; } = "";
|
|
[Reactive] public string ByteCodeView { get; set; } = "";
|
|
[Reactive] public int StartAddress { get; set; }
|
|
[Reactive] public int BytesUsed { get; set; }
|
|
|
|
[Reactive] public bool HasWarning { get; set; }
|
|
[Reactive] public bool IsIdentical { get; set; }
|
|
[Reactive] public bool OriginalSizeExceeded { get; set; }
|
|
[Reactive] public bool MaxSizeExceeded { get; set; }
|
|
|
|
[Reactive] public bool OkEnabled { get; set; } = false;
|
|
[Reactive] public List<AssemblerError> Errors { get; set; } = new List<AssemblerError>();
|
|
|
|
[Reactive] public List<ContextMenuAction> FileMenuActions { get; private set; } = new();
|
|
[Reactive] public List<ContextMenuAction> OptionsMenuActions { get; private set; } = new();
|
|
|
|
public CpuType CpuType { get; }
|
|
private List<byte> _bytes = new();
|
|
|
|
public int? MaxAddress { get; }
|
|
|
|
private int _originalAddress = -1;
|
|
private byte[] _originalCode = Array.Empty<byte>();
|
|
[Reactive] public int OriginalByteCount { get; private set; } = 0;
|
|
|
|
[Obsolete("For designer only")]
|
|
public AssemblerWindowViewModel() : this(CpuType.Snes) { }
|
|
|
|
public AssemblerWindowViewModel(CpuType cpuType)
|
|
{
|
|
Config = ConfigManager.Config.Debug.Assembler;
|
|
CpuType = cpuType;
|
|
|
|
if(Design.IsDesignMode) {
|
|
return;
|
|
}
|
|
|
|
MaxAddress = DebugApi.GetMemorySize(CpuType.ToMemoryType()) - 1;
|
|
|
|
AddDisposable(this.WhenAnyValue(x => x.Code, x => x.StartAddress).Subscribe(_ => {
|
|
UpdateAssembly(Code);
|
|
}));
|
|
}
|
|
|
|
public void InitMenu(Window wnd)
|
|
{
|
|
FileMenuActions = AddDisposables(new List<ContextMenuAction>() {
|
|
SaveRomActionHelper.GetSaveRomAction(wnd),
|
|
SaveRomActionHelper.GetSaveRomAsAction(wnd),
|
|
SaveRomActionHelper.GetSaveEditsAsIpsAction(wnd),
|
|
new ContextMenuSeparator(),
|
|
new ContextMenuAction() {
|
|
ActionType = ActionType.Exit,
|
|
OnClick = () => wnd.Close()
|
|
}
|
|
});
|
|
|
|
OptionsMenuActions = AddDisposables(new List<ContextMenuAction>() {
|
|
new ContextMenuAction() {
|
|
ActionType = ActionType.OpenDebugSettings,
|
|
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.OpenDebugSettings),
|
|
OnClick = () => DebuggerConfigWindow.Open(DebugConfigWindowTab.FontAndColors, wnd)
|
|
}
|
|
});
|
|
}
|
|
|
|
public void InitEditCode(int address, string code, int byteCount)
|
|
{
|
|
OriginalByteCount = byteCount;
|
|
_originalAddress = address;
|
|
|
|
using var delayNotifs = DelayChangeNotifications();
|
|
StartAddress = address;
|
|
Code = code;
|
|
|
|
if(OriginalByteCount > 0) {
|
|
_originalCode = DebugApi.GetMemoryValues(CpuType.ToMemoryType(), (uint)StartAddress, (uint)(StartAddress + OriginalByteCount - 1));
|
|
}
|
|
}
|
|
|
|
private void UpdateAssembly(string code)
|
|
{
|
|
string[] codeLines = code.Replace("\r", "").Split('\n').Select(x => x.Trim()).ToArray();
|
|
short[] byteCode = DebugApi.AssembleCode(CpuType, string.Join('\n', codeLines), (uint)StartAddress);
|
|
List<AssemblerError> errorList = new List<AssemblerError>();
|
|
List<byte> convertedByteCode = new List<byte>(byteCode.Length);
|
|
StringBuilder sb = new StringBuilder();
|
|
int line = 1;
|
|
for(int i = 0; i < byteCode.Length; i++) {
|
|
short s = byteCode[i];
|
|
if(s >= 0) {
|
|
convertedByteCode.Add((byte)s);
|
|
sb.Append(s.ToString("X2") + " ");
|
|
} else if(s == (int)AssemblerSpecialCodes.EndOfLine) {
|
|
line++;
|
|
if(line <= codeLines.Length) {
|
|
sb.Append(Environment.NewLine);
|
|
}
|
|
} else if(s < (int)AssemblerSpecialCodes.EndOfLine) {
|
|
string message = "unknown error";
|
|
switch((AssemblerSpecialCodes)s) {
|
|
case AssemblerSpecialCodes.ParsingError: message = "Invalid syntax"; break;
|
|
case AssemblerSpecialCodes.OutOfRangeJump: message = "Relative jump is out of range (-128 to 127)"; break;
|
|
case AssemblerSpecialCodes.LabelRedefinition: message = "Cannot redefine an existing label"; break;
|
|
case AssemblerSpecialCodes.MissingOperand: message = "Operand is missing"; break;
|
|
case AssemblerSpecialCodes.OperandOutOfRange: message = "Operand is out of range (invalid value)"; break;
|
|
case AssemblerSpecialCodes.InvalidHex: message = "Hexadecimal string is invalid"; break;
|
|
case AssemblerSpecialCodes.InvalidSpaces: message = "Operand cannot contain spaces"; break;
|
|
case AssemblerSpecialCodes.TrailingText: message = "Invalid text trailing at the end of line"; break;
|
|
case AssemblerSpecialCodes.UnknownLabel: message = "Unknown label"; break;
|
|
case AssemblerSpecialCodes.InvalidInstruction: message = "Invalid instruction"; break;
|
|
case AssemblerSpecialCodes.InvalidBinaryValue: message = "Invalid binary value"; break;
|
|
case AssemblerSpecialCodes.InvalidOperands: message = "Invalid operands for instruction"; break;
|
|
case AssemblerSpecialCodes.InvalidLabel: message = "Invalid label name"; break;
|
|
}
|
|
errorList.Add(new AssemblerError() { Message = message + " - " + codeLines[line - 1], LineNumber = line });
|
|
|
|
sb.Append("<error: " + message + ">");
|
|
if(i + 1 < byteCode.Length) {
|
|
sb.Append(Environment.NewLine);
|
|
}
|
|
|
|
line++;
|
|
}
|
|
}
|
|
|
|
BytesUsed = convertedByteCode.Count;
|
|
IsIdentical = MatchesOriginalCode(convertedByteCode);
|
|
OriginalSizeExceeded = BytesUsed > OriginalByteCount;
|
|
MaxSizeExceeded = StartAddress + BytesUsed - 1 > MaxAddress;
|
|
OkEnabled = BytesUsed > 0 && !IsIdentical && !MaxSizeExceeded;
|
|
|
|
HasWarning = errorList.Count > 0 || OriginalSizeExceeded || MaxSizeExceeded;
|
|
|
|
ByteCodeView = sb.ToString();
|
|
Errors = errorList;
|
|
_bytes = convertedByteCode;
|
|
}
|
|
|
|
private bool MatchesOriginalCode(List<byte> convertedByteCode)
|
|
{
|
|
bool isIdentical = false;
|
|
if(_originalCode.Length > 0 && convertedByteCode.Count == _originalCode.Length) {
|
|
isIdentical = true;
|
|
for(int i = 0; i < _originalCode.Length; i++) {
|
|
if(_originalCode[i] != convertedByteCode[i]) {
|
|
isIdentical = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return isIdentical;
|
|
}
|
|
|
|
public async Task<bool> ApplyChanges(Window assemblerWindow)
|
|
{
|
|
MemoryType memType = CpuType.ToMemoryType();
|
|
|
|
List<byte> bytes = new List<byte>(_bytes);
|
|
if(OriginalByteCount > 0) {
|
|
byte nopOpCode = CpuType.GetNopOpCode();
|
|
while(OriginalByteCount > bytes.Count) {
|
|
//Pad data with NOPs as needed
|
|
bytes.Add(nopOpCode);
|
|
}
|
|
}
|
|
|
|
string addrFormat = memType.GetFormatString();
|
|
UInt32 endAddress = (uint)(StartAddress + bytes.Count - 1);
|
|
|
|
List<string> warningMessages = new List<string>();
|
|
if(Errors.Count > 0) {
|
|
warningMessages.Add("Warning: The code contains errors - lines with errors will be ignored.");
|
|
}
|
|
|
|
if(_originalAddress >= 0) {
|
|
if(OriginalSizeExceeded) {
|
|
warningMessages.Add(ResourceHelper.GetViewLabel(nameof(AssemblerWindow), "lblByteCountExceeded"));
|
|
}
|
|
} else {
|
|
warningMessages.Add($"Warning: The code currently mapped to CPU memory addresses ${StartAddress.ToString(addrFormat)} to ${endAddress.ToString(addrFormat)} will be overridden.");
|
|
}
|
|
|
|
if(warningMessages.Count > 0 && await MesenMsgBox.Show(assemblerWindow, "AssemblerConfirmation", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning, string.Join(Environment.NewLine + Environment.NewLine, warningMessages)) != DialogResult.OK) {
|
|
return false;
|
|
}
|
|
|
|
|
|
DebugApi.SetMemoryValues(memType, (uint)StartAddress, bytes.ToArray(), bytes.Count);
|
|
if(OriginalByteCount > 0) {
|
|
_originalCode = bytes.ToArray();
|
|
IsIdentical = MatchesOriginalCode(bytes);
|
|
}
|
|
|
|
AddressInfo absStart = DebugApi.GetAbsoluteAddress(new AddressInfo() { Address = StartAddress, Type = memType });
|
|
AddressInfo absEnd = DebugApi.GetAbsoluteAddress(new AddressInfo() { Address = (int)endAddress, Type = memType });
|
|
if(absStart.Type == absEnd.Type && (absEnd.Address - absStart.Address + 1) == bytes.Count) {
|
|
DebugApi.MarkBytesAs(absStart.Type, (uint)absStart.Address, (uint)absEnd.Address, CdlFlags.Code);
|
|
}
|
|
|
|
DebuggerWindow? wnd = DebugWindowManager.GetDebugWindow<DebuggerWindow>(wnd => wnd.CpuType == CpuType);
|
|
if(wnd != null) {
|
|
wnd.RefreshDisassembly();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private enum AssemblerSpecialCodes
|
|
{
|
|
OK = 0,
|
|
EndOfLine = -1,
|
|
ParsingError = -2,
|
|
OutOfRangeJump = -3,
|
|
LabelRedefinition = -4,
|
|
MissingOperand = -5,
|
|
OperandOutOfRange = -6,
|
|
InvalidHex = -7,
|
|
InvalidSpaces = -8,
|
|
TrailingText = -9,
|
|
UnknownLabel = -10,
|
|
InvalidInstruction = -11,
|
|
InvalidBinaryValue = -12,
|
|
InvalidOperands = -13,
|
|
InvalidLabel = -14,
|
|
}
|
|
}
|
|
|
|
public class AssemblerError
|
|
{
|
|
public string Message { get; set; } = "";
|
|
public int LineNumber { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return "Line " + LineNumber.ToString() + ": " + this.Message;
|
|
}
|
|
}
|
|
}
|