summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbuild.sh79
-rw-r--r--cpu.c101
-rw-r--r--cpu_opcodes.c2287
-rw-r--r--cpu_opcodes_ud.c1232
-rw-r--r--ines2.c90
-rw-r--r--mapper.c28
-rw-r--r--mapper_nrom.c27
-rw-r--r--memory.c95
-rw-r--r--mknes.c102
-rw-r--r--mknes.h146
-rw-r--r--ppu.c490
11 files changed, 4677 insertions, 0 deletions
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..efe3158
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,79 @@
+#!/bin/bash
+
+# Set the project name here
+PROJECT_NAME="mknes" # Change this for each new project
+
+# Base configuration common to all builds
+CFLAGS="-std=gnu11 "
+CFLAGS+="-mavx2 -mtune=native -mfunction-return=keep -mindirect-branch=keep "
+CFLAGS+="-fwrapv -ffast-math -fno-trapping-math -fwhole-program "
+CFLAGS+="-fno-stack-protector -fno-PIE -no-pie -fno-strict-aliasing -ffunction-sections -fdata-sections "
+CFLAGS+="-Wall -Wextra "
+CFLAGS+="-Wno-unused-parameter -Wno-sign-compare -Wno-trigraphs -Wno-maybe-uninitialized "
+CFLAGS+="-Wno-unused-variable -Wno-unused-const-variable -Wno-unused-function "
+
+LDFLAGS="-Wl,--gc-sections "
+
+# Base include paths (common for all platforms)
+INCLUDE_PATHS="-Ibase -I.."
+
+# Linux-specific includes and libraries
+LINUX_INCLUDE="-I/usr/include/pipewire-0.3 -I/usr/include/spa-0.2"
+LINUX_LIBS="-lpipewire-0.3 -lXi -lX11 -lGL -lm -ldl -pthread"
+
+# Windows-specific includes and libraries
+# WINDOWS_INCLUDE=""
+# WINDOWS_LIBS="-lwinmm -lksuser -lole32 -lmmdevapi -lavrt -lgdi32 -lopengl32 -luuid"
+
+# Determine build type-specific flags
+BUILD_TYPE=$1
+
+if [ -z "$BUILD_TYPE" ]; then
+ BUILD_TYPE="normal"
+fi
+
+case "$BUILD_TYPE" in
+ "normal")
+ CFLAGS+=" -g -O2 -DDEBUG_INTERNAL"
+ ;;
+ "release")
+ CFLAGS+=" -s -O2"
+ ;;
+ "debug")
+ CFLAGS+=" -g -O0"
+ LDFLAGS+=" -fno-pie -no-pie"
+ ;;
+ *)
+ echo "Unknown build type: $BUILD_TYPE"
+ exit 1
+ ;;
+esac
+
+# Rebuild assets every time we compile
+#rm -rf data
+#mkdir -p data/p{1,2,3,4,5,6,7,8}
+#env -C org_assets ../../bin/mks_time ./process.sh
+
+# Make sure the <basecode> shaders are up to date if you are experimenting with them.
+#env -C ../base ../bin/shader2h 140 vertex_shader vertex_shader.glsl
+#env -C ../base ../bin/shader2h 140 fragment_shader shader.h fragment_shader.glsl
+
+# Make sure the compilation stops if any error happens.
+set -e
+
+# Build Linux version
+(
+ # ../bin/ctime -begin .${PROJECT_NAME}_linux
+ gcc $CFLAGS ${PROJECT_NAME}.c -o ${PROJECT_NAME} $INCLUDE_PATHS $LINUX_INCLUDE $LDFLAGS $LINUX_LIBS
+ # ../bin/ctime -end .${PROJECT_NAME}_linux $?
+) &
+
+# Build Windows version
+# (
+# ../bin/ctime -begin .${PROJECT_NAME}_windows
+# x86_64-w64-mingw32-gcc $CFLAGS ${PROJECT_NAME}.c -o ${PROJECT_NAME}.exe $INCLUDE_PATHS $WINDOWS_INCLUDE $LDFLAGS $WINDOWS_LIBS
+# # x86_64-w64-mingw32-gcc $CFLAGS ${PROJECT_NAME}.c -o ${PROJECT_NAME}.exe -mwindows $INCLUDE_PATHS $WINDOWS_INCLUDE $LDFLAGS $WINDOWS_LIBS
+# ../bin/ctime -end .${PROJECT_NAME}_windows $?
+# ) &
+
+wait
diff --git a/cpu.c b/cpu.c
new file mode 100644
index 0000000..3e731e5
--- /dev/null
+++ b/cpu.c
@@ -0,0 +1,101 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+// REMOVE FOR NES!!!!!
+// #define ENABLE_DECIMAL_MODE
+
+__attribute__((always_inline))
+static inline uint8_t pack_flags(struct cpu_state *cpu) {
+ return (cpu->n << 7) | (cpu->v << 6) | (1 << 5) | (cpu->d << 3) | (cpu->i << 2) | (cpu->z << 1) | cpu->c;
+}
+
+__attribute__((always_inline))
+static inline void unpack_flags(struct cpu_state *cpu, uint8_t value) {
+ cpu->n = (value >> 7) & 1;
+ cpu->v = (value >> 6) & 1;
+ cpu->d = (value >> 3) & 1;
+ cpu->i = (value >> 2) & 1;
+ cpu->z = (value >> 1) & 1;
+ cpu->c = value & 1;
+}
+
+
+static inline void update_zn(struct cpu_state *cpu, uint8_t result) {
+ cpu->z = (result == 0);
+ cpu->n = (result & 0x80) != 0;
+}
+
+
+
+static void (*opcode_lut[256])(struct nes_state *state);
+
+uint32_t line = 1;
+
+struct addr_result {
+ uint32_t addr;
+ uint8_t value;
+};
+#include "cpu_opcodes.c"
+#include "cpu_opcodes_ud.c"
+
+static inline void do_nmi(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc); // T1: dummy read (fetch suppressed)
+
+ uint8_t pcl = cpu->pc & 0xff;
+ uint8_t pch = cpu->pc >> 8;
+
+ memory_write(state, 0x0100 + cpu->sp--, pch); // T2
+ memory_write(state, 0x0100 + cpu->sp--, pcl); // T3
+ memory_write(state, 0x0100 + cpu->sp--, pack_flags(cpu) & ~0x10); // T4: push P (B flag clear)
+
+ uint8_t lo = memory_read(state, 0xfffa); // T5
+ uint8_t hi = memory_read(state, 0xfffb); // T6
+
+ cpu->pc = lo | (hi << 8); // T7
+ cpu->i = 1;
+}
+
+static inline void do_irq(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc); // T1: dummy read (fetch suppressed)
+
+ uint8_t pcl = cpu->pc & 0xff;
+ uint8_t pch = cpu->pc >> 8;
+
+ memory_write(state, 0x0100 + cpu->sp--, pch); // T2
+ memory_write(state, 0x0100 + cpu->sp--, pcl); // T3
+ memory_write(state, 0x0100 + cpu->sp--, pack_flags(cpu) & ~0x10); // T4: push P (B flag clear)
+
+ uint8_t lo = memory_read(state, 0xfffe); // T5
+ uint8_t hi = memory_read(state, 0xffff); // T6
+
+ cpu->pc = lo | (hi << 8); // T7
+ cpu->i = 1;
+}
+
+static inline void check_interrupts(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ if(state->nmi_pending) {
+ state->nmi_pending = 0;
+ do_nmi(state);
+ } else if(state->irq_pending && cpu->i == 0) {
+ do_irq(state);
+ }
+}
+
+static void cpu_tick(struct nes_state *state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ check_interrupts(state);
+
+ // printf("%5.5d %4.4x: ", line++, cpu->pc);
+ uint8_t opcode = memory_read(state, cpu->pc++);
+ // printf("%2.2x a:%2.2x x:%2.2x y:%2.2x p:%2.2x sp:%2.2x cycle: %d\n", opcode, cpu->a, cpu->x, cpu->y, pack_flags(cpu), cpu->sp, cycle);
+ opcode_lut[opcode](state);
+
+}
diff --git a/cpu_opcodes.c b/cpu_opcodes.c
new file mode 100644
index 0000000..2d308da
--- /dev/null
+++ b/cpu_opcodes.c
@@ -0,0 +1,2287 @@
+
+
+// ADC
+
+static inline void adc(struct cpu_state * restrict cpu, uint8_t value) {
+#ifdef ENABLE_DECIMAL_MODE
+ if(cpu->d) {
+ uint8_t al = (cpu->a & 0x0f) + (value & 0x0f) + cpu->c;
+ uint8_t ah = (cpu->a >> 4) + (value >> 4);
+ if(al > 9) {
+ al += 6;
+ ah++;
+ }
+ cpu->c = (ah > 9);
+ if(cpu->c) {
+ ah += 6;
+ }
+ cpu->a = (ah << 4) | (al & 0x0f);
+ update_zn(cpu, cpu->a);
+ // Note: overflow flag behavior in decimal mode is undefined on 6502
+ return;
+ }
+#endif
+
+ uint16_t sum = cpu->a + value + cpu->c;
+ cpu->c = (sum > 0xff);
+ uint8_t result = sum & 0xff;
+ cpu->v = (~(cpu->a ^ value) & (cpu->a ^ result)) & 0x80 ? 1 : 0;
+ cpu->a = result;
+ update_zn(cpu, result);
+}
+
+
+// ADC ($nn,X)
+static void opcode_adc_indx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++); // T1
+ memory_read_dummy(state, zp); // T2
+
+ uint8_t ptr = (zp + cpu->x) & 0xff;
+ uint8_t lo = memory_read(state, ptr); // T3
+ uint8_t hi = memory_read(state, (ptr + 1) & 0xff); // T4
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr); // T5
+
+ adc(cpu, value); // T6+
+}
+
+// ADC $nn
+static void opcode_adc_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++); // T1
+ uint8_t value = memory_read(state, addr); // T2
+
+ adc(cpu, value); // T3
+}
+
+// ADC #$nn
+static void opcode_adc_imm(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t value = memory_read(state, cpu->pc++); // T1
+ adc(cpu, value); // T2
+}
+
+// ADC $nnnn
+static void opcode_adc_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr); // T3
+ adc(cpu, value); // T4
+}
+
+// ADC ($nn),Y
+static void opcode_adc_indy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++); // T1
+ uint8_t lo = memory_read(state, zp); // T2
+ uint8_t hi = memory_read(state, (zp + 1) & 0xff); // T3
+
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T4 (dummy if crossed)
+ }
+
+ uint8_t value = memory_read(state, addr); // T4 or T5
+ adc(cpu, value); // T5 or T6
+}
+
+// ADC $nn,X
+static void opcode_adc_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++); // T1
+ memory_read_dummy(state, base); // T2
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr); // T3
+ adc(cpu, value); // T4
+}
+
+// ADC $nnnn,Y
+static void opcode_adc_absy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T3 dummy
+ }
+
+ uint8_t value = memory_read(state, addr); // T3 or T4
+ adc(cpu, value); // T4 or T5
+}
+
+// ADC $nnnn,X
+static void opcode_adc_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T3 dummy
+ }
+
+ uint8_t value = memory_read(state, addr); // T3 or T4
+ adc(cpu, value); // T4 or T5
+}
+
+
+// AND ($nn,X)
+static void opcode_and_indx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++); // T1
+ memory_read_dummy(state, zp); // T2
+
+ uint8_t ptr = (zp + cpu->x) & 0xff;
+ uint8_t lo = memory_read(state, ptr); // T3
+ uint8_t hi = memory_read(state, (ptr + 1) & 0xff); // T4
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr); // T5
+
+ cpu->a &= value; // T6+
+ update_zn(cpu, cpu->a);
+}
+
+// AND $nn
+static void opcode_and_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++); // T1
+ uint8_t value = memory_read(state, addr); // T2
+
+ cpu->a &= value; // T3
+ update_zn(cpu, cpu->a);
+}
+
+// AND #$nn
+static void opcode_and_imm(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t value = memory_read(state, cpu->pc++); // T1
+ cpu->a &= value; // T2
+ update_zn(cpu, cpu->a);
+}
+
+// AND $nnnn
+static void opcode_and_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr); // T3
+ cpu->a &= value; // T4
+ update_zn(cpu, cpu->a);
+}
+
+// AND ($nn),Y
+static void opcode_and_indy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++); // T1
+ uint8_t lo = memory_read(state, zp); // T2
+ uint8_t hi = memory_read(state, (zp + 1) & 0xff); // T3
+
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T4 dummy
+ }
+
+ uint8_t value = memory_read(state, addr); // T4 or T5
+ cpu->a &= value; // T5 or T6
+ update_zn(cpu, cpu->a);
+}
+
+// AND $nn,X
+static void opcode_and_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++); // T1
+ memory_read_dummy(state, base); // T2
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr); // T3
+ cpu->a &= value; // T4
+ update_zn(cpu, cpu->a);
+}
+
+// AND $nnnn,Y
+static void opcode_and_absy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T3 dummy
+ }
+
+ uint8_t value = memory_read(state, addr); // T3 or T4
+ cpu->a &= value; // T4 or T5
+ update_zn(cpu, cpu->a);
+}
+
+// AND $nnnn,X
+static void opcode_and_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T3 dummy
+ }
+
+ uint8_t value = memory_read(state, addr); // T3 or T4
+ cpu->a &= value; // T4 or T5
+ update_zn(cpu, cpu->a);
+}
+
+
+// ASL
+
+// ASL $nn
+static void opcode_asl_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++); // T1
+ uint8_t value = memory_read(state, addr); // T2
+
+ memory_write(state, addr, value); // T3 (dummy write)
+
+ cpu->c = (value >> 7) & 1;
+ value <<= 1;
+
+ memory_write(state, addr, value); // T4
+ update_zn(cpu, value);
+}
+
+// ASL A
+static void opcode_asl_acc(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc); // T1 (dummy read at PC)
+
+ cpu->c = (cpu->a >> 7) & 1;
+ cpu->a <<= 1;
+
+ update_zn(cpu, cpu->a); // T2
+}
+
+// ASL $nnnn
+static void opcode_asl_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr); // T3
+ memory_write(state, addr, value); // T4 (dummy write)
+
+ cpu->c = (value >> 7) & 1;
+ value <<= 1;
+
+ memory_write(state, addr, value); // T5
+ update_zn(cpu, value);
+}
+
+// ASL $nn,X
+static void opcode_asl_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++); // T1
+ memory_read_dummy(state, base); // T2
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr); // T3
+ memory_write(state, addr, value); // T4 (dummy write)
+
+ cpu->c = (value >> 7) & 1;
+ value <<= 1;
+
+ memory_write(state, addr, value); // T5
+ update_zn(cpu, value);
+}
+
+// ASL $nnnn,X
+static void opcode_asl_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T3
+
+ uint8_t value = memory_read(state, addr); // T4
+ memory_write(state, addr, value); // T5 (dummy write)
+
+ cpu->c = (value >> 7) & 1;
+ value <<= 1;
+
+ memory_write(state, addr, value); // T6
+ update_zn(cpu, value);
+}
+
+
+// BIT
+
+// BIT $nn
+static void opcode_bit_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++); // T1
+ uint8_t value = memory_read(state, addr); // T2
+
+ cpu->z = ((cpu->a & value) == 0);
+ cpu->n = (value >> 7) & 1;
+ cpu->v = (value >> 6) & 1; // T3
+}
+
+// BIT $nnnn
+static void opcode_bit_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr); // T3
+
+ cpu->z = ((cpu->a & value) == 0);
+ cpu->n = (value >> 7) & 1;
+ cpu->v = (value >> 6) & 1; // T4
+}
+
+
+// BRK
+static void opcode_brk(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ cpu->pc++; // BRK is 1 byte, skip padding byte
+ memory_read_dummy(state, cpu->pc); // T1 padding read
+
+ uint8_t pcl = cpu->pc & 0xff;
+ uint8_t pch = cpu->pc >> 8;
+
+ memory_write(state, 0x0100 + cpu->sp--, pch); // T2
+ memory_write(state, 0x0100 + cpu->sp--, pcl); // T3
+
+ memory_write(state, 0x0100 + cpu->sp--, pack_flags(cpu) | 0x10); // T4
+
+ uint8_t lo = memory_read(state, 0xfffe); // T5
+ uint8_t hi = memory_read(state, 0xffff); // T6
+
+ cpu->pc = lo | (hi << 8); // T7
+ cpu->i = 1;
+}
+
+
+// BRANCHES
+
+static inline int page_crossed(uint16_t a, uint16_t b) {
+ return (a & 0xff00) != (b & 0xff00);
+}
+
+static void opcode_bpl(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t offset = memory_read(state, cpu->pc++); // T1
+ uint16_t new_pc = cpu->pc + (int8_t)offset;
+
+ if(!cpu->n) {
+ memory_read_dummy(state, cpu->pc); // T2
+ if(page_crossed(cpu->pc, new_pc)) {
+ memory_read_dummy(state, (cpu->pc & 0xff00) | (new_pc & 0x00ff)); // T3
+ }
+ cpu->pc = new_pc; // T3 or T4
+ }
+}
+
+static void opcode_bmi(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t offset = memory_read(state, cpu->pc++);
+ uint16_t new_pc = cpu->pc + (int8_t)offset;
+
+ if(cpu->n) {
+ memory_read_dummy(state, cpu->pc);
+ if(page_crossed(cpu->pc, new_pc)) {
+ memory_read_dummy(state, (cpu->pc & 0xff00) | (new_pc & 0x00ff));
+ }
+ cpu->pc = new_pc;
+ }
+}
+
+static void opcode_bvc(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t offset = memory_read(state, cpu->pc++);
+ uint16_t new_pc = cpu->pc + (int8_t)offset;
+
+ if(!cpu->v) {
+ memory_read_dummy(state, cpu->pc);
+ if(page_crossed(cpu->pc, new_pc)) {
+ memory_read_dummy(state, (cpu->pc & 0xff00) | (new_pc & 0x00ff));
+ }
+ cpu->pc = new_pc;
+ }
+}
+
+static void opcode_bvs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t offset = memory_read(state, cpu->pc++);
+ uint16_t new_pc = cpu->pc + (int8_t)offset;
+
+ if(cpu->v) {
+ memory_read_dummy(state, cpu->pc);
+ if(page_crossed(cpu->pc, new_pc)) {
+ memory_read_dummy(state, (cpu->pc & 0xff00) | (new_pc & 0x00ff));
+ }
+ cpu->pc = new_pc;
+ }
+}
+
+static void opcode_bcc(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t offset = memory_read(state, cpu->pc++);
+ uint16_t new_pc = cpu->pc + (int8_t)offset;
+
+ if(!cpu->c) {
+ memory_read_dummy(state, cpu->pc);
+ if(page_crossed(cpu->pc, new_pc)) {
+ memory_read_dummy(state, (cpu->pc & 0xff00) | (new_pc & 0x00ff));
+ }
+ cpu->pc = new_pc;
+ }
+}
+
+static void opcode_bcs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t offset = memory_read(state, cpu->pc++);
+ uint16_t new_pc = cpu->pc + (int8_t)offset;
+
+ if(cpu->c) {
+ memory_read_dummy(state, cpu->pc);
+ if(page_crossed(cpu->pc, new_pc)) {
+ memory_read_dummy(state, (cpu->pc & 0xff00) | (new_pc & 0x00ff));
+ }
+ cpu->pc = new_pc;
+ }
+}
+
+static void opcode_bne(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t offset = memory_read(state, cpu->pc++);
+ uint16_t new_pc = cpu->pc + (int8_t)offset;
+
+ if(!cpu->z) {
+ memory_read_dummy(state, cpu->pc);
+ if(page_crossed(cpu->pc, new_pc)) {
+ memory_read_dummy(state, (cpu->pc & 0xff00) | (new_pc & 0x00ff));
+ }
+ cpu->pc = new_pc;
+ }
+}
+
+static void opcode_beq(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t offset = memory_read(state, cpu->pc++);
+ uint16_t new_pc = cpu->pc + (int8_t)offset;
+
+ if(cpu->z) {
+ memory_read_dummy(state, cpu->pc);
+ if(page_crossed(cpu->pc, new_pc)) {
+ memory_read_dummy(state, (cpu->pc & 0xff00) | (new_pc & 0x00ff));
+ }
+ cpu->pc = new_pc;
+ }
+}
+
+
+// SET/CLEAR flags
+
+static void opcode_clc(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+ memory_read_dummy(state, cpu->pc);
+ cpu->c = 0;
+}
+
+static void opcode_cld(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+ memory_read_dummy(state, cpu->pc);
+ cpu->d = 0;
+}
+
+static void opcode_cli(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+ memory_read_dummy(state, cpu->pc);
+ cpu->i = 0;
+}
+
+static void opcode_clv(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+ memory_read_dummy(state, cpu->pc);
+ cpu->v = 0;
+}
+
+static void opcode_sec(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+ memory_read_dummy(state, cpu->pc);
+ cpu->c = 1;
+}
+
+static void opcode_sed(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+ memory_read_dummy(state, cpu->pc);
+ cpu->d = 1;
+}
+
+static void opcode_sei(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+ memory_read_dummy(state, cpu->pc);
+ cpu->i = 1;
+}
+
+
+// CMP
+
+static inline void cmp(struct cpu_state * restrict cpu, uint8_t value) {
+ uint8_t result = cpu->a - value;
+ cpu->c = (cpu->a >= value);
+ update_zn(cpu, result);
+}
+
+static void opcode_cmp_indx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++); // T1
+ memory_read_dummy(state, zp); // T2
+
+ uint8_t ptr = (zp + cpu->x) & 0xff;
+ uint8_t lo = memory_read(state, ptr); // T3
+ uint8_t hi = memory_read(state, (ptr + 1) & 0xff); // T4
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr); // T5
+
+ cmp(cpu, value); // T6
+}
+
+static void opcode_cmp_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++); // T1
+ uint8_t value = memory_read(state, addr); // T2
+
+ cmp(cpu, value); // T3
+}
+
+static void opcode_cmp_imm(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t value = memory_read(state, cpu->pc++); // T1
+ cmp(cpu, value); // T2
+}
+
+static void opcode_cmp_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr); // T3
+ cmp(cpu, value); // T4
+}
+
+static void opcode_cmp_indy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++); // T1
+ uint8_t lo = memory_read(state, zp); // T2
+ uint8_t hi = memory_read(state, (zp + 1) & 0xff); // T3
+
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T4 dummy
+ }
+
+ uint8_t value = memory_read(state, addr); // T4 or T5
+ cmp(cpu, value); // T5 or T6
+}
+
+static void opcode_cmp_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++); // T1
+ memory_read_dummy(state, base); // T2
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr); // T3
+ cmp(cpu, value); // T4
+}
+
+static void opcode_cmp_absy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T3 dummy
+ }
+
+ uint8_t value = memory_read(state, addr); // T3 or T4
+ cmp(cpu, value); // T4 or T5
+}
+
+static void opcode_cmp_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T3 dummy
+ }
+
+ uint8_t value = memory_read(state, addr); // T3 or T4
+ cmp(cpu, value); // T4 or T5
+}
+
+
+// CPX
+
+static inline void cpx(struct cpu_state * restrict cpu, uint8_t value) {
+ uint8_t result = cpu->x - value;
+ cpu->c = (cpu->x >= value);
+ update_zn(cpu, result);
+}
+
+static void opcode_cpx_imm(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t value = memory_read(state, cpu->pc++); // T1
+ cpx(cpu, value); // T2
+}
+
+static void opcode_cpx_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++); // T1
+ uint8_t value = memory_read(state, addr); // T2
+
+ cpx(cpu, value); // T3
+}
+
+static void opcode_cpx_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr); // T3
+ cpx(cpu, value); // T4
+}
+
+
+// CPY
+
+static inline void cpy(struct cpu_state * restrict cpu, uint8_t value) {
+ uint8_t result = cpu->y - value;
+ cpu->c = (cpu->y >= value);
+ update_zn(cpu, result);
+}
+
+static void opcode_cpy_imm(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t value = memory_read(state, cpu->pc++); // T1
+ cpy(cpu, value); // T2
+}
+
+static void opcode_cpy_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++); // T1
+ uint8_t value = memory_read(state, addr); // T2
+
+ cpy(cpu, value); // T3
+}
+
+static void opcode_cpy_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr); // T3
+ cpy(cpu, value); // T4
+}
+
+
+// DEC
+
+static void opcode_dec_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++); // T1
+ uint8_t value = memory_read(state, addr); // T2
+
+ memory_write(state, addr, value); // T3 (dummy write)
+ value--;
+ memory_write(state, addr, value); // T4
+
+ update_zn(cpu, value); // T5
+}
+
+static void opcode_dec_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++); // T1
+ memory_read_dummy(state, base); // T2
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr); // T3
+ memory_write(state, addr, value); // T4 (dummy write)
+
+ value--;
+ memory_write(state, addr, value); // T5
+ update_zn(cpu, value); // T6
+}
+
+static void opcode_dec_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr); // T3
+ memory_write(state, addr, value); // T4 (dummy write)
+
+ value--;
+ memory_write(state, addr, value); // T5
+ update_zn(cpu, value); // T6
+}
+
+static void opcode_dec_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T3
+
+ uint8_t value = memory_read(state, addr); // T4
+ memory_write(state, addr, value); // T5 (dummy write)
+
+ value--;
+ memory_write(state, addr, value); // T6
+ update_zn(cpu, value); // T7
+}
+
+
+// EOR
+
+static void opcode_eor_indx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++); // T1
+ memory_read_dummy(state, zp); // T2
+
+ uint8_t ptr = (zp + cpu->x) & 0xff;
+ uint8_t lo = memory_read(state, ptr); // T3
+ uint8_t hi = memory_read(state, (ptr + 1) & 0xff); // T4
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr); // T5
+
+ cpu->a ^= value; // T6
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_eor_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++); // T1
+ uint8_t value = memory_read(state, addr); // T2
+
+ cpu->a ^= value; // T3
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_eor_imm(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t value = memory_read(state, cpu->pc++); // T1
+ cpu->a ^= value; // T2
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_eor_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr); // T3
+ cpu->a ^= value; // T4
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_eor_indy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++); // T1
+ uint8_t lo = memory_read(state, zp); // T2
+ uint8_t hi = memory_read(state, (zp + 1) & 0xff); // T3
+
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T4 dummy
+ }
+
+ uint8_t value = memory_read(state, addr); // T4 or T5
+ cpu->a ^= value; // T5 or T6
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_eor_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++); // T1
+ memory_read_dummy(state, base); // T2
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr); // T3
+ cpu->a ^= value; // T4
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_eor_absy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T3 dummy
+ }
+
+ uint8_t value = memory_read(state, addr); // T3 or T4
+ cpu->a ^= value; // T4 or T5
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_eor_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T3 dummy
+ }
+
+ uint8_t value = memory_read(state, addr); // T3 or T4
+ cpu->a ^= value; // T4 or T5
+ update_zn(cpu, cpu->a);
+}
+
+
+// INC
+
+static void opcode_inc_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++); // T1
+ uint8_t value = memory_read(state, addr); // T2
+
+ memory_write(state, addr, value); // T3 (dummy write)
+ value++;
+ memory_write(state, addr, value); // T4
+
+ update_zn(cpu, value); // T5
+}
+
+static void opcode_inc_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++); // T1
+ memory_read_dummy(state, base); // T2
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr); // T3
+ memory_write(state, addr, value); // T4 (dummy write)
+
+ value++;
+ memory_write(state, addr, value); // T5
+ update_zn(cpu, value); // T6
+}
+
+static void opcode_inc_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr); // T3
+ memory_write(state, addr, value); // T4 (dummy write)
+
+ value++;
+ memory_write(state, addr, value); // T5
+ update_zn(cpu, value); // T6
+}
+
+static void opcode_inc_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T3
+
+ uint8_t value = memory_read(state, addr); // T4
+ memory_write(state, addr, value); // T5 (dummy write)
+
+ value++;
+ memory_write(state, addr, value); // T6
+ update_zn(cpu, value); // T7
+}
+
+
+// JMP/JSR
+
+static void opcode_jmp_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ cpu->pc = lo | (hi << 8); // T3
+}
+
+static void opcode_jmp_ind(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t ptr_lo = memory_read(state, cpu->pc++); // T1
+ uint8_t ptr_hi = memory_read(state, cpu->pc++); // T2
+ uint16_t ptr = ptr_lo | (ptr_hi << 8);
+
+ uint8_t lo = memory_read(state, ptr); // T3
+ uint8_t hi;
+ if((ptr & 0x00ff) == 0x00ff) {
+ hi = memory_read(state, ptr & 0xff00); // Emulate 6502 bug
+ } else {
+ hi = memory_read(state, ptr + 1);
+ } // T4
+
+ cpu->pc = lo | (hi << 8); // T5
+}
+
+static void opcode_jsr(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+
+ uint16_t return_addr = cpu->pc - 1;
+
+ memory_read_dummy(state, 0x0100 | cpu->sp); // T4
+ memory_write(state, 0x0100 | cpu->sp--, (return_addr >> 8)); // T5
+ memory_write(state, 0x0100 | cpu->sp--, (return_addr & 0xff)); // T6
+
+ cpu->pc = lo | (hi << 8);
+}
+
+
+
+// LDA
+
+static void opcode_lda_indx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++); // T1
+ memory_read_dummy(state, zp); // T2
+
+ uint8_t ptr = (zp + cpu->x) & 0xff;
+ uint8_t lo = memory_read(state, ptr); // T3
+ uint8_t hi = memory_read(state, (ptr + 1) & 0xff); // T4
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr); // T5
+ cpu->a = value; // T6
+ update_zn(cpu, value);
+}
+
+static void opcode_lda_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++); // T1
+ uint8_t value = memory_read(state, addr); // T2
+
+ cpu->a = value; // T3
+ update_zn(cpu, value);
+}
+
+static void opcode_lda_imm(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t value = memory_read(state, cpu->pc++); // T1
+ cpu->a = value; // T2
+ update_zn(cpu, value);
+}
+
+static void opcode_lda_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr); // T3
+ cpu->a = value; // T4
+ update_zn(cpu, value);
+}
+
+static void opcode_lda_indy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++); // T1
+ uint8_t lo = memory_read(state, zp); // T2
+ uint8_t hi = memory_read(state, (zp + 1) & 0xff); // T3
+
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T4 dummy
+ }
+
+ uint8_t value = memory_read(state, addr); // T4 or T5
+ cpu->a = value; // T5 or T6
+ update_zn(cpu, value);
+}
+
+static void opcode_lda_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++); // T1
+ memory_read_dummy(state, base); // T2
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr); // T3
+ cpu->a = value; // T4
+ update_zn(cpu, value);
+}
+
+static void opcode_lda_absy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T3
+ }
+
+ uint8_t value = memory_read(state, addr); // T3 or T4
+ cpu->a = value; // T4 or T5
+ update_zn(cpu, value);
+}
+
+static void opcode_lda_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T3
+ }
+
+ uint8_t value = memory_read(state, addr); // T3 or T4
+ cpu->a = value; // T4 or T5
+ update_zn(cpu, value);
+}
+
+
+// LDX
+
+static void opcode_ldx_imm(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t value = memory_read(state, cpu->pc++);
+ cpu->x = value;
+ update_zn(cpu, value);
+}
+
+static void opcode_ldx_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++);
+ uint8_t value = memory_read(state, addr);
+
+ cpu->x = value;
+ update_zn(cpu, value);
+}
+
+static void opcode_ldx_zpy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, base);
+ uint8_t addr = (base + cpu->y) & 0xff;
+
+ uint8_t value = memory_read(state, addr);
+ cpu->x = value;
+ update_zn(cpu, value);
+}
+
+static void opcode_ldx_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ cpu->x = value;
+ update_zn(cpu, value);
+}
+
+static void opcode_ldx_absy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+ }
+
+ uint8_t value = memory_read(state, addr);
+ cpu->x = value;
+ update_zn(cpu, value);
+}
+
+
+// LDY
+
+static void opcode_ldy_imm(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t value = memory_read(state, cpu->pc++);
+ cpu->y = value;
+ update_zn(cpu, value);
+}
+
+static void opcode_ldy_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++);
+ uint8_t value = memory_read(state, addr);
+
+ cpu->y = value;
+ update_zn(cpu, value);
+}
+
+static void opcode_ldy_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, base);
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr);
+ cpu->y = value;
+ update_zn(cpu, value);
+}
+
+static void opcode_ldy_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ cpu->y = value;
+ update_zn(cpu, value);
+}
+
+static void opcode_ldy_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+ }
+
+ uint8_t value = memory_read(state, addr);
+ cpu->y = value;
+ update_zn(cpu, value);
+}
+
+
+// LSR
+
+
+static void opcode_lsr_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++); // T1
+ uint8_t value = memory_read(state, addr); // T2
+
+ memory_write(state, addr, value); // T3 (dummy write)
+ cpu->c = value & 1;
+ value >>= 1;
+ memory_write(state, addr, value); // T4
+
+ update_zn(cpu, value); // T5
+}
+
+static void opcode_lsr_acc(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc); // T1
+ cpu->c = cpu->a & 1;
+ cpu->a >>= 1;
+ update_zn(cpu, cpu->a); // T2
+}
+
+static void opcode_lsr_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr); // T3
+ memory_write(state, addr, value); // T4 (dummy write)
+
+ cpu->c = value & 1;
+ value >>= 1;
+
+ memory_write(state, addr, value); // T5
+ update_zn(cpu, value); // T6
+}
+
+static void opcode_lsr_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++); // T1
+ memory_read_dummy(state, base); // T2
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr); // T3
+ memory_write(state, addr, value); // T4 (dummy write)
+
+ cpu->c = value & 1;
+ value >>= 1;
+
+ memory_write(state, addr, value); // T5
+ update_zn(cpu, value); // T6
+}
+
+static void opcode_lsr_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T3
+
+ uint8_t value = memory_read(state, addr); // T4
+ memory_write(state, addr, value); // T5 (dummy write)
+
+ cpu->c = value & 1;
+ value >>= 1;
+
+ memory_write(state, addr, value); // T6
+ update_zn(cpu, value); // T7
+}
+
+
+// NOP
+
+static void opcode_nop(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+ memory_read_dummy(state, cpu->pc); // T1: dummy read
+ // T2: nothing changes
+}
+
+
+// ORA
+
+static void opcode_ora_indx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++); // T1
+ memory_read_dummy(state, zp); // T2
+
+ uint8_t ptr = (zp + cpu->x) & 0xff;
+ uint8_t lo = memory_read(state, ptr); // T3
+ uint8_t hi = memory_read(state, (ptr + 1) & 0xff); // T4
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr); // T5
+ cpu->a |= value; // T6
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_ora_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++);
+ uint8_t value = memory_read(state, addr);
+
+ cpu->a |= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_ora_imm(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t value = memory_read(state, cpu->pc++);
+ cpu->a |= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_ora_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ cpu->a |= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_ora_indy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++);
+ uint8_t lo = memory_read(state, zp);
+ uint8_t hi = memory_read(state, (zp + 1) & 0xff);
+
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+ }
+
+ uint8_t value = memory_read(state, addr);
+ cpu->a |= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_ora_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, base);
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr);
+ cpu->a |= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_ora_absy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+ }
+
+ uint8_t value = memory_read(state, addr);
+ cpu->a |= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_ora_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+ }
+
+ uint8_t value = memory_read(state, addr);
+ cpu->a |= value;
+ update_zn(cpu, cpu->a);
+}
+
+// PHA
+
+static void opcode_pha(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+ memory_read_dummy(state, cpu->pc); // T1
+ memory_write(state, 0x0100 + cpu->sp--, cpu->a); // T2
+}
+
+
+// PHP
+
+static void opcode_php(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+ memory_read_dummy(state, cpu->pc); // T1
+ memory_write(state, 0x0100 + cpu->sp--, pack_flags(cpu) | 0x10);
+}
+
+
+// PLA
+
+static void opcode_pla(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc); // T1
+ memory_read_dummy(state, 0x0100 + ((cpu->sp + 1) & 0xff)); // T2
+ uint8_t value = memory_read(state, 0x0100 + ((cpu->sp + 1) & 0xff)); // T3
+ cpu->sp++;
+ cpu->a = value;
+ update_zn(cpu, value); // T4
+}
+
+
+// PLP
+
+static void opcode_plp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc); // T1
+ memory_read_dummy(state, 0x0100 + ((cpu->sp + 1) & 0xff)); // T2
+ uint8_t value = memory_read(state, 0x0100 + ((cpu->sp + 1) & 0xff)); // T3
+ cpu->sp++;
+ unpack_flags(cpu, value); // T4
+}
+
+
+// RTI
+
+static void opcode_rti(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc); // T1
+ memory_read_dummy(state, 0x0100 + ((cpu->sp + 1) & 0xff)); // T2
+ uint8_t flags = memory_read(state, 0x0100 + ((cpu->sp + 1) & 0xff)); // T3
+ cpu->sp++;
+ unpack_flags(cpu, flags); // T4
+
+ uint8_t lo = memory_read(state, 0x0100 + ((cpu->sp + 1) & 0xff)); // T5
+ cpu->sp++;
+ uint8_t hi = memory_read(state, 0x0100 + ((cpu->sp + 1) & 0xff)); // T6
+ cpu->sp++;
+
+ cpu->pc = lo | (hi << 8);
+}
+
+
+// RTS
+
+static void opcode_rts(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc); // T1
+ memory_read_dummy(state, 0x0100 | cpu->sp); // T2 (discard)
+ uint8_t lo = memory_read(state, 0x0100 | ++cpu->sp); // T3
+ uint8_t hi = memory_read(state, 0x0100 | ++cpu->sp); // T4
+ uint16_t ret = (hi << 8) | lo;
+ memory_read_dummy(state, ret); // T5
+ cpu->pc = ret + 1; // Final PC set
+}
+
+
+// ROL
+
+static void opcode_rol_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++);
+ uint8_t value = memory_read(state, addr);
+
+ memory_write(state, addr, value); // Dummy write
+
+ uint8_t in = cpu->c;
+ cpu->c = (value >> 7) & 1;
+ value = (value << 1) | in;
+
+ memory_write(state, addr, value);
+ update_zn(cpu, value);
+}
+
+static void opcode_rol_acc(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc);
+
+ uint8_t in = cpu->c;
+ cpu->c = (cpu->a >> 7) & 1;
+ cpu->a = (cpu->a << 1) | in;
+
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_rol_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // Dummy write
+
+ uint8_t in = cpu->c;
+ cpu->c = (value >> 7) & 1;
+ value = (value << 1) | in;
+
+ memory_write(state, addr, value);
+ update_zn(cpu, value);
+}
+
+static void opcode_rol_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, base);
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // Dummy write
+
+ uint8_t in = cpu->c;
+ cpu->c = (value >> 7) & 1;
+ value = (value << 1) | in;
+
+ memory_write(state, addr, value);
+ update_zn(cpu, value);
+}
+
+static void opcode_rol_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // Dummy write
+
+ uint8_t in = cpu->c;
+ cpu->c = (value >> 7) & 1;
+ value = (value << 1) | in;
+
+ memory_write(state, addr, value);
+ update_zn(cpu, value);
+}
+
+
+// ROR
+
+static void opcode_ror_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++);
+ uint8_t value = memory_read(state, addr);
+
+ memory_write(state, addr, value); // Dummy write
+
+ uint8_t in = cpu->c;
+ cpu->c = value & 1;
+ value = (value >> 1) | (in << 7);
+
+ memory_write(state, addr, value);
+ update_zn(cpu, value);
+}
+
+static void opcode_ror_acc(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc);
+
+ uint8_t in = cpu->c;
+ cpu->c = cpu->a & 1;
+ cpu->a = (cpu->a >> 1) | (in << 7);
+
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_ror_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // Dummy write
+
+ uint8_t in = cpu->c;
+ cpu->c = value & 1;
+ value = (value >> 1) | (in << 7);
+
+ memory_write(state, addr, value);
+ update_zn(cpu, value);
+}
+
+static void opcode_ror_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, base);
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // Dummy write
+
+ uint8_t in = cpu->c;
+ cpu->c = value & 1;
+ value = (value >> 1) | (in << 7);
+
+ memory_write(state, addr, value);
+ update_zn(cpu, value);
+}
+
+static void opcode_ror_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // Dummy write
+
+ uint8_t in = cpu->c;
+ cpu->c = value & 1;
+ value = (value >> 1) | (in << 7);
+
+ memory_write(state, addr, value);
+ update_zn(cpu, value);
+}
+
+
+// SBC
+
+static inline void sbc(struct cpu_state * restrict cpu, uint8_t value) {
+#ifdef ENABLE_DECIMAL_MODE
+ if(cpu->d) {
+ uint8_t al = (cpu->a & 0x0f) - (value & 0x0f) - (cpu->c ? 0 : 1);
+ uint8_t ah = (cpu->a >> 4) - (value >> 4);
+ if((int8_t)al < 0) {
+ al -= 6;
+ ah--;
+ }
+ cpu->c = ((int8_t)ah >= 0);
+ if(!cpu->c) {
+ ah -= 6;
+ }
+ cpu->a = (ah << 4) | (al & 0x0f);
+ update_zn(cpu, cpu->a);
+ // Note: overflow flag behavior in decimal mode is undefined on 6502
+ return;
+ }
+#endif
+
+ uint16_t tmp = cpu->a - value - (cpu->c ? 0 : 1);
+ cpu->v = ((cpu->a ^ value) & (cpu->a ^ tmp)) & 0x80 ? 1 : 0;
+ cpu->c = (tmp < 0x100);
+ cpu->a = tmp & 0xff;
+ update_zn(cpu, cpu->a);
+}
+
+
+static void opcode_sbc_indx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, zp);
+
+ uint8_t ptr = (zp + cpu->x) & 0xff;
+ uint8_t lo = memory_read(state, ptr);
+ uint8_t hi = memory_read(state, (ptr + 1) & 0xff);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ sbc(cpu, value);
+}
+
+static void opcode_sbc_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++);
+ uint8_t value = memory_read(state, addr);
+
+ sbc(cpu, value);
+}
+
+static void opcode_sbc_imm(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t value = memory_read(state, cpu->pc++);
+ sbc(cpu, value);
+}
+
+static void opcode_sbc_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ sbc(cpu, value);
+}
+
+static void opcode_sbc_indy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++);
+ uint8_t lo = memory_read(state, zp);
+ uint8_t hi = memory_read(state, (zp + 1) & 0xff);
+
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+ }
+
+ uint8_t value = memory_read(state, addr);
+ sbc(cpu, value);
+}
+
+static void opcode_sbc_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, base);
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr);
+ sbc(cpu, value);
+}
+
+static void opcode_sbc_absy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+ }
+
+ uint8_t value = memory_read(state, addr);
+ sbc(cpu, value);
+}
+
+static void opcode_sbc_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+ }
+
+ uint8_t value = memory_read(state, addr);
+ sbc(cpu, value);
+}
+
+
+// TAX
+
+static void opcode_tax(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc);
+ cpu->x = cpu->a;
+ update_zn(cpu, cpu->x);
+}
+
+static void opcode_tay(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc);
+ cpu->y = cpu->a;
+ update_zn(cpu, cpu->y);
+}
+
+static void opcode_txa(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc);
+ cpu->a = cpu->x;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_tya(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc);
+ cpu->a = cpu->y;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_tsx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc);
+ cpu->x = cpu->sp;
+ update_zn(cpu, cpu->x);
+}
+
+static void opcode_txs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc);
+ cpu->sp = cpu->x;
+}
+
+
+// INX
+
+static void opcode_inx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc);
+ cpu->x++;
+ update_zn(cpu, cpu->x);
+}
+
+
+// INY
+
+static void opcode_iny(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc);
+ cpu->y++;
+ update_zn(cpu, cpu->y);
+}
+
+
+// DEX
+
+static void opcode_dex(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc);
+ cpu->x--;
+ update_zn(cpu, cpu->x);
+}
+
+
+// DEY
+
+static void opcode_dey(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc);
+ cpu->y--;
+ update_zn(cpu, cpu->y);
+}
+
+
+// STA
+
+static void opcode_sta_indx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, zp);
+
+ uint8_t ptr = (zp + cpu->x) & 0xff;
+ uint8_t lo = memory_read(state, ptr);
+ uint8_t hi = memory_read(state, (ptr + 1) & 0xff);
+ uint16_t addr = lo | (hi << 8);
+
+ memory_write(state, addr, cpu->a);
+}
+
+static void opcode_sta_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++);
+ memory_write(state, addr, cpu->a);
+}
+
+static void opcode_sta_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t addr = lo | (hi << 8);
+
+ memory_write(state, addr, cpu->a);
+}
+
+static void opcode_sta_indy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++); // T1
+ uint8_t lo = memory_read(state, zp); // T2
+ uint8_t hi = memory_read(state, (zp + 1) & 0xff); // T3
+
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+ memory_write(state, addr, cpu->a); // T5
+}
+
+static void opcode_sta_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, base);
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ memory_write(state, addr, cpu->a);
+}
+
+static void opcode_sta_absy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+ memory_write(state, addr, cpu->a);
+}
+
+static void opcode_sta_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T3 (always)
+ memory_write(state, addr, cpu->a); // T4
+}
+
+
+// STX
+
+static void opcode_stx_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++);
+ memory_write(state, addr, cpu->x);
+}
+
+static void opcode_stx_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t addr = lo | (hi << 8);
+
+ memory_write(state, addr, cpu->x);
+}
+
+static void opcode_stx_zpy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, base);
+ uint8_t addr = (base + cpu->y) & 0xff;
+
+ memory_write(state, addr, cpu->x);
+}
+
+
+// STY
+
+static void opcode_sty_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++);
+ memory_write(state, addr, cpu->y);
+}
+
+static void opcode_sty_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t addr = lo | (hi << 8);
+
+ memory_write(state, addr, cpu->y);
+}
+
+static void opcode_sty_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, base);
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ memory_write(state, addr, cpu->y);
+}
+
+
+__attribute__((noinline))
+static void init_opcode_lut(void) {
+ opcode_lut[0x00] = opcode_brk;
+ opcode_lut[0x01] = opcode_ora_indx;
+ opcode_lut[0x05] = opcode_ora_zp;
+ opcode_lut[0x06] = opcode_asl_zp;
+ opcode_lut[0x08] = opcode_php;
+ opcode_lut[0x09] = opcode_ora_imm;
+ opcode_lut[0x0a] = opcode_asl_acc;
+ opcode_lut[0x0d] = opcode_ora_abs;
+ opcode_lut[0x0e] = opcode_asl_abs;
+ opcode_lut[0x10] = opcode_bpl;
+ opcode_lut[0x11] = opcode_ora_indy;
+ opcode_lut[0x15] = opcode_ora_zpx;
+ opcode_lut[0x16] = opcode_asl_zpx;
+ opcode_lut[0x18] = opcode_clc;
+ opcode_lut[0x19] = opcode_ora_absy;
+ opcode_lut[0x1d] = opcode_ora_absx;
+ opcode_lut[0x1e] = opcode_asl_absx;
+ opcode_lut[0x20] = opcode_jsr;
+ opcode_lut[0x21] = opcode_and_indx;
+ opcode_lut[0x24] = opcode_bit_zp;
+ opcode_lut[0x25] = opcode_and_zp;
+ opcode_lut[0x26] = opcode_rol_zp;
+ opcode_lut[0x28] = opcode_plp;
+ opcode_lut[0x29] = opcode_and_imm;
+ opcode_lut[0x2a] = opcode_rol_acc;
+ opcode_lut[0x2c] = opcode_bit_abs;
+ opcode_lut[0x2d] = opcode_and_abs;
+ opcode_lut[0x2e] = opcode_rol_abs;
+ opcode_lut[0x30] = opcode_bmi;
+ opcode_lut[0x31] = opcode_and_indy;
+ opcode_lut[0x35] = opcode_and_zpx;
+ opcode_lut[0x36] = opcode_rol_zpx;
+ opcode_lut[0x38] = opcode_sec;
+ opcode_lut[0x39] = opcode_and_absy;
+ opcode_lut[0x3d] = opcode_and_absx;
+ opcode_lut[0x3e] = opcode_rol_absx;
+ opcode_lut[0x40] = opcode_rti;
+ opcode_lut[0x41] = opcode_eor_indx;
+ opcode_lut[0x45] = opcode_eor_zp;
+ opcode_lut[0x46] = opcode_lsr_zp;
+ opcode_lut[0x48] = opcode_pha;
+ opcode_lut[0x49] = opcode_eor_imm;
+ opcode_lut[0x4a] = opcode_lsr_acc;
+ opcode_lut[0x4c] = opcode_jmp_abs;
+ opcode_lut[0x4d] = opcode_eor_abs;
+ opcode_lut[0x4e] = opcode_lsr_abs;
+ opcode_lut[0x50] = opcode_bvc;
+ opcode_lut[0x51] = opcode_eor_indy;
+ opcode_lut[0x55] = opcode_eor_zpx;
+ opcode_lut[0x56] = opcode_lsr_zpx;
+ opcode_lut[0x58] = opcode_cli;
+ opcode_lut[0x59] = opcode_eor_absy;
+ opcode_lut[0x5d] = opcode_eor_absx;
+ opcode_lut[0x5e] = opcode_lsr_absx;
+ opcode_lut[0x60] = opcode_rts;
+ opcode_lut[0x61] = opcode_adc_indx;
+ opcode_lut[0x65] = opcode_adc_zp;
+ opcode_lut[0x66] = opcode_ror_zp;
+ opcode_lut[0x68] = opcode_pla;
+ opcode_lut[0x69] = opcode_adc_imm;
+ opcode_lut[0x6a] = opcode_ror_acc;
+ opcode_lut[0x6c] = opcode_jmp_ind;
+ opcode_lut[0x6d] = opcode_adc_abs;
+ opcode_lut[0x6e] = opcode_ror_abs;
+ opcode_lut[0x70] = opcode_bvs;
+ opcode_lut[0x71] = opcode_adc_indy;
+ opcode_lut[0x75] = opcode_adc_zpx;
+ opcode_lut[0x76] = opcode_ror_zpx;
+ opcode_lut[0x78] = opcode_sei;
+ opcode_lut[0x79] = opcode_adc_absy;
+ opcode_lut[0x7d] = opcode_adc_absx;
+ opcode_lut[0x7e] = opcode_ror_absx;
+ opcode_lut[0x81] = opcode_sta_indx;
+ opcode_lut[0x84] = opcode_sty_zp;
+ opcode_lut[0x85] = opcode_sta_zp;
+ opcode_lut[0x86] = opcode_stx_zp;
+ opcode_lut[0x88] = opcode_dey;
+ opcode_lut[0x8a] = opcode_txa;
+ opcode_lut[0x8c] = opcode_sty_abs;
+ opcode_lut[0x8d] = opcode_sta_abs;
+ opcode_lut[0x8e] = opcode_stx_abs;
+ opcode_lut[0x90] = opcode_bcc;
+ opcode_lut[0x91] = opcode_sta_indy;
+ opcode_lut[0x94] = opcode_sty_zpx;
+ opcode_lut[0x95] = opcode_sta_zpx;
+ opcode_lut[0x96] = opcode_stx_zpy;
+ opcode_lut[0x98] = opcode_tya;
+ opcode_lut[0x99] = opcode_sta_absy;
+ opcode_lut[0x9a] = opcode_txs;
+ opcode_lut[0x9d] = opcode_sta_absx;
+ opcode_lut[0xa0] = opcode_ldy_imm;
+ opcode_lut[0xa1] = opcode_lda_indx;
+ opcode_lut[0xa2] = opcode_ldx_imm;
+ opcode_lut[0xa4] = opcode_ldy_zp;
+ opcode_lut[0xa5] = opcode_lda_zp;
+ opcode_lut[0xa6] = opcode_ldx_zp;
+ opcode_lut[0xa8] = opcode_tay;
+ opcode_lut[0xa9] = opcode_lda_imm;
+ opcode_lut[0xaa] = opcode_tax;
+ opcode_lut[0xac] = opcode_ldy_abs;
+ opcode_lut[0xad] = opcode_lda_abs;
+ opcode_lut[0xae] = opcode_ldx_abs;
+ opcode_lut[0xb0] = opcode_bcs;
+ opcode_lut[0xb1] = opcode_lda_indy;
+ opcode_lut[0xb4] = opcode_ldy_zpx;
+ opcode_lut[0xb5] = opcode_lda_zpx;
+ opcode_lut[0xb6] = opcode_ldx_zpy;
+ opcode_lut[0xb8] = opcode_clv;
+ opcode_lut[0xb9] = opcode_lda_absy;
+ opcode_lut[0xba] = opcode_tsx;
+ opcode_lut[0xbc] = opcode_ldy_absx;
+ opcode_lut[0xbd] = opcode_lda_absx;
+ opcode_lut[0xbe] = opcode_ldx_absy;
+ opcode_lut[0xc0] = opcode_cpy_imm;
+ opcode_lut[0xc1] = opcode_cmp_indx;
+ opcode_lut[0xc4] = opcode_cpy_zp;
+ opcode_lut[0xc5] = opcode_cmp_zp;
+ opcode_lut[0xc6] = opcode_dec_zp;
+ opcode_lut[0xc8] = opcode_iny;
+ opcode_lut[0xc9] = opcode_cmp_imm;
+ opcode_lut[0xca] = opcode_dex;
+ opcode_lut[0xcc] = opcode_cpy_abs;
+ opcode_lut[0xcd] = opcode_cmp_abs;
+ opcode_lut[0xce] = opcode_dec_abs;
+ opcode_lut[0xd0] = opcode_bne;
+ opcode_lut[0xd1] = opcode_cmp_indy;
+ opcode_lut[0xd5] = opcode_cmp_zpx;
+ opcode_lut[0xd6] = opcode_dec_zpx;
+ opcode_lut[0xd8] = opcode_cld;
+ opcode_lut[0xd9] = opcode_cmp_absy;
+ opcode_lut[0xdd] = opcode_cmp_absx;
+ opcode_lut[0xde] = opcode_dec_absx;
+ opcode_lut[0xe0] = opcode_cpx_imm;
+ opcode_lut[0xe1] = opcode_sbc_indx;
+ opcode_lut[0xe4] = opcode_cpx_zp;
+ opcode_lut[0xe5] = opcode_sbc_zp;
+ opcode_lut[0xe6] = opcode_inc_zp;
+ opcode_lut[0xe8] = opcode_inx;
+ opcode_lut[0xe9] = opcode_sbc_imm;
+ opcode_lut[0xea] = opcode_nop;
+ opcode_lut[0xeb] = opcode_sbc_imm;
+ opcode_lut[0xec] = opcode_cpx_abs;
+ opcode_lut[0xed] = opcode_sbc_abs;
+ opcode_lut[0xee] = opcode_inc_abs;
+ opcode_lut[0xf0] = opcode_beq;
+ opcode_lut[0xf1] = opcode_sbc_indy;
+ opcode_lut[0xf5] = opcode_sbc_zpx;
+ opcode_lut[0xf6] = opcode_inc_zpx;
+ opcode_lut[0xf8] = opcode_sed;
+ opcode_lut[0xf9] = opcode_sbc_absy;
+ opcode_lut[0xfd] = opcode_sbc_absx;
+ opcode_lut[0xfe] = opcode_inc_absx;
+}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cpu_opcodes_ud.c b/cpu_opcodes_ud.c
new file mode 100644
index 0000000..a679cae
--- /dev/null
+++ b/cpu_opcodes_ud.c
@@ -0,0 +1,1232 @@
+
+
+
+// NOP
+
+static void opcode_nop_imm(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read(state, cpu->pc++); // T1: consume operand
+}
+
+static void opcode_nop_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++); // T1
+ memory_read_dummy(state, addr); // T2
+}
+
+static void opcode_nop_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t addr = lo | (hi << 8);
+
+ memory_read_dummy(state, addr); // T3
+}
+
+static void opcode_nop_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++); // T1: fetch operand
+ memory_read_dummy(state, base); // T2: internal timing quirk (not used, but real)
+ uint8_t addr = (base + cpu->x) & 0xff;
+ memory_read_dummy(state, addr); // T3: final bus read to correct address
+}
+
+static void opcode_nop_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++); // T1
+ uint8_t hi = memory_read(state, cpu->pc++); // T2
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T3 dummy read
+ }
+
+ memory_read_dummy(state, addr); // T4 — final bus cycle (even if not used)
+}
+
+
+static void opcode_nop_implied(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ memory_read_dummy(state, cpu->pc); // T1
+}
+
+
+// LAX
+
+static void opcode_lax_imm(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t value = memory_read(state, cpu->pc++);
+ cpu->a = value;
+ cpu->x = value;
+ update_zn(cpu, value);
+}
+
+static void opcode_lax_indx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, zp);
+
+ uint8_t ptr = (zp + cpu->x) & 0xff;
+ uint8_t lo = memory_read(state, ptr);
+ uint8_t hi = memory_read(state, (ptr + 1) & 0xff);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ cpu->a = value;
+ cpu->x = value;
+ update_zn(cpu, value);
+}
+
+static void opcode_lax_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++);
+ uint8_t value = memory_read(state, addr);
+
+ cpu->a = value;
+ cpu->x = value;
+ update_zn(cpu, value);
+}
+
+static void opcode_lax_zpy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, base);
+ uint8_t addr = (base + cpu->y) & 0xff;
+
+ uint8_t value = memory_read(state, addr);
+ cpu->a = value;
+ cpu->x = value;
+ update_zn(cpu, value);
+}
+
+static void opcode_lax_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ cpu->a = value;
+ cpu->x = value;
+ update_zn(cpu, value);
+}
+
+static void opcode_lax_absy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+ }
+
+ uint8_t value = memory_read(state, addr);
+ cpu->a = value;
+ cpu->x = value;
+ update_zn(cpu, value);
+}
+
+static void opcode_lax_indy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++);
+ uint8_t lo = memory_read(state, zp);
+ uint8_t hi = memory_read(state, (zp + 1) & 0xff);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+ }
+
+ uint8_t value = memory_read(state, addr);
+ cpu->a = value;
+ cpu->x = value;
+ update_zn(cpu, value);
+}
+
+
+// SAX
+
+static void opcode_sax_indx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++); // T1
+ memory_read_dummy(state, zp); // T2
+ uint8_t ptr = zp + cpu->x;
+ uint8_t lo = memory_read(state, ptr & 0xff); // T3
+ uint8_t hi = memory_read(state, (ptr + 1) & 0xff); // T4
+ uint16_t addr = lo | (hi << 8);
+ memory_write(state, addr, cpu->a & cpu->x); // T5
+}
+
+
+static void opcode_sax_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++);
+ memory_write(state, addr, cpu->a & cpu->x);
+}
+
+static void opcode_sax_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t addr = lo | (hi << 8);
+
+ memory_write(state, addr, cpu->a & cpu->x);
+}
+
+static void opcode_sax_zpy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, base);
+ uint8_t addr = (base + cpu->y) & 0xff;
+
+ memory_write(state, addr, cpu->a & cpu->x);
+}
+
+static void opcode_sax_absy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+ memory_write(state, addr, cpu->a & cpu->x);
+}
+
+static void opcode_sax_indy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++); // T1
+ uint8_t lo = memory_read(state, zp); // T2
+ uint8_t hi = memory_read(state, (zp + 1) & 0xff); // T3
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff)); // T4 (always)
+ memory_write(state, addr, cpu->a & cpu->x); // T5
+}
+
+
+
+// DCP
+
+static void opcode_dcp_indx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, zp);
+
+ uint8_t ptr = (zp + cpu->x) & 0xff;
+ uint8_t lo = memory_read(state, ptr);
+ uint8_t hi = memory_read(state, (ptr + 1) & 0xff);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+ value--;
+ memory_write(state, addr, value);
+
+ uint16_t tmp = cpu->a - value;
+ cpu->c = (cpu->a >= value);
+ update_zn(cpu, tmp & 0xff);
+}
+
+static void opcode_dcp_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++);
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+ value--;
+ memory_write(state, addr, value);
+
+ uint16_t tmp = cpu->a - value;
+ cpu->c = (cpu->a >= value);
+ update_zn(cpu, tmp & 0xff);
+}
+
+static void opcode_dcp_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+ value--;
+ memory_write(state, addr, value);
+
+ uint16_t tmp = cpu->a - value;
+ cpu->c = (cpu->a >= value);
+ update_zn(cpu, tmp & 0xff);
+}
+
+static void opcode_dcp_indy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++);
+ uint8_t lo = memory_read(state, zp);
+ uint8_t hi = memory_read(state, (zp + 1) & 0xff);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+ }
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+ value--;
+ memory_write(state, addr, value);
+
+ uint16_t tmp = cpu->a - value;
+ cpu->c = (cpu->a >= value);
+ update_zn(cpu, tmp & 0xff);
+}
+
+static void opcode_dcp_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, base);
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+ value--;
+ memory_write(state, addr, value);
+
+ uint16_t tmp = cpu->a - value;
+ cpu->c = (cpu->a >= value);
+ update_zn(cpu, tmp & 0xff);
+}
+
+static void opcode_dcp_absy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+ value--;
+ memory_write(state, addr, value);
+
+ uint16_t tmp = cpu->a - value;
+ cpu->c = (cpu->a >= value);
+ update_zn(cpu, tmp & 0xff);
+}
+
+static void opcode_dcp_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+ value--;
+ memory_write(state, addr, value);
+
+ uint16_t tmp = cpu->a - value;
+ cpu->c = (cpu->a >= value);
+ update_zn(cpu, tmp & 0xff);
+}
+
+
+// ISC
+
+static void opcode_isc_indx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, zp);
+
+ uint8_t ptr = (zp + cpu->x) & 0xff;
+ uint8_t lo = memory_read(state, ptr);
+ uint8_t hi = memory_read(state, (ptr + 1) & 0xff);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+ value++;
+ memory_write(state, addr, value);
+
+ sbc(cpu, value);
+}
+
+static void opcode_isc_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++);
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+ value++;
+ memory_write(state, addr, value);
+
+ sbc(cpu, value);
+}
+
+static void opcode_isc_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+ value++;
+ memory_write(state, addr, value);
+
+ sbc(cpu, value);
+}
+
+static void opcode_isc_indy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++);
+ uint8_t lo = memory_read(state, zp);
+ uint8_t hi = memory_read(state, (zp + 1) & 0xff);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+ }
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+ value++;
+ memory_write(state, addr, value);
+
+ sbc(cpu, value);
+}
+
+static void opcode_isc_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, base);
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+ value++;
+ memory_write(state, addr, value);
+
+ sbc(cpu, value);
+}
+
+static void opcode_isc_absy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+ value++;
+ memory_write(state, addr, value);
+
+ sbc(cpu, value);
+}
+
+static void opcode_isc_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+ value++;
+ memory_write(state, addr, value);
+
+ sbc(cpu, value);
+}
+
+
+// SLO
+
+static void opcode_slo_indx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, zp);
+
+ uint8_t ptr = (zp + cpu->x) & 0xff;
+ uint8_t lo = memory_read(state, ptr);
+ uint8_t hi = memory_read(state, (ptr + 1) & 0xff);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ cpu->c = (value >> 7) & 1;
+ value <<= 1;
+
+ memory_write(state, addr, value);
+ cpu->a |= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_slo_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++);
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ cpu->c = (value >> 7) & 1;
+ value <<= 1;
+
+ memory_write(state, addr, value);
+ cpu->a |= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_slo_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ cpu->c = (value >> 7) & 1;
+ value <<= 1;
+
+ memory_write(state, addr, value);
+ cpu->a |= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_slo_indy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++);
+ uint8_t lo = memory_read(state, zp);
+ uint8_t hi = memory_read(state, (zp + 1) & 0xff);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+ }
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ cpu->c = (value >> 7) & 1;
+ value <<= 1;
+
+ memory_write(state, addr, value);
+ cpu->a |= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_slo_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, base);
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ cpu->c = (value >> 7) & 1;
+ value <<= 1;
+
+ memory_write(state, addr, value);
+ cpu->a |= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_slo_absy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ cpu->c = (value >> 7) & 1;
+ value <<= 1;
+
+ memory_write(state, addr, value);
+ cpu->a |= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_slo_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ cpu->c = (value >> 7) & 1;
+ value <<= 1;
+
+ memory_write(state, addr, value);
+ cpu->a |= value;
+ update_zn(cpu, cpu->a);
+}
+
+
+// RLA
+
+static void opcode_rla_indx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, zp);
+
+ uint8_t ptr = (zp + cpu->x) & 0xff;
+ uint8_t lo = memory_read(state, ptr);
+ uint8_t hi = memory_read(state, (ptr + 1) & 0xff);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ uint8_t in = cpu->c;
+ cpu->c = (value >> 7) & 1;
+ value = (value << 1) | in;
+
+ memory_write(state, addr, value);
+ cpu->a &= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_rla_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++);
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ uint8_t in = cpu->c;
+ cpu->c = (value >> 7) & 1;
+ value = (value << 1) | in;
+
+ memory_write(state, addr, value);
+ cpu->a &= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_rla_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ uint8_t in = cpu->c;
+ cpu->c = (value >> 7) & 1;
+ value = (value << 1) | in;
+
+ memory_write(state, addr, value);
+ cpu->a &= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_rla_indy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++);
+ uint8_t lo = memory_read(state, zp);
+ uint8_t hi = memory_read(state, (zp + 1) & 0xff);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+ }
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ uint8_t in = cpu->c;
+ cpu->c = (value >> 7) & 1;
+ value = (value << 1) | in;
+
+ memory_write(state, addr, value);
+ cpu->a &= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_rla_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, base);
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ uint8_t in = cpu->c;
+ cpu->c = (value >> 7) & 1;
+ value = (value << 1) | in;
+
+ memory_write(state, addr, value);
+ cpu->a &= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_rla_absy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ uint8_t in = cpu->c;
+ cpu->c = (value >> 7) & 1;
+ value = (value << 1) | in;
+
+ memory_write(state, addr, value);
+ cpu->a &= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_rla_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ uint8_t in = cpu->c;
+ cpu->c = (value >> 7) & 1;
+ value = (value << 1) | in;
+
+ memory_write(state, addr, value);
+ cpu->a &= value;
+ update_zn(cpu, cpu->a);
+}
+
+
+// SRE
+
+static void opcode_sre_indx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, zp);
+
+ uint8_t ptr = (zp + cpu->x) & 0xff;
+ uint8_t lo = memory_read(state, ptr);
+ uint8_t hi = memory_read(state, (ptr + 1) & 0xff);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ cpu->c = value & 1;
+ value >>= 1;
+
+ memory_write(state, addr, value);
+ cpu->a ^= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_sre_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++);
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ cpu->c = value & 1;
+ value >>= 1;
+
+ memory_write(state, addr, value);
+ cpu->a ^= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_sre_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ cpu->c = value & 1;
+ value >>= 1;
+
+ memory_write(state, addr, value);
+ cpu->a ^= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_sre_indy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++);
+ uint8_t lo = memory_read(state, zp);
+ uint8_t hi = memory_read(state, (zp + 1) & 0xff);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+ }
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ cpu->c = value & 1;
+ value >>= 1;
+
+ memory_write(state, addr, value);
+ cpu->a ^= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_sre_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, base);
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ cpu->c = value & 1;
+ value >>= 1;
+
+ memory_write(state, addr, value);
+ cpu->a ^= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_sre_absy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ cpu->c = value & 1;
+ value >>= 1;
+
+ memory_write(state, addr, value);
+ cpu->a ^= value;
+ update_zn(cpu, cpu->a);
+}
+
+static void opcode_sre_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ cpu->c = value & 1;
+ value >>= 1;
+
+ memory_write(state, addr, value);
+ cpu->a ^= value;
+ update_zn(cpu, cpu->a);
+}
+
+
+// opcode_rra
+
+static void opcode_rra_indx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, zp);
+
+ uint8_t ptr = (zp + cpu->x) & 0xff;
+ uint8_t lo = memory_read(state, ptr);
+ uint8_t hi = memory_read(state, (ptr + 1) & 0xff);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ uint8_t new_c = value & 1;
+ value = (value >> 1) | (cpu->c << 7);
+ cpu->c = new_c;
+
+ memory_write(state, addr, value);
+ adc(cpu, value);
+}
+
+static void opcode_rra_zp(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t addr = memory_read(state, cpu->pc++);
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ uint8_t new_c = value & 1;
+ value = (value >> 1) | (cpu->c << 7);
+ cpu->c = new_c;
+
+ memory_write(state, addr, value);
+ adc(cpu, value);
+}
+
+static void opcode_rra_abs(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t addr = lo | (hi << 8);
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ uint8_t new_c = value & 1;
+ value = (value >> 1) | (cpu->c << 7);
+ cpu->c = new_c;
+
+ memory_write(state, addr, value);
+ adc(cpu, value);
+}
+
+static void opcode_rra_indy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t zp = memory_read(state, cpu->pc++);
+ uint8_t lo = memory_read(state, zp);
+ uint8_t hi = memory_read(state, (zp + 1) & 0xff);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ if((base & 0xff00) != (addr & 0xff00)) {
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+ }
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ uint8_t new_c = value & 1;
+ value = (value >> 1) | (cpu->c << 7);
+ cpu->c = new_c;
+
+ memory_write(state, addr, value);
+ adc(cpu, value);
+}
+
+static void opcode_rra_zpx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t base = memory_read(state, cpu->pc++);
+ memory_read_dummy(state, base);
+ uint8_t addr = (base + cpu->x) & 0xff;
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ uint8_t new_c = value & 1;
+ value = (value >> 1) | (cpu->c << 7);
+ cpu->c = new_c;
+
+ memory_write(state, addr, value);
+ adc(cpu, value);
+}
+
+static void opcode_rra_absy(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->y;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ uint8_t new_c = value & 1;
+ value = (value >> 1) | (cpu->c << 7);
+ cpu->c = new_c;
+
+ memory_write(state, addr, value);
+ adc(cpu, value);
+}
+
+static void opcode_rra_absx(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t lo = memory_read(state, cpu->pc++);
+ uint8_t hi = memory_read(state, cpu->pc++);
+ uint16_t base = lo | (hi << 8);
+ uint16_t addr = base + cpu->x;
+
+ memory_read_dummy(state, (base & 0xff00) | (addr & 0x00ff));
+
+ uint8_t value = memory_read(state, addr);
+ memory_write(state, addr, value); // dummy write
+
+ uint8_t new_c = value & 1;
+ value = (value >> 1) | (cpu->c << 7);
+ cpu->c = new_c;
+
+ memory_write(state, addr, value);
+ adc(cpu, value);
+}
+
+
+// ALR
+
+static void opcode_alr_imm(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+ uint8_t value = memory_read(state, cpu->pc++);
+ cpu->a &= value;
+ cpu->c = cpu->a & 1;
+ cpu->a >>= 1;
+ update_zn(cpu, cpu->a);
+}
+
+
+// ANC
+
+static void opcode_anc_imm(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+ uint8_t value = memory_read(state, cpu->pc++);
+ cpu->a &= value;
+ cpu->c = (cpu->a >> 7) & 1;
+ update_zn(cpu, cpu->a);
+}
+
+
+// ARR
+
+static void opcode_arr_imm(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t value = memory_read(state, cpu->pc++);
+ cpu->a &= value;
+
+ uint8_t in = cpu->c << 7;
+ cpu->c = (cpu->a >> 0) & 1;
+ cpu->a = (cpu->a >> 1) | in;
+
+ update_zn(cpu, cpu->a);
+ cpu->v = ((cpu->a >> 6) ^ (cpu->a >> 5)) & 1;
+}
+
+
+// XAA
+
+static void opcode_xaa_imm(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t value = memory_read(state, cpu->pc++);
+ cpu->a = cpu->a & cpu->x & value;
+ update_zn(cpu, cpu->a);
+}
+
+
+// AXS
+
+static void opcode_axs_imm(struct nes_state * restrict state) {
+ struct cpu_state * restrict cpu = &state->cpu;
+
+ uint8_t value = memory_read(state, cpu->pc++);
+ cpu->a = cpu->x & value;
+ update_zn(cpu, cpu->a);
+}
+
+
+
+
+__attribute__((noinline))
+static void init_opcode_ud_lut(void) {
+ opcode_lut[0x80] = opcode_nop_imm;
+ opcode_lut[0x82] = opcode_nop_imm;
+ opcode_lut[0x89] = opcode_nop_imm;
+ opcode_lut[0xc2] = opcode_nop_imm;
+ opcode_lut[0xe2] = opcode_nop_imm;
+
+ opcode_lut[0x04] = opcode_nop_zp;
+ opcode_lut[0x44] = opcode_nop_zp;
+ opcode_lut[0x64] = opcode_nop_zp;
+
+ opcode_lut[0x0c] = opcode_nop_abs;
+
+ opcode_lut[0x14] = opcode_nop_zpx;
+ opcode_lut[0x34] = opcode_nop_zpx;
+ opcode_lut[0x54] = opcode_nop_zpx;
+ opcode_lut[0x74] = opcode_nop_zpx;
+ opcode_lut[0xd4] = opcode_nop_zpx;
+ opcode_lut[0xf4] = opcode_nop_zpx;
+
+ opcode_lut[0x1c] = opcode_nop_absx;
+ opcode_lut[0x3c] = opcode_nop_absx;
+ opcode_lut[0x5c] = opcode_nop_absx;
+ opcode_lut[0x7c] = opcode_nop_absx;
+ opcode_lut[0xdc] = opcode_nop_absx;
+ opcode_lut[0xfc] = opcode_nop_absx;
+
+ opcode_lut[0x1a] = opcode_nop_implied;
+ opcode_lut[0x3a] = opcode_nop_implied;
+ opcode_lut[0x5a] = opcode_nop_implied;
+ opcode_lut[0x7a] = opcode_nop_implied;
+ opcode_lut[0xda] = opcode_nop_implied;
+ opcode_lut[0xfa] = opcode_nop_implied;
+
+ opcode_lut[0xab] = opcode_lax_imm;
+ opcode_lut[0xa3] = opcode_lax_indx;
+ opcode_lut[0xaf] = opcode_lax_abs;
+ opcode_lut[0xbb] = opcode_lax_absy;
+ opcode_lut[0xbf] = opcode_lax_absy;
+ opcode_lut[0xa7] = opcode_lax_zp;
+ opcode_lut[0xb7] = opcode_lax_zpy;
+ opcode_lut[0xb3] = opcode_lax_indy;
+
+ opcode_lut[0x83] = opcode_sax_indx;
+ opcode_lut[0x8f] = opcode_sax_abs;
+ opcode_lut[0x9f] = opcode_sax_absy;
+ opcode_lut[0x87] = opcode_sax_zp;
+ opcode_lut[0x97] = opcode_sax_zpy;
+ opcode_lut[0x93] = opcode_sax_indy;
+
+ opcode_lut[0xc3] = opcode_dcp_indx;
+ opcode_lut[0xc7] = opcode_dcp_zp;
+ opcode_lut[0xcf] = opcode_dcp_abs;
+ opcode_lut[0xd3] = opcode_dcp_indy;
+ opcode_lut[0xd7] = opcode_dcp_zpx;
+ opcode_lut[0xdb] = opcode_dcp_absy;
+ opcode_lut[0xdf] = opcode_dcp_absx;
+
+ opcode_lut[0xe3] = opcode_isc_indx;
+ opcode_lut[0xe7] = opcode_isc_zp;
+ opcode_lut[0xef] = opcode_isc_abs;
+ opcode_lut[0xf3] = opcode_isc_indy;
+ opcode_lut[0xf7] = opcode_isc_zpx;
+ opcode_lut[0xfb] = opcode_isc_absy;
+ opcode_lut[0xff] = opcode_isc_absx;
+
+ opcode_lut[0x03] = opcode_slo_indx;
+ opcode_lut[0x07] = opcode_slo_zp;
+ opcode_lut[0x0f] = opcode_slo_abs;
+ opcode_lut[0x13] = opcode_slo_indy;
+ opcode_lut[0x17] = opcode_slo_zpx;
+ opcode_lut[0x1b] = opcode_slo_absy;
+ opcode_lut[0x1f] = opcode_slo_absx;
+
+ opcode_lut[0x23] = opcode_rla_indx;
+ opcode_lut[0x27] = opcode_rla_zp;
+ opcode_lut[0x2f] = opcode_rla_abs;
+ opcode_lut[0x33] = opcode_rla_indy;
+ opcode_lut[0x37] = opcode_rla_zpx;
+ opcode_lut[0x3b] = opcode_rla_absy;
+ opcode_lut[0x3f] = opcode_rla_absx;
+
+ opcode_lut[0x43] = opcode_sre_indx;
+ opcode_lut[0x47] = opcode_sre_zp;
+ opcode_lut[0x4f] = opcode_sre_abs;
+ opcode_lut[0x53] = opcode_sre_indy;
+ opcode_lut[0x57] = opcode_sre_zpx;
+ opcode_lut[0x5b] = opcode_sre_absy;
+ opcode_lut[0x5f] = opcode_sre_absx;
+
+ opcode_lut[0x63] = opcode_rra_indx;
+ opcode_lut[0x67] = opcode_rra_zp;
+ opcode_lut[0x6f] = opcode_rra_abs;
+ opcode_lut[0x73] = opcode_rra_indy;
+ opcode_lut[0x77] = opcode_rra_zpx;
+ opcode_lut[0x7b] = opcode_rra_absy;
+ opcode_lut[0x7f] = opcode_rra_absx;
+
+ opcode_lut[0x32] = opcode_anc_imm;
+ opcode_lut[0x4b] = opcode_alr_imm;
+ opcode_lut[0x8b] = opcode_xaa_imm;
+ opcode_lut[0xcb] = opcode_axs_imm;
+ opcode_lut[0x6b] = opcode_arr_imm;
+}
diff --git a/ines2.c b/ines2.c
new file mode 100644
index 0000000..450c22e
--- /dev/null
+++ b/ines2.c
@@ -0,0 +1,90 @@
+
+
+
+// iNES header fields
+#define INES_HEADER_SIZE 16
+#define INES_PRG_SIZE_LSB 4
+#define INES_CHR_SIZE_LSB 5
+#define INES_FLAGS6 6
+#define INES_FLAGS7 7
+#define INES_PRG_CHR_MSB 9
+#define INES_CHR_EXP 8
+#define INES_PRG_EXP 9
+
+// Size units
+#define PRG_ROM_UNIT 16384
+#define CHR_ROM_UNIT 8192
+#define TRAINER_SIZE 512
+
+// Flag masks
+#define FLAG6_TRAINER 0x04
+#define FLAG6_FOUR_SCREEN 0x08
+#define FLAG6_VERTICAL_MIRROR 0x01
+
+// Mirroring modes
+#define MIRROR_HORIZONTAL 0
+#define MIRROR_VERTICAL 1
+#define MIRROR_FOUR_SCREEN 2
+
+
+static int load_ines2(struct nes_state *state, const char *path) {
+ FILE *f = fopen(path, "rb");
+ if(!f) {
+ return -1;
+ }
+
+ uint8_t header[INES_HEADER_SIZE];
+ fread(header, 1, INES_HEADER_SIZE, f);
+
+ uint8_t prg_lsb = header[INES_PRG_SIZE_LSB];
+ uint8_t chr_lsb = header[INES_CHR_SIZE_LSB];
+ uint8_t prg_msb = header[INES_PRG_CHR_MSB] & 0x0f;
+ uint8_t chr_msb = (header[INES_PRG_CHR_MSB] & 0xf0) >> 4;
+
+ uint32_t prg_size = (prg_msb << 8 | prg_lsb) * PRG_ROM_UNIT;
+ uint32_t chr_size = (chr_msb << 8 | chr_lsb) * CHR_ROM_UNIT;
+
+ // NES 2.0 exponential format (only if lower byte is 0x0f)
+ if(prg_lsb == 0x0f) {
+ prg_size = 1 << header[INES_PRG_EXP];
+ }
+ if(chr_lsb == 0x0f) {
+ chr_size = 1 << header[INES_CHR_EXP];
+ }
+
+ // Extract mapper
+ uint8_t mapper_low = (header[INES_FLAGS6] >> 4);
+ uint8_t mapper_high = (header[INES_FLAGS7] & 0xf0);
+ uint8_t mapper_ext = (header[INES_PRG_CHR_MSB] & 0x0f) << 8;
+ state->ines.mapper = mapper_low | mapper_high | mapper_ext;
+
+ // Extract mirroring
+ if(header[INES_FLAGS6] & FLAG6_FOUR_SCREEN) {
+ state->ines.mirroring = MIRROR_FOUR_SCREEN;
+ } else if(header[INES_FLAGS6] & FLAG6_VERTICAL_MIRROR) {
+ state->ines.mirroring = MIRROR_VERTICAL;
+ } else {
+ state->ines.mirroring = MIRROR_HORIZONTAL;
+ }
+
+ state->ines.prg_size = prg_size;
+ state->ines.chr_size = chr_size;
+
+ // Skip trainer if present
+ if(header[INES_FLAGS6] & FLAG6_TRAINER) {
+ fseek(f, TRAINER_SIZE, SEEK_CUR);
+ }
+
+ // Read PRG
+ fread(state->rom, 1, prg_size, f);
+
+ // Read CHR if present
+ if(chr_size > 0) {
+ fread(state->chrrom, 1, chr_size, f);
+ }
+
+ fclose(f);
+ return 0;
+}
+
+
diff --git a/mapper.c b/mapper.c
new file mode 100644
index 0000000..620be7d
--- /dev/null
+++ b/mapper.c
@@ -0,0 +1,28 @@
+
+
+#include "mapper_nrom.c"
+// #include "mapper_mmc1.c"
+// #include "mapper_uxrom.c"
+
+static void mapper_setup(struct state *state) {
+ switch(state->ines.mapper) {
+ case 0:
+ state->mapper.read = mapper_nrom_read;
+ state->mapper.write = mapper_nrom_write;
+ state->mapper.tick = mapper_nrom_tick;
+ state->mapper.init = mapper_nrom_init;
+ state->mapper.init(state);
+ break;
+
+ default:
+ // fallback: NROM-compatible
+ state->mapper.read = mapper_nrom_read;
+ state->mapper.write = mapper_nrom_write;
+ state->mapper.tick = mapper_nrom_tick;
+ state->mapper.init = mapper_nrom_init;
+ state->mapper.init(state);
+ break;
+ }
+}
+
+
diff --git a/mapper_nrom.c b/mapper_nrom.c
new file mode 100644
index 0000000..425db31
--- /dev/null
+++ b/mapper_nrom.c
@@ -0,0 +1,27 @@
+
+
+static void mapper_nrom_init(struct state *state) {
+ // Nothing to initialize for NROM
+}
+
+static uint8_t mapper_nrom_read(struct state *state, uint16_t addr) {
+ uint32_t prg_size = state->ines.prg_size;
+
+ if(state->ines.prg_size == 16384) {
+ return state->rom[addr & 0x3fff];
+ } else {
+ return state->rom[addr - 0x8000];
+ }
+ return 0;
+}
+
+static void mapper_nrom_write(struct state *state, uint16_t addr, uint8_t value) {
+ (void)state;
+ (void)addr;
+ (void)value;
+}
+
+static void mapper_nrom_tick(struct state *state) {
+ (void)state;
+}
+
diff --git a/memory.c b/memory.c
new file mode 100644
index 0000000..cb29344
--- /dev/null
+++ b/memory.c
@@ -0,0 +1,95 @@
+
+
+
+static uint8_t memory_read(struct nes_state *restrict state, uint32_t offset) {
+ state->cycle++;
+ for(uint32_t i = 0; i < 3; ++i) {
+ ppu_tick(state);
+ }
+
+ if(offset < 0x2000) {
+ return state->ram[offset & 0x07ff];
+ } else if(offset < 0x4000) {
+ switch(offset & 7) {
+ case 2: return ppu_read_2002(state);
+ case 4: return ppu_read_2004(state);
+ case 7: return ppu_read_2007(state);
+ default: return 0;
+ }
+ } else if(offset < 0x4020) {
+ // TODO: APU and I/O reads
+ return 0;
+ } else if(offset >= 0x6000) {
+ return state->mapper.read(state, offset);
+ } else {
+ return 0;
+ }
+}
+
+static void memory_write(struct nes_state *restrict state, uint32_t offset, uint8_t value) {
+ state->cycle++;
+ for(uint32_t i = 0; i < 3; ++i) {
+ ppu_tick(state);
+ }
+
+ if(offset < 0x2000) {
+ state->ram[offset & 0x07ff] = value;
+ } else if(offset < 0x4000) {
+ switch(offset & 7) {
+ case 0: state->ppu.ctrl = value; break;
+ case 1: state->ppu.mask = value; break;
+ case 3: ppu_write_2003(state, value); break;
+ case 4: ppu_write_2004(state, value); break;
+ case 5: ppu_write_2005(state, value); break;
+ case 6: ppu_write_2006(state, value); break;
+ case 7: ppu_write_2007(state, value); break;
+ default: break;
+ }
+ } else if(offset == 0x4014) {
+ ppu_dma_4014(state, value);
+ } else if(offset < 0x4020) {
+ // TODO: APU and I/O writes
+ } else if(offset >= 0x6000) {
+ state->mapper.write(state, offset, value);
+ }
+}
+
+static uint8_t memory_read_dma(struct nes_state *restrict state, uint32_t offset) {
+ // Do not tick CPU/PPU/APU — caller handles timing
+ if(offset < 0x2000) {
+ return state->ram[offset & 0x07ff];
+ } else if(offset < 0x4000) {
+ // PPU register reads are ignored during DMA
+ return 0;
+ } else if(offset < 0x4020) {
+ // APU and I/O — usually ignored or blocked during DMA
+ return 0;
+ } else if(offset >= 0x6000) {
+ return state->mapper.read(state, offset);
+ } else {
+ return 0;
+ }
+}
+
+static uint8_t memory_read_dummy(struct nes_state *restrict state, uint32_t offset) {
+ state->cycle++;
+ for(uint32_t i = 0; i < 3; ++i) {
+ ppu_tick(state);
+ }
+
+ if(offset < 0x2000) {
+ return 0;
+ } else if(offset < 0x4000) {
+ switch(offset & 7) {
+ case 2: return ppu_read_2002(state);
+ case 7: return ppu_read_2007(state);
+ default: return 0;
+ }
+ } else if(offset < 0x4020) {
+ return 0;
+ } else if(offset >= 0x6000) {
+ return state->mapper.read(state, offset);
+ } else {
+ return 0;
+ }
+}
diff --git a/mknes.c b/mknes.c
new file mode 100644
index 0000000..0027ce6
--- /dev/null
+++ b/mknes.c
@@ -0,0 +1,102 @@
+
+
+#include "base.c"
+
+#include "mknes.h"
+// #include "apu.c"
+#include "ppu.c"
+#include "memory.c"
+#include "cpu.c"
+#include "ines2.c"
+
+
+
+
+static void render_callback() {
+
+}
+
+static void audio_callback(int16_t *buffer, size_t frames) {
+
+}
+
+static void init_callback() {
+}
+
+// int main(void) {
+// struct nes_state state = {0};
+
+// init_opcode_lut();
+// init_opcode_ud_lut();
+// state.cpu.sp = 0xfd;
+
+// // FILE *f = fopen("nestest.nes", "rb");
+// // fseek(f, 16, SEEK_SET);
+// // fread(&state.memory[0xc000], 1, 0x4000, f);
+// // fclose(f);
+// // state.cpu.pc = 0xc000;
+
+// FILE *f = fopen("6502_functional_test.bin", "rb");
+// fread(state.memory, 1, 0x10000, f);
+// fclose(f);
+// state.cpu.pc = 0x0400;
+
+// size_t i;
+// for(i = 0; i < 70000000; ++i) {
+// cpu_tick(&state);
+// if(state.cpu.pc == 0x3469) break;
+// }
+// // printf("%lld", i);
+// return 0;
+// }
+
+
+
+
+// bool running = true;
+// uint64_t next_update = mkfw_gettime();
+// int64_t frame_duration_ns = 16666667; // NTSC ~60.0988 Hz
+
+// audio_sync_init(next_update); // Initialize sync base time
+
+// while(running && !mkfw_should_close()) {
+// mkfw_pump_messages();
+// if(key_pressed(MKS_KEY_ESCAPE)) { running = false; }
+
+// #ifdef PROFILER
+// reset_profiling_data();
+// #endif
+
+// render_callback();
+// apply_phosphor_decay();
+// update_keyboard_state();
+// update_modifier_state();
+// update_mouse_state();
+// state.frame_number++;
+
+// #ifndef PERF_TEST
+// render_frame();
+// #ifdef PROFILER
+// debug_render();
+// #endif
+
+// audio_throttle_emulator(state.frame_number, &frame_duration_ns);
+
+// uint64_t now = mkfw_gettime();
+// int64_t remaining = next_update - now;
+
+// if(remaining > 0) {
+// if(remaining > ONE_MILLISECOND_NS) {
+// mkfw_sleep(remaining - ONE_MILLISECOND_NS);
+// }
+// while(mkfw_gettime() < next_update) { /**/ }
+// } else {
+// next_update = now;
+// }
+
+// next_update += frame_duration_ns;
+
+// mkfw_swap_buffers();
+// #endif
+// }
+
diff --git a/mknes.h b/mknes.h
new file mode 100644
index 0000000..1f82ae1
--- /dev/null
+++ b/mknes.h
@@ -0,0 +1,146 @@
+
+
+
+// PPUSTATUS ($2002) flags
+#define PPU_STATUS_VBLANK 0x80
+#define PPU_STATUS_SPRITE0_HIT 0x40
+#define PPU_STATUS_OVERFLOW 0x20
+
+// PPUMASK ($2001) flags
+#define PPU_MASK_SHOW_BG 0x08
+#define PPU_MASK_SHOW_SPRITES 0x10
+
+// PPUCTRL ($2000) flags
+#define PPU_CTRL_NMI_ENABLE 0x80
+#define PPU_CTRL_MASTER_SLAVE 0x40
+#define PPU_CTRL_SPRITE_HEIGHT 0x20
+#define PPU_CTRL_BG_TABLE 0x10
+#define PPU_CTRL_SPRITE_TABLE 0x08
+#define PPU_CTRL_INCREMENT 0x04
+#define PPU_CTRL_NT_SELECT_Y 0x02
+#define PPU_CTRL_NT_SELECT_X 0x01
+
+
+struct nes_state;
+
+struct ppu_state {
+ uint8_t pixels[240*256];
+ uint8_t sprite_pixels[256]; // Sprite pixel color indexes (BG priority resolved)
+ uint8_t sprite_zero_flags[256]; // 1 if pixel came from sprite #0 and is nonzero
+ // Sprite memory
+ uint8_t oam[256];
+ uint8_t sec_oam[32];
+
+ uint8_t oam_addr;
+ uint8_t read_buffer;
+
+ uint32_t scanline; // 0–261
+ uint32_t dot; // 0–340
+
+ // Scroll state
+ uint32_t coarse_x_offs;
+ uint32_t coarse_y_offs;
+ uint32_t fine_x;
+ uint32_t fine_y;
+ uint32_t nt_base_x;
+ uint32_t nt_base_y;
+
+ // Latch state for $2005/$2006
+ uint8_t write_toggle;
+ uint32_t temp_coarse_x_offs;
+ uint32_t temp_coarse_y_offs;
+ uint32_t temp_fine_x;
+ uint32_t temp_fine_y;
+ uint32_t temp_nt_base_x;
+ uint32_t temp_nt_base_y;
+
+ // Background shift registers
+ uint32_t bg_tile_lsb;
+ uint32_t bg_tile_msb;
+ uint32_t bg_attr_lsb;
+ uint32_t bg_attr_msb;
+
+ // Tile fetch latches
+ uint8_t nt_byte;
+ uint8_t attr_byte;
+ uint8_t tile_lsb;
+ uint8_t tile_msb;
+
+ // Control and status
+ uint8_t ctrl; // $2000
+ uint8_t mask; // $2001
+ uint8_t status; // $2002
+
+ // Flags
+ uint8_t frame_even;
+ uint8_t nmi_occurred;
+ uint8_t nmi_output;
+
+};
+
+
+struct cpu_state {
+ uint32_t pc; // Program Counter
+ uint8_t sp; // Stack Pointer
+ uint8_t a; // Accumulator
+ uint8_t x; // X Register
+ uint8_t y; // Y Register
+ uint8_t p; // Processor Status Flags (this can be expanded with separate flags if needed)
+ uint8_t n; // Negative Flag
+ uint8_t v; // Overflow Flag
+ // uint8_t b; // Break Flag (Set by BRK instruction)
+ uint8_t d; // Decimal Flag
+ uint8_t i; // Interrupt Disable Flag
+ uint8_t z; // Zero Flag
+ uint8_t c; // Carry Flag
+// --
+ uint8_t die; // KIL instruction found!
+};
+
+
+struct ines_state {
+ uint16_t mapper;
+ uint8_t mirroring; // 0 = H, 1 = V, 2 = 4-screen
+ uint32_t prg_size;
+ uint32_t chr_size;
+};
+
+struct mapper {
+ void (*init)(struct nes_state *state);
+ uint8_t (*read)(struct nes_state *state, uint16_t addr);
+ void (*write)(struct nes_state *state, uint16_t addr, uint8_t value);
+ void (*tick)(struct nes_state *state);
+};
+
+union mapper_data {
+ // struct nrom_mapper nrom;
+ // struct mmc1_mapper mmc1;
+ // ... others
+};
+
+struct nes_state {
+ struct ines_state ines;
+ struct cpu_state cpu;
+ struct ppu_state ppu;
+ struct mapper mapper;
+ union mapper_data map;
+ size_t cycle;
+ uint8_t ram[2048];
+ uint8_t rom[4 * 1024 * 1024];
+ uint8_t chrrom[4 * 1024 * 1024];
+ uint8_t ciram[2048];
+ uint8_t palette[0x20];
+ uint8_t irq_pending;
+ uint8_t nmi_pending;
+};
+
+static const uint32_t nes_palette_argb[64] = {
+ 0xff757575, 0xff8f1b27, 0xffab0000, 0xff9f0047, 0xff77008f, 0xff1300ab, 0xff0000a7, 0xff000b7f,
+ 0xff002f43, 0xff004700, 0xff005100, 0xff173f00, 0xff5f3f1b, 0xff000000, 0xff000000, 0xff000000,
+ 0xffbcbcbc, 0xffef7300, 0xffef3b23, 0xfff30083, 0xffbf00bf, 0xff5b00e7, 0xff002bdb, 0xff0f4fcb,
+ 0xff00738b, 0xff009700, 0xff00ab00, 0xff3b9300, 0xff8b8300, 0xff000000, 0xff000000, 0xff000000,
+ 0xffffffff, 0xffffbf3f, 0xffff975f, 0xfffd8ba7, 0xffff7bff, 0xffb777ff, 0xff6377ff, 0xff3b9bff,
+ 0xff3fbff3, 0xff13d383, 0xff4bdf4f, 0xff98f858, 0xffdbeb00, 0xff000000, 0xff000000, 0xff000000,
+ 0xffffffff, 0xffffe7ab, 0xffffd7c7, 0xffffcbd7, 0xffffc7ff, 0xffdbc7ff, 0xffb3bfff, 0xffabdbff,
+ 0xffa3e7ff, 0xff83f7c7, 0xffb3ffbf, 0xffcfffb3, 0xfff3ff9f, 0xff000000, 0xff000000, 0xff000000
+};
diff --git a/ppu.c b/ppu.c
new file mode 100644
index 0000000..adfdc13
--- /dev/null
+++ b/ppu.c
@@ -0,0 +1,490 @@
+
+static uint8_t memory_read_dma(struct nes_state *state, uint32_t offset);
+
+static void ppu_reset_scroll_latch(struct nes_state *state) {
+ state->ppu.write_toggle = 0;
+}
+
+static uint32_t compute_nt_address(struct nes_state *state) {
+ struct ppu_state *ppu = &state->ppu;
+ return 0x2000 | ppu->nt_base_y | ppu->nt_base_x | ppu->coarse_y_offs | ppu->coarse_x_offs;
+}
+
+
+static uint32_t compute_at_address(struct nes_state *state) {
+ struct ppu_state *ppu = &state->ppu;
+ uint32_t nt_offset = ppu->nt_base_y | ppu->nt_base_x;
+ uint32_t attr_x = (ppu->coarse_x_offs >> 2) & 0x07;
+ uint32_t attr_y = (ppu->coarse_y_offs >> 2) & 0x38;
+ return 0x23c0 | nt_offset | attr_y | attr_x;
+}
+
+static void increment_x(struct nes_state *state) {
+ struct ppu_state *ppu = &state->ppu;
+
+ if(ppu->coarse_x_offs == 31 << 0) {
+ ppu->coarse_x_offs = 0;
+ ppu->nt_base_x ^= 0x400;
+ } else {
+ ppu->coarse_x_offs += 1 << 0;
+ }
+}
+
+static void increment_y(struct nes_state *state) {
+ struct ppu_state *ppu = &state->ppu;
+
+ if(ppu->fine_y < 7) {
+ ppu->fine_y++;
+ return;
+ }
+
+ ppu->fine_y = 0;
+
+ uint32_t coarse_y = ppu->coarse_y_offs >> 5;
+
+ if(coarse_y == 29) {
+ ppu->coarse_y_offs = 0;
+ ppu->nt_base_y ^= 0x800;
+ } else if(coarse_y == 31) {
+ ppu->coarse_y_offs = 0;
+ // Do not toggle nt_base_y — stays the same
+ } else {
+ ppu->coarse_y_offs += 1 << 5;
+ }
+}
+
+
+static void ppu_vram_write(struct nes_state *state, uint32_t addr, uint8_t value) {
+ // PPU address bus is 14 bits, wraps every 0x4000
+ addr &= 0x3fff;
+
+ if(addr < 0x2000) {
+ // Pattern table (CHR ROM is read-only, ignore write)
+ return;
+ }
+
+ if(addr < 0x3f00) {
+ // Nametable RAM (CIRAM), mirrored every 0x1000
+ uint32_t nt_index = addr & 0x0fff;
+ state->ciram[nt_index] = value;
+ return;
+ }
+
+ if(addr < 0x4000) {
+ // Palette RAM (0x3F00–0x3FFF), mirrored every 32 bytes
+ addr = 0x3f00 + (addr & 0x1f);
+
+ // Palette mirroring: $3F10/$3F14/$3F18/$3F1C mirror $3F00/$3F04/$3F08/$3F0C
+ if((addr & 0x13) == 0x10) {
+ addr &= ~0x10;
+ }
+
+ state->palette[addr & 0x1f] = value;
+ }
+}
+
+static uint8_t ppu_vram_read(struct nes_state *state, uint32_t addr) {
+ // PPU address bus is 14 bits, wraps every 0x4000
+ addr &= 0x3fff;
+
+ if(addr < 0x2000) {
+ // Pattern table (CHR ROM)
+ return state->chrrom[addr];
+ }
+
+ if(addr < 0x3f00) {
+ // Nametables (0x2000–0x2FFF mirrored every 0x1000)
+ uint32_t nt_index = addr & 0x0fff;
+ return state->ciram[nt_index];
+ }
+
+ if(addr < 0x4000) {
+ // Palette RAM (0x3F00–0x3FFF, mirrored every 32 bytes)
+ addr = 0x3f00 + (addr & 0x1f);
+
+ // Palette mirroring: $3F10/$3F14/$3F18/$3F1C mirror $3F00/$3F04/$3F08/$3F0C
+ if((addr & 0x13) == 0x10) {
+ addr &= ~0x10;
+ }
+
+ return state->palette[addr & 0x1f];
+ }
+
+ return 0;
+}
+
+static uint8_t ppu_read_2002(struct nes_state *state) {
+ struct ppu_state *ppu = &state->ppu;
+ uint8_t result = ppu->status;
+
+ ppu->status &= ~PPU_STATUS_VBLANK;
+ ppu->write_toggle = 0;
+
+ return result;
+}
+
+static void ppu_write_2003(struct nes_state *state, uint8_t value) {
+ state->ppu.oam_addr = value;
+}
+
+static uint8_t ppu_read_2004(struct nes_state *state) {
+ return state->ppu.oam[state->ppu.oam_addr];
+}
+
+static void ppu_write_2004(struct nes_state *state, uint8_t value) {
+ state->ppu.oam[state->ppu.oam_addr] = value;
+ state->ppu.oam_addr++;
+}
+
+
+static void ppu_write_2005(struct nes_state *state, uint8_t value)
+{
+ struct ppu_state *ppu = &state->ppu;
+
+ if(ppu->write_toggle == 0) {
+ ppu->temp_fine_x = value & 7;
+ ppu->temp_coarse_x_offs = (value >> 3) & 0x1f;
+ ppu->temp_coarse_x_offs <<= 0;
+
+ ppu->write_toggle = 1;
+ } else {
+ ppu->temp_fine_y = value & 7;
+ uint8_t coarse_y = (value >> 3) & 0x1f;
+ ppu->temp_coarse_y_offs = coarse_y << 5;
+
+ if(coarse_y >= 30) {
+ // vertical nametable select
+ ppu->temp_nt_base_y = 0x800;
+ } else {
+ ppu->temp_nt_base_y = 0x000;
+ }
+
+ ppu->write_toggle = 0;
+ }
+}
+
+static void ppu_write_2006(struct nes_state *state, uint8_t value)
+{
+ struct ppu_state *ppu = &state->ppu;
+
+ if(ppu->write_toggle == 0) {
+ // High byte: bits 14–8 of VRAM address
+ ppu->temp_nt_base_y = (value & 0x0c) << 8;
+ ppu->temp_nt_base_x = (value & 0x01) << 10;
+ ppu->temp_coarse_y_offs = (value & 0x03) << 3;
+ ppu->temp_coarse_y_offs <<= 5;
+
+ ppu->write_toggle = 1;
+ } else {
+ // Low byte: bits 7–0 of VRAM address
+ ppu->temp_coarse_x_offs = (value & 0x1f) << 0;
+ ppu->temp_fine_y = (value >> 2) & 7;
+
+ // On second write, transfer temp -> current scroll state
+ ppu->coarse_x_offs = ppu->temp_coarse_x_offs;
+ ppu->coarse_y_offs = ppu->temp_coarse_y_offs;
+ ppu->fine_x = ppu->temp_fine_x;
+ ppu->fine_y = ppu->temp_fine_y;
+ ppu->nt_base_x = ppu->temp_nt_base_x;
+ ppu->nt_base_y = ppu->temp_nt_base_y;
+
+ ppu->write_toggle = 0;
+ }
+}
+
+static uint8_t ppu_read_2007(struct nes_state *state) {
+ struct ppu_state *ppu = &state->ppu;
+
+ uint32_t addr = compute_nt_address(state);
+ if(ppu->fine_y) {
+ addr += ppu->fine_y;
+ }
+
+ uint8_t result;
+
+ if(addr < 0x3f00) {
+ // Return buffered value, load new one
+ result = ppu->read_buffer;
+ ppu->read_buffer = ppu_vram_read(state, addr);
+ } else {
+ // Palette reads bypass buffer
+ result = ppu_vram_read(state, addr);
+ ppu->read_buffer = ppu_vram_read(state, addr & 0x2fff); // mirror to pattern/nametable area
+ }
+
+ // Auto-increment address
+ if(ppu->ctrl & PPU_CTRL_INCREMENT) {
+ increment_y(state);
+ } else {
+ increment_x(state);
+ }
+
+ return result;
+}
+
+
+static void ppu_write_2007(struct nes_state *state, uint8_t value) {
+ struct ppu_state *ppu = &state->ppu;
+ uint32_t addr = compute_nt_address(state);
+
+ if(ppu->fine_y)
+ addr += ppu->fine_y;
+
+ ppu_vram_write(state, addr, value);
+
+ if(ppu->ctrl & PPU_CTRL_INCREMENT) {
+ increment_y(state);
+ } else {
+ increment_x(state);
+ }
+}
+
+static uint8_t ppu_pattern_read(struct nes_state *state, uint8_t tile_index, uint8_t fine_y, uint8_t plane) {
+ struct ppu_state *ppu = &state->ppu;
+ uint32_t base = (ppu->ctrl & PPU_CTRL_BG_TABLE) ? 0x1000 : 0x0000;
+ uint32_t addr = base + (tile_index << 4) + fine_y;
+
+ if(plane) {
+ addr += 8;
+ }
+
+ return ppu_vram_read(state, addr);
+}
+
+static void ppu_eval_sprites(struct nes_state *state) {
+ struct ppu_state *ppu = &state->ppu;
+
+ uint8_t scanline = ppu->scanline;
+ uint8_t height = (ppu->ctrl & PPU_CTRL_SPRITE_HEIGHT) ? 16 : 8;
+
+ uint8_t count = 0;
+
+ for(uint8_t i = 0; i < 64; i++) {
+ uint8_t *sprite = &ppu->oam[i * 4];
+ uint8_t sprite_y = sprite[0];
+
+ if(scanline >= sprite_y && scanline < sprite_y + height) {
+ if(count < 8) {
+ uint8_t *dest = &ppu->sec_oam[count * 4];
+ dest[0] = sprite[0];
+ dest[1] = sprite[1];
+ dest[2] = sprite[2];
+ dest[3] = sprite[3];
+ count++;
+ } else {
+ ppu->status |= PPU_STATUS_OVERFLOW;
+ break;
+ }
+ }
+ }
+}
+
+static void ppu_clear_sec_oam(struct nes_state *state) {
+ struct ppu_state *ppu = &state->ppu;
+ for(int i=0; i<32; i++) {
+ ppu->sec_oam[i] = 0xff;
+ }
+}
+
+static void ppu_render_sprites(struct nes_state *state)
+{
+ struct ppu_state *ppu = &state->ppu;
+
+ // Clear sprite pixel buffer
+ for(int x=0;x<256;x++) {
+ ppu->sprite_pixels[x] = 0;
+ ppu->sprite_zero_flags[x] = 0;
+ }
+
+ for(int i=0;i<8;i++) {
+ uint8_t *sprite = &ppu->sec_oam[i * 4];
+ uint8_t y = sprite[0];
+ uint8_t tile = sprite[1];
+ uint8_t attr = sprite[2];
+ uint8_t x = sprite[3];
+
+ uint8_t scanline = ppu->scanline;
+ uint8_t height = (ppu->ctrl & PPU_CTRL_SPRITE_HEIGHT) ? 16 : 8;
+ int sprite_y = scanline - y;
+
+ if(attr & 0x80) {
+ // Vertical flip
+ sprite_y = height - 1 - sprite_y;
+ }
+
+ uint32_t base = (ppu->ctrl & PPU_CTRL_SPRITE_TABLE) ? 0x1000 : 0x0000;
+ uint32_t addr = base + tile * 16 + sprite_y;
+
+ uint8_t lo = ppu_vram_read(state, addr);
+ uint8_t hi = ppu_vram_read(state, addr + 8);
+
+ for(int j=0;j<8;j++) {
+ int sx = x + (attr & 0x40 ? j : (7 - j)); // horizontal flip
+ if(sx >= 256)
+ continue;
+
+ uint8_t bit = 1 << j;
+ uint8_t p0 = (lo & (1 << (7 - j))) ? 1 : 0;
+ uint8_t p1 = (hi & (1 << (7 - j))) ? 1 : 0;
+ uint8_t color = (attr & 0x03) << 2 | (p1 << 1) | p0;
+
+ if((p0 | p1) == 0)
+ continue;
+
+ if(ppu->sprite_pixels[sx] == 0) {
+ ppu->sprite_pixels[sx] = color | 0x10;
+ if(i == 0)
+ ppu->sprite_zero_flags[sx] = 1;
+ }
+ }
+ }
+}
+
+
+static void ppu_tick(struct nes_state *state) {
+ struct ppu_state *ppu = &state->ppu;
+
+ if(ppu->scanline == 241 && ppu->dot == 1) {
+ ppu->status |= PPU_STATUS_VBLANK;
+ ppu->nmi_occurred = 1;
+ }
+
+ if(ppu->scanline == 261 && ppu->dot == 1) {
+ ppu->status &= ~PPU_STATUS_VBLANK;
+ ppu->status &= ~PPU_STATUS_SPRITE0_HIT;
+ ppu->status &= ~PPU_STATUS_OVERFLOW;
+ ppu->nmi_occurred = 0;
+ }
+
+ if((ppu->scanline < 240 || ppu->scanline == 261) && (ppu->dot >= 1 && ppu->dot <= 256)) {
+ uint32_t cycle = (ppu->dot - 1) % 8;
+
+ switch(cycle) {
+ case 0: {
+ ppu->bg_tile_lsb = (ppu->bg_tile_lsb & 0xffff0000) | ppu->tile_lsb;
+ ppu->bg_tile_msb = (ppu->bg_tile_msb & 0xffff0000) | ppu->tile_msb;
+ ppu->bg_attr_lsb = (ppu->attr_byte & 1) ? 0xff : 0x00;
+ ppu->bg_attr_msb = (ppu->attr_byte & 2) ? 0xff : 0x00;
+ } break;
+
+ case 1: {
+ ppu->nt_byte = ppu_vram_read(state, compute_nt_address(state));
+ } break;
+
+ case 3: {
+ ppu->attr_byte = ppu_vram_read(state, compute_at_address(state));
+ } break;
+
+ case 5: {
+ ppu->tile_lsb = ppu_pattern_read(state, ppu->nt_byte, ppu->fine_y, 0);
+ } break;
+
+ case 7: {
+ ppu->tile_msb = ppu_pattern_read(state, ppu->nt_byte, ppu->fine_y, 1);
+ } break;
+ }
+
+ ppu->bg_tile_lsb <<= 1;
+ ppu->bg_tile_msb <<= 1;
+ ppu->bg_attr_lsb <<= 1;
+ ppu->bg_attr_msb <<= 1;
+
+ if(ppu->scanline < 240) {
+ uint8_t p0 = (ppu->bg_tile_lsb & 0x8000) ? 1 : 0;
+ uint8_t p1 = (ppu->bg_tile_msb & 0x8000) ? 1 : 0;
+ uint8_t a0 = (ppu->bg_attr_lsb & 0x80) ? 1 : 0;
+ uint8_t a1 = (ppu->bg_attr_msb & 0x80) ? 1 : 0;
+ uint8_t color = (a1 << 3) | (a0 << 2) | (p1 << 1) | p0;
+
+ uint32_t index = ppu->scanline * 256 + (ppu->dot - 1);
+
+ uint8_t bg_color = color;
+ uint8_t sprite_color = ppu->sprite_pixels[ppu->dot - 1];
+
+ uint8_t final_color = bg_color;
+
+ if(sprite_color && ((bg_color & 3) == 0 || !(ppu->mask & 0x04))) {
+ // Sprite above BG, or BG is transparent
+ final_color = sprite_color;
+ }
+
+ if(ppu->sprite_zero_flags[ppu->dot - 1] &&
+ (bg_color & 3) && (sprite_color & 0x1f) &&
+ (ppu->mask & 0x18)) {
+ ppu->status |= PPU_STATUS_SPRITE0_HIT;
+ }
+
+ ppu->pixels[index] = final_color;
+
+ }
+ }
+
+ if(ppu->scanline < 240 && ppu->dot == 257) {
+ ppu_render_sprites(state);
+ }
+
+ if(ppu->scanline < 240 || ppu->scanline == 261) {
+ if(ppu->dot == 256) {
+ increment_x(state);
+ increment_y(state);
+ } else if(ppu->dot == 328) {
+ increment_x(state);
+ }
+ }
+
+ ppu->dot++;
+
+ if(ppu->scanline == 261 && ppu->dot == 339 && ppu->frame_even) {
+ if(ppu->mask & (PPU_MASK_SHOW_BG | PPU_MASK_SHOW_SPRITES)) {
+ ppu->dot++;
+ }
+ }
+
+ if(ppu->dot > 340) {
+ ppu->dot = 0;
+ ppu->scanline++;
+
+ if(ppu->scanline > 261) {
+ ppu->scanline = 0;
+ ppu->frame_even ^= 1;
+ }
+ }
+
+ // Clear secondary OAM at start of scanline
+ if(ppu->scanline < 240 || ppu->scanline == 261) {
+ if(ppu->dot == 1) {
+ ppu_clear_sec_oam(state);
+ }
+ }
+
+ // Perform sprite evaluation
+ if(ppu->scanline < 240 && ppu->dot == 65) {
+ ppu_eval_sprites(state);
+ }
+}
+
+
+static void ppu_dma_4014(struct nes_state *state, uint8_t page) {
+ uint32_t base = page << 8;
+
+ for(int i = 0; i < 256; i++) {
+ uint8_t value = memory_read_dma(state, base + i);
+ state->ppu.oam[i] = value;
+
+ ppu_tick(state);
+ ppu_tick(state);
+ ppu_tick(state);
+ // apu_tick(state);
+ state->cycle++;
+ }
+
+ // DMA takes 513 cycles on even CPU cycles, 514 on odd
+ int extra = (state->cycle & 1) ? 514 : 513;
+ for(int i = 0; i < extra; i++) {
+ ppu_tick(state);
+ ppu_tick(state);
+ ppu_tick(state);
+ // apu_tick(state);
+ state->cycle++;
+ }
+}