1050 lines
16 KiB
C
1050 lines
16 KiB
C
#include <libgen.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "apu.h"
|
|
#include "cpu.h"
|
|
#include "opcodes.h"
|
|
#include "ppu.h"
|
|
#include "rom.h"
|
|
|
|
#define MAX(a, b) ((a > b) ? a : b)
|
|
|
|
#define STATUS_UPDATE_ZERO(r) \
|
|
(regs.status.zero = r == 0)
|
|
#define STATUS_UPDATE_NEGATIVE(r) \
|
|
(regs.status.negative = ((r & (1 << 7)) != 0))
|
|
#define STATUS_UPDATE_NZ(r) \
|
|
{STATUS_UPDATE_ZERO(r); \
|
|
STATUS_UPDATE_NEGATIVE(r);}
|
|
#define STATUS_TO_INT() \
|
|
(regs.status.carry | (regs.status.zero << 1) \
|
|
| (regs.status.interrupt_disable << 2) | (regs.status.decimal_mode << 3) \
|
|
| (regs.status.brk << 4) | (regs.status.unused << 5) \
|
|
| (regs.status.overflow << 6) | (regs.status.negative << 7))
|
|
|
|
#define MEMORY_MIRROR(addr) \
|
|
if (addr < 0x2000) \
|
|
addr &= 0x07FF;
|
|
|
|
#define PUSH(b) \
|
|
(memwrite(0x0100 + regs.sp--, b))
|
|
#define PULL() \
|
|
(peek(0x0100 + ++regs.sp))
|
|
|
|
struct rom rom = {0};
|
|
uint32_t cycles = 0;
|
|
|
|
bool page_crossed = false;
|
|
|
|
static uint8_t
|
|
peek(uint16_t addr)
|
|
{
|
|
MEMORY_MIRROR(addr);
|
|
|
|
if (addr > 0x7FFF) {
|
|
if (rom.prg_rom_size == 0x4000)
|
|
return rom.prg_rom[(addr - 0x8000) % 0x4000];
|
|
else if (rom.prg_rom_size == 0x8000)
|
|
return rom.prg_rom[addr - 0x8000];
|
|
else
|
|
fprintf(stderr, "PRG ROG size is not 0x4000 nor 0x8000\n"), exit(1);
|
|
} else if ((addr >= 0x4000 && addr <= 0x4013) || addr == 0x4015 || addr == 0x4017)
|
|
return apu_read(addr);
|
|
else
|
|
return memory[addr];
|
|
}
|
|
|
|
static uint16_t
|
|
peek16(uint16_t addr)
|
|
{
|
|
/* bytes are stored in little-endian (low then high) */
|
|
return peek(addr) | (peek(addr + 1) << 8);
|
|
}
|
|
|
|
static void
|
|
memwrite(uint16_t addr, uint8_t byte)
|
|
{
|
|
if (addr >= 0x4000 && addr <= 0x4017) {
|
|
apu_write(addr, byte);
|
|
return;
|
|
}
|
|
|
|
MEMORY_MIRROR(addr);
|
|
|
|
memory[addr] = byte;
|
|
}
|
|
|
|
static void
|
|
memwrite16(uint16_t addr, uint16_t word)
|
|
{
|
|
MEMORY_MIRROR(addr);
|
|
|
|
/* bytes are stored in little-endian (low then high) */
|
|
memory[addr] = word & 0xFF;
|
|
memory[addr + 1] = (word & 0xFF00) >> 8;
|
|
}
|
|
|
|
static uint16_t
|
|
opcode_mem(enum addressing_mode mode)
|
|
{
|
|
uint16_t arg, val;
|
|
|
|
if (mode == AM_ACC || mode == AM_NONE) {
|
|
return 0;
|
|
} else if (mode != AM_ABS && mode != AM_ABS_X && mode != AM_ABS_Y && mode != AM_IND && mode) {
|
|
arg = peek(regs.pc++);
|
|
} else {
|
|
arg = peek16(regs.pc);
|
|
regs.pc += 2;
|
|
}
|
|
|
|
switch (mode) {
|
|
case AM_ZP:
|
|
val = arg % 256;
|
|
break;
|
|
case AM_ZP_X:
|
|
val = (arg + regs.x) % 256;
|
|
break;
|
|
case AM_ZP_Y:
|
|
val = (arg + regs.y) % 256;
|
|
break;
|
|
case AM_REL:
|
|
val = (int8_t)arg + regs.pc;
|
|
break;
|
|
case AM_IMM:
|
|
case AM_ABS:
|
|
val = arg;
|
|
break;
|
|
case AM_ABS_X:
|
|
val = arg + regs.x;
|
|
|
|
if ((arg & 0xFF00) != (val & 0xFF00))
|
|
page_crossed = true;
|
|
break;
|
|
case AM_ABS_Y:
|
|
val = arg + regs.y;
|
|
|
|
if ((arg & 0xFF00) != (val & 0xFF00))
|
|
page_crossed = true;
|
|
break;
|
|
case AM_IND:
|
|
val = peek(arg) | (peek(((arg + 1) & 0xFF) | (arg & 0xFF00)) << 8);
|
|
break;
|
|
case AM_IND_X:
|
|
val = peek((arg + regs.x) % 256) + peek((arg + regs.x + 1) % 256) * 256;
|
|
break;
|
|
case AM_IND_Y:
|
|
val = peek(arg) + peek((arg + 1) % 256) * 256 + regs.y;
|
|
|
|
if (((uint16_t)(val - regs.y) & 0xFF00) != (val & 0xFF00))
|
|
page_crossed = true;
|
|
break;
|
|
default:
|
|
fprintf(stderr, "INVALID ADDRESSING MODE %i\n", mode);
|
|
abort();
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static void
|
|
tick(void)
|
|
{
|
|
cycles++;
|
|
ppu_tick();
|
|
ppu_tick();
|
|
ppu_tick();
|
|
}
|
|
|
|
static void
|
|
branch(uint16_t addr, bool cond)
|
|
{
|
|
if (!cond)
|
|
return;
|
|
tick();
|
|
|
|
if (((regs.pc + 1) & 0xFF00) != (addr & 0xFF00))
|
|
tick();
|
|
|
|
regs.pc = addr;
|
|
}
|
|
|
|
/* OFFICIAL OPCODES */
|
|
|
|
void
|
|
ADC(uint16_t arg)
|
|
{
|
|
uint16_t sum; // 16-bit sum makes it easier to determine carry flag
|
|
|
|
sum = regs.a + arg + regs.status.carry;
|
|
|
|
regs.status.carry = sum > 0xFF;
|
|
/* overflow flag formula: https://stackoverflow.com/a/29224684 */
|
|
regs.status.overflow = (~(regs.a ^ arg) & (regs.a ^ sum) & 0x80) != 0;
|
|
regs.a = sum & 0xFF;
|
|
|
|
STATUS_UPDATE_NZ(regs.a);
|
|
}
|
|
|
|
void
|
|
AND(uint16_t arg)
|
|
{
|
|
regs.a &= arg;
|
|
|
|
STATUS_UPDATE_NZ(regs.a);
|
|
}
|
|
|
|
void
|
|
ASL_acc(uint16_t arg)
|
|
{
|
|
uint16_t tmp;
|
|
|
|
tmp = regs.a << 1;
|
|
regs.a = tmp & 0xFF;
|
|
|
|
regs.status.carry = tmp > 0xFF;
|
|
STATUS_UPDATE_NZ(regs.a);
|
|
}
|
|
|
|
void
|
|
ASL(uint16_t mem)
|
|
{
|
|
uint16_t tmp;
|
|
|
|
tmp = peek(mem) << 1;
|
|
memwrite(mem, tmp & 0xFF);
|
|
|
|
regs.status.carry = tmp > 0xFF;
|
|
regs.status.negative = (tmp & (1 << 7)) != 0;
|
|
regs.status.zero = (tmp << 1 & 0xFF) == 0;
|
|
}
|
|
|
|
void
|
|
BCC(uint16_t arg)
|
|
{
|
|
branch(arg, regs.status.carry == 0);
|
|
}
|
|
|
|
void
|
|
BCS(uint16_t arg)
|
|
{
|
|
branch(arg, regs.status.carry == 1);
|
|
}
|
|
|
|
void
|
|
BEQ(uint16_t arg)
|
|
{
|
|
branch(arg, regs.status.zero == 1);
|
|
}
|
|
|
|
void
|
|
BIT(uint16_t arg)
|
|
{
|
|
uint8_t tmp = arg;
|
|
|
|
regs.status.zero = (regs.a & tmp) == 0;
|
|
regs.status.overflow = (tmp & (1 << 6)) != 0;
|
|
STATUS_UPDATE_NEGATIVE(tmp);
|
|
}
|
|
|
|
void
|
|
BMI(uint16_t arg)
|
|
{
|
|
branch(arg, regs.status.negative == 1);
|
|
}
|
|
|
|
void
|
|
BNE(uint16_t arg)
|
|
{
|
|
branch(arg, regs.status.zero == 0);
|
|
}
|
|
|
|
void
|
|
BPL(uint16_t arg)
|
|
{
|
|
branch(arg, regs.status.negative == 0);
|
|
}
|
|
|
|
void
|
|
BRK(uint16_t arg)
|
|
{
|
|
/* TODO: push regs.pc and regs.status to stack and load IRQ vector */
|
|
regs.status.brk = 1;
|
|
exit(0);
|
|
}
|
|
|
|
void
|
|
BVC(uint16_t arg)
|
|
{
|
|
//regs.status.overflow = (STATUS_TO_INT() & (1 << 6)) == 0;
|
|
//if (regs.status.overflow == 0)
|
|
branch(arg, (STATUS_TO_INT() & (1 << 6)) == 0);
|
|
}
|
|
|
|
void
|
|
BVS(uint16_t arg)
|
|
{
|
|
regs.status.overflow = (STATUS_TO_INT() & (1 << 6)) != 0;
|
|
branch(arg, regs.status.overflow == 1);
|
|
}
|
|
|
|
void
|
|
CLC(uint16_t arg)
|
|
{
|
|
regs.status.carry = 0;
|
|
}
|
|
|
|
void
|
|
CLD(uint16_t arg)
|
|
{
|
|
regs.status.decimal_mode = 0;
|
|
}
|
|
|
|
void
|
|
CLI(uint16_t arg)
|
|
{
|
|
regs.status.interrupt_disable = 0;
|
|
}
|
|
|
|
void
|
|
CLV(uint16_t arg)
|
|
{
|
|
regs.status.overflow = 0;
|
|
}
|
|
|
|
void
|
|
CMP(uint16_t arg)
|
|
{
|
|
uint8_t tmp;
|
|
|
|
tmp = regs.a - arg;
|
|
|
|
regs.status.carry = regs.a >= arg;
|
|
regs.status.zero = regs.a == arg;
|
|
STATUS_UPDATE_NEGATIVE(tmp);
|
|
}
|
|
|
|
void
|
|
CPX(uint16_t arg)
|
|
{
|
|
uint8_t tmp;
|
|
|
|
tmp = regs.x - arg;
|
|
|
|
regs.status.carry = regs.x >= arg;
|
|
regs.status.zero = regs.x == arg;
|
|
STATUS_UPDATE_NEGATIVE(tmp);
|
|
}
|
|
|
|
void
|
|
CPY(uint16_t arg)
|
|
{
|
|
uint8_t tmp;
|
|
|
|
tmp = regs.y - arg;
|
|
|
|
regs.status.carry = regs.y >= arg;
|
|
regs.status.zero = regs.y == arg;
|
|
STATUS_UPDATE_NEGATIVE(tmp);
|
|
}
|
|
|
|
void
|
|
DEC(uint16_t mem)
|
|
{
|
|
memwrite(mem, peek(mem) - 1);
|
|
|
|
STATUS_UPDATE_NZ(peek(mem));
|
|
}
|
|
|
|
void
|
|
DEX(uint16_t arg)
|
|
{
|
|
regs.x--;
|
|
|
|
STATUS_UPDATE_NZ(regs.x);
|
|
}
|
|
|
|
void
|
|
DEY(uint16_t arg)
|
|
{
|
|
regs.y--;
|
|
|
|
STATUS_UPDATE_NZ(regs.y);
|
|
}
|
|
|
|
void
|
|
EOR(uint16_t arg)
|
|
{
|
|
regs.a ^= arg;
|
|
|
|
STATUS_UPDATE_NZ(regs.a);
|
|
}
|
|
|
|
void
|
|
INC(uint16_t mem)
|
|
{
|
|
memwrite(mem, peek(mem) + 1);
|
|
|
|
STATUS_UPDATE_NZ(peek(mem));
|
|
}
|
|
|
|
void
|
|
INX(uint16_t arg)
|
|
{
|
|
regs.x++;
|
|
|
|
STATUS_UPDATE_NZ(regs.x);
|
|
}
|
|
|
|
void
|
|
INY(uint16_t arg)
|
|
{
|
|
regs.y++;
|
|
|
|
STATUS_UPDATE_NZ(regs.y);
|
|
}
|
|
|
|
void
|
|
JMP(uint16_t arg)
|
|
{
|
|
regs.pc = arg;
|
|
}
|
|
|
|
void
|
|
JSR(uint16_t arg)
|
|
{
|
|
uint16_t tmp = regs.pc - 1;
|
|
|
|
/*
|
|
* first push high-byte of return address then low-byte
|
|
* https://www.masswerk.at/6502/6502_instruction_set.html
|
|
*/
|
|
PUSH((tmp & 0xFF00) >> 8);
|
|
PUSH(tmp & 0xFF);
|
|
regs.pc = arg;
|
|
}
|
|
|
|
void
|
|
LDA(uint16_t arg)
|
|
{
|
|
regs.a = arg;
|
|
|
|
STATUS_UPDATE_NZ(regs.a);
|
|
}
|
|
|
|
void
|
|
LDX(uint16_t arg)
|
|
{
|
|
regs.x = arg;
|
|
|
|
STATUS_UPDATE_NZ(regs.x);
|
|
}
|
|
|
|
void
|
|
LDY(uint16_t arg)
|
|
{
|
|
regs.y = arg;
|
|
|
|
STATUS_UPDATE_NZ(regs.y);
|
|
}
|
|
|
|
void
|
|
LSR_acc(uint16_t arg)
|
|
{
|
|
regs.status.carry = regs.a & 1; // bit 0 in carry
|
|
regs.a >>= 1;
|
|
regs.a &= ~(1 << 7); // bit 7 cleared
|
|
|
|
STATUS_UPDATE_NZ(regs.a);
|
|
}
|
|
|
|
void
|
|
LSR(uint16_t mem)
|
|
{
|
|
uint8_t tmp;
|
|
|
|
tmp = peek(mem);
|
|
|
|
regs.status.carry = tmp & 1; // bit 0 in carry
|
|
tmp >>= 1;
|
|
tmp &= ~(1 << 7); // bit 7 cleared
|
|
|
|
memwrite(mem, tmp);
|
|
|
|
STATUS_UPDATE_NZ(tmp);
|
|
}
|
|
|
|
void
|
|
NOP(uint16_t arg)
|
|
{
|
|
return;
|
|
}
|
|
|
|
void
|
|
ORA(uint16_t arg)
|
|
{
|
|
regs.a |= arg;
|
|
|
|
STATUS_UPDATE_NZ(regs.a);
|
|
}
|
|
|
|
void
|
|
PHA(uint16_t arg)
|
|
{
|
|
PUSH(regs.a);
|
|
}
|
|
|
|
void
|
|
PHP(uint16_t arg)
|
|
{
|
|
PUSH(STATUS_TO_INT() | (1 << 4));
|
|
}
|
|
|
|
void
|
|
PLA(uint16_t arg)
|
|
{
|
|
regs.a = PULL();
|
|
|
|
STATUS_UPDATE_NZ(regs.a);
|
|
}
|
|
|
|
void
|
|
PLP(uint16_t arg)
|
|
{
|
|
uint8_t status;
|
|
|
|
status = PULL();
|
|
|
|
regs.status.carry = (status & 1) != 0;
|
|
regs.status.zero = (status & (1 << 1)) != 0;
|
|
regs.status.interrupt_disable = (status & (1 << 2)) != 0;
|
|
regs.status.decimal_mode = (status & (1 << 3)) != 0;
|
|
regs.status.brk = 0;
|
|
regs.status.unused = 1;
|
|
regs.status.overflow = (status & (1 << 6)) != 0;
|
|
regs.status.negative = (status & (1 << 7)) != 0;
|
|
}
|
|
|
|
void
|
|
ROL_acc(uint16_t arg)
|
|
{
|
|
uint8_t carry;
|
|
carry = (regs.a & (1 << 7)) != 0;
|
|
|
|
regs.a <<= 1;
|
|
regs.a |= regs.status.carry;
|
|
|
|
regs.status.carry = carry;
|
|
STATUS_UPDATE_NZ(regs.a);
|
|
}
|
|
|
|
void
|
|
ROL(uint16_t mem)
|
|
{
|
|
uint8_t carry, tmp;
|
|
carry = (peek(mem) & (1 << 7)) != 0;
|
|
|
|
tmp = (peek(mem) << 1) | regs.status.carry;
|
|
memwrite(mem, tmp);
|
|
|
|
regs.status.carry = carry;
|
|
STATUS_UPDATE_NZ(tmp);
|
|
}
|
|
|
|
void
|
|
ROR_acc(uint16_t arg)
|
|
{
|
|
uint8_t carry;
|
|
carry = regs.a & 1;
|
|
|
|
regs.a >>= 1;
|
|
regs.a |= regs.status.carry << 7;
|
|
|
|
regs.status.carry = carry;
|
|
STATUS_UPDATE_NZ(regs.a);
|
|
}
|
|
|
|
void
|
|
ROR(uint16_t mem)
|
|
{
|
|
uint8_t carry, tmp;
|
|
carry = peek(mem) & 1;
|
|
|
|
tmp = (peek(mem) >> 1) | (regs.status.carry << 7);
|
|
memwrite(mem, tmp);
|
|
|
|
regs.status.carry = carry;
|
|
STATUS_UPDATE_NZ(tmp);
|
|
}
|
|
|
|
void
|
|
RTI(uint16_t arg)
|
|
{
|
|
PLP(0);
|
|
regs.pc = PULL() | (PULL() << 8);
|
|
}
|
|
|
|
void
|
|
RTS(uint16_t arg)
|
|
{
|
|
regs.pc = (PULL() | (PULL() << 8)) + 1;
|
|
}
|
|
|
|
void
|
|
SBC(uint16_t arg)
|
|
{
|
|
uint8_t tmp = arg & 0xFF;
|
|
uint16_t diff;
|
|
|
|
diff = regs.a - tmp - !regs.status.carry;
|
|
|
|
regs.status.carry = diff > 0xFF;
|
|
regs.status.carry = !regs.status.carry;
|
|
/* overflow flag formula: https://stackoverflow.com/a/29224684 */
|
|
regs.status.overflow = ((regs.a ^ arg) & (regs.a ^ diff) & 0x80) != 0;
|
|
regs.a = diff & 0xFF;
|
|
|
|
STATUS_UPDATE_NZ(regs.a);
|
|
}
|
|
|
|
void
|
|
SEC(uint16_t arg)
|
|
{
|
|
regs.status.carry = 1;
|
|
}
|
|
|
|
void
|
|
SED(uint16_t arg)
|
|
{
|
|
regs.status.decimal_mode = 1;
|
|
}
|
|
|
|
void
|
|
SEI(uint16_t arg)
|
|
{
|
|
regs.status.interrupt_disable = 1;
|
|
}
|
|
|
|
void
|
|
STA(uint16_t mem)
|
|
{
|
|
memwrite(mem, regs.a);
|
|
}
|
|
|
|
void
|
|
STX(uint16_t mem)
|
|
{
|
|
memwrite(mem, regs.x);
|
|
}
|
|
|
|
void
|
|
STY(uint16_t mem)
|
|
{
|
|
memwrite(mem, regs.y);
|
|
}
|
|
|
|
void
|
|
TAX(uint16_t arg)
|
|
{
|
|
regs.x = regs.a;
|
|
|
|
STATUS_UPDATE_NZ(regs.x);
|
|
}
|
|
|
|
void
|
|
TAY(uint16_t arg)
|
|
{
|
|
regs.y = regs.a;
|
|
|
|
STATUS_UPDATE_NZ(regs.y);
|
|
}
|
|
|
|
void
|
|
TSX(uint16_t arg)
|
|
{
|
|
// regs.x = PULL();
|
|
regs.x = regs.sp;
|
|
|
|
STATUS_UPDATE_NZ(regs.x);
|
|
}
|
|
|
|
void
|
|
TXA(uint16_t arg)
|
|
{
|
|
regs.a = regs.x;
|
|
|
|
STATUS_UPDATE_NZ(regs.a);
|
|
}
|
|
|
|
void
|
|
TXS(uint16_t arg)
|
|
{
|
|
// PUSH(regs.x);
|
|
regs.sp = regs.x;
|
|
}
|
|
|
|
void
|
|
TYA(uint16_t arg)
|
|
{
|
|
regs.a = regs.y;
|
|
|
|
STATUS_UPDATE_NZ(regs.a);
|
|
}
|
|
|
|
/* UNOFFICIAL OPCODES */
|
|
|
|
void
|
|
AAC(uint16_t arg)
|
|
{
|
|
AND(arg);
|
|
|
|
regs.status.carry = regs.status.negative;
|
|
}
|
|
|
|
void
|
|
SAX(uint16_t arg)
|
|
{
|
|
uint8_t tmp = regs.x & regs.a;
|
|
memwrite(arg, tmp);
|
|
}
|
|
|
|
void
|
|
ARR(uint16_t arg)
|
|
{
|
|
uint8_t tmp = arg & regs.a;
|
|
|
|
AND(arg);
|
|
ROR_acc(0);
|
|
|
|
if ((tmp & (1 << 5)) != 0 && (tmp & (1 << 6)) != 0)
|
|
SEC(0), CLV(0);
|
|
else if ((tmp & (1 << 5)) == 0 && (tmp & (1 << 6)) == 0)
|
|
CLC(0), CLV(0);
|
|
else if ((tmp & (1 << 5)) != 0)
|
|
CLC(0), regs.status.overflow = 1;
|
|
else if ((tmp & (1 << 6)) != 0)
|
|
SEC(0), regs.status.overflow = 1;
|
|
}
|
|
|
|
void
|
|
ASR(uint16_t arg)
|
|
{
|
|
AND(arg);
|
|
LSR_acc(0);
|
|
}
|
|
|
|
void
|
|
ATX(uint16_t arg)
|
|
{
|
|
AND(arg);
|
|
TAX(0);
|
|
}
|
|
|
|
void
|
|
AXA(uint16_t arg)
|
|
{
|
|
uint8_t tmp = regs.x & regs.a & 7;
|
|
memwrite(arg, tmp);
|
|
}
|
|
|
|
void
|
|
AXS(uint16_t arg)
|
|
{
|
|
regs.x &= regs.a;
|
|
regs.x -= arg;
|
|
|
|
regs.status.carry = arg <= regs.x;
|
|
STATUS_UPDATE_NZ(regs.x);
|
|
}
|
|
|
|
void
|
|
DCP(uint16_t arg)
|
|
{
|
|
uint8_t tmp = peek(arg) - 1;
|
|
memwrite(arg, tmp);
|
|
|
|
regs.status.carry = tmp <= regs.a;
|
|
STATUS_UPDATE_NZ(regs.a - tmp);
|
|
}
|
|
|
|
void
|
|
ISB(uint16_t arg)
|
|
{
|
|
INC(arg);
|
|
SBC(peek(arg));
|
|
}
|
|
|
|
void
|
|
KIL(uint16_t arg)
|
|
{
|
|
/* TODO: figure out how to stop interpret(), I guess with global bool */
|
|
NOP(0);
|
|
}
|
|
|
|
void
|
|
LAR(uint16_t arg)
|
|
{
|
|
regs.a = regs.x = regs.sp = regs.a & regs.sp;
|
|
|
|
STATUS_UPDATE_NZ(regs.a);
|
|
}
|
|
|
|
void
|
|
LAX(uint16_t arg)
|
|
{
|
|
LDA(arg);
|
|
LDX(arg);
|
|
}
|
|
|
|
void
|
|
RLA(uint16_t arg)
|
|
{
|
|
ROL(arg);
|
|
AND(peek(arg));
|
|
}
|
|
|
|
void
|
|
RRA(uint16_t arg)
|
|
{
|
|
ROR(arg);
|
|
ADC(peek(arg));
|
|
}
|
|
|
|
void
|
|
SLO(uint16_t arg)
|
|
{
|
|
ASL(arg);
|
|
ORA(peek(arg));
|
|
}
|
|
|
|
void
|
|
SRE(uint16_t arg)
|
|
{
|
|
LSR(arg);
|
|
EOR(peek(arg));
|
|
}
|
|
|
|
void
|
|
SXA(uint16_t arg)
|
|
{
|
|
memwrite(arg, regs.x & ((arg & 0xFF00) + 1));
|
|
}
|
|
|
|
void
|
|
SYA(uint16_t arg)
|
|
{
|
|
memwrite(arg, regs.x & ((arg & 0xFF00) + 1));
|
|
}
|
|
|
|
void
|
|
XAA(uint16_t arg)
|
|
{
|
|
/* TODO: apparently the exact operation is unknown */
|
|
NOP(0);
|
|
}
|
|
|
|
void
|
|
XAS(uint16_t arg)
|
|
{
|
|
regs.sp = regs.a & regs.x;
|
|
memwrite(arg, regs.sp & ((arg & 0xFF00) + 1));
|
|
}
|
|
|
|
static void
|
|
interpret(void)
|
|
{
|
|
uint8_t op, spaces;
|
|
uint16_t arg;
|
|
enum addressing_mode mode;
|
|
|
|
for (;;) {
|
|
printf("%04X ", regs.pc);
|
|
|
|
op = peek(regs.pc++);
|
|
|
|
printf("%02X", op);
|
|
for (uint8_t i = 0; i < opcodes[op].bytes - 1; i++)
|
|
printf(" %02X", peek(regs.pc + i));
|
|
|
|
if (opcodes[op].bytes == 1)
|
|
printf(" ");
|
|
else if (opcodes[op].bytes == 2)
|
|
printf(" ");
|
|
else if (opcodes[op].bytes == 3)
|
|
putchar(' ');
|
|
putchar((opcodes[op].unofficial) ? '*' : ' ');
|
|
printf("%s ", opcodes[op].name);
|
|
|
|
mode = opcodes[op].mode;
|
|
arg = opcode_mem(mode);
|
|
|
|
switch (mode) {
|
|
case AM_IMM:
|
|
printf("#$%02X", arg);
|
|
break;
|
|
case AM_ZP:
|
|
printf("$%02X", arg);
|
|
break;
|
|
case AM_ZP_X:
|
|
printf("$%02X,X @ %02X", peek(regs.pc - 1), arg);
|
|
break;
|
|
case AM_ZP_Y:
|
|
printf("$%02X,Y @ %02X", peek(regs.pc - 1), arg);
|
|
break;
|
|
case AM_REL:
|
|
case AM_ABS:
|
|
printf("$%04X", arg);
|
|
break;
|
|
case AM_ABS_X:
|
|
printf("$%04X,X @ %04X", (uint16_t)(arg - regs.x), arg);
|
|
break;
|
|
case AM_ABS_Y:
|
|
printf("$%04X,Y @ %04X", (uint16_t)(arg - regs.y), arg);
|
|
break;
|
|
case AM_IND:
|
|
printf("($%04X) = %04X", peek16(regs.pc - 2), arg);
|
|
break;
|
|
case AM_IND_X:
|
|
printf("($%02X,X) @ %02X = %04X", peek(regs.pc - 1), (peek(regs.pc - 1) + regs.x) & 0xFF, arg);
|
|
break;
|
|
case AM_IND_Y:
|
|
printf("($%02X),Y = %04X @ %04X", peek(regs.pc - 1), (uint16_t)(arg - regs.y), arg);
|
|
break;
|
|
case AM_ACC:
|
|
printf("A ");
|
|
break;
|
|
case AM_NONE:
|
|
printf(" ");
|
|
break;
|
|
}
|
|
|
|
spaces = 9;
|
|
|
|
if (opcodes[op].memread || opcodes[op].memwrite)
|
|
printf(" = %02X", peek(arg));
|
|
else
|
|
spaces += 5;
|
|
|
|
if (mode != AM_IND && mode != AM_ABS_X && mode != AM_ABS_Y)
|
|
spaces += 7;
|
|
|
|
switch (opcodes[op].mode) {
|
|
case AM_IND_X:
|
|
spaces -= 12;
|
|
break;
|
|
case AM_IND_Y:
|
|
spaces -= 14;
|
|
break;
|
|
case AM_ZP:
|
|
spaces += 4;
|
|
break;
|
|
case AM_ZP_X:
|
|
case AM_ZP_Y:
|
|
spaces -= 3;
|
|
break;
|
|
case AM_REL:
|
|
case AM_ABS:
|
|
spaces += 2;
|
|
break;
|
|
case AM_IMM:
|
|
case AM_ACC:
|
|
case AM_NONE:
|
|
spaces += 3;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
while (spaces--) putchar(' ');
|
|
|
|
printf("A:%02X X:%02X Y:%02X P:%02X SP:%02X PPU:%3d,%3d CYC:%d\n",
|
|
regs.a, regs.x, regs.y, STATUS_TO_INT(), regs.sp,
|
|
ppu.scanlines, ppu.cycles, cycles);
|
|
|
|
if (opcodes[op].memread)
|
|
opcodes[op].instr(peek(arg));
|
|
else
|
|
opcodes[op].instr(arg);
|
|
|
|
for (uint8_t i = 0; i < opcodes[op].cycles; i++)
|
|
tick();
|
|
|
|
if (page_crossed) {
|
|
if (opcodes[op].page_cross)
|
|
tick();
|
|
page_crossed = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* https://www.nesdev.org/wiki/CPU_power_up_state */
|
|
void
|
|
cpu_init(void)
|
|
{
|
|
regs.a = regs.x = regs.y = 0;
|
|
regs.pc = 0xFFFC;
|
|
regs.sp = 0xFD;
|
|
|
|
//memset(®s.status, 0, sizeof(regs.status));
|
|
regs.status.interrupt_disable = 1;
|
|
regs.status.unused = 1;
|
|
|
|
cycles += 7;
|
|
for (uint8_t i = 0; i < 7 * 3; i++)
|
|
ppu_tick();
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
FILE *fp;
|
|
uint8_t *buf;
|
|
size_t buflen;
|
|
|
|
if (argc != 2) {
|
|
fprintf(stderr, "Usage: %s rom.nes\n", basename(argv[0]));
|
|
return 1;
|
|
}
|
|
|
|
fp = fopen(argv[1], "r");
|
|
fseek(fp, 0, SEEK_END);
|
|
buflen = ftell(fp);
|
|
buf = calloc(1, buflen);
|
|
fseek(fp, 0, SEEK_SET);
|
|
if (fread(buf, 1, buflen, fp) != buflen && ferror(fp)) {
|
|
fprintf(stderr, "file %s was not read properly\n", argv[1]);
|
|
clearerr(fp);
|
|
fclose(fp);
|
|
return 1;
|
|
}
|
|
fclose(fp);
|
|
|
|
parse_rom(buf, buflen, &rom);
|
|
free(buf);
|
|
ppu.rom = &rom;
|
|
|
|
cpu_init();
|
|
|
|
/* TODO: move to separate file? */
|
|
if (rom.mapper != 0) {
|
|
fprintf(stderr, "Only iNES ROMs using Mapper 0 are supported for now.\n");
|
|
return 1;
|
|
}
|
|
|
|
memwrite16(0xFFFC, 0xC000);
|
|
regs.pc = 0xC000;
|
|
|
|
interpret();
|
|
|
|
printf("\n$02 = %02X\n", memory[0x02]);
|
|
printf("$03 = %02X\n", memory[0x03]);
|
|
|
|
free_rom(&rom);
|
|
|
|
return 0;
|
|
}
|