using System.Collections.Generic;
static readonly byte[] ram = new byte[0xB0];
static readonly byte[] flag = {
0x4B,0x5C,0x4E,0x64,0x6D,0x69,0x7A,0x66,
0x73,0x60,0x38,0x65,0x3B,0x57,0x6B,0x38,
0x65,0x78,0x7D,0x7C,0x3B,0x7A,0x57,0x7A,
0x3B,0x7E,0x38,0x64,0x7D,0x7C,0x39,0x38,
static readonly byte[] program = {
public static byte OPCODE_0x49(byte value, byte accumulator) {
static void Main(string[] args) {
Console.WriteLine("***********************************************************************");
Console.WriteLine("* Welcome to the CTF 6502 challenge! *");
Console.WriteLine("* *");
Console.WriteLine("* This 6502 assembly program should process the flag data and make it *");
Console.WriteLine("* readable, but the data in the memory dump looks corrupted. *");
Console.WriteLine("* I guess there's a bug in the assembly program, or maybe in the *");
Console.WriteLine("* emulator? This `OPCODE_0x49`-method looks strange... *");
Console.WriteLine("***********************************************************************\n");
Console.WriteLine("- Loading flag data into memory location 0x0000 ...");
Array.Copy(flag, 0, ram, 0, flag.Length);
Console.WriteLine("- Loading program into memory location 0x0080 ...");
Array.Copy(program, 0, ram, 0x0080, program.Length);
Console.WriteLine("- Setting the program counter (PC) to 0x0080 and executing program ...\n");
var cpu = new MOS6502(ram) {
while (cpu.PC != (0x80 + program.Length)) {
Console.WriteLine("- Dumping memory...\n");
static void DumpMemory() {
for (int i = 0; i < 0xB0; i += 0x10) {
Console.Write($"0x{i:X4} ");
for (int j = 0; j < 0x10; j++) {
if (j == 0x08) Console.Write(" ");
Console.Write($"{ram[i + j]:X2} ");
for (int j = 0; j < 0x10; j++) {
var c = (char)ram[i + j];
public byte[] Memory { get; private set; }
public Dictionary<byte, OpCode> OpCodes { get; set; }
public StatusRegister SR = new StatusRegister();
public OpCode OpCode { get; set; }
return OpCode.AddressingMode == AddressingMode.Accumulator ? AR : Memory[Address];
if (OpCode.AddressingMode == AddressingMode.Accumulator) {
public MOS6502(byte[] memory) {
var opCodesMethods = GetType()
.SelectMany(m => m.GetCustomAttributes(typeof(OpCodeDefinitionAttribute), true)
Attribute = a as OpCodeDefinitionAttribute,
var addressingMethods = GetType()
.SelectMany(m => m.GetCustomAttributes(typeof(AddressingModeAttribute), true)
Attribute = a as AddressingModeAttribute,
})).ToDictionary(x => x.Attribute.AddressingMode, x => x);
var opCodes = opCodesMethods
.Select(x => OpCode.FromOpCodeDefinitionAttribute(() => x.Method.Invoke(this, null), () => addressingMethods[x.Attribute.AddressingMode].Method.Invoke(this, null), x.Attribute))
OpCodes = opCodes.ToDictionary(x => x.Code, x => x);
OpCode = OpCodes[Memory[PC]];
OpCode.OpCodeAddress = PC;
if (OpCode.AddressingMode != AddressingMode.Implied && OpCode.AddressingMode != AddressingMode.Accumulator) {
[AddressingMode(AddressingMode = AddressingMode.Immediate)]
public void Immediate() {
[AddressingMode(AddressingMode = AddressingMode.Relative)]
sbyte rel_addr = (sbyte)Memory[PC];
Address = (ushort)(PC + rel_addr);
[AddressingMode(AddressingMode = AddressingMode.Zeropage)]
[AddressingMode(AddressingMode = AddressingMode.ZeropageX)]
public void ZeropageX() {
ushort a = (ushort)(Memory[PC] + XR);
[OpCodeDefinition(Name = nameof(NOP), Code = 0x00, Length = 1, AddressingMode = AddressingMode.Implied, Description = "")]
[OpCodeDefinition(Name = nameof(LDA), Code = 0xA9, Length = 2, AddressingMode = AddressingMode.Immediate, Description = "Load Accumulator")]
[OpCodeDefinition(Name = nameof(LDA), Code = 0xB5, Length = 2, AddressingMode = AddressingMode.ZeropageX, Description = "Load Accumulator")]
[OpCodeDefinition(Name = nameof(LDX), Code = 0xA2, Length = 2, AddressingMode = AddressingMode.Immediate, Description = "Load X-register")]
[OpCodeDefinition(Name = nameof(STA), Code = 0x95, Length = 2, AddressingMode = AddressingMode.ZeropageX, Description = "Store Accumulator")]
[OpCodeDefinition(Name = nameof(OPCODE_0x49), Code = 0x49, Length = 2, AddressingMode = AddressingMode.Immediate, Description = "")]
public void OPCODE_0x49() {
AR = Program.OPCODE_0x49(Value, AR);
[OpCodeDefinition(Name = nameof(INX), Code = 0xE8, Length = 1, AddressingMode = AddressingMode.Implied, Description = "Increment X-register")]
[OpCodeDefinition(Name = nameof(BNE), Code = 0xD0, Length = 2, AddressingMode = AddressingMode.Relative, Description = "Branch on Not Equal")]
[OpCodeDefinition(Name = nameof(CPX), Code = 0xE0, Length = 2, AddressingMode = AddressingMode.Immediate, Description = "Compare X-register")]
SR.Negative = ((r >> 7) & 1) == 1;
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class AddressingModeAttribute : Attribute {
public AddressingMode AddressingMode { get; set; }
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class OpCodeDefinitionAttribute : Attribute {
public byte Code { get; set; }
public string Name { get; set; }
public ushort Length { get; set; }
public AddressingMode AddressingMode { get; set; }
public string Description { get; set; }
public enum AddressingMode {
public enum ProcessorStatusFlags : byte {
DecimalMode = 0b00001000,
BreakCommand = 0b00010000,
public class OpCode : OpCodeDefinitionAttribute {
public int OpCodeAddress { get; set; }
public Action GetAddress { get; set; }
public Action Run { get; set; }
public static OpCode FromOpCodeDefinitionAttribute(Action action, Action getAddress, OpCodeDefinitionAttribute a) {
AddressingMode = a.AddressingMode,
Description = a.Description,
public class StatusRegister {
public byte Register { get; set; }
get => Get(ProcessorStatusFlags.Carry);
set => SetValue(ProcessorStatusFlags.Carry, value);
get => Get(ProcessorStatusFlags.Zero);
set => SetValue(ProcessorStatusFlags.Zero, value);
get => Get(ProcessorStatusFlags.Negative);
set => SetValue(ProcessorStatusFlags.Negative, value);
get => Get(ProcessorStatusFlags.Reserved);
set => SetValue(ProcessorStatusFlags.Reserved, value);
public bool Get(ProcessorStatusFlags flag) {
return (Register & (byte)flag) == (byte)flag;
public void SetValue(ProcessorStatusFlags flag, bool value) {
Register = value ? Register |= (byte)flag : Register &= (byte)~flag;
public void SetNegative(byte operand) {
Negative = (operand & 0b10000000) == 0b10000000;
public void SetZero(byte operand) {