feat(core): add CHIP-8 disassembler
This commit is contained in:
1 parent
096c95e2c9
commit
e18e59beb4
1 file changed
+196
@@ -0,0 +1,196 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Chip8.Core;
|
||||||
|
|
||||||
|
public readonly struct DecodedInstruction
|
||||||
|
{
|
||||||
|
public DecodedInstruction(ushort address, ushort opcode, ushort? extraWord, int byteLength, string mnemonic)
|
||||||
|
{
|
||||||
|
this.address = address;
|
||||||
|
this.opcode = opcode;
|
||||||
|
this.extraWord = extraWord;
|
||||||
|
this.byteLength = byteLength;
|
||||||
|
this.mnemonic = mnemonic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort address { get; }
|
||||||
|
public ushort opcode { get; }
|
||||||
|
public ushort? extraWord { get; }
|
||||||
|
public int byteLength { get; }
|
||||||
|
public string mnemonic { get; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
string bytes = extraWord.HasValue
|
||||||
|
? opcode.ToString("X4", CultureInfo.InvariantCulture) + " " + extraWord.Value.ToString("X4", CultureInfo.InvariantCulture)
|
||||||
|
: opcode.ToString("X4", CultureInfo.InvariantCulture);
|
||||||
|
return "0x" + address.ToString("X4", CultureInfo.InvariantCulture) + ": " + bytes.PadRight(9) + " " + mnemonic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Disassembler
|
||||||
|
{
|
||||||
|
public static bool isLongLoad(ushort opcode) => opcode == 0xF000;
|
||||||
|
|
||||||
|
public static string describe(ushort opcode, ushort extraWord = 0)
|
||||||
|
{
|
||||||
|
int x = (opcode & 0x0F00) >> 8;
|
||||||
|
int y = (opcode & 0x00F0) >> 4;
|
||||||
|
int n = opcode & 0x000F;
|
||||||
|
int nn = opcode & 0x00FF;
|
||||||
|
int nnn = opcode & 0x0FFF;
|
||||||
|
|
||||||
|
return (opcode & 0xF000) switch
|
||||||
|
{
|
||||||
|
0x0000 => describeSystem(opcode, n, nnn),
|
||||||
|
0x1000 => "JP 0x" + hex3(nnn),
|
||||||
|
0x2000 => "CALL 0x" + hex3(nnn),
|
||||||
|
0x3000 => "SE V" + hex1(x) + ", 0x" + hex2(nn),
|
||||||
|
0x4000 => "SNE V" + hex1(x) + ", 0x" + hex2(nn),
|
||||||
|
0x5000 => describeSkipRegister(opcode, x, y, n),
|
||||||
|
0x6000 => "LD V" + hex1(x) + ", 0x" + hex2(nn),
|
||||||
|
0x7000 => "ADD V" + hex1(x) + ", 0x" + hex2(nn),
|
||||||
|
0x8000 => describeArithmetic(opcode, x, y, n),
|
||||||
|
0x9000 => n == 0 ? "SNE V" + hex1(x) + ", V" + hex1(y) : dataWord(opcode),
|
||||||
|
0xA000 => "LD I, 0x" + hex3(nnn),
|
||||||
|
0xB000 => "JP V0, 0x" + hex3(nnn),
|
||||||
|
0xC000 => "RND V" + hex1(x) + ", 0x" + hex2(nn),
|
||||||
|
0xD000 => "DRW V" + hex1(x) + ", V" + hex1(y) + ", " + n,
|
||||||
|
0xE000 => describeKey(opcode, x, nn),
|
||||||
|
0xF000 => describeMisc(opcode, x, nn, extraWord),
|
||||||
|
_ => dataWord(opcode),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyList<DecodedInstruction> disassemble(byte[] program, ushort baseAddress = 0x200)
|
||||||
|
{
|
||||||
|
var lines = new List<DecodedInstruction>();
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
while (offset + 1 < program.Length)
|
||||||
|
{
|
||||||
|
ushort address = (ushort)(baseAddress + offset);
|
||||||
|
ushort opcode = (ushort)((program[offset] << 8) | program[offset + 1]);
|
||||||
|
|
||||||
|
if (isLongLoad(opcode) && offset + 3 < program.Length)
|
||||||
|
{
|
||||||
|
ushort extraWord = (ushort)((program[offset + 2] << 8) | program[offset + 3]);
|
||||||
|
lines.Add(new DecodedInstruction(address, opcode, extraWord, 4, describe(opcode, extraWord)));
|
||||||
|
offset += 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lines.Add(new DecodedInstruction(address, opcode, null, 2, describe(opcode)));
|
||||||
|
offset += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string render(byte[] program, ushort baseAddress = 0x200)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
foreach (var line in disassemble(program, baseAddress))
|
||||||
|
{
|
||||||
|
builder.Append(line).Append('\n');
|
||||||
|
}
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static string describeSystem(ushort opcode, int n, int nnn)
|
||||||
|
{
|
||||||
|
if ((opcode & 0xFFF0) == 0x00C0)
|
||||||
|
{
|
||||||
|
return "SCD " + n;
|
||||||
|
}
|
||||||
|
if ((opcode & 0xFFF0) == 0x00D0)
|
||||||
|
{
|
||||||
|
return "SCU " + n;
|
||||||
|
}
|
||||||
|
return opcode switch
|
||||||
|
{
|
||||||
|
0x00E0 => "CLS",
|
||||||
|
0x00EE => "RET",
|
||||||
|
0x00FB => "SCR",
|
||||||
|
0x00FC => "SCL",
|
||||||
|
0x00FD => "EXIT",
|
||||||
|
0x00FE => "LORES",
|
||||||
|
0x00FF => "HIRES",
|
||||||
|
_ => "SYS 0x" + hex3(nnn),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static string describeSkipRegister(ushort opcode, int x, int y, int n) => n switch
|
||||||
|
{
|
||||||
|
0x0 => "SE V" + hex1(x) + ", V" + hex1(y),
|
||||||
|
0x2 => "SAVE V" + hex1(x) + "-V" + hex1(y),
|
||||||
|
0x3 => "LOAD V" + hex1(x) + "-V" + hex1(y),
|
||||||
|
_ => dataWord(opcode),
|
||||||
|
};
|
||||||
|
|
||||||
|
static string describeArithmetic(ushort opcode, int x, int y, int n) => n switch
|
||||||
|
{
|
||||||
|
0x0 => "LD V" + hex1(x) + ", V" + hex1(y),
|
||||||
|
0x1 => "OR V" + hex1(x) + ", V" + hex1(y),
|
||||||
|
0x2 => "AND V" + hex1(x) + ", V" + hex1(y),
|
||||||
|
0x3 => "XOR V" + hex1(x) + ", V" + hex1(y),
|
||||||
|
0x4 => "ADD V" + hex1(x) + ", V" + hex1(y),
|
||||||
|
0x5 => "SUB V" + hex1(x) + ", V" + hex1(y),
|
||||||
|
0x6 => "SHR V" + hex1(x),
|
||||||
|
0x7 => "SUBN V" + hex1(x) + ", V" + hex1(y),
|
||||||
|
0xE => "SHL V" + hex1(x),
|
||||||
|
_ => dataWord(opcode),
|
||||||
|
};
|
||||||
|
|
||||||
|
static string describeKey(ushort opcode, int x, int nn) => nn switch
|
||||||
|
{
|
||||||
|
0x9E => "SKP V" + hex1(x),
|
||||||
|
0xA1 => "SKNP V" + hex1(x),
|
||||||
|
_ => dataWord(opcode),
|
||||||
|
};
|
||||||
|
|
||||||
|
static string describeMisc(ushort opcode, int x, int nn, ushort extraWord)
|
||||||
|
{
|
||||||
|
if (opcode == 0xF000)
|
||||||
|
{
|
||||||
|
return "LD I, 0x" + extraWord.ToString("X4", CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
if ((opcode & 0xF0FF) == 0xF001)
|
||||||
|
{
|
||||||
|
return "PLANE " + x;
|
||||||
|
}
|
||||||
|
if (opcode == 0xF002)
|
||||||
|
{
|
||||||
|
return "AUDIO";
|
||||||
|
}
|
||||||
|
|
||||||
|
return nn switch
|
||||||
|
{
|
||||||
|
0x07 => "LD V" + hex1(x) + ", DT",
|
||||||
|
0x0A => "LD V" + hex1(x) + ", K",
|
||||||
|
0x15 => "LD DT, V" + hex1(x),
|
||||||
|
0x18 => "LD ST, V" + hex1(x),
|
||||||
|
0x1E => "ADD I, V" + hex1(x),
|
||||||
|
0x29 => "LD F, V" + hex1(x),
|
||||||
|
0x30 => "LD HF, V" + hex1(x),
|
||||||
|
0x33 => "LD B, V" + hex1(x),
|
||||||
|
0x3A => "PITCH V" + hex1(x),
|
||||||
|
0x55 => "LD [I], V" + hex1(x),
|
||||||
|
0x65 => "LD V" + hex1(x) + ", [I]",
|
||||||
|
0x75 => "LD R, V" + hex1(x),
|
||||||
|
0x85 => "LD V" + hex1(x) + ", R",
|
||||||
|
_ => dataWord(opcode),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static string dataWord(ushort opcode) => "DW 0x" + opcode.ToString("X4", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
static string hex1(int value) => value.ToString("X1", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
static string hex2(int value) => value.ToString("X2", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
static string hex3(int value) => value.ToString("X3", CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user