Last active
February 25, 2020 00:31
-
-
Save superllama/0d6f7f5dfbf25c817bf50ddd63159ccb to your computer and use it in GitHub Desktop.
A Chip-8 Emulator written in PowerShell
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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