Add disassembler
diff --git a/cpu.c b/cpu.c
new file mode 100644
index 0000000..f0d7d68
--- /dev/null
+++ b/cpu.c
@@ -0,0 +1,204 @@
+#include "cpu.h"
+#include "instructions.h"
+
+#include <endian.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define die(m, ...) \
+	printf("\033[31mError: " m "\033[0m\n", ##__VA_ARGS__); \
+	exit(1);
+
+cpu_t new_cpu()
+{
+	cpu_t cpu = { 0 };
+	cpu.regs[SR] = UNUSED; // unused flag always set
+	cpu.regs[SP] = 0xFD; // stack at is 0x100 + SP
+	cpu.pc = 0; // arbitrary program counter start
+	cpu.mem = malloc(0xFFFF);
+	memset(cpu.mem, 0, 0xFFFF);
+
+	if (!cpu.mem)
+	{
+		die("Could not allocate memory for CPU");
+	}
+
+	return cpu;
+}
+
+void free_cpu(cpu_t *cpu)
+{
+	free(cpu->mem);
+}
+
+void execute(cpu_t *cpu, const char *mnemonic, uint8_t op, uint16_t addr)
+{
+
+}
+
+uint16_t le_to_native(uint8_t a, uint8_t b)
+{
+#ifdef LITTLE_ENDIAN
+	return a << 8 | b;
+#else
+	return b << 8 | a;
+#endif
+}
+
+uint16_t fetch_le(cpu_t *cpu)
+{
+	uint8_t a = cpu->mem[cpu->pc++];
+	uint8_t b = cpu->mem[cpu->pc++];
+	return le_to_native(a, b);
+}
+
+uint16_t fetch_addr(cpu_t *cpu, uint8_t am)
+{
+	switch (am)
+	{
+		case AM_ACC:
+		case AM_IMP:
+			return 0;
+
+		// In both cases return immediate 8 bit value
+		case AM_IMM:
+		case AM_ZP:
+			return cpu->mem[cpu->pc++];
+
+		case AM_ABS:
+			return fetch_le(cpu); 
+
+		case AM_REL:
+		{
+			// PC should point to the opcode
+			// braces needed to avoid c stupidity
+			uint16_t pc = cpu->pc - 1;
+			return cpu->mem[cpu->pc++] + pc;
+		}
+
+		case AM_IND:
+		{
+			uint16_t addr = fetch_le(cpu);
+			uint8_t low = cpu->mem[addr],
+				high = cpu->mem[addr + 1];
+
+			return le_to_native(low, high);
+		}
+
+		case AM_AX:
+			return fetch_le(cpu) + cpu->regs[X];
+
+		case AM_AY:
+			return fetch_le(cpu) + cpu->regs[Y];
+
+		case AM_ZPX:
+			return cpu->mem[cpu->pc++] + cpu->regs[X];
+
+		case AM_ZPY:
+			return cpu->mem[cpu->pc++] + cpu->regs[Y];
+
+		case AM_ZIX:
+		{
+			uint8_t zp = cpu->mem[cpu->pc++];
+			return le_to_native(cpu->mem[zp + cpu->regs[X]], cpu->mem[zp + cpu->regs[X] + 1]);
+		}
+
+		case AM_ZIY:
+		{
+			uint8_t zp = cpu->mem[cpu->pc++];
+			uint16_t base = le_to_native(cpu->mem[zp], cpu->mem[zp + 1]);
+			return base + cpu->regs[Y];
+		}
+
+		default:
+			die("Unknown address mode %x", am);
+			return -1; // unreachable
+	}
+}
+
+void step(cpu_t *cpu)
+{
+	switch (cpu->mem[cpu->pc++])
+	{
+#define INST(mn, am, op) \
+		case op: \
+			execute(cpu, #mn, mn, fetch_addr(cpu, am)); \
+			break;
+
+		INSTRUCTIONS
+
+#undef INST
+
+		default:
+			die("Undefined opcode");
+	}
+}
+
+void dump_inst(cpu_t *cpu, const char *mn, uint16_t addr, uint8_t am)
+{
+	printf("\t%s\t", mn);
+
+	switch (am)
+	{
+		case AM_IMM:
+			printf("#");
+		case AM_REL:
+		case AM_ABS:
+		case AM_ZP:
+			printf("$%x", addr);
+			break;
+
+		case AM_IND:
+			printf("($%x)", addr);
+			break;
+
+		case AM_AX:
+		case AM_ZPX:
+			printf("$%x, X", addr);
+			break;
+
+		case AM_AY:
+		case AM_ZPY:
+			printf("$%x, Y", addr);
+			break;
+
+		case AM_ZIX:
+			printf("($%x, X)", addr);
+			break;
+
+		case AM_ZIY:
+			printf("($%x), Y", addr);
+			break;
+	}
+
+	printf("\n");
+}
+
+void disas_step(cpu_t *cpu)
+{
+	printf("%x", cpu->pc);
+	uint8_t op = cpu->mem[cpu->pc++];
+	switch (op)
+	{
+#define INST(mn, am, op) \
+		case op: \
+			dump_inst(cpu, #mn, fetch_addr(cpu, am), am); \
+			break;
+
+		INSTRUCTIONS
+
+#undef INST
+
+		default:
+			die("Undefined opcode %x", op);
+	}
+}
+
+void disas(cpu_t *cpu)
+{
+	while (cpu->pc < 0xFFFF)
+	{
+		disas_step(cpu);
+	}
+}