/* * Copyright 2018 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.Diagnostics; using System.Text; using System.Windows.Forms; using Asm65; namespace SourceGen.AppForms { public partial class EditOperand : Form { /// /// 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 EditOperand(int offset, DisasmProject project, Asm65.Formatter formatter) { InitializeComponent(); 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 EditOperand_Load(object sender, EventArgs e) { mIsInitialSetup = true; // Can this be represented as high or low ASCII? radioButtonAscii.Enabled = CommonUtil.TextUtil.IsHiLoAscii(mOperandValue); // 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: labelInsteadRadioButton.Checked = true; break; case SymbolShortcutAction.CreateLabelAlso: operandAndLabelRadioButton.Checked = true; break; case SymbolShortcutAction.CreateProjectSymbolAlso: operandAndProjRadioButton.Checked = true; break; default: operandOnlyRadioButton.Checked = true; break; } mIsInitialSetup = false; UpdateControls(); } private void EditOperand_Shown(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, EventArgs e) { // Make sure Symbol is checked if they're typing text in. radioButtonSymbol.Checked = true; UpdateControls(); } /// /// Handles CheckedChanged event for all radio buttons in main group. /// private void MainGroup_CheckedChanged(object sender, EventArgs e) { // Enable/disable the low/high/bank radio group. // Update preview window. UpdateControls(); } /// /// Handles CheckedChanged event for all radio buttons in symbol-part group. /// private void PartGroup_CheckedChanged(object sender, EventArgs e) { // Update preview window. UpdateControls(); } private void okButton_Click(object sender, EventArgs 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 (labelInsteadRadioButton.Checked) { action = SymbolShortcutAction.CreateLabelInstead; } else if (operandAndLabelRadioButton.Checked) { action = SymbolShortcutAction.CreateLabelAlso; } else if (operandAndProjRadioButton.Checked) { action = SymbolShortcutAction.CreateProjectSymbolAlso; } else if (operandOnlyRadioButton.Checked) { action = SymbolShortcutAction.None; } else { Debug.Assert(false); action = SymbolShortcutAction.None; } sLastAction = action; if (radioButtonSymbol.Checked && 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; } } /// /// Updates all of the controls to reflect the current internal state. /// private void UpdateControls() { if (mIsInitialSetup) { return; } symbolPartPanel.Enabled = radioButtonSymbol.Checked && mIsExtendedImmediate; symbolShortcutsGroupBox.Enabled = radioButtonSymbol.Checked; SetPreviewText(); bool isOk = true; if (radioButtonSymbol.Checked) { // 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.Enabled = 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: preview.Append(mFormatter.FormatAsciiOrHex(mOperandValue)); 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) { radioButtonBank.Checked = true; } else { radioButtonLow.Checked = 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)) { labelInsteadRadioButton.Enabled = operandAndLabelRadioButton.Enabled = operandAndProjRadioButton.Enabled = false; operandOnlyRadioButton.Checked = 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); labelInsteadRadioButton.Enabled = operandAndLabelRadioButton.Enabled = !hasLabel; operandAndProjRadioButton.Enabled = false; ShortcutArg = targetOffset; } else if (mAttr.OperandAddress >= 0) { // Operand target is outside the file. labelInsteadRadioButton.Enabled = operandAndLabelRadioButton.Enabled = false; operandAndProjRadioButton.Enabled = true; ShortcutArg = mAttr.OperandAddress; } else { // Probably an immediate operand. labelInsteadRadioButton.Enabled = operandAndLabelRadioButton.Enabled = operandAndProjRadioButton.Enabled = false; } // Select the default option if the currently-selected option is no longer available. if ((labelInsteadRadioButton.Checked && !labelInsteadRadioButton.Enabled) || (operandAndLabelRadioButton.Checked && !operandAndLabelRadioButton.Enabled) || (operandAndProjRadioButton.Checked && !operandAndProjRadioButton.Enabled)) { operandOnlyRadioButton.Checked = true; } } /// /// Configures the dialog controls based on the provided format descriptor. /// /// FormatDescriptor to use. private void SetControlsFromDescriptor(FormatDescriptor dfd) { Debug.Assert(mIsInitialSetup); radioButtonLow.Checked = true; if (dfd == null) { radioButtonDefault.Checked = 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: radioButtonHex.Checked = true; break; case FormatDescriptor.SubType.Decimal: radioButtonDecimal.Checked = true; break; case FormatDescriptor.SubType.Binary: radioButtonBinary.Checked = true; break; case FormatDescriptor.SubType.Ascii: radioButtonAscii.Checked = true; break; case FormatDescriptor.SubType.Symbol: Debug.Assert(dfd.HasSymbol); radioButtonSymbol.Checked = true; switch (dfd.SymbolRef.ValuePart) { case WeakSymbolRef.Part.Low: radioButtonLow.Checked = true; break; case WeakSymbolRef.Part.High: radioButtonHigh.Checked = true; break; case WeakSymbolRef.Part.Bank: radioButtonBank.Checked = true; break; default: Debug.Assert(false); break; } symbolTextBox.Text = dfd.SymbolRef.Label; break; case FormatDescriptor.SubType.None: default: // Unexpected; call it hex. radioButtonHex.Checked = true; break; } break; case FormatDescriptor.Type.NumericBE: case FormatDescriptor.Type.String: case FormatDescriptor.Type.Fill: default: // Unexpected; used to be data? radioButtonDefault.Checked = true; break; } } /// /// Creates a FormatDescriptor from the current state of the dialog controls. /// /// New FormatDescriptor. private FormatDescriptor CreateDescriptorFromControls() { if (radioButtonSymbol.Checked) { if (string.IsNullOrEmpty(symbolTextBox.Text)) { // empty symbol --> default format (intuitive way to delete label reference) return null; } WeakSymbolRef.Part part; if (radioButtonLow.Checked) { part = WeakSymbolRef.Part.Low; } else if (radioButtonHigh.Checked) { part = WeakSymbolRef.Part.High; } else if (radioButtonBank.Checked) { 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 (radioButtonDefault.Checked) { return null; } else if (radioButtonHex.Checked) { subType = FormatDescriptor.SubType.Hex; } else if (radioButtonDecimal.Checked) { subType = FormatDescriptor.SubType.Decimal; } else if (radioButtonBinary.Checked) { subType = FormatDescriptor.SubType.Binary; } else if (radioButtonAscii.Checked) { subType = FormatDescriptor.SubType.Ascii; } else if (radioButtonSymbol.Checked) { subType = FormatDescriptor.SubType.Symbol; } else { Debug.Assert(false); subType = FormatDescriptor.SubType.None; } return FormatDescriptor.Create(mInstructionLength, FormatDescriptor.Type.NumericLE, subType); } } }