/// <summary> /// Formats the string as an opcode mnemonic. /// /// It may be necessary to modify the mnemonic for some assemblers, e.g. LDA from a /// 24-bit address might need to be LDAL, even if the high byte is nonzero. /// </summary> /// <param name="mnemonic">Instruction mnemonic string.</param> /// <param name="wdis">Width disambiguation specifier.</param> /// <returns></returns> public string FormatMnemonic(string mnemonic, OpDef.WidthDisambiguation wdis) { string opcodeStr = mnemonic; if (wdis == OpDef.WidthDisambiguation.ForceDirect) { // nothing to do for opcode } else if (wdis == OpDef.WidthDisambiguation.ForceAbs) { if (!string.IsNullOrEmpty(mFormatConfig.mForceAbsOpcodeSuffix)) { opcodeStr += mFormatConfig.mForceAbsOpcodeSuffix; } } else if (wdis == OpDef.WidthDisambiguation.ForceLong || wdis == OpDef.WidthDisambiguation.ForceLongMaybe) { if (!string.IsNullOrEmpty(mFormatConfig.mForceLongOpcodeSuffix)) { opcodeStr += mFormatConfig.mForceLongOpcodeSuffix; } } else { Debug.Assert(wdis == OpDef.WidthDisambiguation.None); } if (mFormatConfig.mUpperOpcodes) { opcodeStr = opcodeStr.ToUpperInvariant(); } return(opcodeStr); }
/// <summary> /// Formats the instruction operand. /// </summary> /// <param name="op">Opcode definition (needed for address mode).</param> /// <param name="contents">Label or numeric operand value.</param> /// <param name="wdis">Width disambiguation value.</param> /// <returns>Formatted string.</returns> public string FormatOperand(OpDef op, string contents, OpDef.WidthDisambiguation wdis) { Debug.Assert(((int)op.AddrMode & 0xff) == (int)op.AddrMode); int key = (int)op.AddrMode | ((int)wdis << 8); if (!mOperandFormats.TryGetValue(key, out string format)) { format = mOperandFormats[key] = GenerateOperandFormat(op.AddrMode, wdis); } return(string.Format(format, contents)); }
/// <summary> /// Formats the instruction opcode mnemonic, and caches the result. /// /// It may be necessary to modify the mnemonic for some assemblers, e.g. LDA from a /// 24-bit address might need to be LDAL, even if the high byte is nonzero. /// </summary> /// <param name="op">Opcode to format</param> /// <param name="wdis">Width disambiguation specifier.</param> /// <returns>Formatted string.</returns> public string FormatOpcode(OpDef op, OpDef.WidthDisambiguation wdis) { // TODO(someday): using op.Opcode as the key is a bad idea, as the operation may // not be the same on different CPUs. We currently rely on the caller to discard // the Formatter when the CPU definition changes. We'd be better off keying off of // the OpDef object and factoring wdis in some other way. int key = op.Opcode | ((int)wdis << 8); if (!mOpcodeStrings.TryGetValue(key, out string opcodeStr)) { // Not found, generate value. opcodeStr = FormatMnemonic(op.Mnemonic, wdis); // Memoize. mOpcodeStrings[key] = opcodeStr; } return(opcodeStr); }
/// <summary> /// Generates an operand format. /// </summary> /// <param name="addrMode">Addressing mode.</param> /// <param name="wdis">Width disambiguation mode.</param> /// <returns>Format string.</returns> private string GenerateOperandFormat(OpDef.AddressMode addrMode, OpDef.WidthDisambiguation wdis) { string fmt; string wdisStr = string.Empty; if (wdis == OpDef.WidthDisambiguation.ForceDirect) { if (!string.IsNullOrEmpty(mFormatConfig.mForceDirectOperandPrefix)) { wdisStr = mFormatConfig.mForceDirectOperandPrefix; } } else if (wdis == OpDef.WidthDisambiguation.ForceAbs) { if (!string.IsNullOrEmpty(mFormatConfig.mForceAbsOperandPrefix)) { wdisStr = mFormatConfig.mForceAbsOperandPrefix; } } else if (wdis == OpDef.WidthDisambiguation.ForceLong) { if (!string.IsNullOrEmpty(mFormatConfig.mForceLongOperandPrefix)) { wdisStr = mFormatConfig.mForceLongOperandPrefix; } } else if (wdis == OpDef.WidthDisambiguation.ForceLongMaybe) { // Don't add a width disambiguator to an operand that is unambiguously long. } else { Debug.Assert(wdis == OpDef.WidthDisambiguation.None); } switch (addrMode) { case AddressMode.Abs: case AddressMode.AbsLong: case AddressMode.BlockMove: case AddressMode.StackAbs: case AddressMode.DP: case AddressMode.PCRel: case AddressMode.PCRelLong: // BRL case AddressMode.StackInt: // COP and two-byte BRK case AddressMode.StackPCRelLong: // PER case AddressMode.WDM: fmt = wdisStr + "{0}"; break; case AddressMode.AbsIndexX: case AddressMode.AbsIndexXLong: case AddressMode.DPIndexX: fmt = wdisStr + "{0}," + mXregChar; break; case AddressMode.DPIndexY: case AddressMode.AbsIndexY: fmt = wdisStr + "{0}," + mYregChar; break; case AddressMode.AbsIndexXInd: case AddressMode.DPIndexXInd: fmt = wdisStr + "({0}," + mXregChar + ")"; break; case AddressMode.AbsInd: case AddressMode.DPInd: case AddressMode.StackDPInd: // PEI fmt = "({0})"; break; case AddressMode.AbsIndLong: case AddressMode.DPIndLong: // IIgs monitor uses "()" for AbsIndLong, E&L says "[]". Assemblers // seem to expect the latter. fmt = "[{0}]"; break; case AddressMode.Acc: fmt = mAccChar; break; case AddressMode.DPIndIndexY: fmt = "({0})," + mYregChar; break; case AddressMode.DPIndIndexYLong: fmt = "[{0}]," + mYregChar; break; case AddressMode.Imm: case AddressMode.ImmLongA: case AddressMode.ImmLongXY: fmt = "#{0}"; break; case AddressMode.Implied: case AddressMode.StackPull: case AddressMode.StackPush: case AddressMode.StackRTI: case AddressMode.StackRTL: case AddressMode.StackRTS: fmt = string.Empty; break; case AddressMode.StackRel: fmt = "{0}," + mSregChar; break; case AddressMode.StackRelIndIndexY: fmt = "({0}," + mSregChar + ")," + mYregChar; break; case AddressMode.Unknown: default: Debug.Assert(false); fmt = "???"; break; } return(fmt); }
private static void GenerateInstruction(IGenerator gen, StreamWriter sw, LocalVariableLookup lvLookup, int offset, int instrBytes, bool doAddCycles) { DisasmProject proj = gen.Project; Formatter formatter = gen.SourceFormatter; byte[] data = proj.FileData; Anattrib attr = proj.GetAnattrib(offset); string labelStr = string.Empty; if (attr.Symbol != null) { labelStr = gen.Localizer.ConvLabel(attr.Symbol.Label); } OpDef op = proj.CpuDef.GetOpDef(data[offset]); int operand = op.GetOperand(data, offset, attr.StatusFlags); int instrLen = op.GetLength(attr.StatusFlags); OpDef.WidthDisambiguation wdis = OpDef.WidthDisambiguation.None; if (op.IsWidthPotentiallyAmbiguous) { wdis = OpDef.GetWidthDisambiguation(instrLen, operand); } if (gen.Quirks.SinglePassAssembler && wdis == OpDef.WidthDisambiguation.None && (op.AddrMode == OpDef.AddressMode.DP || op.AddrMode == OpDef.AddressMode.DPIndexX) || op.AddrMode == OpDef.AddressMode.DPIndexY) { // Could be a forward reference to a direct-page label. For ACME, we don't // care if it's forward or not. if ((gen.Quirks.SinglePassNoLabelCorrection && IsLabelReference(gen, offset)) || IsForwardLabelReference(gen, offset)) { wdis = OpDef.WidthDisambiguation.ForceDirect; } } string opcodeStr = formatter.FormatOpcode(op, wdis); string formattedOperand = null; int operandLen = instrLen - 1; PseudoOp.FormatNumericOpFlags opFlags = PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix; bool isPcRelBankWrap = false; // Tweak branch instructions. We want to show the absolute address rather // than the relative offset (which happens with the OperandAddress assignment // below), and 1-byte branches should always appear as a 4-byte hex value. if (op.AddrMode == OpDef.AddressMode.PCRel) { Debug.Assert(attr.OperandAddress >= 0); operandLen = 2; opFlags |= PseudoOp.FormatNumericOpFlags.IsPcRel; } else if (op.AddrMode == OpDef.AddressMode.PCRelLong || op.AddrMode == OpDef.AddressMode.StackPCRelLong) { opFlags |= PseudoOp.FormatNumericOpFlags.IsPcRel; } else if (op.AddrMode == OpDef.AddressMode.Imm || op.AddrMode == OpDef.AddressMode.ImmLongA || op.AddrMode == OpDef.AddressMode.ImmLongXY) { opFlags |= PseudoOp.FormatNumericOpFlags.HasHashPrefix; } if ((opFlags & PseudoOp.FormatNumericOpFlags.IsPcRel) != 0) { int branchDist = attr.Address - attr.OperandAddress; isPcRelBankWrap = branchDist > 32767 || branchDist < -32768; } // 16-bit operands outside bank 0 need to include the bank when computing // symbol adjustment. int operandForSymbol = operand; if (attr.OperandAddress >= 0) { operandForSymbol = attr.OperandAddress; } // Check Length to watch for bogus descriptors. (ApplyFormatDescriptors() should // now be screening bad descriptors out, so we may not need the Length test.) if (attr.DataDescriptor != null && attr.Length == attr.DataDescriptor.Length) { FormatDescriptor dfd = gen.ModifyInstructionOperandFormat(offset, attr.DataDescriptor, operand); // Format operand as directed. if (op.AddrMode == OpDef.AddressMode.BlockMove) { // Special handling for the double-operand block move. string opstr1 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, gen.Localizer.LabelMap, dfd, operand >> 8, 1, PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix); string opstr2 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, gen.Localizer.LabelMap, dfd, operand & 0xff, 1, PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix); if (gen.Quirks.BlockMoveArgsReversed) { string tmp = opstr1; opstr1 = opstr2; opstr2 = tmp; } string hash = gen.Quirks.BlockMoveArgsNoHash ? "" : "#"; formattedOperand = hash + opstr1 + "," + hash + opstr2; } else { if (attr.DataDescriptor.IsStringOrCharacter) { gen.UpdateCharacterEncoding(dfd); } formattedOperand = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, lvLookup, gen.Localizer.LabelMap, dfd, offset, operandForSymbol, operandLen, opFlags); } } else { // Show operand value in hex. if (op.AddrMode == OpDef.AddressMode.BlockMove) { int arg1, arg2; if (gen.Quirks.BlockMoveArgsReversed) { arg1 = operand & 0xff; arg2 = operand >> 8; } else { arg1 = operand >> 8; arg2 = operand & 0xff; } string hash = gen.Quirks.BlockMoveArgsNoHash ? "" : "#"; formattedOperand = hash + formatter.FormatHexValue(arg1, 2) + "," + hash + formatter.FormatHexValue(arg2, 2); } else { if (operandLen == 2) { // This is necessary for 16-bit operands, like "LDA abs" and "PEA val", // when outside bank zero. The bank is included in the operand address, // but we don't want to show it here. operandForSymbol &= 0xffff; } formattedOperand = formatter.FormatHexValue(operandForSymbol, operandLen * 2); } } string operandStr = formatter.FormatOperand(op, formattedOperand, wdis); if (gen.Quirks.StackIntOperandIsImmediate && op.AddrMode == OpDef.AddressMode.StackInt) { // COP $02 is standard, but some require COP #$02 operandStr = '#' + operandStr; } string eolComment = proj.Comments[offset]; if (doAddCycles) { bool branchCross = (attr.Address & 0xff00) != (operandForSymbol & 0xff00); int cycles = proj.CpuDef.GetCycles(op.Opcode, attr.StatusFlags, attr.BranchTaken, branchCross); if (cycles > 0) { if (!string.IsNullOrEmpty(eolComment)) { eolComment = cycles.ToString() + " " + eolComment; } else { eolComment = cycles.ToString(); } } else { if (!string.IsNullOrEmpty(eolComment)) { eolComment = (-cycles).ToString() + "+ " + eolComment; } else { eolComment = (-cycles).ToString() + "+"; } } } string commentStr = formatter.FormatEolComment(eolComment); string replMnemonic = gen.ModifyOpcode(offset, op); if (attr.Length != instrBytes) { // This instruction has another instruction inside it. Throw out what we // computed and just output as bytes. gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); } else if (isPcRelBankWrap && gen.Quirks.NoPcRelBankWrap) { // Some assemblers have trouble generating PC-relative operands that wrap // around the bank. Output as raw hex. gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); } else if (op.AddrMode == OpDef.AddressMode.BlockMove && gen.Quirks.BlockMoveArgsReversed) { // On second thought, just don't even output the wrong thing. gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); } else if (replMnemonic == null) { // No mnemonic exists for this opcode. gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); } else if (replMnemonic != string.Empty) { // A replacement mnemonic has been provided. opcodeStr = formatter.FormatMnemonic(replMnemonic, wdis); } gen.OutputLine(labelStr, opcodeStr, operandStr, commentStr); // Assemblers like Merlin32 try to be helpful and track SEP/REP, but they do the // wrong thing if we're in emulation mode. Force flags back to short. if (proj.CpuDef.HasEmuFlag && gen.Quirks.TracksSepRepNotEmu && op == OpDef.OpREP_Imm) { if ((operand & 0x30) != 0 && attr.StatusFlags.E == 1) { gen.OutputRegWidthDirective(offset, 0, 0, 1, 1); } } }
private static void GenerateInstruction(IGenerator gen, StreamWriter sw, int offset, int instrBytes, bool doAddCycles) { DisasmProject proj = gen.Project; Formatter formatter = gen.SourceFormatter; byte[] data = proj.FileData; Anattrib attr = proj.GetAnattrib(offset); string labelStr = string.Empty; if (attr.Symbol != null) { labelStr = gen.Localizer.ConvLabel(attr.Symbol.Label); } OpDef op = proj.CpuDef.GetOpDef(data[offset]); int operand = op.GetOperand(data, offset, attr.StatusFlags); int instrLen = op.GetLength(attr.StatusFlags); OpDef.WidthDisambiguation wdis = OpDef.WidthDisambiguation.None; if (op.IsWidthPotentiallyAmbiguous) { wdis = OpDef.GetWidthDisambiguation(instrLen, operand); } string replMnemonic = gen.ReplaceMnemonic(op); string opcodeStr = formatter.FormatOpcode(op, wdis); string formattedOperand = null; int operandLen = instrLen - 1; bool isPcRel = false; bool isPcRelBankWrap = false; // Tweak branch instructions. We want to show the absolute address rather // than the relative offset (which happens with the OperandAddress assignment // below), and 1-byte branches should always appear as a 4-byte hex value. if (op.AddrMode == OpDef.AddressMode.PCRel) { Debug.Assert(attr.OperandAddress >= 0); operandLen = 2; isPcRel = true; } else if (op.AddrMode == OpDef.AddressMode.PCRelLong || op.AddrMode == OpDef.AddressMode.StackPCRelLong) { isPcRel = true; } if (isPcRel) { int branchDist = attr.Address - attr.OperandAddress; isPcRelBankWrap = branchDist > 32767 || branchDist < -32768; } // 16-bit operands outside bank 0 need to include the bank when computing // symbol adjustment. int operandForSymbol = operand; if (attr.OperandAddress >= 0) { operandForSymbol = attr.OperandAddress; } // Check Length to watch for bogus descriptors (?) if (attr.DataDescriptor != null && attr.Length == attr.DataDescriptor.Length) { // Format operand as directed. if (op.AddrMode == OpDef.AddressMode.BlockMove) { // Special handling for the double-operand block move. string opstr1 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, gen.Localizer.LabelMap, attr.DataDescriptor, operand >> 8, 1, false); string opstr2 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, gen.Localizer.LabelMap, attr.DataDescriptor, operand & 0xff, 1, false); if (gen.Quirks.BlockMoveArgsReversed) { string tmp = opstr1; opstr1 = opstr2; opstr2 = tmp; } formattedOperand = opstr1 + "," + opstr2; } else { formattedOperand = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, gen.Localizer.LabelMap, attr.DataDescriptor, operandForSymbol, operandLen, isPcRel); } } else { // Show operand value in hex. if (op.AddrMode == OpDef.AddressMode.BlockMove) { int arg1, arg2; if (gen.Quirks.BlockMoveArgsReversed) { arg1 = operand & 0xff; arg2 = operand >> 8; } else { arg1 = operand >> 8; arg2 = operand & 0xff; } formattedOperand = formatter.FormatHexValue(arg1, 2) + "," + formatter.FormatHexValue(arg2, 2); } else { if (operandLen == 2) { // This is necessary for 16-bit operands, like "LDA abs" and "PEA val", // when outside bank zero. The bank is included in the operand address, // but we don't want to show it here. operandForSymbol &= 0xffff; } formattedOperand = formatter.FormatHexValue(operandForSymbol, operandLen * 2); } } string operandStr = formatter.FormatOperand(op, formattedOperand, wdis); string eolComment = proj.Comments[offset]; if (doAddCycles) { bool branchCross = (attr.Address & 0xff00) != (operandForSymbol & 0xff00); int cycles = proj.CpuDef.GetCycles(op.Opcode, attr.StatusFlags, attr.BranchTaken, branchCross); if (cycles > 0) { eolComment = cycles.ToString() + " " + eolComment; } else { eolComment = (-cycles).ToString() + "+ " + eolComment; } } string commentStr = formatter.FormatEolComment(eolComment); if (attr.Length != instrBytes) { // This instruction has another instruction inside it. Throw out what we // computed and just output as bytes. gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); } else if (isPcRelBankWrap && gen.Quirks.NoPcRelBankWrap) { // Some assemblers have trouble generating PC-relative operands that wrap // around the bank. Output as raw hex. gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); } else if (op.AddrMode == OpDef.AddressMode.BlockMove && gen.Quirks.BlockMoveArgsReversed) { // On second thought, just don't even output the wrong thing. gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); } else if (replMnemonic == null) { // No mnemonic exists for this opcode. gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); } else if (replMnemonic != string.Empty) { // A replacement mnemonic has been provided. opcodeStr = formatter.FormatMnemonic(replMnemonic, wdis); } gen.OutputLine(labelStr, opcodeStr, operandStr, commentStr); // Assemblers like Merlin32 try to be helpful and track SEP/REP, but they do the // wrong thing if we're in emulation mode. Force flags back to short. if (proj.CpuDef.HasEmuFlag && gen.Quirks.TracksSepRepNotEmu && op == OpDef.OpREP_Imm) { if ((operand & 0x30) != 0 && attr.StatusFlags.E == 1) { gen.OutputRegWidthDirective(offset, 0, 0, 1, 1); } } }
private static void GenerateInstruction(IGenerator gen, StreamWriter sw, LocalVariableLookup lvLookup, int offset, int instrBytes, bool doAddCycles) { DisasmProject proj = gen.Project; Formatter formatter = gen.SourceFormatter; byte[] data = proj.FileData; Anattrib attr = proj.GetAnattrib(offset); string labelStr = string.Empty; if (attr.Symbol != null) { labelStr = gen.Localizer.ConvLabel(attr.Symbol.Label); } OpDef op = proj.CpuDef.GetOpDef(data[offset]); int operand = op.GetOperand(data, offset, attr.StatusFlags); int instrLen = op.GetLength(attr.StatusFlags); OpDef.WidthDisambiguation wdis = OpDef.WidthDisambiguation.None; if (op.IsWidthPotentiallyAmbiguous) { wdis = OpDef.GetWidthDisambiguation(instrLen, operand); } if (gen.Quirks.SinglePassAssembler && wdis == OpDef.WidthDisambiguation.None && (op.AddrMode == OpDef.AddressMode.DP || op.AddrMode == OpDef.AddressMode.DPIndexX) || op.AddrMode == OpDef.AddressMode.DPIndexY) { // Could be a forward reference to a direct-page label. For ACME, we don't // care if it's forward or not. if ((gen.Quirks.SinglePassNoLabelCorrection && IsLabelReference(gen, offset)) || IsForwardLabelReference(gen, offset)) { wdis = OpDef.WidthDisambiguation.ForceDirect; } } if (wdis == OpDef.WidthDisambiguation.ForceLongMaybe && gen.Quirks.SinglePassAssembler && IsForwardLabelReference(gen, offset)) { // Assemblers like cc65 can't tell if a symbol reference is Absolute or // Long if they haven't seen the symbol yet. Irrelevant for ACME, which // doesn't currently handle 65816 outside bank 0. wdis = OpDef.WidthDisambiguation.ForceLong; } string opcodeStr = formatter.FormatOpcode(op, wdis); string formattedOperand = null; int operandLen = instrLen - 1; PseudoOp.FormatNumericOpFlags opFlags = PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix; bool isPcRelBankWrap = false; // Tweak branch instructions. We want to show the absolute address rather // than the relative offset (which happens with the OperandAddress assignment // below), and 1-byte branches should always appear as a 4-byte hex value. // Unless we're outside bank 0 on 65816, in which case most assemblers require // them to be 6-byte hex values. if (op.AddrMode == OpDef.AddressMode.PCRel || op.AddrMode == OpDef.AddressMode.DPPCRel) { Debug.Assert(attr.OperandAddress >= 0); operandLen = 2; opFlags |= PseudoOp.FormatNumericOpFlags.IsPcRel; } else if (op.AddrMode == OpDef.AddressMode.PCRelLong || op.AddrMode == OpDef.AddressMode.StackPCRelLong) { opFlags |= PseudoOp.FormatNumericOpFlags.IsPcRel; } else if (op.AddrMode == OpDef.AddressMode.Imm || op.AddrMode == OpDef.AddressMode.ImmLongA || op.AddrMode == OpDef.AddressMode.ImmLongXY) { opFlags |= PseudoOp.FormatNumericOpFlags.HasHashPrefix; } if ((opFlags & PseudoOp.FormatNumericOpFlags.IsPcRel) != 0) { int branchDist = attr.Address - attr.OperandAddress; isPcRelBankWrap = branchDist > 32767 || branchDist < -32768; } if (op.IsAbsolutePBR) { opFlags |= PseudoOp.FormatNumericOpFlags.IsAbsolutePBR; } if (gen.Quirks.BankZeroAbsPBRRestrict) { // Hack to avoid having to define a new FormatConfig.ExpressionMode for 64tass. // Get rid of this 64tass gets its own exp mode. opFlags |= PseudoOp.FormatNumericOpFlags.Is64Tass; } // 16-bit operands outside bank 0 need to include the bank when computing // symbol adjustment. int operandForSymbol = operand; if (attr.OperandAddress >= 0) { operandForSymbol = attr.OperandAddress; } // Check Length to watch for bogus descriptors. (ApplyFormatDescriptors() should // now be screening bad descriptors out, so we may not need the Length test.) if (attr.DataDescriptor != null && attr.Length == attr.DataDescriptor.Length) { FormatDescriptor dfd = gen.ModifyInstructionOperandFormat(offset, attr.DataDescriptor, operand); // Format operand as directed. if (op.AddrMode == OpDef.AddressMode.BlockMove) { // Special handling for the double-operand block move. string opstr1 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, gen.Localizer.LabelMap, dfd, operand >> 8, 1, PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix); string opstr2 = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, gen.Localizer.LabelMap, dfd, operand & 0xff, 1, PseudoOp.FormatNumericOpFlags.OmitLabelPrefixSuffix); if (gen.Quirks.BlockMoveArgsReversed) { string tmp = opstr1; opstr1 = opstr2; opstr2 = tmp; } string hash = gen.Quirks.BlockMoveArgsNoHash ? "" : "#"; formattedOperand = hash + opstr1 + "," + hash + opstr2; } else if (op.AddrMode == OpDef.AddressMode.DPPCRel) { // Special handling for double-operand BBR/BBS. The instruction generally // behaves like a branch, so format that first. string branchStr = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, gen.Localizer.LabelMap, dfd, operandForSymbol, operandLen, opFlags); string dpStr = formatter.FormatHexValue(operand & 0xff, 2); formattedOperand = dpStr + "," + branchStr; } else { if (attr.DataDescriptor.IsStringOrCharacter) { gen.UpdateCharacterEncoding(dfd); } formattedOperand = PseudoOp.FormatNumericOperand(formatter, proj.SymbolTable, lvLookup, gen.Localizer.LabelMap, dfd, offset, operandForSymbol, operandLen, opFlags); } } else { // Show operand value in hex. if (op.AddrMode == OpDef.AddressMode.BlockMove) { int arg1, arg2; if (gen.Quirks.BlockMoveArgsReversed) { arg1 = operand & 0xff; arg2 = operand >> 8; } else { arg1 = operand >> 8; arg2 = operand & 0xff; } string hash = gen.Quirks.BlockMoveArgsNoHash ? "" : "#"; formattedOperand = hash + formatter.FormatHexValue(arg1, 2) + "," + hash + formatter.FormatHexValue(arg2, 2); } else if (op.AddrMode == OpDef.AddressMode.DPPCRel) { formattedOperand = formatter.FormatHexValue(operand & 0xff, 2) + "," + formatter.FormatHexValue(operandForSymbol, operandLen * 2); } else { if (operandLen == 2 && !(op.IsAbsolutePBR && gen.Quirks.Need24BitsForAbsPBR) && (opFlags & PseudoOp.FormatNumericOpFlags.IsPcRel) == 0) { // This is necessary for 16-bit operands, like "LDA abs" and "PEA val", // when outside bank zero. The bank is included in the operand address, // but we don't want to show it here. We may need it for JSR/JMP though, // and the bank is required for relative branch instructions. operandForSymbol &= 0xffff; } formattedOperand = formatter.FormatHexValue(operandForSymbol, operandLen * 2); } } string operandStr = formatter.FormatOperand(op, formattedOperand, wdis); if (gen.Quirks.StackIntOperandIsImmediate && op.AddrMode == OpDef.AddressMode.StackInt) { // COP $02 is standard, but some require COP #$02 operandStr = '#' + operandStr; } // The BBR/BBS/RMB/SMB instructions include a bit index (0-7). The standard way is // to make it part of the mnemonic, but some assemblers make it an argument. if (gen.Quirks.BitNumberIsArg && op.IsNumberedBitOp) { // Easy way: do some string manipulation. char bitIndex = opcodeStr[opcodeStr.Length - 1]; opcodeStr = opcodeStr.Substring(0, opcodeStr.Length - 1); operandStr = bitIndex.ToString() + "," + operandStr; } string eolComment = proj.Comments[offset]; if (doAddCycles) { bool branchCross = (attr.Address & 0xff00) != (operandForSymbol & 0xff00); int cycles = proj.CpuDef.GetCycles(op.Opcode, attr.StatusFlags, attr.BranchTaken, branchCross); if (cycles > 0) { if (!string.IsNullOrEmpty(eolComment)) { eolComment = cycles.ToString() + " " + eolComment; } else { eolComment = cycles.ToString(); } } else { if (!string.IsNullOrEmpty(eolComment)) { eolComment = (-cycles).ToString() + "+ " + eolComment; } else { eolComment = (-cycles).ToString() + "+"; } } } string commentStr = formatter.FormatEolComment(eolComment); string replMnemonic = gen.ModifyOpcode(offset, op); if (attr.Length != instrBytes) { // This instruction has another instruction inside it. Throw out what we // computed and just output as bytes. // TODO: in some odd situations we can split something that doesn't need // to be split (see note at end of #107). Working around the problem at // this stage is a little awkward because I think we need to check for the // presence of labels on one or more later lines. gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); } else if (isPcRelBankWrap && gen.Quirks.NoPcRelBankWrap) { // Some assemblers have trouble generating PC-relative operands that wrap // around the bank. Output as raw hex. gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); } else if (op.AddrMode == OpDef.AddressMode.BlockMove && gen.Quirks.BlockMoveArgsReversed) { // On second thought, just don't even output the wrong thing. gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); } else if (replMnemonic == null) { // No mnemonic exists for this opcode. gen.GenerateShortSequence(offset, instrBytes, out opcodeStr, out operandStr); } else if (replMnemonic != string.Empty) { // A replacement mnemonic has been provided. opcodeStr = formatter.FormatMnemonic(replMnemonic, wdis); } gen.OutputLine(labelStr, opcodeStr, operandStr, commentStr); // Assemblers like Merlin32 try to be helpful and track SEP/REP, but they do the // wrong thing if we're in emulation mode. Force flags back to short. if (proj.CpuDef.HasEmuFlag && gen.Quirks.TracksSepRepNotEmu && op == OpDef.OpREP_Imm) { if ((operand & 0x30) != 0 && attr.StatusFlags.IsEmulationMode) { gen.OutputRegWidthDirective(offset, 0, 0, 1, 1); } } }