AppleWin/source/Debugger/Debugger_Assembler.cpp
Michael "Code Poet" Pohoreski a3c6156508
Debugger step-over can fail (#1194, PR #1203)
. QoL cleanup (show RTS address) for step-over failure cases
. Add source code for repro test 1 and 2
2023-03-31 09:45:54 +01:00

1573 lines
50 KiB
C++

/*
AppleWin : An Apple //e emulator for Windows
Copyright (C) 1994-1996, Michael O'Brien
Copyright (C) 1999-2001, Oliver Schmidt
Copyright (C) 2002-2005, Tom Charlesworth
Copyright (C) 2006-2014, Tom Charlesworth, Michael Pohoreski
AppleWin is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
AppleWin is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with AppleWin; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* Description: Debugger Assembler
*
* Author: Copyright (C) 2006-2010 Michael Pohoreski
*/
#include "StdAfx.h"
#include "Debug.h"
#include "../Interface.h"
#include "../CPU.h"
#include "../Memory.h"
#define DEBUG_ASSEMBLER 0
// Globals __________________________________________________________________
// Addressing _____________________________________________________________________________________
AddressingMode_t g_aOpmodes[ NUM_ADDRESSING_MODES ] =
{ // Output, but eventually used for Input when Assembler is working.
{TEXT("") , 1 , "(implied)" }, // (implied)
{TEXT("") , 1 , "n/a 1" }, // INVALID1
{TEXT("") , 2 , "n/a 2" }, // INVALID2
{TEXT("") , 3 , "n/a 3" }, // INVALID3
{TEXT("%02X") , 2 , "Immediate" }, // AM_M // #$%02X -> %02X
{TEXT("%04X") , 3 , "Absolute" }, // AM_A
{TEXT("%02X") , 2 , "Zero Page" }, // AM_Z
{TEXT("%04X,X") , 3 , "Absolute,X" }, // AM_AX // %s,X
{TEXT("%04X,Y") , 3 , "Absolute,Y" }, // AM_AY // %s,Y
{TEXT("%02X,X") , 2 , "Zero Page,X" }, // AM_ZX // %s,X
{TEXT("%02X,Y") , 2 , "Zero Page,Y" }, // AM_ZY // %s,Y
{TEXT("%s") , 2 , "Relative" }, // AM_R
{TEXT("(%02X,X)"), 2 , "(Zero Page),X" }, // AM_IZX // ($%02X,X) -> %s,X
{TEXT("(%04X,X)"), 3 , "(Absolute),X" }, // AM_IAX // ($%04X,X) -> %s,X
{TEXT("(%02X),Y"), 2 , "(Zero Page),Y" }, // AM_NZY // ($%02X),Y
{TEXT("(%02X)") , 2 , "(Zero Page)" }, // AM_NZ // ($%02X) -> $%02X
{TEXT("(%04X)") , 3 , "(Absolute)" } // AM_NA // (%04X) -> %s
};
// Assembler ______________________________________________________________________________________
int g_bAssemblerOpcodesHashed = false;
Hash_t g_aOpcodesHash[ NUM_OPCODES ]; // for faster mnemonic lookup, for the assembler
bool g_bAssemblerInput = false;
int g_nAssemblerAddress = 0;
const Opcodes_t *g_aOpcodes = NULL; // & g_aOpcodes65C02[ 0 ];
// Disassembler Data _____________________________________________________________________________
std::vector<DisasmData_t> g_aDisassemblerData;
// Instructions / Opcodes _________________________________________________________________________
// @reference: http://www.6502.org/tutorials/compare_instructions.html
// 10 signed: BPL BGE
// B0 unsigned: BCS BGE
#define R_ MEM_R
#define _W MEM_W
#define RW MEM_R | MEM_W
#define _S MEM_S
#define im MEM_IM
#define SW MEM_S | MEM_WI
#define SR MEM_S | MEM_RI
const Opcodes_t g_aOpcodes65C02[ NUM_OPCODES ] =
{
{"BRK", 0 , SW}, {"ORA", AM_IZX, R_}, {"nop", AM_M , im}, {"nop", 0 , 0 }, // 00 .. 03
{"TSB", AM_Z , _W}, {"ORA", AM_Z , R_}, {"ASL", AM_Z , RW}, {"nop", 0 , 0 }, // 04 .. 07
{"PHP", 0 , SW}, {"ORA", AM_M , im}, {"ASL", 0 , 0}, {"nop", 0 , 0 }, // 08 .. 0B
{"TSB", AM_A , _W}, {"ORA", AM_A , R_}, {"ASL", AM_A , RW}, {"nop", 0 , 0 }, // 0C .. 0F
{"BPL", AM_R , 0}, {"ORA", AM_NZY, R_}, {"ORA", AM_NZ , R_}, {"nop", 0 , 0 }, // 10 .. 13
{"TRB", AM_Z , _W}, {"ORA", AM_ZX , R_}, {"ASL", AM_ZX , RW}, {"nop", 0 , 0 }, // 14 .. 17
{"CLC", 0 , 0}, {"ORA", AM_AY , R_}, {"INC", 0 , 0}, {"nop", 0 , 0 }, // 18 .. 1B
{"TRB", AM_A , _W}, {"ORA", AM_AX , R_}, {"ASL", AM_AX , RW}, {"nop", 0 , 0 }, // 1C .. 1F
{"JSR", AM_A , SW}, {"AND", AM_IZX, R_}, {"nop", AM_M , im}, {"nop", 0 , 0 }, // 20 .. 23
{"BIT", AM_Z , R_}, {"AND", AM_Z , R_}, {"ROL", AM_Z , RW}, {"nop", 0 , 0 }, // 24 .. 27
{"PLP", 0 , SR}, {"AND", AM_M , im}, {"ROL", 0 , 0}, {"nop", 0 , 0 }, // 28 .. 2B
{"BIT", AM_A , R_}, {"AND", AM_A , R_}, {"ROL", AM_A , RW}, {"nop", 0 , 0 }, // 2C .. 2F
{"BMI", AM_R , 0}, {"AND", AM_NZY, R_}, {"AND", AM_NZ , R_}, {"nop", 0 , 0 }, // 30 .. 33
{"BIT", AM_ZX , R_}, {"AND", AM_ZX , R_}, {"ROL", AM_ZX , RW}, {"nop", 0 , 0 }, // 34 .. 37
{"SEC", 0 , 0}, {"AND", AM_AY , R_}, {"DEC", 0 , 0}, {"nop", 0 , 0 }, // 38 .. 3B
{"BIT", AM_AX , R_}, {"AND", AM_AX , R_}, {"ROL", AM_AX , RW}, {"nop", 0 , 0 }, // 3C .. 3F
{"RTI", 0 , SR}, {"EOR", AM_IZX, R_}, {"nop", AM_M , im}, {"nop", 0 , 0 }, // 40 .. 43
{"nop", AM_Z , 0}, {"EOR", AM_Z , R_}, {"LSR", AM_Z , _W}, {"nop", 0 , 0 }, // 44 .. 47
{"PHA", 0 , SW}, {"EOR", AM_M , im}, {"LSR", 0 , 0}, {"nop", 0 , 0 }, // 48 .. 4B
{"JMP", AM_A , 0}, {"EOR", AM_A , R_}, {"LSR", AM_A , _W}, {"nop", 0 , 0 }, // 4C .. 4F
{"BVC", AM_R , 0}, {"EOR", AM_NZY, R_}, {"EOR", AM_NZ , R_}, {"nop", 0 , 0 }, // 50 .. 53
{"nop", AM_ZX , 0}, {"EOR", AM_ZX , R_}, {"LSR", AM_ZX , _W}, {"nop", 0 , 0 }, // 54 .. 57
{"CLI", 0 , 0}, {"EOR", AM_AY , R_}, {"PHY", 0 , SW}, {"nop", 0 , 0 }, // 58 .. 5B
{"nop", AM_AX , 0}, {"EOR", AM_AX , R_}, {"LSR", AM_AX , RW}, {"nop", 0 , 0 }, // 5C .. 5F
{"RTS", 0 , SR}, {"ADC", AM_IZX, R_}, {"nop", AM_M , im}, {"nop", 0 , 0 }, // 60 .. 63
{"STZ", AM_Z , _W}, {"ADC", AM_Z , R_}, {"ROR", AM_Z , RW}, {"nop", 0 , 0 }, // 64 .. 67
{"PLA", 0 , SR}, {"ADC", AM_M , im}, {"ROR", 0 , 0}, {"nop", 0 , 0 }, // 68 .. 6B
{"JMP", AM_NA , R_}, {"ADC", AM_A , R_}, {"ROR", AM_A , RW}, {"nop", 0 , 0 }, // 6C .. 6F
{"BVS", AM_R , 0}, {"ADC", AM_NZY, R_}, {"ADC", AM_NZ , R_}, {"nop", 0 , 0 }, // 70 .. 73
{"STZ", AM_ZX , _W}, {"ADC", AM_ZX , R_}, {"ROR", AM_ZX , RW}, {"nop", 0 , 0 }, // 74 .. 77
{"SEI", 0 , 0}, {"ADC", AM_AY , R_}, {"PLY", 0 , SR}, {"nop", 0 , 0 }, // 78 .. 7B
{"JMP", AM_IAX, R_}, {"ADC", AM_AX , R_}, {"ROR", AM_AX , RW}, {"nop", 0 , 0 }, // 7C .. 7F
{"BRA", AM_R , 0}, {"STA", AM_IZX, _W}, {"nop", AM_M , im}, {"nop", 0 , 0 }, // 80 .. 83
{"STY", AM_Z , _W}, {"STA", AM_Z , _W}, {"STX", AM_Z , _W}, {"nop", 0 , 0 }, // 84 .. 87
{"DEY", 0 , 0}, {"BIT", AM_M , im}, {"TXA", 0 , 0}, {"nop", 0 , 0 }, // 88 .. 8B
{"STY", AM_A , _W}, {"STA", AM_A , _W}, {"STX", AM_A , _W}, {"nop", 0 , 0 }, // 8C .. 8F
{"BCC", AM_R , 0}, {"STA", AM_NZY, _W}, {"STA", AM_NZ , _W}, {"nop", 0 , 0 }, // 90 .. 93
{"STY", AM_ZX , _W}, {"STA", AM_ZX , _W}, {"STX", AM_ZY , _W}, {"nop", 0 , 0 }, // 94 .. 97
{"TYA", 0 , 0}, {"STA", AM_AY , _W}, {"TXS", 0 , 0}, {"nop", 0 , 0 }, // 98 .. 9B
{"STZ", AM_A , _W}, {"STA", AM_AX , _W}, {"STZ", AM_AX , _W}, {"nop", 0 , 0 }, // 9C .. 9F
{"LDY", AM_M , im}, {"LDA", AM_IZX, R_}, {"LDX", AM_M , im}, {"nop", 0 , 0 }, // A0 .. A3
{"LDY", AM_Z , R_}, {"LDA", AM_Z , R_}, {"LDX", AM_Z , R_}, {"nop", 0 , 0 }, // A4 .. A7
{"TAY", 0 , 0}, {"LDA", AM_M , im}, {"TAX", 0 , 0 }, {"nop", 0 , 0 }, // A8 .. AB
{"LDY", AM_A , R_}, {"LDA", AM_A , R_}, {"LDX", AM_A , R_}, {"nop", 0 , 0 }, // AC .. AF
{"BCS", AM_R , 0}, {"LDA", AM_NZY, R_}, {"LDA", AM_NZ , R_}, {"nop", 0 , 0 }, // B0 .. B3
{"LDY", AM_ZX , R_}, {"LDA", AM_ZX , R_}, {"LDX", AM_ZY , R_}, {"nop", 0 , 0 }, // B4 .. B7
{"CLV", 0 , 0}, {"LDA", AM_AY , R_}, {"TSX", 0 , 0 }, {"nop", 0 , 0 }, // B8 .. BB
{"LDY", AM_AX , R_}, {"LDA", AM_AX , R_}, {"LDX", AM_AY , R_}, {"nop", 0 , 0 }, // BC .. BF
{"CPY", AM_M , im}, {"CMP", AM_IZX, R_}, {"nop", AM_M , im}, {"nop", 0 , 0 }, // C0 .. C3
{"CPY", AM_Z , R_}, {"CMP", AM_Z , R_}, {"DEC", AM_Z , RW}, {"nop", 0 , 0 }, // C4 .. C7
{"INY", 0 , 0}, {"CMP", AM_M , im}, {"DEX", 0 , 0}, {"nop", 0 , 0 }, // C8 .. CB
{"CPY", AM_A , R_}, {"CMP", AM_A , R_}, {"DEC", AM_A , RW}, {"nop", 0 , 0 }, // CC .. CF
{"BNE", AM_R , 0}, {"CMP", AM_NZY, R_}, {"CMP", AM_NZ , 0}, {"nop", 0 , 0 }, // D0 .. D3
{"nop", AM_ZX , 0}, {"CMP", AM_ZX , R_}, {"DEC", AM_ZX , RW}, {"nop", 0 , 0 }, // D4 .. D7
{"CLD", 0 , 0}, {"CMP", AM_AY , R_}, {"PHX", 0 , SW}, {"nop", 0 , 0 }, // D8 .. DB
{"nop", AM_AX , 0}, {"CMP", AM_AX , R_}, {"DEC", AM_AX , RW}, {"nop", 0 , 0 }, // DC .. DF
{"CPX", AM_M , im}, {"SBC", AM_IZX, R_}, {"nop", AM_M , im}, {"nop", 0 , 0 }, // E0 .. E3
{"CPX", AM_Z , R_}, {"SBC", AM_Z , R_}, {"INC", AM_Z , RW}, {"nop", 0 , 0 }, // E4 .. E7
{"INX", 0 , 0}, {"SBC", AM_M , R_}, {"NOP", 0 , 0}, {"nop", 0 , 0 }, // E8 .. EB
{"CPX", AM_A , R_}, {"SBC", AM_A , R_}, {"INC", AM_A , RW}, {"nop", 0 , 0 }, // EC .. EF
{"BEQ", AM_R , 0}, {"SBC", AM_NZY, R_}, {"SBC", AM_NZ , 0}, {"nop", 0 , 0 }, // F0 .. F3
{"nop", AM_ZX , 0}, {"SBC", AM_ZX , R_}, {"INC", AM_ZX , RW}, {"nop", 0 , 0 }, // F4 .. F7
{"SED", 0 , 0}, {"SBC", AM_AY , R_}, {"PLX", 0 , SR}, {"nop", 0 , 0 }, // F8 .. FB
{"nop", AM_AX , 0}, {"SBC", AM_AX , R_}, {"INC", AM_AX , RW}, {"nop", 0 , 0 } // FF .. FF
};
const Opcodes_t g_aOpcodes6502[ NUM_OPCODES ] =
{ // Should match Cpu.cpp InternalCpuExecute() switch (*(mem+regs.pc++)) !!
/*
Based on: http://axis.llx.com/~nparker/a2/opcodes.html
If you really want to know what the undocumented --- (n/a) opcodes do, see
CPU.cpp
x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF
0x BRK ORA (d,X) --- --- tsb z ORA d ASL z --- PHP ORA # ASL A --- tsb a ORA a ASL a ---
1x BPL r ORA (d),Y ora (z) --- trb d ORA d,X ASL z,X --- CLC ORA a,Y ina A --- trb a ORA a,X ASL a,X ---
2x JSR a AND (d,X) --- --- BIT d AND d ROL z --- PLP AND # ROL A --- BIT a AND a ROL a ---
3x BMI r AND (d),Y and (z) --- bit d,X AND d,X ROL z,X --- SEC AND a,Y dea A --- bit a,X AND a,X ROL a,X ---
4x RTI EOR (d,X) --- --- --- EOR d LSR z --- PHA EOR # LSR A --- JMP a EOR a LSR a ---
5x BVC r EOR (d),Y eor (z) --- --- EOR d,X LSR z,X --- CLI EOR a,Y phy --- --- EOR a,X LSR a,X ---
6x RTS ADC (d,X) --- --- stz d ADC d ROR z --- PLA ADC # ROR A --- JMP (a) ADC a ROR a ---
7x BVS r ADC (d),Y adc (z) --- stz d,X ADC d,X ROR z,X --- SEI ADC a,Y ply --- jmp (a,X) ADC a,X ROR a,X ---
8x bra r STA (d,X) --- --- STY d STA d STX z --- DEY bit # TXA --- STY a STA a STX a ---
9x BCC r STA (d),Y sta (z) --- STY d,X STA d,X STX z,Y --- TYA STA a,Y TXS --- Stz a STA a,X stz a,X ---
Ax LDY # LDA (d,X) LDX # --- LDY d LDA d LDX z --- TAY LDA # TAX --- LDY a LDA a LDX a ---
Bx BCS r LDA (d),Y lda (z) --- LDY d,X LDA d,X LDX z,Y --- CLV LDA a,Y TSX --- LDY a,X LDA a,X LDX a,Y ---
Cx CPY # CMP (d,X) --- --- CPY d CMP d DEC z --- INY CMP # DEX --- CPY a CMP a DEC a ---
Dx BNE r CMP (d),Y cmp (z) --- --- CMP d,X DEC z,X --- CLD CMP a,Y phx --- --- CMP a,X DEC a,X ---
Ex CPX # SBC (d,X) --- --- CPX d SBC d INC z --- INX SBC # NOP --- CPX a SBC a INC a ---
Fx BEQ r SBC (d),Y sbc (z) --- --- SBC d,X INC z,X --- SED SBC a,Y plx --- --- SBC a,X INC a,X ---
Legend:
--- illegal instruction
UPPERCASE 6502
lowercase 65C02
80
12, 32, 52, 72, 92, B2, D2, F2
04, 14, 34, 64, 74
89
1A, 3A, 5A, 7A, DA, FA
0C, 1C, 3C, 7C, 9C;
# Immediate
A Accumulator (implicit for mnemonic)
a absolute
r Relative
d Destination 16-bit Address
z Destination Zero Page Address
z,x Base=Zero-Page, Offset=X
d,x
(d,X)
(d),Y
*/
{"BRK", 0 , SW}, {"ORA", AM_IZX, R_}, {"hlt", 0 , 0 }, {"aso", AM_IZX, RW}, // 00 .. 03
{"nop", AM_Z , R_}, {"ORA", AM_Z , R_}, {"ASL", AM_Z , RW}, {"aso", AM_Z , RW}, // 04 .. 07
{"PHP", 0 , SW}, {"ORA", AM_M , im}, {"ASL", 0 , 0}, {"anc", AM_M , im}, // 08 .. 0B
{"nop", AM_AX , 0}, {"ORA", AM_A , R_}, {"ASL", AM_A , RW}, {"aso", AM_A , RW}, // 0C .. 0F
{"BPL", AM_R , 0}, {"ORA", AM_NZY, R_}, {"hlt", 0 , 0}, {"aso", AM_NZY, RW}, // 10 .. 13
{"nop", AM_ZX , 0}, {"ORA", AM_ZX , R_}, {"ASL", AM_ZX , RW}, {"aso", AM_ZX , RW}, // 14 .. 17
{"CLC", 0 , 0}, {"ORA", AM_AY , R_}, {"nop", 0 , 0}, {"aso", AM_AY , RW}, // 18 .. 1B
{"nop", AM_AX , 0}, {"ORA", AM_AX , R_}, {"ASL", AM_AX , RW}, {"aso", AM_AX , RW}, // 1C .. 1F
{"JSR", AM_A , SW}, {"AND", AM_IZX, R_}, {"hlt", 0 , 0}, {"rla", AM_IZX, RW}, // 20 .. 23
{"BIT", AM_Z , R_}, {"AND", AM_Z , R_}, {"ROL", AM_Z , RW}, {"rla", AM_Z , RW}, // 24 .. 27
{"PLP", 0 , SR}, {"AND", AM_M , im}, {"ROL", 0 , 0}, {"anc", AM_M , im}, // 28 .. 2B
{"BIT", AM_A , R_}, {"AND", AM_A , R_}, {"ROL", AM_A , RW}, {"rla", AM_A , RW}, // 2C .. 2F
{"BMI", AM_R , 0}, {"AND", AM_NZY, R_}, {"hlt", 0 , 0}, {"rla", AM_NZY, RW}, // 30 .. 33
{"nop", AM_ZX , 0}, {"AND", AM_ZX , R_}, {"ROL", AM_ZX , RW}, {"rla", AM_ZX , RW}, // 34 .. 37
{"SEC", 0 , 0}, {"AND", AM_AY , R_}, {"nop", 0 , 0}, {"rla", AM_AY , RW}, // 38 .. 3B
{"nop", AM_AX , 0}, {"AND", AM_AX , R_}, {"ROL", AM_AX , RW}, {"rla", AM_AX , RW}, // 3C .. 3F
{"RTI", 0 , SR}, {"EOR", AM_IZX, R_}, {"hlt", 0 , 0}, {"lse", AM_IZX, RW}, // 40 .. 43
{"nop", AM_Z , 0}, {"EOR", AM_Z , R_}, {"LSR", AM_Z , RW}, {"lse", AM_Z , RW}, // 44 .. 47
{"PHA", 0 , SW}, {"EOR", AM_M , im}, {"LSR", 0 , 0}, {"alr", AM_M , im}, // 48 .. 4B
{"JMP", AM_A , 0}, {"EOR", AM_A , R_}, {"LSR", AM_A , RW}, {"lse", AM_A , RW}, // 4C .. 4F
{"BVC", AM_R , 0}, {"EOR", AM_NZY, R_}, {"hlt", 0 , 0}, {"lse", AM_NZY, RW}, // 50 .. 53
{"nop", AM_ZX , 0}, {"EOR", AM_ZX , R_}, {"LSR", AM_ZX , RW}, {"lse", AM_ZX , RW}, // 54 .. 57
{"CLI", 0 , 0}, {"EOR", AM_AY , R_}, {"nop", 0 , 0}, {"lse", AM_AY , RW}, // 58 .. 5B
{"nop", AM_AX , 0}, {"EOR", AM_AX , R_}, {"LSR", AM_AX , RW}, {"lse", AM_AX , RW}, // 5C .. 5F
{"RTS", 0 , SR}, {"ADC", AM_IZX, R_}, {"hlt", 0 , 0}, {"rra", AM_IZX, RW}, // 60 .. 63
{"nop", AM_Z , 0}, {"ADC", AM_Z , R_}, {"ROR", AM_Z , RW}, {"rra", AM_Z , RW}, // 64 .. 67
{"PLA", 0 , SR}, {"ADC", AM_M , im}, {"ROR", 0 , 0}, {"arr", AM_M , im}, // 68 .. 6B
{"JMP", AM_NA , R_}, {"ADC", AM_A , R_}, {"ROR", AM_A , RW}, {"rra", AM_A , RW}, // 6C .. 6F
{"BVS", AM_R , 0}, {"ADC", AM_NZY, R_}, {"hlt", 0 , 0}, {"rra", AM_NZY, RW}, // 70 .. 73
{"nop", AM_ZX , 0}, {"ADC", AM_ZX , R_}, {"ROR", AM_ZX , RW}, {"rra", AM_ZX , RW}, // 74 .. 77
{"SEI", 0 , 0}, {"ADC", AM_AY , R_}, {"nop", 0 , 0}, {"rra", AM_AY , RW}, // 78 .. 7B
{"nop", AM_AX , 0}, {"ADC", AM_AX , R_}, {"ROR", AM_AX , RW}, {"rra", AM_AX , RW}, // 7C .. 7F
{"nop", AM_M , im}, {"STA", AM_IZX, _W}, {"nop", AM_M , im}, {"axs", AM_IZX, _W}, // 80 .. 83
{"STY", AM_Z , _W}, {"STA", AM_Z , _W}, {"STX", AM_Z , _W}, {"axs", AM_Z , _W}, // 84 .. 87
{"DEY", 0 , 0}, {"nop", AM_M , im}, {"TXA", 0 , 0}, {"xaa", AM_M , im}, // 88 .. 8B
{"STY", AM_A , _W}, {"STA", AM_A , _W}, {"STX", AM_A , _W}, {"axs", AM_A , _W}, // 8C .. 8F
{"BCC", AM_R , 0}, {"STA", AM_NZY, _W}, {"hlt", 0 , 0}, {"axa", AM_NZY, _W}, // 90 .. 93
{"STY", AM_ZX , _W}, {"STA", AM_ZX , _W}, {"STX", AM_ZY , _W}, {"axs", AM_ZY , _W}, // 94 .. 97
{"TYA", 0 , 0}, {"STA", AM_AY , _W}, {"TXS", 0 , 0}, {"tas", AM_AY , _W}, // 98 .. 9B
{"say", AM_AX , _W}, {"STA", AM_AX , _W}, {"xas", AM_AX , _W}, {"axa", AM_AY , _W}, // 9C .. 9F
{"LDY", AM_M , im}, {"LDA", AM_IZX, R_}, {"LDX", AM_M , im}, {"lax", AM_IZX, R_}, // A0 .. A3
{"LDY", AM_Z , R_}, {"LDA", AM_Z , R_}, {"LDX", AM_Z , R_}, {"lax", AM_Z , R_}, // A4 .. A7
{"TAY", 0 , 0}, {"LDA", AM_M , im}, {"TAX", 0 , 0 }, {"oal", AM_M , im}, // A8 .. AB
{"LDY", AM_A , R_}, {"LDA", AM_A , R_}, {"LDX", AM_A , R_}, {"lax", AM_A , R_}, // AC .. AF
{"BCS", AM_R , 0}, {"LDA", AM_NZY, R_}, {"hlt", 0 , 0 }, {"lax", AM_NZY, R_}, // B0 .. B3
{"LDY", AM_ZX , R_}, {"LDA", AM_ZX , R_}, {"LDX", AM_ZY , R_}, {"lax", AM_ZY , 0 }, // B4 .. B7
{"CLV", 0 , 0}, {"LDA", AM_AY , R_}, {"TSX", 0 , 0 }, {"las", AM_AY , R_}, // B8 .. BB
{"LDY", AM_AX , R_}, {"LDA", AM_AX , R_}, {"LDX", AM_AY , R_}, {"lax", AM_AY , R_}, // BC .. BF
{"CPY", AM_M , im}, {"CMP", AM_IZX, R_}, {"nop", AM_M , im}, {"dcm", AM_IZX, RW}, // C0 .. C3
{"CPY", AM_Z , R_}, {"CMP", AM_Z , R_}, {"DEC", AM_Z , RW}, {"dcm", AM_Z , RW}, // C4 .. C7
{"INY", 0 , 0}, {"CMP", AM_M , im}, {"DEX", 0 , 0}, {"sax", AM_M , im}, // C8 .. CB
{"CPY", AM_A , R_}, {"CMP", AM_A , R_}, {"DEC", AM_A , RW}, {"dcm", AM_A , RW}, // CC .. CF
{"BNE", AM_R , 0}, {"CMP", AM_NZY, R_}, {"hlt", 0 , 0}, {"dcm", AM_NZY, RW}, // D0 .. D3
{"nop", AM_ZX , 0}, {"CMP", AM_ZX , R_}, {"DEC", AM_ZX , RW}, {"dcm", AM_ZX , RW}, // D4 .. D7
{"CLD", 0 , 0}, {"CMP", AM_AY , R_}, {"nop", 0 , 0}, {"dcm", AM_AY , RW}, // D8 .. DB
{"nop", AM_AX , 0}, {"CMP", AM_AX , R_}, {"DEC", AM_AX , RW}, {"dcm", AM_AX , RW}, // DC .. DF
{"CPX", AM_M , im}, {"SBC", AM_IZX, R_}, {"nop", AM_M , im}, {"ins", AM_IZX, RW}, // E0 .. E3
{"CPX", AM_Z , R_}, {"SBC", AM_Z , R_}, {"INC", AM_Z , RW}, {"ins", AM_Z , RW}, // E4 .. E7
{"INX", 0 , 0}, {"SBC", AM_M , im}, {"NOP", 0 , 0}, {"sbc", AM_M , im}, // E8 .. EB
{"CPX", AM_A , R_}, {"SBC", AM_A , R_}, {"INC", AM_A , RW}, {"ins", AM_A , RW}, // EC .. EF
{"BEQ", AM_R , 0}, {"SBC", AM_NZY, R_}, {"hlt", 0 , 0}, {"ins", AM_NZY, RW}, // F0 .. F3
{"nop", AM_ZX , 0}, {"SBC", AM_ZX , R_}, {"INC", AM_ZX , RW}, {"ins", AM_ZX , RW}, // F4 .. F7
{"SED", 0 , 0}, {"SBC", AM_AY , R_}, {"nop", 0 , 0}, {"ins", AM_AY , RW}, // F8 .. FB
{"nop", AM_AX , 0}, {"SBC", AM_AX , R_}, {"INC", AM_AX , RW}, {"ins", AM_AX , RW} // FF .. FF
};
#undef R_
#undef _W
#undef RW
#undef _S
#undef im
#undef SW
#undef SR
// @reference: http://www.textfiles.com/apple/DOCUMENTATION/merlin.docs1
// Private __________________________________________________________________
// NOTE: Keep in sync AsmDirectives_e g_aAssemblerDirectives !
AssemblerDirective_t g_aAssemblerDirectives[ NUM_ASM_DIRECTIVES ] =
{
// NULL n/a
{""},
// Origin, Target Address, EndProg, Equate, Data, AsciiString,HexString
// Acme
{"???"},
// Big Mac
{"???"},
// DOS Tool Kit
{"???"},
// Lisa
{"???"},
// Merlin
{"ASC"}, // ASC "postive" 'negative'
{"DDB"}, // Define Double Byte (Define WORD)
{"DFB"}, // DeFine Byte
{"DS" }, // Define Storage
{"HEX"}, // HEX ###### or HEX ##,##,...
{"ORG"}, // Origin
// MicroSparc
{"???"},
// ORCA/M
{"???"},
// SC ...
{".OR"}, // ORigin
{".TA"}, // Target Address
{".EN"}, // ENd of program
{".EQ"}, // EQuate
{".DA"}, // DAta
{".AS"}, // Ascii String
{".HS"}, // Hex String
// Ted II
{"???"},
// Weller
{"???"},
// User-Defined
// NOTE: Keep in sync AsmCustomDirective_e g_aAssemblerDirectives !
{"db" }, // ASM_DEFINE_BYTE
{"dw" }, // ASM_DEFINE_WORD
{"da" }, // ASM_DEFINE_ADDRESS_16
// d memory Dump
// da Memory Ascii, Define Address
// ds S = Ascii (Low),
// dt T = Apple (High)
// dm M = Mixed (Low,High=EndofString)
{"ds" }, // ASM_DEFINE_ASCII_TEXT
{"dt" }, // ASM_DEFINE_APPLE_TEXT
{"dm" }, // ASM_DEFINE_TEXT_HI_LO
{"df" }, // ASM_DEFINE_FLOAT
{"dfx"}, // ASM_DEFINE_FLOAT_X
};
int g_iAssemblerSyntax = ASM_CUSTOM; // Which assembler syntax to use
int g_aAssemblerFirstDirective[ NUM_ASSEMBLERS ] =
{
FIRST_A_DIRECTIVE,
FIRST_B_DIRECTIVE,
FIRST_D_DIRECTIVE,
FIRST_L_DIRECTIVE,
FIRST_M_DIRECTIVE,
FIRST_u_DIRECTIVE,
FIRST_O_DIRECTIVE,
FIRST_S_DIRECTIVE,
FIRST_T_DIRECTIVE,
FIRST_W_DIRECTIVE,
FIRST_Z_DIRECTIVE
};
// Assemblers
enum AssemblerFlags_e
{
AF_HaveLabel = (1 << 0),
AF_HaveComma = (1 << 1),
AF_HaveHash = (1 << 2),
AF_HaveImmediate = (1 << 3),
AF_HaveDollar = (1 << 4),
AF_HaveLeftParen = (1 << 5),
AF_HaveRightParen = (1 << 6),
AF_HaveEitherParen= (1 << 7),
AF_HaveBothParen = (1 << 8),
AF_HaveRegisterX = (1 << 9),
AF_HaveRegisterY = (1 <<10),
AF_HaveZeroPage = (1 <<11),
AF_HaveTarget = (1 <<12),
};
enum AssemblerState_e
{
AS_GET_MNEMONIC
, AS_GET_MNEMONIC_PARM
, AS_GET_HASH
, AS_GET_TARGET
, AS_GET_PAREN
, AS_GET_INDEX
, AS_DONE
};
int m_bAsmFlags;
std::vector<int> m_vAsmOpcodes;
int m_iAsmAddressMode = AM_IMPLIED;
struct DelayedTarget_t
{
char m_sAddress[ MAX_SYMBOLS_LEN + 1 ];
WORD m_nBaseAddress; // mem address to store symbol at
int m_nOpcode ;
int m_iOpmode ; // AddressingMode_e
};
std::vector<DelayedTarget_t> m_vDelayedTargets;
bool m_bDelayedTargetsDirty = false;
int m_nAsmBytes = 0;
WORD m_nAsmBaseAddress = 0;
WORD m_nAsmTargetAddress = 0;
WORD m_nAsmTargetValue = 0;
// Private
void AssemblerHashOpcodes ();
void AssemblerHashDirectives ();
// Utility __________________________________________________________________
// === Stack ===
// Return stack offset if the address is on the stack, else -1 if not found
//===========================================================================
int _6502_FindStackReturnAddress (const WORD nAddress)
{
WORD nReturnAddress;
WORD nStack = regs.sp + 1;
int nDepth = -1; // not found
// Normally would <= _6502_STACK_END-1 since JSR always pushes 2 bytes
// but the SP could be $00 before JSR forcing RTS address to be split $100/$1FF
// or the SP could be $01 before JSR forcing RTS address to be at $100/$101
// R PC 300
// R S 0
// 300:20 04 03 60 60
// <Ctrl-Space>
while (nStack <= (_6502_STACK_END + 1))
{
nReturnAddress = _6502_PeekStackReturnAddress(nStack);
if (nReturnAddress == nAddress)
{
nDepth = (nStack - 2 - regs.sp);
return nDepth;
}
}
return nDepth;
}
// NOTE: If the stack pointer is <= 01 before a JSR the stack will wrap around.
//===========================================================================
WORD _6502_GetStackReturnAddress ()
{
WORD nStack = regs.sp + 1;
WORD nAddress = _6502_PeekStackReturnAddress(nStack);
return nAddress;
}
// NOTES: nStack is both an input and output;
// If nStack is 0x1FF it WILL return overflow 0x200. Current callers are:
// _6502_FindStackReturnAddress(), and
// _6502_GetStackReturnAddress()
// which DON'T return this overflow stack value to previous callers.
//===========================================================================
WORD _6502_PeekStackReturnAddress (WORD & nStack)
{
WORD nAddress;
nAddress = ((unsigned) *(LPBYTE)(mem + 0x100 + (nStack & 0xFF)) ); nStack++;
nAddress += ((unsigned) *(LPBYTE)(mem + 0x100 + (nStack & 0xFF)) << 8);
nAddress++;
return nAddress;
}
// == Opcodes ===
//===========================================================================
bool _6502_CalcRelativeOffset ( int nOpcode, int nBaseAddress, int nTargetAddress, WORD * pTargetOffset_ )
{
if (_6502_IsOpcodeBranch(nOpcode))
{
// Branch is
// a) relative to address+2
// b) in 2's compliment
//
// i.e.
// 300: D0 7F -> BNE $381 0x381 - 0x300 = 0x81 +129
// 300: D0 80 -> BNE $282 0x282 - 0x300 = -126
//
// 300: D0 7E BNE $380
// ^ ^ ^ ^
// | | | TargetAddress
// | | TargetOffset
// | Opcode
// BaseAddress
int nDistance = nTargetAddress - nBaseAddress;
if (pTargetOffset_)
*pTargetOffset_ = (BYTE)(nDistance - 2);
if ((nDistance - 2) > _6502_BRANCH_POS)
m_iAsmAddressMode = NUM_OPMODES; // signal bad
if ((nDistance - 2) < _6502_BRANCH_NEG)
m_iAsmAddressMode = NUM_OPMODES; // signal bad
return true;
}
return false;
}
//===========================================================================
int _6502_GetOpmodeOpbyte ( const int nBaseAddress, int & iOpmode_, int & nOpbyte_, const DisasmData_t** pData_ )
{
#if _DEBUG
if (! g_aOpcodes)
{
GetFrame().FrameMessageBox("Debugger not properly initialized", "ERROR", MB_OK );
g_aOpcodes = &g_aOpcodes65C02[ 0 ]; // Enhanced Apple //e
g_aOpmodes[ AM_2 ].m_nBytes = 2;
g_aOpmodes[ AM_3 ].m_nBytes = 3;
}
#endif
int iOpcode_ = *(mem + nBaseAddress);
iOpmode_ = g_aOpcodes[ iOpcode_ ].nAddressMode;
nOpbyte_ = g_aOpmodes[ iOpmode_ ].m_nBytes;
// 2.6.2.25 Fixed: DB DW custom data byte sizes weren't scrolling properly in the disasm view.
// Changed _6502_GetOpmodeOpbyte() to be aware of data bytes.
//
// NOTE: _6502_GetOpmodeOpbyte() needs to (effectively) call Disassembly_GetData()
// a) the CmdCursorLineUp() calls us to calc for -X bytes back up how to reach the cursor (address) line below
// b) The disassembler view needs to know how many bytes each line is.
int nSlack;
// 2.7.0.0 TODO: FIXME: Opcode length that over-lap data, should be shortened ... if (nOpbyte_ > 1) if Disassembly_IsDataAddress( nBaseAddress + 1 ) nOpbyte_ = 1;
DisasmData_t* pData = Disassembly_IsDataAddress( nBaseAddress );
if ( pData )
{
if ( pData_ )
*pData_ = pData;
const DWORD nEndAddress = pData->nEndAddress;
const int nDisplayLen = nEndAddress - nBaseAddress + 1; // *inclusive* KEEP IN SYNC: _CmdDefineByteRange() CmdDisasmDataList() _6502_GetOpmodeOpbyte() FormatNopcodeBytes()
nSlack = nDisplayLen;
// Data Disassembler
// Smart Disassembly - Data Section
// Assemblyer Directives - Psuedo Mnemonics
switch ( pData->eElementType )
{
case NOP_BYTE_1: nOpbyte_ = 1; iOpmode_ = AM_M; break;
case NOP_BYTE_2: nOpbyte_ = 2; iOpmode_ = AM_M; break;
case NOP_BYTE_4: nOpbyte_ = 4; iOpmode_ = AM_M; break;
case NOP_BYTE_8: nOpbyte_ = 8; iOpmode_ = AM_M; break;
case NOP_FAC : nOpbyte_ = 5; iOpmode_ = AM_M; break;
case NOP_WORD_1: nOpbyte_ = 2; iOpmode_ = AM_M; break;
case NOP_WORD_2: nOpbyte_ = 4; iOpmode_ = AM_M; break;
case NOP_WORD_4: nOpbyte_ = 8; iOpmode_ = AM_M; break;
case NOP_ADDRESS:nOpbyte_ = 2; iOpmode_ = AM_A; // BUGFIX: 2.6.2.33 Define Address should be shown as Absolute mode, not Indirect Absolute mode. DA BASIC.FPTR D000:D080 // was showing as "da (END-1)" now shows as "da END-1"
pData->nTargetAddress = *(LPWORD)(mem+nBaseAddress);
break;
case NOP_STRING_APPLE:
iOpmode_ = AM_DATA;
nOpbyte_ = nSlack;
break;
case NOP_STRING_APPLESOFT:
// TODO: FIXME: scan memory for high byte
nOpbyte_ = 8;
iOpmode_ = AM_DATA;
break;
default:
#if _DEBUG // not implemented!
int *fatal = 0;
*fatal = 0xDEADC0DE;
#endif
break;
}
/*
// REMOVED in v1.25 ... because of AppleSoft Basic: DW NEXT1 801 DW LINE1 803
// Check if we are not element aligned ...
nSlack = (nOpbyte_ > 1) ? (nBaseAddress & nOpbyte_-1 ) : 0;
if (nSlack)
{
nOpbyte_ = nSlack;
iOpmode_ = AM_M;
}
*/
//iOpcode_ = NUM_OPCODES; // Don't have valid opcodes ... we have data !
// iOpcode_ = (int)( pData ); // HACK: pass pData back to caller ...
iOpcode_ = OPCODE_NOP;
}
#if _DEBUG
if (iOpcode_ >= NUM_OPCODES)
{
bool bStop = true;
}
#endif
return iOpcode_;
}
//===========================================================================
void _6502_GetOpcodeOpmodeOpbyte (int & iOpcode_, int & iOpmode_, int & nOpbyte_)
{
iOpcode_ = _6502_GetOpmodeOpbyte( regs.pc, iOpmode_, nOpbyte_ );
}
//===========================================================================
bool _6502_GetTargets (WORD nAddress, int *pTargetPartial_, int *pTargetPartial2_, int *pTargetPointer_, int * pTargetBytes_,
bool bIgnoreBranch /*= true*/, bool bIncludeNextOpcodeAddress /*= true*/ )
{
if (! pTargetPartial_)
return false;
if (! pTargetPartial2_)
return false;
if (! pTargetPointer_)
return false;
// if (! pTargetBytes_)
// return false;
*pTargetPartial_ = NO_6502_TARGET;
*pTargetPartial2_ = NO_6502_TARGET;
*pTargetPointer_ = NO_6502_TARGET;
if (pTargetBytes_)
*pTargetBytes_ = 0;
BYTE nOpcode = mem[nAddress];
BYTE nTarget8 = mem[(nAddress+1)&0xFFFF];
WORD nTarget16 = (mem[(nAddress+2)&0xFFFF]<<8) | nTarget8;
int eMode = g_aOpcodes[ nOpcode ].nAddressMode;
// We really need to use the values that are code and data assembler
// TODO: FIXME: _6502_GetOpmodeOpbyte( iAddress, iOpmode, nOpbytes );
switch (eMode)
{
case AM_IMPLIED:
if (g_aOpcodes[ nOpcode ].nMemoryAccess & MEM_S) // Stack R/W?
{
if (nOpcode == OPCODE_RTI || nOpcode == OPCODE_RTS) // RTI or RTS?
{
WORD sp = regs.sp;
if (nOpcode == OPCODE_RTI)
{
//*pTargetPartial3_ = _6502_STACK_BEGIN + ((sp+1) & 0xFF); // TODO: PLP
++sp;
}
*pTargetPartial_ = _6502_STACK_BEGIN + ((sp+1) & 0xFF);
*pTargetPartial2_ = _6502_STACK_BEGIN + ((sp+2) & 0xFF);
nTarget16 = mem[*pTargetPartial_] + (mem[*pTargetPartial2_]<<8);
if (nOpcode == OPCODE_RTS)
++nTarget16;
}
else if (nOpcode == OPCODE_BRK) // BRK?
{
*pTargetPartial_ = _6502_STACK_BEGIN + ((regs.sp+0) & 0xFF);
*pTargetPartial2_ = _6502_STACK_BEGIN + ((regs.sp-1) & 0xFF);
//*pTargetPartial3_ = _6502_STACK_BEGIN + ((regs.sp-2) & 0xFF); // TODO: PHP
//*pTargetPartial4_ = _6502_BRK_VECTOR + 0; // TODO
//*pTargetPartial5_ = _6502_BRK_VECTOR + 1; // TODO
nTarget16 = *(LPWORD)(mem + _6502_BRK_VECTOR);
}
else // PHn/PLn
{
if (g_aOpcodes[ nOpcode ].nMemoryAccess & MEM_WI)
nTarget16 = _6502_STACK_BEGIN + ((regs.sp+0) & 0xFF);
else
nTarget16 = _6502_STACK_BEGIN + ((regs.sp+1) & 0xFF);
}
if (bIncludeNextOpcodeAddress || (nOpcode != OPCODE_RTI && nOpcode != OPCODE_RTS && nOpcode != OPCODE_BRK))
*pTargetPointer_ = nTarget16;
if (pTargetBytes_)
*pTargetBytes_ = 1;
}
break;
case AM_A: // Absolute
if (nOpcode == OPCODE_JSR)
{
*pTargetPartial_ = _6502_STACK_BEGIN + ((regs.sp+0) & 0xFF);
*pTargetPartial2_ = _6502_STACK_BEGIN + ((regs.sp-1) & 0xFF);
}
if (bIncludeNextOpcodeAddress || (nOpcode != OPCODE_JSR && nOpcode != OPCODE_JMP_A))
*pTargetPointer_ = nTarget16;
if (pTargetBytes_)
*pTargetBytes_ = 2;
break;
case AM_IAX: // Indexed (Absolute) Indirect - ie. JMP (abs,x)
_ASSERT(nOpcode == OPCODE_JMP_IAX);
nTarget16 += regs.x;
*pTargetPartial_ = nTarget16;
*pTargetPartial2_ = nTarget16+1;
if (bIncludeNextOpcodeAddress)
*pTargetPointer_ = *(LPWORD)(mem + nTarget16);
if (pTargetBytes_)
*pTargetBytes_ = 2;
break;
case AM_AX: // Absolute, X
nTarget16 += regs.x;
*pTargetPointer_ = nTarget16;
if (pTargetBytes_)
*pTargetBytes_ = 2;
break;
case AM_AY: // Absolute, Y
nTarget16 += regs.y;
*pTargetPointer_ = nTarget16;
if (pTargetBytes_)
*pTargetBytes_ = 2;
break;
case AM_NA: // Indirect (Absolute) - ie. JMP (abs)
_ASSERT(nOpcode == OPCODE_JMP_NA);
*pTargetPartial_ = nTarget16;
*pTargetPartial2_ = (nTarget16+1) & _6502_MEM_END;
if (GetMainCpu() == CPU_6502 && (nTarget16 & 0xff) == 0xff)
*pTargetPartial2_ = nTarget16 & 0xff00;
if (bIncludeNextOpcodeAddress)
*pTargetPointer_ = mem[*pTargetPartial_] | (mem[*pTargetPartial2_] << 8);
if (pTargetBytes_)
*pTargetBytes_ = 2;
break;
case AM_IZX: // Indexed (Zeropage Indirect, X)
nTarget8 = (nTarget8 + regs.x) & 0xFF;
*pTargetPartial_ = nTarget8;
*pTargetPointer_ = *(LPWORD)(mem + nTarget8);
if (pTargetBytes_)
*pTargetBytes_ = 2;
break;
case AM_NZY: // Indirect (Zeropage) Indexed, Y
*pTargetPartial_ = nTarget8;
*pTargetPointer_ = ((*(LPWORD)(mem + nTarget8)) + regs.y) & _6502_MEM_END; // Bugfix:
if (pTargetBytes_)
*pTargetBytes_ = 1;
break;
case AM_NZ: // Indirect (Zeropage)
*pTargetPartial_ = nTarget8;
*pTargetPointer_ = *(LPWORD)(mem + nTarget8);
if (pTargetBytes_)
*pTargetBytes_ = 2;
break;
case AM_R:
if (!bIgnoreBranch)
{
*pTargetPartial_ = nTarget8;
*pTargetPointer_ = nAddress + 2;
if (nTarget8 <= _6502_BRANCH_POS)
*pTargetPointer_ += nTarget8; // +
else
*pTargetPointer_ -= nTarget8; // -
*pTargetPointer_ &= _6502_MEM_END;
if (pTargetBytes_)
*pTargetBytes_ = 1;
}
break;
case AM_Z: // Zeropage
*pTargetPointer_ = nTarget8;
if (pTargetBytes_)
*pTargetBytes_ = 1;
break;
case AM_ZX: // Zeropage, X
*pTargetPointer_ = (nTarget8 + regs.x) & 0xFF; // .21 Bugfix: shouldn't this wrap around? Yes.
if (pTargetBytes_)
*pTargetBytes_ = 1;
break;
case AM_ZY: // Zeropage, Y
*pTargetPointer_ = (nTarget8 + regs.y) & 0xFF; // .21 Bugfix: shouldn't this wrap around? Yes.
if (pTargetBytes_)
*pTargetBytes_ = 1;
break;
default:
if (pTargetBytes_)
*pTargetBytes_ = 0;
break;
}
return true;
}
//===========================================================================
bool _6502_GetTargetAddress ( const WORD & nAddress, WORD & nTarget_ )
{
int iOpcode;
int iOpmode;
int nOpbytes;
iOpcode = _6502_GetOpmodeOpbyte( nAddress, iOpmode, nOpbytes );
// Composite string that has the target nAddress
if ((iOpmode != AM_IMPLIED) &&
(iOpmode != AM_1) &&
(iOpmode != AM_2) &&
(iOpmode != AM_3))
{
int nTargetPartial;
int nTargetPartial2;
int nTargetPointer;
int nTargetBytes;
_6502_GetTargets( nAddress, &nTargetPartial, &nTargetPartial2, &nTargetPointer, &nTargetBytes, false );
// if (nTargetPointer == NO_6502_TARGET)
// {
// if (_6502_IsOpcodeBranch( nOpcode )
// {
// return true;
// }
// }
if (nTargetPointer != NO_6502_TARGET)
// else
{
nTarget_ = nTargetPointer & _6502_MEM_END;
return true;
}
}
return false;
}
//===========================================================================
bool _6502_IsOpcodeBranch ( int iOpcode )
{
// 76543210 Bit
// xxx10000 Branch
if (iOpcode == OPCODE_BRA)
return true;
if ((iOpcode & 0x1F) != 0x10) // low nibble not zero?
return false;
if ((iOpcode >> 4) & 1)
return true;
// (nOpcode == 0x10) || // BPL
// (nOpcode == 0x30) || // BMI
// (nOpcode == 0x50) || // BVC
// (nOpcode == 0x70) || // BVS
// (nOpcode == 0x90) || // BCC
// (nOpcode == 0xB0) || // BCS
// (nOpcode == 0xD0) || // BNE
// (nOpcode == 0xF0) || // BEQ
return false;
}
//===========================================================================
bool _6502_IsOpcodeValid ( int iOpcode )
{
if ((iOpcode & 0x3) == 0x3)
return false;
if (islower( g_aOpcodes6502[ iOpcode ].sMnemonic[ 0 ] ))
return false;
return true;
}
// Assembler ________________________________________________________________
//===========================================================================
Hash_t AssemblerHashMnemonic ( const TCHAR * pMnemonic )
{
const TCHAR *pText = pMnemonic;
Hash_t nMnemonicHash = 0;
int iHighBits;
const int NUM_LOW_BITS = 19; // 24 -> 19 prime
const int NUM_MSK_BITS = 5; // 4 -> 5 prime
const Hash_t BIT_MSK_HIGH = ((1 << NUM_MSK_BITS) - 1) << NUM_LOW_BITS;
#if DEBUG_ASSEMBLER
int nLen = strlen( pMnemonic );
static int nMaxLen = 0;
if (nMaxLen < nLen) {
nMaxLen = nLen;
ConsolePrintFormat( "New Max Len: %d %s", nMaxLen, pMnemonic );
}
#endif
while ( *pText )
// for ( int iChar = 0; iChar < 4; iChar++ )
{
char c = tolower( *pText ); // TODO: based on ALLOW_INPUT_LOWERCASE ??
nMnemonicHash = (nMnemonicHash << NUM_MSK_BITS) + c;
iHighBits = (nMnemonicHash & BIT_MSK_HIGH);
if (iHighBits)
{
nMnemonicHash = (nMnemonicHash ^ (iHighBits >> NUM_LOW_BITS)) & ~ BIT_MSK_HIGH;
}
pText++;
}
return nMnemonicHash;
}
//===========================================================================
void AssemblerHashOpcodes ()
{
Hash_t nMnemonicHash;
int iOpcode;
for ( iOpcode = 0; iOpcode < NUM_OPCODES; iOpcode++ )
{
const TCHAR *pMnemonic = g_aOpcodes65C02[ iOpcode ].sMnemonic;
nMnemonicHash = AssemblerHashMnemonic( pMnemonic );
g_aOpcodesHash[ iOpcode ] = nMnemonicHash;
#if DEBUG_ASSEMBLER
//OutputDebugString( "" );
ConsolePrintFormat( "%s : %08X ", pMnemonic, nMnemonicHash );
// CLC: 002B864
#endif
}
ConsoleUpdate();
}
//===========================================================================
void AssemblerHashDirectives ()
{
Hash_t nMnemonicHash;
int iOpcode;
for ( iOpcode = 0; iOpcode < NUM_ASM_M_DIRECTIVES; iOpcode++ )
{
int iNopcode = FIRST_M_DIRECTIVE + iOpcode;
//. const TCHAR *pMnemonic = g_aAssemblerDirectivesMerlin[ iOpcode ].m_pMnemonic;
const TCHAR *pMnemonic = g_aAssemblerDirectives[ iNopcode ].m_pMnemonic;
nMnemonicHash = AssemblerHashMnemonic( pMnemonic );
g_aAssemblerDirectives[ iNopcode ].m_nHash = nMnemonicHash;
}
}
//===========================================================================
void AssemblerStartup()
{
AssemblerHashOpcodes();
AssemblerHashDirectives();
}
//===========================================================================
void _CmdAssembleHashDump ()
{
// #if DEBUG_ASM_HASH
std::vector<HashOpcode_t> vHashes;
HashOpcode_t tHash;
int iOpcode;
for ( iOpcode = 0; iOpcode < NUM_OPCODES; iOpcode++ )
{
tHash.m_iOpcode = iOpcode;
tHash.m_nValue = g_aOpcodesHash[ iOpcode ];
vHashes.push_back( tHash );
}
std::sort( vHashes.begin(), vHashes.end(), HashOpcode_t() );
// Hash_t nPrevHash = vHashes.at( 0 ).m_nValue;
Hash_t nThisHash = 0;
for ( iOpcode = 0; iOpcode < NUM_OPCODES; iOpcode++ )
{
tHash = vHashes.at( iOpcode );
Hash_t iThisHash = tHash.m_nValue;
int nOpcode = tHash.m_iOpcode;
int nOpmode = g_aOpcodes[ nOpcode ].nAddressMode;
ConsoleBufferPushFormat( "%08X %02X %s %s"
, iThisHash
, nOpcode
, g_aOpcodes65C02[ nOpcode ].sMnemonic
, g_aOpmodes[ nOpmode ].m_sName
);
nThisHash++;
// if (nPrevHash != iThisHash)
// {
// ConsoleBufferPushFormat( "Total: %d", nThisHash );
// nThisHash = 0;
// }
}
ConsoleUpdate();
//#endif
}
//===========================================================================
int AssemblerPokeAddress( const int Opcode, const int nOpmode, const WORD nBaseAddress, const WORD nTargetOffset )
{
// int nOpmode = g_aOpcodes[ nOpcode ].nAddressMode;
int nOpbytes = g_aOpmodes[ nOpmode ].m_nBytes;
// if (nOpbytes != nBytes)
// ConsoleDisplayError( " ERROR: Input Opcode bytes differs from actual!" );
*(memdirty + (nBaseAddress >> 8)) |= 1;
// *(mem + nBaseAddress) = (BYTE) nOpcode;
if (nOpbytes > 1)
*(mem + nBaseAddress + 1) = (BYTE)(nTargetOffset >> 0);
if (nOpbytes > 2)
*(mem + nBaseAddress + 2) = (BYTE)(nTargetOffset >> 8);
return nOpbytes;
}
//===========================================================================
bool AssemblerPokeOpcodeAddress( const WORD nBaseAddress )
{
int iAddressMode = m_iAsmAddressMode; // opmode detected from input
int nTargetValue = m_nAsmTargetValue;
int iOpcode;
int nOpcodes = m_vAsmOpcodes.size();
for ( iOpcode = 0; iOpcode < nOpcodes; iOpcode++ )
{
int nOpcode = m_vAsmOpcodes.at( iOpcode ); // m_iOpcode;
int nOpmode = g_aOpcodes[ nOpcode ].nAddressMode;
if (nOpmode == iAddressMode)
{
*(mem + nBaseAddress) = (BYTE) nOpcode;
int nOpbytes = AssemblerPokeAddress( nOpcode, nOpmode, nBaseAddress, nTargetValue );
if (m_bDelayedTargetsDirty)
{
int nDelayedTargets = m_vDelayedTargets.size();
DelayedTarget_t *pTarget = & m_vDelayedTargets.at( nDelayedTargets - 1 );
pTarget->m_nOpcode = nOpcode;
pTarget->m_iOpmode = nOpmode;
}
g_nAssemblerAddress += nOpbytes;
return true;
}
}
return false;
}
//===========================================================================
bool TestFlag( AssemblerFlags_e eFlag )
{
if (m_bAsmFlags & eFlag)
return true;
return false;
}
//===========================================================================
void SetFlag( AssemblerFlags_e eFlag, bool bValue = true )
{
if (bValue)
m_bAsmFlags |= eFlag;
else
m_bAsmFlags &= ~ eFlag;
}
/*
Output
AM_IMPLIED
AM_M
AM_A
AM_Z
AM_I // indexed or indirect
*/
//===========================================================================
bool AssemblerGetArgs( int iArg, int nArgs, WORD nBaseAddress )
{
m_iAsmAddressMode = AM_IMPLIED;
AssemblerState_e eNextState = AS_GET_MNEMONIC;
m_bAsmFlags = 0;
m_nAsmTargetAddress = 0;
int nBase = 10;
// Sync up to Raw Args for matching mnemonic
// Process them instead of the cooked args, since we need the orginal tokens
Arg_t *pArg = &g_aArgRaw[ iArg ];
while (iArg < g_nArgRaw)
{
int iToken = pArg->eToken;
// int iType = pArg->bType;
if (iToken == TOKEN_HASH)
{
if (eNextState != AS_GET_MNEMONIC_PARM)
{
ConsoleBufferPush( TEXT( " Syntax Error: '#'" ) );
return false;
}
if (TestFlag( AF_HaveHash ))
{
ConsoleBufferPush( TEXT( " Syntax Error: Extra '#'" ) ); // No thanks, we already have one
return false;
}
SetFlag( AF_HaveHash );
m_iAsmAddressMode = AM_M; // Immediate
eNextState = AS_GET_TARGET;
m_nAsmBytes = 1;
}
else
if (iToken == TOKEN_DOLLAR)
{
if (TestFlag( AF_HaveDollar ))
{
ConsoleBufferPush( TEXT( " Syntax Error: Extra '$'" ) ); // No thanks, we already have one
return false;
}
nBase = 16; // switch to hex
if (! TestFlag( AF_HaveHash))
{
SetFlag( AF_HaveDollar );
m_iAsmAddressMode = AM_A; // Absolute
}
eNextState = AS_GET_TARGET;
m_nAsmBytes = 2;
}
else
if (iToken == TOKEN_PAREN_L)
{
if (TestFlag( AF_HaveLeftParen ))
{
ConsoleBufferPush( TEXT( " Syntax Error: Extra '('" ) ); // No thanks, we already have one
return false;
}
SetFlag( AF_HaveLeftParen );
// Indexed or Indirect
m_iAsmAddressMode = AM_I;
}
else
if (iToken == TOKEN_PAREN_R)
{
if (TestFlag( AF_HaveRightParen ))
{
ConsoleBufferPush( TEXT( " Syntax Error: Extra ')'" ) ); // No thanks, we already have one
return false;
}
SetFlag( AF_HaveRightParen );
// Indexed or Indirect
m_iAsmAddressMode = AM_I;
}
else
if (iToken == TOKEN_COMMA)
{
if (TestFlag( AF_HaveComma ))
{
ConsoleBufferPush( TEXT( " Syntax Error: Extra ','" ) ); // No thanks, we already have one
return false;
}
SetFlag( AF_HaveComma );
eNextState = AS_GET_INDEX;
// We should have address by now
}
else
if (iToken == TOKEN_LESS_THAN)
{
}
else
if (iToken == TOKEN_GREATER_THAN)
{
}
else
if (iToken == TOKEN_SEMI) // comment
{
break;
}
else
if (iToken == TOKEN_ALPHANUMERIC)
{
if (eNextState == AS_GET_MNEMONIC)
{
eNextState = AS_GET_MNEMONIC_PARM;
}
else
if (eNextState == AS_GET_MNEMONIC_PARM)
{
eNextState = AS_GET_TARGET;
}
if (eNextState == AS_GET_TARGET)
{
SetFlag( AF_HaveTarget );
ArgsGetValue( pArg, & m_nAsmTargetAddress, nBase );
// Do Symbol Lookup
WORD nSymbolAddress;
bool bExists = FindAddressFromSymbol( pArg->sArg, &nSymbolAddress );
if (bExists)
{
m_nAsmTargetAddress = nSymbolAddress;
if (m_iAsmAddressMode == AM_IMPLIED)
m_iAsmAddressMode = AM_A;
}
else
{
// if valid hex address, don't have delayed target
std::string strAddress = StrFormat( "%X", m_nAsmTargetAddress);
if (strAddress != pArg->sArg)
{
DelayedTarget_t tDelayedTarget;
tDelayedTarget.m_nBaseAddress = nBaseAddress;
strncpy( tDelayedTarget.m_sAddress, pArg->sArg, MAX_SYMBOLS_LEN );
tDelayedTarget.m_sAddress[ MAX_SYMBOLS_LEN ] = 0;
// Flag this target that we need to update it when we have the relevent info
m_bDelayedTargetsDirty = true;
tDelayedTarget.m_nOpcode = 0;
tDelayedTarget.m_iOpmode = m_iAsmAddressMode;
m_vDelayedTargets.push_back( tDelayedTarget );
m_nAsmTargetAddress = 0;
}
}
if ((m_iAsmAddressMode != AM_M) &&
(m_iAsmAddressMode != AM_IMPLIED) &&
(! m_bDelayedTargetsDirty))
{
if (m_nAsmTargetAddress <= _6502_ZEROPAGE_END)
{
m_iAsmAddressMode = AM_Z;
m_nAsmBytes = 1;
}
}
}
if (eNextState == AS_GET_INDEX)
{
if (pArg->nArgLen == 1)
{
if (pArg->sArg[0] == 'X')
{
if (! TestFlag( AF_HaveComma ))
{
ConsoleBufferPush( TEXT( " Syntax Error: Missing ','" ) );
return false;
}
SetFlag( AF_HaveRegisterX );
}
if (pArg->sArg[0] == 'Y')
{
if (! (TestFlag( AF_HaveComma )))
{
ConsoleBufferPush( TEXT( " Syntax Error: Missing ','" ) );
return false;
}
SetFlag( AF_HaveRegisterY );
}
}
}
}
iArg++;
pArg++;
}
return true;
}
//===========================================================================
bool AssemblerUpdateAddressingMode()
{
SetFlag( AF_HaveEitherParen, TestFlag(AF_HaveLeftParen) || TestFlag(AF_HaveRightParen) );
SetFlag( AF_HaveBothParen, TestFlag(AF_HaveLeftParen) && TestFlag(AF_HaveRightParen) );
if ((TestFlag( AF_HaveLeftParen )) && (! TestFlag( AF_HaveRightParen )))
{
ConsoleBufferPush( TEXT( " Syntax Error: Missing ')'" ) );
return false;
}
if ((! TestFlag( AF_HaveLeftParen )) && ( TestFlag( AF_HaveRightParen )))
{
ConsoleBufferPush( TEXT( " Syntax Error: Missing '('" ) );
return false;
}
if (TestFlag( AF_HaveComma ))
{
if ((! TestFlag( AF_HaveRegisterX )) && (! TestFlag( AF_HaveRegisterY )))
{
ConsoleBufferPush( TEXT( " Syntax Error: Index 'X' or 'Y'" ) );
return false;
}
}
if (TestFlag( AF_HaveBothParen ))
{
if (TestFlag( AF_HaveComma ))
{
if (TestFlag( AF_HaveRegisterX ))
{
m_iAsmAddressMode = AM_AX;
m_nAsmBytes = 2;
if (m_nAsmTargetAddress <= _6502_ZEROPAGE_END)
{
m_iAsmAddressMode = AM_ZX;
m_nAsmBytes = 1;
}
}
if (TestFlag( AF_HaveRegisterY ))
{
m_iAsmAddressMode = AM_AY;
m_nAsmBytes = 2;
if (m_nAsmTargetAddress <= _6502_ZEROPAGE_END)
{
m_iAsmAddressMode = AM_ZY;
m_nAsmBytes = 1;
}
}
}
}
if ((m_iAsmAddressMode == AM_A) || (m_iAsmAddressMode == AM_Z))
{
if (! TestFlag( AF_HaveEitherParen)) // if no paren
{
if (TestFlag( AF_HaveComma ) && TestFlag( AF_HaveRegisterX ))
{
if (m_iAsmAddressMode == AM_Z)
m_iAsmAddressMode = AM_ZX;
else
m_iAsmAddressMode = AM_AX;
}
if (TestFlag( AF_HaveComma ) && TestFlag( AF_HaveRegisterY ))
{
if (m_iAsmAddressMode == AM_Z)
m_iAsmAddressMode = AM_ZY;
else
m_iAsmAddressMode = AM_AY;
}
}
}
if (m_iAsmAddressMode == AM_I)
{
if (! TestFlag( AF_HaveEitherParen)) // if no paren
{
// Indirect Zero Page
// Indirect Absolute
}
}
m_nAsmTargetValue = m_nAsmTargetAddress;
int nOpcode = m_vAsmOpcodes.at( 0 ); // branch opcodes don't vary (only 1 Addressing Mode)
if (_6502_CalcRelativeOffset( nOpcode, m_nAsmBaseAddress, m_nAsmTargetAddress, & m_nAsmTargetValue ))
{
if (m_iAsmAddressMode == NUM_OPMODES)
return false;
m_iAsmAddressMode = AM_R;
}
return true;
}
//===========================================================================
int AssemblerDelayedTargetsSize()
{
int nSize = m_vDelayedTargets.size();
return nSize;
}
// The Assembler was terminated, with Symbol(s) declared, but not (yet) defined.
// i.e.
// A 300
// BNE $DONE
// <enter>
//===========================================================================
void AssemblerProcessDelayedSymols()
{
m_bDelayedTargetsDirty = false; // assembler set signal if new symbol was added
bool bModified = false;
while (! bModified)
{
bModified = false;
std::vector<DelayedTarget_t>::iterator iSymbol;
for ( iSymbol = m_vDelayedTargets.begin(); iSymbol != m_vDelayedTargets.end(); ++iSymbol )
{
DelayedTarget_t *pTarget = & (*iSymbol); // m_vDelayedTargets.at( iSymbol );
WORD nTargetAddress;
bool bExists = FindAddressFromSymbol( pTarget->m_sAddress, & nTargetAddress );
if (bExists)
{
// TODO: need to handle #<symbol, #>symbol, symbol+n, symbol-n
bModified = true;
int nOpcode = pTarget->m_nOpcode;
int nOpmode = g_aOpcodes[ nOpcode ].nAddressMode;
// int nOpbytes = g_aOpmodes[ nOpmode ].m_nBytes;
// 300: D0 7E BNE $380
// ^ ^ ^
// | | TargetAddress
// | TargetValue
// BaseAddress
WORD nTargetValue = nTargetAddress;
if (_6502_CalcRelativeOffset( nOpcode, pTarget->m_nBaseAddress, nTargetAddress, & nTargetValue ))
{
if (m_iAsmAddressMode == NUM_OPMODES)
{
nTargetValue = 0;
bModified = false;
}
}
if (bModified)
{
AssemblerPokeAddress( nOpcode, nOpmode, pTarget->m_nBaseAddress, nTargetValue );
*(memdirty + (pTarget->m_nBaseAddress >> 8)) |= 1;
m_vDelayedTargets.erase( iSymbol );
// iterators are invalid after the point of deletion
// need to restart enumeration
break;
}
}
}
if (! bModified)
break;
}
}
bool Assemble( int iArg, int nArgs, WORD nAddress )
{
bool bGotArgs;
bool bGotMode;
bool bGotByte;
// Since, making 2-passes is not an option,
// we need to buffer the target address fix-ups.
AssemblerProcessDelayedSymols();
m_nAsmBaseAddress = nAddress;
TCHAR *pMnemonic = g_aArgs[ iArg ].sArg;
Hash_t nMnemonicHash = AssemblerHashMnemonic( pMnemonic );
#if DEBUG_ASSEMBLER
ConsolePrintFormat( "%s%04X%s: %s%s%s -> %s%08X",
CHC_ADDRESS, nAddress,
CHC_DEFAULT,
CHC_STRING, pMnemonic,
CHC_DEFAULT,
CHC_NUM_HEX, nMnemonicHash );
#endif
m_vAsmOpcodes.clear(); // Candiate opcodes
int iOpcode;
// Ugh! Linear search.
for ( iOpcode = 0; iOpcode < NUM_OPCODES; iOpcode++ )
{
if (nMnemonicHash == g_aOpcodesHash[ iOpcode ])
{
m_vAsmOpcodes.push_back( iOpcode );
}
}
int nOpcodes = m_vAsmOpcodes.size();
if (! nOpcodes)
{
// Check for assembler directive
ConsoleBufferPush( TEXT(" Syntax Error: Invalid mnemonic") );
return false;
}
else
{
bGotArgs = AssemblerGetArgs( iArg, nArgs, nAddress );
if (bGotArgs)
{
bGotMode = AssemblerUpdateAddressingMode();
if (bGotMode)
{
bGotByte = AssemblerPokeOpcodeAddress( nAddress );
}
}
}
return true;
}
//===========================================================================
void AssemblerOn ()
{
g_bAssemblerInput = true;
g_sConsolePrompt[0] = g_aConsolePrompt[ PROMPT_ASSEMBLER ];
}
//===========================================================================
void AssemblerOff ()
{
g_bAssemblerInput = false;
g_sConsolePrompt[0] = g_aConsolePrompt[ PROMPT_COMMAND ];
}