/* * Copyright 2019 faddenSoft * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.ComponentModel; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text; using System.Windows; using System.Windows.Controls; using Asm65; namespace SourceGen.WpfGui { /// /// Instruction operand editor. /// /// This is a pretty direct port from WinForms. /// public partial class EditInstructionOperand : Window { /// /// In/out. May be null on entry if the offset doesn't have a format descriptor /// specified. Will be null on exit if "default" is selected. /// public FormatDescriptor FormatDescriptor { get; set; } public enum SymbolShortcutAction { None = 0, CreateLabelInstead, CreateLabelAlso, CreateProjectSymbolAlso } /// /// Remember the last option we used. /// private static SymbolShortcutAction sLastAction = SymbolShortcutAction.None; /// /// On OK dialog exit, specifies that an additional action should be taken. /// public SymbolShortcutAction ShortcutAction { get; private set; } /// /// Additional argument, meaning dependent on ShortcutAction. This will either be /// the target label offset or the project symbol value. /// public int ShortcutArg { get; private set; } /// /// Width of full instruction, including opcode. /// private int mInstructionLength; /// /// Number of hexadecimal digits to show in the preview. Sometimes you want /// to force this to be longer or shorter than InstructionLength would indicate, /// e.g. "BRA $1000" has a 1-byte operand. /// private int mPreviewHexDigits; /// /// Operand value, extracted from file data. For a relative branch, this will be /// an address instead. Only used for preview window. /// private int mOperandValue; /// /// Is the operand an immediate value? If so, we enable the symbol part selection. /// private bool mIsExtendedImmediate; /// /// Is the operand a PC relative offset? /// private bool mIsPcRelative; /// /// Special handling for block move instructions (MVN/MVP). /// private bool mIsBlockMove; /// /// If set, show a '#' in the preview indow. /// private bool mShowHashPrefix; ///// ///// Symbol table to use when resolving symbolic values. ///// //private SymbolTable SymbolTable { get; set; } /// /// Project reference. /// private DisasmProject mProject; /// /// Formatter to use when displaying addresses and hex values. /// private Formatter mFormatter; /// /// Copy of operand Anattribs. /// private Anattrib mAttr; /// /// Set this during initial control configuration, so we know to ignore the CheckedChanged /// events. /// private bool mIsInitialSetup; /// /// Set to true if the user has entered a symbol that matches an auto-generated symbol. /// private bool mIsSymbolAuto; public EditInstructionOperand(Window owner, int offset, DisasmProject project, Asm65.Formatter formatter) { InitializeComponent(); Owner = owner; DataContext = this; mProject = project; mFormatter = formatter; // Configure the appearance. mAttr = mProject.GetAnattrib(offset); OpDef op = mProject.CpuDef.GetOpDef(mProject.FileData[offset]); mInstructionLength = mAttr.Length; mPreviewHexDigits = (mAttr.Length - 1) * 2; if (mAttr.OperandAddress >= 0) { // Use this as the operand value when available. This lets us present // relative branch instructions in the expected form. mOperandValue = mAttr.OperandAddress; if (op.AddrMode == OpDef.AddressMode.PCRel) { mPreviewHexDigits = 4; mIsPcRelative = true; } else if (op.AddrMode == OpDef.AddressMode.PCRelLong || op.AddrMode == OpDef.AddressMode.StackPCRelLong) { mIsPcRelative = true; } } else { int opVal = op.GetOperand(mProject.FileData, offset, mAttr.StatusFlags); mOperandValue = opVal; if (op.AddrMode == OpDef.AddressMode.BlockMove) { // MVN and MVP screw things up by having two operands in one instruction. // We deal with this by passing in the value from the second byte // (source bank) as the value, and applying the chosen format to both bytes. mIsBlockMove = true; mOperandValue = opVal >> 8; mPreviewHexDigits = 2; } } mIsExtendedImmediate = op.IsExtendedImmediate; // Imm, PEA, MVN/MVP mShowHashPrefix = op.IsImmediate; // just Imm } private void Window_Loaded(object sender, RoutedEventArgs e) { mIsInitialSetup = true; // Can this be represented as a character? We only allow the printable set // here, not the extended set (which includes control characters). if (mOperandValue == (byte) mOperandValue) { asciiButton.IsEnabled = CharEncoding.IsPrintableLowOrHighAscii((byte)mOperandValue); petsciiButton.IsEnabled = CharEncoding.IsPrintableC64Petscii((byte)mOperandValue); screenCodeButton.IsEnabled = CharEncoding.IsPrintableC64ScreenCode((byte)mOperandValue); } else { asciiButton.IsEnabled = petsciiButton.IsEnabled = screenCodeButton.IsEnabled = false; } // Configure the dialog from the FormatDescriptor, if one is available. SetControlsFromDescriptor(FormatDescriptor); // Do this whether or not symbol is checked -- want to have this set when the // dialog is initially in default format. switch (sLastAction) { case SymbolShortcutAction.CreateLabelInstead: labelInsteadButton.IsChecked = true; break; case SymbolShortcutAction.CreateLabelAlso: operandAndLabelButton.IsChecked = true; break; case SymbolShortcutAction.CreateProjectSymbolAlso: operandAndProjButton.IsChecked = true; break; default: operandOnlyButton.IsChecked = true; break; } mIsInitialSetup = false; UpdateControls(); } private void Window_ContentRendered(object sender, EventArgs e) { // Start with the focus in the text box. This way they can start typing // immediately. symbolTextBox.Focus(); } private void SymbolTextBox_TextChanged(object sender, TextChangedEventArgs e) { // Make sure Symbol is checked if they're typing text in. symbolButton.IsChecked = true; UpdateControls(); } /// /// Handles Checked/Unchecked events for all radio buttons in main group. /// private void MainGroup_CheckedChanged(object sender, RoutedEventArgs e) { // Enable/disable the low/high/bank radio group. // Update preview window. UpdateControls(); } /// /// Handles Checked/Unchecked events for all radio buttons in symbol-part group. /// private void PartGroup_CheckedChanged(object sender, RoutedEventArgs e) { // Update preview window. UpdateControls(); } private void OkButton_Click(object sender, RoutedEventArgs e) { FormatDescriptor = CreateDescriptorFromControls(); // // Extract the current shortcut action. For dialog configuration purposes we // want to capture the current state. For the caller, we force it to "none" // if we're not using a symbol format. // SymbolShortcutAction action = SymbolShortcutAction.None; if (labelInsteadButton.IsChecked == true) { action = SymbolShortcutAction.CreateLabelInstead; } else if (operandAndLabelButton.IsChecked == true) { action = SymbolShortcutAction.CreateLabelAlso; } else if (operandAndProjButton.IsChecked == true) { action = SymbolShortcutAction.CreateProjectSymbolAlso; } else if (operandOnlyButton.IsChecked == true) { action = SymbolShortcutAction.None; } else { Debug.Assert(false); action = SymbolShortcutAction.None; } sLastAction = action; if (symbolButton.IsChecked == true && FormatDescriptor != null) { // Only report a shortcut action if they've entered a symbol. If they // checked symbol but left the field blank, they're just trying to delete // the format. ShortcutAction = action; } else { ShortcutAction = SymbolShortcutAction.None; } DialogResult = true; } /// /// Updates all of the controls to reflect the current internal state. /// private void UpdateControls() { if (mIsInitialSetup) { return; } symbolPartPanel.IsEnabled = (symbolButton.IsChecked == true && mIsExtendedImmediate); symbolShortcutsGroupBox.IsEnabled = symbolButton.IsChecked == true; SetPreviewText(); bool isOk = true; if (symbolButton.IsChecked == true) { // Just check for correct format. References to non-existent labels are allowed. // // We try to block references to auto labels, but it's possible to get around it // (replace auto label with user label, reference non-existent auto label, // remove user label). We could try harder, but currently not necessary. isOk = !mIsSymbolAuto && Asm65.Label.ValidateLabel(symbolTextBox.Text); // Allow empty strings as a way to delete the label and return to "default". if (string.IsNullOrEmpty(symbolTextBox.Text)) { isOk = true; } ConfigureSymbolShortcuts(); } okButton.IsEnabled = isOk; } /// /// Sets the text displayed in the "preview" text box. /// private void SetPreviewText() { //symbolValueLabel.Text = string.Empty; mIsSymbolAuto = false; FormatDescriptor dfd = CreateDescriptorFromControls(); if (dfd == null) { // Default format. We can't actually know what this look like, so just // clear the box. previewTextBox.Text = string.Empty; return; } if (dfd.FormatSubType == FormatDescriptor.SubType.Symbol && string.IsNullOrEmpty(dfd.SymbolRef.Label)) { // no label yet, nothing to show previewTextBox.Text = string.Empty; return; } StringBuilder preview = new StringBuilder(); if (mShowHashPrefix) { preview.Append('#'); } switch (dfd.FormatSubType) { case FormatDescriptor.SubType.Hex: preview.Append(mFormatter.FormatHexValue(mOperandValue, mPreviewHexDigits)); break; case FormatDescriptor.SubType.Decimal: preview.Append(mFormatter.FormatDecimalValue(mOperandValue)); break; case FormatDescriptor.SubType.Binary: preview.Append(mFormatter.FormatBinaryValue(mOperandValue, 8)); break; case FormatDescriptor.SubType.Ascii: case FormatDescriptor.SubType.HighAscii: case FormatDescriptor.SubType.C64Petscii: case FormatDescriptor.SubType.C64Screen: CharEncoding.Encoding enc = PseudoOp.SubTypeToEnc(dfd.FormatSubType); preview.Append(mFormatter.FormatCharacterValue(mOperandValue, enc)); break; case FormatDescriptor.SubType.Symbol: if (mProject.SymbolTable.TryGetValue(dfd.SymbolRef.Label, out Symbol sym)) { if (mIsBlockMove) { // For a 24-bit symbol, we grab the high byte. This is the // expected behavior, according to Eyes & Lichty; see the // explanation of the MVP instruction. For an 8-bit symbol // the assembler just takes the value. // TODO(someday): allow a different symbol for each part of the // operand. if (sym.Value > 0xff) { bankButton.IsChecked = true; } else { lowButton.IsChecked = true; } dfd = CreateDescriptorFromControls(); } // Hack to make relative branches look right in the preview window. // Otherwise they show up like " /// Configures the buttons in the "symbol shortcuts" group box. The entire box is /// disabled unless "symbol" is selected. Other options are selectively enabled or /// disabled as appropriate for the current input. If we disable the selection option, /// the selection will be reset to default. /// private void ConfigureSymbolShortcuts() { // operandOnlyRadioButton: always enabled // labelInsteadRadioButton: symbol is unknown and operand address has no label // operandAndLabelRadioButton: same as labelInstead // operandAndProjRadioButton: symbol is unknown and operand address is outside project string labelStr = symbolTextBox.Text; ShortcutArg = -1; // Is this a known symbol? If so, disable most options and bail. if (mProject.SymbolTable.TryGetValue(labelStr, out Symbol sym)) { labelInsteadButton.IsEnabled = operandAndLabelButton.IsEnabled = operandAndProjButton.IsEnabled = false; operandOnlyButton.IsChecked = true; return; } if (mAttr.OperandOffset >= 0) { // Operand target is inside the file. Does the target offset already have a label? int targetOffset = DataAnalysis.GetBaseOperandOffset(mProject, mAttr.OperandOffset); bool hasLabel = mProject.UserLabels.ContainsKey(targetOffset); labelInsteadButton.IsEnabled = operandAndLabelButton.IsEnabled = !hasLabel; operandAndProjButton.IsEnabled = false; ShortcutArg = targetOffset; } else if (mAttr.OperandAddress >= 0) { // Operand target is outside the file. labelInsteadButton.IsEnabled = operandAndLabelButton.IsEnabled = false; operandAndProjButton.IsEnabled = true; ShortcutArg = mAttr.OperandAddress; } else { // Probably an immediate operand. // ?? Should operandAndProjButton be enabled for 8-bit constants? We'd want // to add it as a constant rather than an address. labelInsteadButton.IsEnabled = operandAndLabelButton.IsEnabled = operandAndProjButton.IsEnabled = false; } // Select the default option if the currently-selected option is no longer available. if ((labelInsteadButton.IsChecked == true && labelInsteadButton.IsEnabled != true) || (operandAndLabelButton.IsChecked == true && !operandAndLabelButton.IsEnabled == true) || (operandAndProjButton.IsChecked == true && !operandAndProjButton.IsEnabled == true)) { operandOnlyButton.IsChecked = true; } } /// /// Configures the dialog controls based on the provided format descriptor. /// /// FormatDescriptor to use. private void SetControlsFromDescriptor(FormatDescriptor dfd) { Debug.Assert(mIsInitialSetup); lowButton.IsChecked = true; if (dfd == null) { defaultButton.IsChecked = true; return; } // NOTE: it's entirely possible to have a weird format (e.g. string) if the // instruction used to be hinted as data. Handle it gracefully. switch (dfd.FormatType) { case FormatDescriptor.Type.NumericLE: switch (dfd.FormatSubType) { case FormatDescriptor.SubType.Hex: hexButton.IsChecked = true; break; case FormatDescriptor.SubType.Decimal: decimalButton.IsChecked = true; break; case FormatDescriptor.SubType.Binary: binaryButton.IsChecked = true; break; case FormatDescriptor.SubType.Ascii: case FormatDescriptor.SubType.HighAscii: asciiButton.IsChecked = true; break; case FormatDescriptor.SubType.C64Petscii: petsciiButton.IsChecked = true; break; case FormatDescriptor.SubType.C64Screen: screenCodeButton.IsChecked = true; break; case FormatDescriptor.SubType.Symbol: Debug.Assert(dfd.HasSymbol); symbolButton.IsChecked = true; switch (dfd.SymbolRef.ValuePart) { case WeakSymbolRef.Part.Low: lowButton.IsChecked = true; break; case WeakSymbolRef.Part.High: highButton.IsChecked = true; break; case WeakSymbolRef.Part.Bank: bankButton.IsChecked = true; break; default: Debug.Assert(false); break; } symbolTextBox.Text = dfd.SymbolRef.Label; break; case FormatDescriptor.SubType.None: default: // Unexpected; call it hex. hexButton.IsChecked = true; break; } break; case FormatDescriptor.Type.NumericBE: case FormatDescriptor.Type.StringGeneric: case FormatDescriptor.Type.StringReverse: case FormatDescriptor.Type.StringNullTerm: case FormatDescriptor.Type.StringL8: case FormatDescriptor.Type.StringL16: case FormatDescriptor.Type.StringDci: case FormatDescriptor.Type.Dense: case FormatDescriptor.Type.Fill: default: // Unexpected; used to be data? defaultButton.IsChecked = true; break; } } /// /// Creates a FormatDescriptor from the current state of the dialog controls. /// /// New FormatDescriptor. private FormatDescriptor CreateDescriptorFromControls() { if (symbolButton.IsChecked == true) { if (string.IsNullOrEmpty(symbolTextBox.Text)) { // empty symbol --> default format (intuitive way to delete label reference) return null; } WeakSymbolRef.Part part; if (lowButton.IsChecked == true) { part = WeakSymbolRef.Part.Low; } else if (highButton.IsChecked == true) { part = WeakSymbolRef.Part.High; } else if (bankButton.IsChecked == true) { part = WeakSymbolRef.Part.Bank; } else { Debug.Assert(false); part = WeakSymbolRef.Part.Low; } return FormatDescriptor.Create(mInstructionLength, new WeakSymbolRef(symbolTextBox.Text, part), false); } FormatDescriptor.SubType subType; if (defaultButton.IsChecked == true) { return null; } else if (hexButton.IsChecked == true) { subType = FormatDescriptor.SubType.Hex; } else if (decimalButton.IsChecked == true) { subType = FormatDescriptor.SubType.Decimal; } else if (binaryButton.IsChecked == true) { subType = FormatDescriptor.SubType.Binary; } else if (asciiButton.IsChecked == true) { if (mOperandValue > 0x7f) { subType = FormatDescriptor.SubType.HighAscii; } else { subType = FormatDescriptor.SubType.Ascii; } } else if (petsciiButton.IsChecked == true) { subType = FormatDescriptor.SubType.C64Petscii; } else if (screenCodeButton.IsChecked == true) { subType = FormatDescriptor.SubType.C64Screen; } else if (symbolButton.IsChecked == true) { subType = FormatDescriptor.SubType.Symbol; } else { Debug.Assert(false); subType = FormatDescriptor.SubType.None; } return FormatDescriptor.Create(mInstructionLength, FormatDescriptor.Type.NumericLE, subType); } } }