Skip to content

Instantly share code, notes, and snippets.

@superllama
Last active February 25, 2020 00:31
Show Gist options
  • Save superllama/0d6f7f5dfbf25c817bf50ddd63159ccb to your computer and use it in GitHub Desktop.
Save superllama/0d6f7f5dfbf25c817bf50ddd63159ccb to your computer and use it in GitHub Desktop.
A Chip-8 Emulator written in PowerShell
param(
[string]$rom,
[int]$cpulimit = 0,
[switch]$nowrap = $false,
[switch]$noclear = $false
)
if ($rom -eq "") {
"PowerShell Chip-8 Emulator 1.0`n"+
" by SuperLlama ([email protected])`n"+
"-"*48+"`n"+
" .\chip8.ps1 -rom C:\Path\To\ROM [-cpulimit SpeedInHz] [-nowrap] [-noclear]"
return
}
#initialize vram and ram using a rom
$scr = (new-object uInt16[] (32*4))
$ram = [byte[]](
#hex font
0xF0,0x90,0x90,0x90,0xF0,0x20,0x60,0x20,0x20,0x70,
0xF0,0x10,0xF0,0x80,0xF0,0xF0,0x10,0xF0,0x10,0xF0,
0x90,0x90,0xF0,0x10,0x10,0xF0,0x80,0xF0,0x10,0xF0,
0xF0,0x80,0xF0,0x90,0xF0,0xF0,0x10,0x20,0x40,0x40,
0xF0,0x90,0xF0,0x90,0xF0,0xF0,0x90,0xF0,0x10,0xF0,
0xF0,0x90,0xF0,0x90,0x90,0xE0,0x90,0xE0,0x90,0xE0,
0xF0,0x80,0x80,0x80,0xF0,0xE0,0x90,0x90,0x90,0xE0,
0xF0,0x80,0xF0,0x80,0xF0,0xF0,0x80,0xF0,0x80,0x80
)
$ram += (new-object byte[] (0x200 - $ram.length)) #pad to 200
$ram += Get-Content $rom -Encoding byte #load cartridge
$ram += (new-object byte[] (0x1000 - $ram.length)) #pad to 1000
#initialize registers
$pc = 0x200
$V = (new-object byte[] 0x16)
$call_stack = (new-object int16[] 0x16)
$stack_ptr = 0
$I = 0
$delay = 0
$sound = 0
#keyboard bindings
$keybind = ('X','1','2','3','Q','W','E','A','S','D','Z','C','4','R','F','V')
$o_keystate = 0
$keystate = 0
#screen drawing routine
$title = "PowerShell Chip-8 Emulator"
$title_bar = ("═"*((64-$title.Length)/2))
$frame_top = "╔"+$title_bar+$title+$title_bar+"╗"
$frame_bottom = "╚"+("═"*64)+"╝`n" +
" "*14+"┌─────────┒ ┌─────────┒`n"+
" "*14+"│ Chip8 ┃ │ Windows ┃`n"+
" "*14+"│ 1 2 3 C ┃ ━━━━━━ │ 1 2 3 4 ┃`n"+
" "*14+"│ 4 5 6 D ┃ ┃ Q W E R ┃`n"+
" "*14+"│ 7 8 9 E ┃ ━━━━━━ ┃ A S D F ┃`n"+
" "*14+"│ A 0 B F ┃ ┃ Z X C V ┃`n"+
" "*14+"┕━━━━━━━━━┛ ┕━━━━━━━━━┛`n"
$draw = {
$frame = "$frame_top`n"
for ($i = 0; $i -lt (32*4); $i+=8) {
$frame += "║"
for ($j = 0; $j -lt 4; $j++) {
$frame += (
[int64][Convert]::ToString($scr[$i+$j],2) +
2*[int64][Convert]::ToString($scr[$i+$j+4],2)
).ToString().PadLeft(16,"0").Replace(0," ").Replace(1,"▀").Replace(2,"▄").Replace(3,"█")
}
$frame += "║`n"
}
$frame += $frame_bottom
if (!$noclear) {
Clear
}
$frame
}
Add-Type -AssemblyName PresentationFramework
$last_tick = (Get-Date).Ticks
while ($true) {
$x = ($ram[$pc] -band 0xF) #_x__
$y = ($ram[$pc+1] -shr 4) #__y_
$nn = $ram[$pc+1] #__nn
$nnn = $ram[$pc+1] -bor ($x -shl 8) #_nnn
#[Convert]::ToString($ram[$pc],16).PadLeft(2,"0")+[Convert]::ToString($ram[$pc+1],16).PadLeft(2,"0") + " -> $I"
switch ($ram[$pc] -shr 4) {
0x0 {
switch ($ram[$pc+1]) {
0xE0 { #clear screen
for ($n = 0; $n -lt (32*4); $n++) {
$scr[$n] = 0;
}
&$draw
}
0xEE { #return
$stack_ptr--
$pc = $call_stack[$stack_ptr]
}
}
}
0x1 { #jump
$pc = $nnn - 2
}
0x2 { #call
$call_stack[$stack_ptr] = $pc
$stack_ptr++
$pc = $nnn - 2
}
0x3 { #se
if ($V[$x] -eq $nn) {
$pc += 2
}
}
0x4 { #sne
if ($V[$x] -ne $nn) {
$pc += 2
}
}
0x5 { #ser
if ($V[$x] -eq $V[$y]) {
$pc += 2
}
}
0x6 { #ld
$V[$x] = $nn
}
0x7 { #add
$V[$x] = ($V[$x] + $nn) -band 0xFF
}
0x8 {
switch ($nn -band 0xF) {
0 {
$V[$x] = $V[$y]
}
1 {
$V[$x] = $V[$x] -bor $V[$y]
}
2 {
$V[$x] = $V[$x] -band $V[$y]
}
3 {
$V[$x] = $V[$x] -bxor $V[$y]
}
4 {
$new_val = $V[$x] + $V[$y]
$V[$x] = $new_val -band 0xFF
if ($new_val -gt 0xFF) {
$V[0xF] = 1
} else {
$V[0xF] = 0
}
}
5 {
if ($V[$x] -gt $V[$y]) {
$V[0xF] = 1
} else {
$V[0xF] = 0
}
$V[$x] = (256 + $V[$x] - $V[$y]) % 256
}
6 {
$V[0xF] = $V[$x] -band 1
$V[$x] = $V[$x] -shr 1
}
7 {
if ($V[$y] -gt $V[$x]) {
$V[0xF] = 1
} else {
$V[0xF] = 0
}
$V[$x] = (256 + $V[$y] - $V[$x]) % 256
}
0xE {
$V[0xF] = $V[$x] -band 1
$V[$x] = $V[$x] -shl 1
}
}
}
0x9 { #sner
if ($V[$x] -ne $V[$y]) {
$pc += 2
}
}
0xA { #addr
$I = $nnn
}
0xB { #jmp offset
$pc = $nnn + $V[0] - 2
}
0xC {
$V[$x] = (Get-Random) -band $nn
}
0xD {
$x = $V[$x]
$y = $V[$y]
if (!$nowrap) {
$x = $x % 64
$y = $y % 64
}
$V[0xF] = 0
for ($n = 0; $n -lt ($nn -band 0xF); $n++) {
$spr_line = ([uInt16]$ram[$I+$n] -shl 8)
$c = (($y+$n)%32)*4+[Math]::Floor($x/16)
if ($nowrap -and (($y+$n -ge 32) -or ($y+$n) -lt 0)) {
continue
}
$sub_line = ($spr_line -shr ($x % 16))
if ($scr[$c] -band $sub_line) {
$V[0xF] = 1
}
$scr[$c] = $scr[$c] -bxor $sub_line
if ($x % 16 -gt 8) {
$c++
if ($x -gt 64-16) {
$c-=4
if ($nowrap) {
continue
}
}
$sub_line = ($spr_line -shl (16 - ($x % 16)))
if ($scr[$c] -band $sub_line) {
$V[0xF] = 1
}
$scr[$c] = $scr[$c] -bxor $sub_line
}
}
&$draw
}
0xE {
switch ($nn) {
0x9E {
if ([System.Windows.Input.Keyboard]::IsKeyDown($keybind[$V[$x]])) {
$pc += 2
}
}
0xA1 {
if (![System.Windows.Input.Keyboard]::IsKeyDown($keybind[$V[$x]])) {
$pc += 2
}
}
}
}
0xF {
switch ($nn) {
0x07 {
$V[$x] = $delay
}
0x0A {
$wait = $true
for ($k = 0; $k -lt 0xF; $k++) {
$bit = (1 -shl $k)
if ([System.Windows.Input.Keyboard]::IsKeyDown($keybind[$k])) {
$keystate = ($keystate -bor $bit)
if (($o_keystate -band $bit) -eq 0) {
$V[$x] = $k
$wait = $false
}
} else {
$keystate = ($keystate -band (-bnot $bit))
}
}
if ($wait) {
$pc -= 2
} else {
$V[$x]
}
}
0x15 {
$delay = $V[$x]
}
0x18 {
$sound = $V[$x]
}
0x1E {
$I += $V[$x]
}
0x29 {
$I = $V[$x]*5
}
0x33 {
$ram[$I] = [Math]::Floor($V[$x]/100)
$ram[$I+1] = [Math]::Floor($V[$x]/10)%10
$ram[$I+2] = $V[$x]%10
}
0x55 {
for ($n = 0; $n -le $x; $n++) {
$ram[$I+$n] = $V[$n]
}
}
0x65 {
for ($n = 0; $n -le $x; $n++) {
$V[$n] = $ram[$I+$n]
}
}
}
}
}
$pc += 2
do {
$tick = (Get-Date).Ticks
} while ($cpulimit -ne 0 -and ($tick - $last_tick) -lt (10000000/$cpulimit))
$rate = 60*($tick - $last_tick)/10000000
$last_tick = $tick
$delay -= $rate
if ($delay -lt 0) { $delay = 0 }
$sound -= $rate
if ($sound -lt 0) { $sound = 0 }
$o_keystate = $keystate
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment