Skip to content

Instantly share code, notes, and snippets.

@etscrivner
Created October 14, 2013 18:55
Show Gist options
  • Save etscrivner/6980266 to your computer and use it in GitHub Desktop.
Save etscrivner/6980266 to your computer and use it in GitHub Desktop.
/****************************************
* Disassembler for Chip8 binary file format.
*
* Author: Eric Scrivner (eric@shift.com)
*/
import core.exception;
import std.exception;
import std.stdio;
import std.string;
import std.file : file_exists = exists;
/** The maximum number of bytes to read from a file at once */
immutable uint FILE_CHUNK_SIZE = 4096;
/****************************************
* Assembles a correctly byte ordered instruction from the two component
* bytes.
*
* This method constructs a new `ushort' value from two `ubyte' values and is
* used to assemble instructions read from a Chip8 program file.
*
* Params:
* msb = the most significant byte
* lsb = the least significant byte
*
* Returns: The fully assembled instruction
*/
pure ushort assemble_instruction(immutable ubyte msb, immutable ubyte lsb)
body {
return msb << 8 | lsb;
}
unittest {
assert(assemble_instruction(0xFF, 0xAA) == 0xFFAA);
}
/****************************************
* Print out the instruction indicated by the given 16-bit value.
*
* Params:
* instruction = The instruction to be printed
*
* Returns: The string equivalent of the given instruction.
*
*/
string instruction_to_string(immutable ushort instruction)
body {
switch(instruction & 0xF000) {
default:
return "UNKNOWN";
case 0x0000:
switch (instruction & 0x00FF) {
case 0xE0:
return "CLS";
case 0xEE:
return "RET";
default:
return format("SYS %03X", instruction & 0x0FFF);
}
break;
case 0x1000:
return format("JP %03X", instruction & 0x0FFF);
case 0x2000:
return format("CALL %03X", instruction & 0x0FFF);
case 0x3000:
return format("SE V%01X, %02X", (instruction >> 8) & 0xF, instruction & 0xFF);
}
}
unittest {
assert(instruction_to_string(0x00E0) == "CLS");
assert(instruction_to_string(0x00EE) == "RET");
assert(instruction_to_string(0x01AB) == "SYS 1AB");
assert(instruction_to_string(0x1ABC) == "JP ABC");
assert(instruction_to_string(0x2ABC) == "CALL ABC");
assert(instruction_to_string(0x3F23) == "SE VF, 23");
}
/****************************************
* Displays the disassembled format of the given file assuming it is a Chp8
* ROM file.
*
* Params:
* file = The file to be disassembled
*
*/
void disassemble_file(File file)
in {
assert(file.isOpen(), "File is not open");
assert(file.size % 2 == 0, "File does not contain an even number of bytes");
}
body {
foreach (ubyte[] buffer; file.byChunk(FILE_CHUNK_SIZE)) {
for (uint i = 0; i < buffer.length; i += 2) {
writeln(i, " ", i + 1, "(", buffer.length, ")");
auto next_instruction = assemble_instruction(buffer[i], buffer[i + 1]);
writeln(instruction_to_string(next_instruction));
}
}
}
unittest {
assertThrown!AssertError(disassemble_file(File()));
}
void main(string args[])
in {
assert(args.length > 1, "At least one command line argument required.");
assert(file_exists(args[1]), format("File `%s' does not exist.", args[1]));
}
body {
auto rom_file = File(args[1]);
disassemble_file(rom_file);
}
unittest {
assertThrown!AssertError(main([]));
assertThrown!AssertError(main(["badErroryName.d"]));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment