diff options
Diffstat (limited to 'src/GameBoy')
-rw-r--r-- | src/GameBoy/Canvas/DrawContextInterface.php | 18 | ||||
-rw-r--r-- | src/GameBoy/Canvas/TerminalCanvas.php | 58 | ||||
-rw-r--r-- | src/GameBoy/Cbopcode.php | 1145 | ||||
-rw-r--r-- | src/GameBoy/Core.php | 2860 | ||||
-rw-r--r-- | src/GameBoy/Keyboard.php | 63 | ||||
-rw-r--r-- | src/GameBoy/Opcode.php | 1985 | ||||
-rw-r--r-- | src/GameBoy/Settings.php | 72 | ||||
-rw-r--r-- | src/GameBoy/TICKTables.php | 55 |
8 files changed, 6256 insertions, 0 deletions
diff --git a/src/GameBoy/Canvas/DrawContextInterface.php b/src/GameBoy/Canvas/DrawContextInterface.php new file mode 100644 index 0000000..73f0c43 --- /dev/null +++ b/src/GameBoy/Canvas/DrawContextInterface.php @@ -0,0 +1,18 @@ +<?php +namespace GameBoy\Canvas; + +/** + * Interface to draw the GameBoy output + * GameBoy screen size: 160 x 144 + */ +interface DrawContextInterface +{ + /** + * Draw image on canvas + * + * @param Array $canvasBuffer Each pixel => 4 items on array (RGBA) + * @param int $left + * @param int $top + */ + public function draw($canvasBuffer, $left, $top); +}
\ No newline at end of file diff --git a/src/GameBoy/Canvas/TerminalCanvas.php b/src/GameBoy/Canvas/TerminalCanvas.php new file mode 100644 index 0000000..c3a4ac9 --- /dev/null +++ b/src/GameBoy/Canvas/TerminalCanvas.php @@ -0,0 +1,58 @@ +<?php +namespace GameBoy\Canvas; + +use Drawille\Canvas; +use GameBoy\Settings; + +class TerminalCanvas implements DrawContextInterface +{ + protected $canvas; + protected $currentSecond = 0; + protected $framesInSecond = 0; + protected $fps = 0; + + public function __construct() + { + $this->canvas = new Canvas(); + } + + /** + * Draw image on canvas using braille font + * + * @param Object $canvasBuffer $data = Each pixel = 4 items on array (RGBA) + * @param int $left + * @param int $top + */ + public function draw($canvasBuffer, $left, $top) + { + //Corner pixel, to draw same size each time + $this->canvas->set(0, 0); + $this->canvas->set(159, 143); + + for ($i = 0; $i < count($canvasBuffer); $i = $i + 4) { + // Sum of all colors, Ignore alpha + $total = $canvasBuffer[$i] + $canvasBuffer[$i + 1] + $canvasBuffer[$i + 2]; + + $x = ($i / 4) % 160; + $y = ceil(($i / 4) / 160); + + // 350 is a good threshold for black and white + if ($total > 350) { + $this->canvas->set($x, $y); + } + } + + if ($this->currentSecond != time()) { + $this->fps = $this->framesInSecond; + $this->currentSecond = time(); + $this->framesInSecond = 1; + } else { + $this->framesInSecond++; + } + + echo "\e[H\e[2J"; + echo 'FPS: ' . $this->fps . ' - Frame Skip: ' . Settings::$settings[4] . PHP_EOL; + echo $this->canvas->frame(); + $this->canvas->clear(); + } +}
\ No newline at end of file diff --git a/src/GameBoy/Cbopcode.php b/src/GameBoy/Cbopcode.php new file mode 100644 index 0000000..062a691 --- /dev/null +++ b/src/GameBoy/Cbopcode.php @@ -0,0 +1,1145 @@ +<?php +namespace GameBoy; + +class Cbopcode +{ + public $functionsArray = []; + + public function __construct() + { + //#0x00: + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerB & 0x80) == 0x80); + $parentObj->registerB = (($parentObj->registerB << 1) & 0xFF) + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerB == 0); + }; + + //#0x01: + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerC & 0x80) == 0x80); + $parentObj->registerC = (($parentObj->registerC << 1) & 0xFF) + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerC == 0); + }; + //#0x02: + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerD & 0x80) == 0x80); + $parentObj->registerD = (($parentObj->registerD << 1) & 0xFF) + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerD == 0); + }; + //#0x03: + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerE & 0x80) == 0x80); + $parentObj->registerE = (($parentObj->registerE << 1) & 0xFF) + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerE == 0); + }; + //#0x04: + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registersHL & 0x8000) == 0x8000); + $parentObj->registersHL = (($parentObj->registersHL << 1) & 0xFE00) + (($parentObj->FCarry) ? 0x100 : 0) + ($parentObj->registersHL & 0xFF); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registersHL <= 0xFF); + }; + //#0x05: + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registersHL & 0x80) == 0x80); + $parentObj->registersHL = ($parentObj->registersHL & 0xFF00) + (($parentObj->registersHL << 1) & 0xFF) + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0xFF) == 0x00); + }; + //#0x06: + $this->functionsArray[] = function ($parentObj) { + $temp_var = $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + $parentObj->FCarry = (($temp_var & 0x80) == 0x80); + $temp_var = (($temp_var << 1) & 0xFF) + (($parentObj->FCarry) ? 1 : 0); + $parentObj->memoryWrite($parentObj->registersHL, $temp_var); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($temp_var == 0x00); + }; + //#0x07: + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerA & 0x80) == 0x80); + $parentObj->registerA = (($parentObj->registerA << 1) & 0xFF) + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerA == 0x00); + }; + //#0x08: + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerB & 0x01) == 0x01); + $parentObj->registerB = (($parentObj->FCarry) ? 0x80 : 0) + ($parentObj->registerB >> 1); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerB == 0); + }; + //#0x09: + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerC & 0x01) == 0x01); + $parentObj->registerC = (($parentObj->FCarry) ? 0x80 : 0) + ($parentObj->registerC >> 1); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerC == 0); + }; + //#0x0A: + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerD & 0x01) == 0x01); + $parentObj->registerD = (($parentObj->FCarry) ? 0x80 : 0) + ($parentObj->registerD >> 1); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerD == 0); + }; + //#0x0B: + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerE & 0x01) == 0x01); + $parentObj->registerE = (($parentObj->FCarry) ? 0x80 : 0) + ($parentObj->registerE >> 1); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerE == 0); + }; + //#0x0C: + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registersHL & 0x0100) == 0x0100); + $parentObj->registersHL = (($parentObj->FCarry) ? 0x8000 : 0) + (($parentObj->registersHL >> 1) & 0xFF00) + ($parentObj->registersHL & 0xFF); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registersHL <= 0xFF); + }; + //#0x0D: + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registersHL & 0x01) == 0x01); + $parentObj->registersHL = ($parentObj->registersHL & 0xFF00) + (($parentObj->FCarry) ? 0x80 : 0) + (($parentObj->registersHL & 0xFF) >> 1); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0xFF) == 0x00); + }; + //#0x0E: + $this->functionsArray[] = function ($parentObj) { + $temp_var = $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + $parentObj->FCarry = (($temp_var & 0x01) == 0x01); + $temp_var = (($parentObj->FCarry) ? 0x80 : 0) + ($temp_var >> 1); + $parentObj->memoryWrite($parentObj->registersHL, $temp_var); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($temp_var == 0x00); + }; + //#0x0F: + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerA & 0x01) == 0x01); + $parentObj->registerA = (($parentObj->FCarry) ? 0x80 : 0) + ($parentObj->registerA >> 1); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerA == 0x00); + }; + //#0x10: + $this->functionsArray[] = function ($parentObj) { + $newFCarry = (($parentObj->registerB & 0x80) == 0x80); + $parentObj->registerB = (($parentObj->registerB << 1) & 0xFF) + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FCarry = $newFCarry; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerB == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $newFCarry = (($parentObj->registerC & 0x80) == 0x80); + $parentObj->registerC = (($parentObj->registerC << 1) & 0xFF) + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FCarry = $newFCarry; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerC == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $newFCarry = (($parentObj->registerD & 0x80) == 0x80); + $parentObj->registerD = (($parentObj->registerD << 1) & 0xFF) + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FCarry = $newFCarry; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerD == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $newFCarry = (($parentObj->registerE & 0x80) == 0x80); + $parentObj->registerE = (($parentObj->registerE << 1) & 0xFF) + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FCarry = $newFCarry; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerE == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $newFCarry = (($parentObj->registersHL & 0x8000) == 0x8000); + $parentObj->registersHL = (($parentObj->registersHL << 1) & 0xFE00) + (($parentObj->FCarry) ? 0x100 : 0) + ($parentObj->registersHL & 0xFF); + $parentObj->FCarry = $newFCarry; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registersHL <= 0xFF); + }; + $this->functionsArray[] = function ($parentObj) { + $newFCarry = (($parentObj->registersHL & 0x80) == 0x80); + $parentObj->registersHL = ($parentObj->registersHL & 0xFF00) + (($parentObj->registersHL << 1) & 0xFF) + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FCarry = $newFCarry; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0xFF) == 0x00); + }; + $this->functionsArray[] = function ($parentObj) { + $temp_var = $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + $newFCarry = (($temp_var & 0x80) == 0x80); + $temp_var = (($temp_var << 1) & 0xFF) + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FCarry = $newFCarry; + $parentObj->memoryWrite($parentObj->registersHL, $temp_var); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($temp_var == 0x00); + }; + $this->functionsArray[] = function ($parentObj) { + $newFCarry = (($parentObj->registerA & 0x80) == 0x80); + $parentObj->registerA = (($parentObj->registerA << 1) & 0xFF) + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FCarry = $newFCarry; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerA == 0x00); + }; + $this->functionsArray[] = function ($parentObj) { + $newFCarry = (($parentObj->registerB & 0x01) == 0x01); + $parentObj->registerB = (($parentObj->FCarry) ? 0x80 : 0) + ($parentObj->registerB >> 1); + $parentObj->FCarry = $newFCarry; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerB == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $newFCarry = (($parentObj->registerC & 0x01) == 0x01); + $parentObj->registerC = (($parentObj->FCarry) ? 0x80 : 0) + ($parentObj->registerC >> 1); + $parentObj->FCarry = $newFCarry; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerC == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $newFCarry = (($parentObj->registerD & 0x01) == 0x01); + $parentObj->registerD = (($parentObj->FCarry) ? 0x80 : 0) + ($parentObj->registerD >> 1); + $parentObj->FCarry = $newFCarry; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerD == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $newFCarry = (($parentObj->registerE & 0x01) == 0x01); + $parentObj->registerE = (($parentObj->FCarry) ? 0x80 : 0) + ($parentObj->registerE >> 1); + $parentObj->FCarry = $newFCarry; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerE == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $newFCarry = (($parentObj->registersHL & 0x0100) == 0x0100); + $parentObj->registersHL = (($parentObj->FCarry) ? 0x8000 : 0) + (($parentObj->registersHL >> 1) & 0xFF00) + ($parentObj->registersHL & 0xFF); + $parentObj->FCarry = $newFCarry; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registersHL <= 0xFF); + }; + $this->functionsArray[] = function ($parentObj) { + $newFCarry = (($parentObj->registersHL & 0x01) == 0x01); + $parentObj->registersHL = ($parentObj->registersHL & 0xFF00) + (($parentObj->FCarry) ? 0x80 : 0) + (($parentObj->registersHL & 0xFF) >> 1); + $parentObj->FCarry = $newFCarry; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0xFF) == 0x00); + }; + $this->functionsArray[] = function ($parentObj) { + $temp_var = $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + $newFCarry = (($temp_var & 0x01) == 0x01); + $temp_var = (($parentObj->FCarry) ? 0x80 : 0) + ($temp_var >> 1); + $parentObj->FCarry = $newFCarry; + $parentObj->memoryWrite($parentObj->registersHL, $temp_var); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($temp_var == 0x00); + }; + $this->functionsArray[] = function ($parentObj) { + $newFCarry = (($parentObj->registerA & 0x01) == 0x01); + $parentObj->registerA = (($parentObj->FCarry) ? 0x80 : 0) + ($parentObj->registerA >> 1); + $parentObj->FCarry = $newFCarry; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerA == 0x00); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerB & 0x80) == 0x80); + $parentObj->registerB = ($parentObj->registerB << 1) & 0xFF; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerB == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerC & 0x80) == 0x80); + $parentObj->registerC = ($parentObj->registerC << 1) & 0xFF; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerC == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerD & 0x80) == 0x80); + $parentObj->registerD = ($parentObj->registerD << 1) & 0xFF; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerD == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerE & 0x80) == 0x80); + $parentObj->registerE = ($parentObj->registerE << 1) & 0xFF; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerE == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registersHL & 0x8000) == 0x8000); + $parentObj->registersHL = (($parentObj->registersHL << 1) & 0xFE00) + ($parentObj->registersHL & 0xFF); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registersHL <= 0xFF); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registersHL & 0x0080) == 0x0080); + $parentObj->registersHL = ($parentObj->registersHL & 0xFF00) + (($parentObj->registersHL << 1) & 0xFF); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0xFF) == 0x00); + }; + $this->functionsArray[] = function ($parentObj) { + $temp_var = $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + $parentObj->FCarry = (($temp_var & 0x80) == 0x80); + $temp_var = ($temp_var << 1) & 0xFF; + $parentObj->memoryWrite($parentObj->registersHL, $temp_var); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($temp_var == 0x00); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerA & 0x80) == 0x80); + $parentObj->registerA = ($parentObj->registerA << 1) & 0xFF; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerA == 0x00); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerB & 0x01) == 0x01); + $parentObj->registerB = ($parentObj->registerB & 0x80) + ($parentObj->registerB >> 1); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerB == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerC & 0x01) == 0x01); + $parentObj->registerC = ($parentObj->registerC & 0x80) + ($parentObj->registerC >> 1); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerC == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerD & 0x01) == 0x01); + $parentObj->registerD = ($parentObj->registerD & 0x80) + ($parentObj->registerD >> 1); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerD == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerE & 0x01) == 0x01); + $parentObj->registerE = ($parentObj->registerE & 0x80) + ($parentObj->registerE >> 1); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerE == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registersHL & 0x0100) == 0x0100); + $parentObj->registersHL = (($parentObj->registersHL >> 1) & 0xFF00) + ($parentObj->registersHL & 0x80FF); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registersHL <= 0xFF); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registersHL & 0x0001) == 0x0001); + $parentObj->registersHL = ($parentObj->registersHL & 0xFF80) + (($parentObj->registersHL & 0xFF) >> 1); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0xFF) == 0x00); + }; + $this->functionsArray[] = function ($parentObj) { + $temp_var = $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + $parentObj->FCarry = (($temp_var & 0x01) == 0x01); + $temp_var = ($temp_var & 0x80) + ($temp_var >> 1); + $parentObj->memoryWrite($parentObj->registersHL, $temp_var); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($temp_var == 0x00); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerA & 0x01) == 0x01); + $parentObj->registerA = ($parentObj->registerA & 0x80) + ($parentObj->registerA >> 1); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerA == 0x00); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB = (($parentObj->registerB & 0xF) << 4) + ($parentObj->registerB >> 4); + $parentObj->FZero = ($parentObj->registerB == 0); + $parentObj->FCarry = $parentObj->FHalfCarry = $parentObj->FSubtract = false; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC = (($parentObj->registerC & 0xF) << 4) + ($parentObj->registerC >> 4); + $parentObj->FZero = ($parentObj->registerC == 0); + $parentObj->FCarry = $parentObj->FHalfCarry = $parentObj->FSubtract = false; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD = (($parentObj->registerD & 0xF) << 4) + ($parentObj->registerD >> 4); + $parentObj->FZero = ($parentObj->registerD == 0); + $parentObj->FCarry = $parentObj->FHalfCarry = $parentObj->FSubtract = false; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE = (($parentObj->registerE & 0xF) << 4) + ($parentObj->registerE >> 4); + $parentObj->FZero = ($parentObj->registerE == 0); + $parentObj->FCarry = $parentObj->FHalfCarry = $parentObj->FSubtract = false; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = (($parentObj->registersHL & 0xF00) << 4) + (($parentObj->registersHL & 0xF000) >> 4) + ($parentObj->registersHL & 0xFF); + $parentObj->FZero = ($parentObj->registersHL <= 0xFF); + $parentObj->FCarry = $parentObj->FHalfCarry = $parentObj->FSubtract = false; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = ($parentObj->registersHL & 0xFF00) + (($parentObj->registersHL & 0xF) << 4) + (($parentObj->registersHL & 0xF0) >> 4); + $parentObj->FZero = (($parentObj->registersHL & 0xFF) == 0); + $parentObj->FCarry = $parentObj->FHalfCarry = $parentObj->FSubtract = false; + }; + $this->functionsArray[] = function ($parentObj) { + $temp_var = $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + $temp_var = (($temp_var & 0xF) << 4) + ($temp_var >> 4); + $parentObj->memoryWrite($parentObj->registersHL, $temp_var); + $parentObj->FZero = ($temp_var == 0); + $parentObj->FCarry = $parentObj->FHalfCarry = $parentObj->FSubtract = false; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA = (($parentObj->registerA & 0xF) << 4) + ($parentObj->registerA >> 4); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FCarry = $parentObj->FHalfCarry = $parentObj->FSubtract = false; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerB & 0x01) == 0x01); + $parentObj->registerB >>= 1; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerB == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerC & 0x01) == 0x01); + $parentObj->registerC >>= 1; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerC == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerD & 0x01) == 0x01); + $parentObj->registerD >>= 1; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerD == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerE & 0x01) == 0x01); + $parentObj->registerE >>= 1; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerE == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registersHL & 0x0100) == 0x0100); + $parentObj->registersHL = (($parentObj->registersHL >> 1) & 0xFF00) + ($parentObj->registersHL & 0xFF); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registersHL <= 0xFF); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registersHL & 0x0001) == 0x0001); + $parentObj->registersHL = ($parentObj->registersHL & 0xFF00) + (($parentObj->registersHL & 0xFF) >> 1); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0xFF) == 0x00); + }; + $this->functionsArray[] = function ($parentObj) { + $temp_var = $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + $parentObj->FCarry = (($temp_var & 0x01) == 0x01); + $parentObj->memoryWrite($parentObj->registersHL, $temp_var >>= 1); + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($temp_var == 0x00); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerA & 0x01) == 0x01); + $parentObj->registerA >>= 1; + $parentObj->FHalfCarry = $parentObj->FSubtract = false; + $parentObj->FZero = ($parentObj->registerA == 0x00); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerB & 0x01) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerC & 0x01) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerD & 0x01) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerE & 0x01) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0x0100) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0x0001) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) & 0x01) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerA & 0x01) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerB & 0x02) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerC & 0x02) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerD & 0x02) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerE & 0x02) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0x0200) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0x0002) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) & 0x02) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerA & 0x02) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerB & 0x04) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerC & 0x04) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerD & 0x04) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerE & 0x04) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0x0400) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0x0004) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) & 0x04) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerA & 0x04) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerB & 0x08) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerC & 0x08) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerD & 0x08) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerE & 0x08) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0x0800) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0x0008) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) & 0x08) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerA & 0x08) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerB & 0x10) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerC & 0x10) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerD & 0x10) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerE & 0x10) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0x1000) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0x0010) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) & 0x10) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerA & 0x10) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerB & 0x20) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerC & 0x20) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerD & 0x20) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerE & 0x20) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0x2000) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0x0020) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) & 0x20) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerA & 0x20) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerB & 0x40) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerC & 0x40) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerD & 0x40) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerE & 0x40) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0x4000) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0x0040) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) & 0x40) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerA & 0x40) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerB & 0x80) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerC & 0x80) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerD & 0x80) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerE & 0x80) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0x8000) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registersHL & 0x0080) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) & 0x80) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = false; + $parentObj->FZero = (($parentObj->registerA & 0x80) == 0); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB &= 0xFE; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC &= 0xFE; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD &= 0xFE; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE &= 0xFE; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL &= 0xFEFF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL &= 0xFFFE; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) & 0xFE); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA &= 0xFE; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB &= 0xFD; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC &= 0xFD; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD &= 0xFD; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE &= 0xFD; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL &= 0xFDFF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL &= 0xFFFD; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) & 0xFD); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA &= 0xFD; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB &= 0xFB; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC &= 0xFB; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD &= 0xFB; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE &= 0xFB; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL &= 0xFBFF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL &= 0xFFFB; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) & 0xFB); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA &= 0xFB; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB &= 0xF7; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC &= 0xF7; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD &= 0xF7; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE &= 0xF7; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL &= 0xF7FF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL &= 0xFFF7; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) & 0xF7); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA &= 0xF7; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB &= 0xEF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC &= 0xEF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD &= 0xEF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE &= 0xEF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL &= 0xEFFF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL &= 0xFFEF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) & 0xEF); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA &= 0xEF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB &= 0xDF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC &= 0xDF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD &= 0xDF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE &= 0xDF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL &= 0xDFFF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL &= 0xFFDF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) & 0xDF); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA &= 0xDF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB &= 0xBF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC &= 0xBF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD &= 0xBF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE &= 0xBF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL &= 0xBFFF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL &= 0xFFBF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) & 0xBF); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA &= 0xBF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB &= 0x7F; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC &= 0x7F; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD &= 0x7F; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE &= 0x7F; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL &= 0x7FFF; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL &= 0xFF7F; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) & 0x7F); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA &= 0x7F; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB |= 0x01; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC |= 0x01; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD |= 0x01; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE |= 0x01; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL |= 0x0100; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL |= 0x01; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) | 0x01); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA |= 0x01; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB |= 0x02; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC |= 0x02; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD |= 0x02; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE |= 0x02; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL |= 0x0200; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL |= 0x02; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) | 0x02); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA |= 0x02; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB |= 0x04; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC |= 0x04; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD |= 0x04; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE |= 0x04; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL |= 0x0400; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL |= 0x04; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) | 0x04); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA |= 0x04; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB |= 0x08; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC |= 0x08; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD |= 0x08; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE |= 0x08; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL |= 0x0800; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL |= 0x08; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) | 0x08); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA |= 0x08; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB |= 0x10; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC |= 0x10; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD |= 0x10; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE |= 0x10; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL |= 0x1000; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL |= 0x10; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) | 0x10); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA |= 0x10; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB |= 0x20; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC |= 0x20; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD |= 0x20; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE |= 0x20; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL |= 0x2000; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL |= 0x20; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) | 0x20); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA |= 0x20; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB |= 0x40; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC |= 0x40; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD |= 0x40; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE |= 0x40; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL |= 0x4000; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL |= 0x40; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) | 0x40); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA |= 0x40; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB |= 0x80; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC |= 0x80; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD |= 0x80; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE |= 0x80; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL |= 0x8000; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL |= 0x80; + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) | 0x80); + }; + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA |= 0x80; + }; + } + + public function get() + { + return $this->functionsArray; + } +}
\ No newline at end of file diff --git a/src/GameBoy/Core.php b/src/GameBoy/Core.php new file mode 100644 index 0000000..97097f2 --- /dev/null +++ b/src/GameBoy/Core.php @@ -0,0 +1,2860 @@ +<?php +namespace GameBoy; + +class Core +{ + // LCD Context + public $drawContext = null; + + //The game's ROM. + public $ROMImage; + + //The full ROM file dumped to an array. + public $ROM = []; + + //Whether we're in the GBC boot ROM. + public $inBootstrap = true; + + //Updated upon ROM loading... + public $usedBootROM = false; + + // Accumulator (default is GB mode) + public $registerA = 0x01; + + // bit 7 - Zero + public $FZero = true; + + // bit 6 - Sub + public $FSubtract = false; + + // bit 5 - Half Carry + public $FHalfCarry = true; + + // bit 4 - Carry + public $FCarry = true; + + // Register B + public $registerB = 0x00; + + // Register C + public $registerC = 0x13; + + // Register D + public $registerD = 0x00; + + // Register E + public $registerE = 0xD8; + + // Registers H and L + public $registersHL = 0x014D; + + //Array of functions mapped to read back memory + public $memoryReader = []; + + //Array of functions mapped to write to memory + public $memoryWriter = []; + + // Stack Pointer + public $stackPointer = 0xFFFE; + + // Program Counter + public $programCounter = 0x0100; + + //Has the CPU been suspended until the next interrupt? + public $halt = false; + + //Did we trip the DMG Halt bug? + public $skipPCIncrement = false; + + //Has the emulation been paused or a frame has ended? + public $stopEmulator = 3; + + //Are interrupts enabled? + public $IME = true; + + //HDMA Transfer Flag - GBC only + public $hdmaRunning = false; + + //The number of clock cycles emulated. + public $CPUTicks = 0; + + //GBC Speed Multiplier + public $multiplier = 1; + + // + //Main RAM, MBC RAM, GBC Main RAM, VRAM, etc. + // + + //Main Core Memory + public $memory = []; + + //Switchable RAM (Used by games for more RAM) for the main memory range 0xA000 - 0xC000. + public $MBCRam = []; + + //Extra VRAM bank for GBC. + public $VRAM = []; + + //Current VRAM bank for GBC. + public $currVRAMBank = 0; + + //GBC main RAM Banks + public $GBCMemory = []; + + //MBC1 Type (4/32, 16/8) + public $MBC1Mode = false; + + //MBC RAM Access Control. + public $MBCRAMBanksEnabled = false; + + //MBC Currently Indexed RAM Bank + public $currMBCRAMBank = 0; + + //MBC Position Adder; + public $currMBCRAMBankPosition = -0xA000; + + //GameBoy Color detection. + public $cGBC = false; + + //Currently Switched GameBoy Color ram bank + public $gbcRamBank = 1; + + //GBC RAM offset from address start. + public $gbcRamBankPosition = -0xD000; + + //GBC RAM (ECHO mirroring) offset from address start. + public $gbcRamBankPositionECHO = -0xF000; + + //Used to map the RAM banks to maximum size the MBC used can do. + public $RAMBanks = [0, 1, 2, 4, 16]; + + //Offset of the ROM bank switching. + public $ROMBank1offs = 0; + + //The parsed current ROM bank selection. + public $currentROMBank = 0; + + //Cartridge Type + public $cartridgeType = 0; + + //Name of the game + public $name = ""; + + //Game code (Suffix for older games) + public $gameCode = ""; + + //A boolean to see if this was loaded in as a save state. + public $fromSaveState = false; + + //When loaded in as a save state, this will not be empty. + public $savedStateFileName = ""; + + //Tracker for STAT triggering. + public $STATTracker = 0; + + //The scan line mode (for lines 1-144 it's 2-3-0, for 145-154 it's 1) + public $modeSTAT = 0; + + //Should we trigger an interrupt if LY==LYC? + public $LYCMatchTriggerSTAT = false; + + //Should we trigger an interrupt if in mode 2? + public $mode2TriggerSTAT = false; + + //Should we trigger an interrupt if in mode 1? + public $mode1TriggerSTAT = false; + + //Should we trigger an interrupt if in mode 0? + public $mode0TriggerSTAT = false; + + //Is the emulated LCD controller on? + public $LCDisOn = false; + + //Array of functions to handle each scan line we do (onscreen + offscreen) + public $LINECONTROL; + + public $DISPLAYOFFCONTROL = []; + + //Pointer to either LINECONTROL or DISPLAYOFFCONTROL. + public $LCDCONTROL = null; + + public $gfxWindowY = false; + + public $gfxWindowDisplay = false; + + public $gfxSpriteShow = false; + + public $gfxSpriteDouble = false; + + public $gfxBackgroundY = false; + + public $gfxBackgroundX = false; + + public $TIMAEnabled = false; + + //Joypad State (two four-bit states actually) + public $JoyPad = 0xFF; + + // + //RTC: + // + public $RTCisLatched = true; + + public $latchedSeconds = 0; + + public $latchedMinutes = 0; + + public $latchedHours = 0; + + public $latchedLDays = 0; + + public $latchedHDays = 0; + + public $RTCSeconds = 0; + + public $RTCMinutes = 0; + + public $RTCHours = 0; + + public $RTCDays = 0; + + public $RTCDayOverFlow = false; + + public $RTCHALT = false; + + // + //Timing Variables + // + + //Used to sample the audio system every x CPU instructions. + public $audioTicks = 0; + + //Times for how many instructions to execute before ending the loop. + public $emulatorTicks = 0; + + // DIV Ticks Counter (Invisible lower 8-bit) + public $DIVTicks = 14; + + // ScanLine Counter + public $LCDTicks = 15; + + // Timer Ticks Count + public $timerTicks = 0; + + // Timer Max Ticks + public $TACClocker = 256; + + //Are the interrupts on queue to be enabled? + public $untilEnable = 0; + + //The last time we iterated the main loop. + public $lastIteration = 0; + + //Actual scan line... + public $actualScanLine = 0; + + // + //ROM Cartridge Components: + // + + //Does the cartridge use MBC1? + public $cMBC1 = false; + + //Does the cartridge use MBC2? + public $cMBC2 = false; + + //Does the cartridge use MBC3? + public $cMBC3 = false; + + //Does the cartridge use MBC5? + public $cMBC5 = false; + + //Does the cartridge use save RAM? + public $cSRAM = false; + + public $cMMMO1 = false; + + //Does the cartridge use the RUMBLE addressing (modified MBC5)? + public $cRUMBLE = false; + + public $cCamera = false; + + public $cTAMA5 = false; + + public $cHuC3 = false; + + public $cHuC1 = false; + + // 1 Bank = 16 KBytes = 256 Kbits + public $ROMBanks = [ + 2, 4, 8, 16, 32, 64, 128, 256, 512 + ]; + + //How many RAM banks were actually allocated? + public $numRAMBanks = 0; + + // + //Graphics Variables + // + + //To prevent the repeating of drawing a blank screen. + public $drewBlank = 0; + + // tile data arrays + public $tileData = []; + + public $frameBuffer = []; + + public $canvasBuffer; + + public $gbcRawPalette = []; + + //GB: 384, GBC: 384 * 2 + public $tileCount = 384; + + public $tileCountInvalidator; + + public $colorCount = 12; + + public $gbPalette = []; + + public $gbColorizedPalette = []; + + public $gbcPalette = []; + + // min "attrib" value where transparency can occur (Default is 4 (GB mode)) + public $transparentCutoff = 4; + + public $bgEnabled = true; + + public $spritePriorityEnabled = true; + + // true if there are any images to be invalidated + public $tileReadState = []; + + public $windowSourceLine = 0; + + //"Classic" GameBoy palette colors. + public $colors = [0x80EFFFDE, 0x80ADD794, 0x80529273, 0x80183442]; + + //Frame skip tracker + public $frameCount; + + public $weaveLookup = []; + + public $width = 160; + + public $height = 144; + + public $pixelCount; + + public $rgbCount; + + public $widthRatio; + + public $heightRatio; + + //Pointer to the current palette we're using (Used for palette switches during boot or so it can be done anytime) + public $palette = null; + + // + //Data + // + + public $DAATable; + + public $GBCBOOTROM; + + public $ffxxDump; + + public $OPCODE; + + public $CBOPCODE; + + public $TICKTable; + + public $SecondaryTICKTable; + + // Added + + public $cTIMER = null; + + public function __construct($ROMImage, $drawContext) + { + $this->drawContext = $drawContext; + $this->ROMImage = $ROMImage; + + $this->DISPLAYOFFCONTROL[] = function ($parentObj) { + //Array of line 0 function to handle the LCD controller when it's off (Do nothing!). + }; + + $this->tileCountInvalidator = $this->tileCount * 4; + + $this->ROMBanks[0x52] = 72; + $this->ROMBanks[0x53] = 80; + $this->ROMBanks[0x54] = 96; + + $this->frameCount = Settings::$settings[12]; + $this->pixelCount = $this->width * $this->height; + $this->rgbCount = $this->pixelCount * 4; + $this->widthRatio = 160 / $this->width; + $this->heightRatio = 144 / $this->height; + + // Copy Data + $this->DAATable = Data::$DAATable; + $this->GBCBOOTROM = Data::$GBCBOOTROM; + $this->ffxxDump = Data::$ffxxDump; + + $opcode = new Opcode(); + $this->OPCODE = $opcode->get(); + + $cbopcode = new Cbopcode(); + $this->CBOPCODE = $cbopcode->get(); + + $this->TICKTable = TICKTables::$primary; + $this->SecondaryTICKTable = TICKTables::$secondary; + + $this->LINECONTROL = array_fill(0, 154, null); + } + + public function saveState() + { + return [ + $this->fromTypedArray($this->ROM), + $this->inBootstrap, + $this->registerA, + $this->FZero, + $this->FSubtract, + $this->FHalfCarry, + $this->FCarry, + $this->registerB, + $this->registerC, + $this->registerD, + $this->registerE, + $this->registersHL, + $this->stackPointer, + $this->programCounter, + $this->halt, + $this->IME, + $this->hdmaRunning, + $this->CPUTicks, + $this->multiplier, + $this->fromTypedArray($this->memory), + $this->fromTypedArray($this->MBCRam), + $this->fromTypedArray($this->VRAM), + $this->currVRAMBank, + $this->fromTypedArray($this->GBCMemory), + $this->MBC1Mode, + $this->MBCRAMBanksEnabled, + $this->currMBCRAMBank, + $this->currMBCRAMBankPosition, + $this->cGBC, + $this->gbcRamBank, + $this->gbcRamBankPosition, + $this->ROMBank1offs, + $this->currentROMBank, + $this->cartridgeType, + $this->name, + $this->gameCode, + $this->modeSTAT, + $this->LYCMatchTriggerSTAT, + $this->mode2TriggerSTAT, + $this->mode1TriggerSTAT, + $this->mode0TriggerSTAT, + $this->LCDisOn, + $this->gfxWindowY, + $this->gfxWindowDisplay, + $this->gfxSpriteShow, + $this->gfxSpriteDouble, + $this->gfxBackgroundY, + $this->gfxBackgroundX, + $this->TIMAEnabled, + $this->DIVTicks, + $this->LCDTicks, + $this->timerTicks, + $this->TACClocker, + $this->untilEnable, + $this->lastIteration, + $this->cMBC1, + $this->cMBC2, + $this->cMBC3, + $this->cMBC5, + $this->cSRAM, + $this->cMMMO1, + $this->cRUMBLE, + $this->cCamera, + $this->cTAMA5, + $this->cHuC3, + $this->cHuC1, + $this->drewBlank, + $this->tileData, + $this->fromTypedArray($this->frameBuffer), + $this->tileCount, + $this->colorCount, + $this->gbPalette, + $this->gbcRawPalette, + $this->gbcPalette, + $this->transparentCutoff, + $this->bgEnabled, + $this->spritePriorityEnabled, + $this->fromTypedArray($this->tileReadState), + $this->windowSourceLine, + $this->actualScanLine, + $this->RTCisLatched, + $this->latchedSeconds, + $this->latchedMinutes, + $this->latchedHours, + $this->latchedLDays, + $this->latchedHDays, + $this->RTCSeconds, + $this->RTCMinutes, + $this->RTCHours, + $this->RTCDays, + $this->RTCDayOverFlow, + $this->RTCHALT, + $this->gbColorizedPalette, + $this->usedBootROM, + $this->skipPCIncrement, + $this->STATTracker, + $this->gbcRamBankPositionECHO, + $this->numRAMBanks + ]; + } + + public function returnFromState($returnedFrom) + { + $index = 0; + $state = $returnedFrom->slice(0); + + $this->ROM = $this->toTypedArray($state[$index++], false, false); + $this->inBootstrap = $state[$index++]; + $this->registerA = $state[$index++]; + $this->FZero = $state[$index++]; + $this->FSubtract = $state[$index++]; + $this->FHalfCarry = $state[$index++]; + $this->FCarry = $state[$index++]; + $this->registerB = $state[$index++]; + $this->registerC = $state[$index++]; + $this->registerD = $state[$index++]; + $this->registerE = $state[$index++]; + $this->registersHL = $state[$index++]; + $this->stackPointer = $state[$index++]; + $this->programCounter = $state[$index++]; + $this->halt = $state[$index++]; + $this->IME = $state[$index++]; + $this->hdmaRunning = $state[$index++]; + $this->CPUTicks = $state[$index++]; + $this->multiplier = $state[$index++]; + $this->memory = $this->toTypedArray($state[$index++], false, false); + $this->MBCRam = $this->toTypedArray($state[$index++], false, false); + $this->VRAM = $this->toTypedArray($state[$index++], false, false); + $this->currVRAMBank = $state[$index++]; + $this->GBCMemory = $this->toTypedArray($state[$index++], false, false); + $this->MBC1Mode = $state[$index++]; + $this->MBCRAMBanksEnabled = $state[$index++]; + $this->currMBCRAMBank = $state[$index++]; + $this->currMBCRAMBankPosition = $state[$index++]; + $this->cGBC = $state[$index++]; + $this->gbcRamBank = $state[$index++]; + $this->gbcRamBankPosition = $state[$index++]; + $this->ROMBank1offs = $state[$index++]; + $this->currentROMBank = $state[$index++]; + $this->cartridgeType = $state[$index++]; + $this->name = $state[$index++]; + $this->gameCode = $state[$index++]; + $this->modeSTAT = $state[$index++]; + $this->LYCMatchTriggerSTAT = $state[$index++]; + $this->mode2TriggerSTAT = $state[$index++]; + $this->mode1TriggerSTAT = $state[$index++]; + $this->mode0TriggerSTAT = $state[$index++]; + $this->LCDisOn = $state[$index++]; + $this->gfxWindowY = $state[$index++]; + $this->gfxWindowDisplay = $state[$index++]; + $this->gfxSpriteShow = $state[$index++]; + $this->gfxSpriteDouble = $state[$index++]; + $this->gfxBackgroundY = $state[$index++]; + $this->gfxBackgroundX = $state[$index++]; + $this->TIMAEnabled = $state[$index++]; + $this->DIVTicks = $state[$index++]; + $this->LCDTicks = $state[$index++]; + $this->timerTicks = $state[$index++]; + $this->TACClocker = $state[$index++]; + $this->untilEnable = $state[$index++]; + $this->lastIteration = $state[$index++]; + $this->cMBC1 = $state[$index++]; + $this->cMBC2 = $state[$index++]; + $this->cMBC3 = $state[$index++]; + $this->cMBC5 = $state[$index++]; + $this->cSRAM = $state[$index++]; + $this->cMMMO1 = $state[$index++]; + $this->cRUMBLE = $state[$index++]; + $this->cCamera = $state[$index++]; + $this->cTAMA5 = $state[$index++]; + $this->cHuC3 = $state[$index++]; + $this->cHuC1 = $state[$index++]; + $this->drewBlank = $state[$index++]; + $this->tileData = $state[$index++]; + $this->frameBuffer = $this->toTypedArray($state[$index++], true, false); + $this->tileCount = $state[$index++]; + $this->colorCount = $state[$index++]; + $this->gbPalette = $state[$index++]; + $this->gbcRawPalette = $state[$index++]; + $this->gbcPalette = $state[$index++]; + $this->transparentCutoff = $state[$index++]; + $this->bgEnabled = $state[$index++]; + $this->spritePriorityEnabled = $state[$index++]; + $this->tileReadState = $this->toTypedArray($state[$index++], false, false); + $this->windowSourceLine = $state[$index++]; + $this->actualScanLine = $state[$index++]; + $this->RTCisLatched = $state[$index++]; + $this->latchedSeconds = $state[$index++]; + $this->latchedMinutes = $state[$index++]; + $this->latchedHours = $state[$index++]; + $this->latchedLDays = $state[$index++]; + $this->latchedHDays = $state[$index++]; + $this->RTCSeconds = $state[$index++]; + $this->RTCMinutes = $state[$index++]; + $this->RTCHours = $state[$index++]; + $this->RTCDays = $state[$index++]; + $this->RTCDayOverFlow = $state[$index++]; + $this->RTCHALT = $state[$index++]; + $this->gbColorizedPalette = $state[$index++]; + $this->usedBootROM = $state[$index++]; + $this->skipPCIncrement = $state[$index++]; + $this->STATTracker = $state[$index++]; + $this->gbcRamBankPositionECHO = $state[$index++]; + $this->numRAMBanks = $state[$index]; + $this->tileCountInvalidator = $this->tileCount * 4; + $this->fromSaveState = true; + $this->checkPaletteType(); + $this->initializeLCDController(); + $this->memoryReadJumpCompile(); + $this->memoryWriteJumpCompile(); + $this->initLCD(); + $this->drawToCanvas(); + } + + public function start() + { + Settings::$settings[4] = 0; //Reset the frame skip setting. + $this->initializeLCDController(); //Compile the LCD controller functions. + $this->initMemory(); //Write the startup memory. + $this->ROMLoad(); //Load the ROM into memory and get cartridge information from it. + $this->initLCD(); //Initializae the graphics. + $this->run(); //Start the emulation. + } + + public function initMemory() { + //Initialize the RAM: + $this->memory = $this->getTypedArray(0x10000, 0, 'uint8'); + $this->frameBuffer = $this->getTypedArray(23040, 0x00FFFFFF, 'int32'); + $this->gbPalette = $this->ArrayPad(12, 0); //32-bit signed + $this->gbColorizedPalette = $this->ArrayPad(12, 0); //32-bit signed + $this->gbcRawPalette = $this->ArrayPad(0x80, -1000); //32-bit signed + $this->gbcPalette = [0x40]; //32-bit signed + //Initialize the GBC Palette: + $index = 0x3F; + + while ($index >= 0) { + $this->gbcPalette[$index] = ($index < 0x20) ? -1 : 0; + $index--; + } + } + + public function initSkipBootstrap() { + //Start as an unset device: + echo 'Starting without the GBC boot ROM' . PHP_EOL; + + $this->programCounter = 0x100; + $this->stackPointer = 0xFFFE; + $this->IME = true; + $this->LCDTicks = 15; + $this->DIVTicks = 14; + $this->registerA = ($this->cGBC) ? 0x11 : 0x1; + $this->registerB = 0; + $this->registerC = 0x13; + $this->registerD = 0; + $this->registerE = 0xD8; + $this->FZero = true; + $this->FSubtract = false; + $this->FHalfCarry = true; + $this->FCarry = true; + $this->registersHL = 0x014D; + + //Fill in the boot ROM set register values + //Default values to the GB boot ROM values, then fill in the GBC boot ROM values after ROM loading + $index = 0xFF; + + while ($index >= 0) { + if ($index >= 0x30 && $index < 0x40) { + $this->memoryWrite(0xFF00 + $index, $this->ffxxDump[$index]); + } else { + switch ($index) { + case 0x00: + case 0x01: + case 0x02: + case 0x07: + case 0x0F: + case 0x40: + case 0xFF: + $this->memoryWrite(0xFF00 + $index, $this->ffxxDump[$index]); + break; + default: + $this->memory[0xFF00 + $index] = $this->ffxxDump[$index]; + } + } + $index--; + } + } + + public function initBootstrap() { + //Start as an unset device: + echo 'Starting the GBC boot ROM.' . PHP_EOL; + + $this->programCounter = 0; + $this->stackPointer = 0; + $this->IME = false; + $this->LCDTicks = 0; + $this->DIVTicks = 0; + $this->registerA = 0; + $this->registerB = 0; + $this->registerC = 0; + $this->registerD = 0; + $this->registerE = 0; + $this->FZero = $this->FSubtract = $this->FHalfCarry = $this->FCarry = false; + $this->registersHL = 0; + $this->memory[0xFF00] = 0xF; //Set the joypad state. + } + + public function ROMLoad() { + //Load the first two ROM banks (0x0000 - 0x7FFF) into regular gameboy memory: + $this->ROM = $this->getTypedArray(strlen($this->ROMImage), 0, "uint8"); + + $this->usedBootROM = Settings::$settings[16]; + + for ($romIndex = 0; $romIndex < strlen($this->ROMImage); $romIndex++) { + + $this->ROM[$romIndex] = (ord($this->ROMImage[$romIndex]) & 0xFF); + if ($romIndex < 0x8000) { + if (!$this->usedBootROM || $romIndex >= 0x900 || ($romIndex >= 0x100 && $romIndex < 0x200)) { + $this->memory[$romIndex] = $this->ROM[$romIndex]; //Load in the game ROM. + } + else { + $this->memory[$romIndex] = $this->GBCBOOTROM[$romIndex]; //Load in the GameBoy Color BOOT ROM. + } + } + } + // ROM name + for ($index = 0x134; $index < 0x13F; $index++) { + if (ord($this->ROMImage[$index]) > 0) { + $this->name .= $this->ROMImage[$index]; + } + } + + // ROM game code (for newer games) + for ($index = 0x13F; $index < 0x143; $index++) { + if (ord($this->ROMImage[$index]) > 0) { + $this->gameCode .= $this->ROMImage[$index]; + } + } + + echo "Game Title: " . $this->name . "[" . $this->gameCode . "][" . $this->ROMImage[0x143] . "]" . PHP_EOL; + + echo "Game Code: " . $this->gameCode . PHP_EOL; + + // Cartridge type + $this->cartridgeType = $this->ROM[0x147]; + echo "Cartridge type #" . $this->cartridgeType . PHP_EOL; + + //Map out ROM cartridge sub-types. + $MBCType = ""; + + switch ($this->cartridgeType) { + case 0x00: + //ROM w/o bank switching + if (!Settings::$settings[9]) { + $MBCType = "ROM"; + break; + } + case 0x01: + $this->cMBC1 = true; + $MBCType = "MBC1"; + break; + case 0x02: + $this->cMBC1 = true; + $this->cSRAM = true; + $MBCType = "MBC1 + SRAM"; + break; + case 0x03: + $this->cMBC1 = true; + $this->cSRAM = true; + $this->cBATT = true; + $MBCType = "MBC1 + SRAM + BATT"; + break; + case 0x05: + $this->cMBC2 = true; + $MBCType = "MBC2"; + break; + case 0x06: + $this->cMBC2 = true; + $this->cBATT = true; + $MBCType = "MBC2 + BATT"; + break; + case 0x08: + $this->cSRAM = true; + $MBCType = "ROM + SRAM"; + break; + case 0x09: + $this->cSRAM = true; + $this->cBATT = true; + $MBCType = "ROM + SRAM + BATT"; + break; + case 0x0B: + $this->cMMMO1 = true; + $MBCType = "MMMO1"; + break; + case 0x0C: + $this->cMMMO1 = true; + $this->cSRAM = true; + $MBCType = "MMMO1 + SRAM"; + break; + case 0x0D: + $this->cMMMO1 = true; + $this->cSRAM = true; + $this->cBATT = true; + $MBCType = "MMMO1 + SRAM + BATT"; + break; + case 0x0F: + $this->cMBC3 = true; + $this->cTIMER = true; + $this->cBATT = true; + $MBCType = "MBC3 + TIMER + BATT"; + break; + case 0x10: + $this->cMBC3 = true; + $this->cTIMER = true; + $this->cBATT = true; + $this->cSRAM = true; + $MBCType = "MBC3 + TIMER + BATT + SRAM"; + break; + case 0x11: + $this->cMBC3 = true; + $MBCType = "MBC3"; + break; + case 0x12: + $this->cMBC3 = true; + $this->cSRAM = true; + $MBCType = "MBC3 + SRAM"; + break; + case 0x13: + $this->cMBC3 = true; + $this->cSRAM = true; + $this->cBATT = true; + $MBCType = "MBC3 + SRAM + BATT"; + break; + case 0x19: + $this->cMBC5 = true; + $MBCType = "MBC5"; + break; + case 0x1A: + $this->cMBC5 = true; + $this->cSRAM = true; + $MBCType = "MBC5 + SRAM"; + break; + case 0x1B: + $this->cMBC5 = true; + $this->cSRAM = true; + $this->cBATT = true; + $MBCType = "MBC5 + SRAM + BATT"; + break; + case 0x1C: + $this->cRUMBLE = true; + $MBCType = "RUMBLE"; + break; + case 0x1D: + $this->cRUMBLE = true; + $this->cSRAM = true; + $MBCType = "RUMBLE + SRAM"; + break; + case 0x1E: + $this->cRUMBLE = true; + $this->cSRAM = true; + $this->cBATT = true; + $MBCType = "RUMBLE + SRAM + BATT"; + break; + case 0x1F: + $this->cCamera = true; + $MBCType = "GameBoy Camera"; + break; + case 0xFD: + $this->cTAMA5 = true; + $MBCType = "TAMA5"; + break; + case 0xFE: + $this->cHuC3 = true; + $MBCType = "HuC3"; + break; + case 0xFF: + $this->cHuC1 = true; + $MBCType = "HuC1"; + break; + default: + $MBCType = "Unknown"; + echo "Cartridge type is unknown." . PHP_EOL; + + // @TODO + //pause(); + } + + echo "Cartridge Type: " . $MBCType . PHP_EOL; + + // ROM and RAM banks + $this->numROMBanks = $this->ROMBanks[$this->ROM[0x148]]; + + echo $this->numROMBanks . " ROM banks." . PHP_EOL; + + switch ($this->RAMBanks[$this->ROM[0x149]]) { + case 0: + echo "No RAM banking requested for allocation or MBC is of type 2." . PHP_EOL; + break; + case 2: + echo "1 RAM bank requested for allocation." . PHP_EOL; + break; + case 3: + echo "4 RAM banks requested for allocation." . PHP_EOL; + break; + case 4: + echo "16 RAM banks requested for allocation." . PHP_EOL; + break; + default: + echo "RAM bank amount requested is unknown, will use maximum allowed by specified MBC type." . PHP_EOL; + } + + //Check the GB/GBC mode byte: + if (!$this->usedBootROM) { + switch ($this->ROM[0x143]) { + case 0x00: //Only GB mode + $this->cGBC = false; + echo "Only GB mode detected." . PHP_EOL; + break; + case 0x80: //Both GB + GBC modes + $this->cGBC = ! Settings::$settings[2]; + echo "GB and GBC mode detected." . PHP_EOL; + break; + case 0xC0: //Only GBC mode + $this->cGBC = true; + echo "Only GBC mode detected." . PHP_EOL; + break; + default: + $this->cGBC = false; + echo "Unknown GameBoy game type code #" . $this->ROM[0x143] . ", defaulting to GB mode (Old games don't have a type code)." . PHP_EOL; + } + + $this->inBootstrap = false; + $this->setupRAM(); //CPU/(V)RAM initialization. + $this->initSkipBootstrap(); + } + else { + $this->cGBC = true; //Allow the GBC boot ROM to run in GBC mode... + $this->setupRAM(); //CPU/(V)RAM initialization. + $this->initBootstrap(); + } + $this->checkPaletteType(); + //License Code Lookup: + $cOldLicense = $this->ROM[0x14B]; + $cNewLicense = ($this->ROM[0x144] & 0xFF00) | ($this->ROM[0x145] & 0xFF); + if ($cOldLicense != 0x33) { + //Old Style License Header + echo "Old style license code: " . $cOldLicense . PHP_EOL; + } + else { + //New Style License Header + echo "New style license code: " . $cNewLicense . PHP_EOL; + } + } + + public function disableBootROM() { + //Remove any traces of the boot ROM from ROM memory. + for ($index = 0; $index < 0x900; $index++) { + if ($index < 0x100 || $index >= 0x200) { //Skip the already loaded in ROM header. + $this->memory[$index] = $this->ROM[$index]; //Replace the GameBoy Color boot ROM with the game ROM. + } + } + $this->checkPaletteType(); + + if (!$this->cGBC) { + //Clean up the post-boot (GB mode only) state: + echo "Stepping down from GBC mode." . PHP_EOL; + $this->tileCount /= 2; + $this->tileCountInvalidator = $this->tileCount * 4; + if (!Settings::$settings[17]) { + $this->transparentCutoff = 4; + } + $this->colorCount = 12; + + // @TODO + // $this->tileData.length = $this->tileCount * $this->colorCount; + + unset($this->VRAM); + unset($this->GBCMemory); + //Possible Extra: shorten some gfx arrays to the length that we need (Remove the unused indices) + } + + $this->memoryReadJumpCompile(); + $this->memoryWriteJumpCompile(); + } + + public function setupRAM() { + //Setup the auxilliary/switchable RAM to their maximum possible size (Bad headers can lie). + if ($this->cMBC2) { + $this->numRAMBanks = 1 / 16; + } + else if ($this->cMBC1 || $this->cRUMBLE || $this->cMBC3 || $this->cHuC3) { + $this->numRAMBanks = 4; + } + else if ($this->cMBC5) { + $this->numRAMBanks = 16; + } + else if ($this->cSRAM) { + $this->numRAMBanks = 1; + } + if ($this->numRAMBanks > 0) { + if (!$this->MBCRAMUtilized()) { + //For ROM and unknown MBC cartridges using the external RAM: + $this->MBCRAMBanksEnabled = true; + } + //Switched RAM Used + $this->MBCRam = $this->getTypedArray($this->numRAMBanks * 0x2000, 0, "uint8"); + } + echo "Actual bytes of MBC RAM allocated: " . ($this->numRAMBanks * 0x2000) . PHP_EOL; + //Setup the RAM for GBC mode. + if ($this->cGBC) { + $this->VRAM = $this->getTypedArray(0x2000, 0, "uint8"); + $this->GBCMemory = $this->getTypedArray(0x7000, 0, "uint8"); + $this->tileCount *= 2; + $this->tileCountInvalidator = $this->tileCount * 4; + $this->colorCount = 64; + $this->transparentCutoff = 32; + } + $this->tileData = $this->ArrayPad($this->tileCount * $this->colorCount, null); + $this->tileReadState = $this->getTypedArray($this->tileCount, 0, "uint8"); + $this->memoryReadJumpCompile(); + $this->memoryWriteJumpCompile(); + } + + public function MBCRAMUtilized() { + return $this->cMBC1 || $this->cMBC2 || $this->cMBC3 || $this->cMBC5 || $this->cRUMBLE; + } + + public function initLCD() { + $this->transparentCutoff = (Settings::$settings[17] || $this->cGBC) ? 32 : 4; + if (count($this->weaveLookup) == 0) { + //Setup the image decoding lookup table: + $this->weaveLookup = $this->getTypedArray(256, 0, "uint16"); + for ($i_ = 0x1; $i_ <= 0xFF; $i_++) { + for ($d_ = 0; $d_ < 0x8; $d_++) { + $this->weaveLookup[$i_] += (($i_ >> $d_) & 1) << ($d_ * 2); + } + } + } + + $this->width = 160; + $this->height = 144; + + //Get a CanvasPixelArray buffer: + //Create a white screen + $this->canvasBuffer = array_fill(0, 4 * $this->width * $this->height, 255); + + $index = $this->pixelCount; + $index2 = $this->rgbCount; + + while ($index > 0) { + $this->frameBuffer[--$index] = 0x00FFFFFF; + $this->canvasBuffer[$index2 -= 4] = 0xFF; + $this->canvasBuffer[$index2 + 1] = 0xFF; + $this->canvasBuffer[$index2 + 2] = 0xFF; + $this->canvasBuffer[$index2 + 3] = 0xFF; + } + + $this->drawContext->draw($this->canvasBuffer, 0, 0); + } + + public function JoyPadEvent($key, $down) + { + if ($down) { + $this->JoyPad &= 0xFF ^ (1 << $key); + } else { + $this->JoyPad |= (1 << $key); + } + $this->memory[0xFF00] = ($this->memory[0xFF00] & 0x30) + (((($this->memory[0xFF00] & 0x20) == 0) ? ($this->JoyPad >> 4) : 0xF) & ((($this->memory[0xFF00] & 0x10) == 0) ? ($this->JoyPad & 0xF) : 0xF)); + } + + public function run() { + //The preprocessing before the actual iteration loop: + try { + if (($this->stopEmulator & 2) == 0) { + if (($this->stopEmulator & 1) == 1) { + $this->stopEmulator = 0; + $this->clockUpdate(); //Frame skip and RTC code. + + if (!$this->halt) { //If no HALT... Execute normally + $this->executeIteration(); + } + else { //If we bailed out of a halt because the iteration ran down its timing. + $this->CPUTicks = 1; + $this->OPCODE[0x76]($this); + //Execute Interrupt: + $this->runInterrupt(); + //Timing: + $this->updateCore(); + $this->executeIteration(); + } + } + else { //We can only get here if there was an internal error, but the loop was restarted. + echo "Iterator restarted a faulted core." . PHP_EOL; + pause(); + } + } + } catch (\Exception $error) { + if ($error->getMessage() != "HALT_OVERRUN") { + echo 'GameBoy runtime error' . PHP_EOL; + } + } + } + + public function executeIteration() { + //Iterate the interpreter loop: + $op = 0; + + while ($this->stopEmulator == 0) { + //Fetch the current opcode. + $op = $this->memoryRead($this->programCounter); + if (!$this->skipPCIncrement) { + //Increment the program counter to the next instruction: + $this->programCounter = ($this->programCounter + 1) & 0xFFFF; + } + $this->skipPCIncrement = false; + //Get how many CPU cycles the current op code counts for: + $this->CPUTicks = $this->TICKTable[$op]; + //Execute the OP code instruction: + $this->OPCODE[$op]($this); + //Interrupt Arming: + switch ($this->untilEnable) { + case 1: + $this->IME = true; + case 2: + $this->untilEnable--; + } + //Execute Interrupt: + if ($this->IME) { + $this->runInterrupt(); + } + //Timing: + $this->updateCore(); + } + } + + public function runInterrupt() { + $bitShift = 0; + $testbit = 1; + $interrupts = $this->memory[0xFFFF] & $this->memory[0xFF0F]; + + while ($bitShift < 5) { + //Check to see if an interrupt is enabled AND requested. + if (($testbit & $interrupts) == $testbit) { + $this->IME = false; //Reset the interrupt enabling. + $this->memory[0xFF0F] -= $testbit; //Reset the interrupt request. + //Set the stack pointer to the current program counter value: + $this->stackPointer = $this->unswtuw($this->stackPointer - 1); + $this->memoryWrite($this->stackPointer, $this->programCounter >> 8); + $this->stackPointer = $this->unswtuw($this->stackPointer - 1); + $this->memoryWrite($this->stackPointer, $this->programCounter & 0xFF); + //Set the program counter to the interrupt's address: + $this->programCounter = 0x0040 + ($bitShift * 0x08); + //Interrupts have a certain clock cycle length: + $this->CPUTicks += 5; //People say it's around 5. + break; //We only want the highest priority interrupt. + } + + $testbit = 1 << ++$bitShift; + } + } + + public function scanLineMode2() { // OAM in use + if ($this->modeSTAT != 2) { + if ($this->mode2TriggerSTAT) { + $this->memory[0xFF0F] |= 0x2;// set IF bit 1 + } + $this->STATTracker = 1; + $this->modeSTAT = 2; + } + } + + public function scanLineMode3() { // OAM in use + if ($this->modeSTAT != 3) { + if ($this->mode2TriggerSTAT && $this->STATTracker == 0) { + $this->memory[0xFF0F] |= 0x2;// set IF bit 1 + } + $this->STATTracker = 1; + $this->modeSTAT = 3; + } + } + + public function scanLineMode0() { // H-Blank + if ($this->modeSTAT != 0) { + if ($this->hdmaRunning && !$this->halt && $this->LCDisOn) { + $this->performHdma(); //H-Blank DMA + } + if ($this->mode0TriggerSTAT || ($this->mode2TriggerSTAT && $this->STATTracker == 0)) { + $this->memory[0xFF0F] |= 0x2; // if STAT bit 3 -> set IF bit1 + } + $this->notifyScanline(); + $this->STATTracker = 2; + $this->modeSTAT = 0; + } + } + + public function matchLYC() { // LY - LYC Compare + if ($this->memory[0xFF44] == $this->memory[0xFF45]) { // If LY==LCY + $this->memory[0xFF41] |= 0x04; // set STAT bit 2: LY-LYC coincidence flag + if ($this->LYCMatchTriggerSTAT) { + $this->memory[0xFF0F] |= 0x2; // set IF bit 1 + } + } + else { + $this->memory[0xFF41] &= 0xFB; // reset STAT bit 2 (LY!=LYC) + } + } + + public function updateCore() { + // DIV control + $this->DIVTicks += $this->CPUTicks; + if ($this->DIVTicks >= 0x40) { + $this->DIVTicks -= 0x40; + $this->memory[0xFF04] = ($this->memory[0xFF04] + 1) & 0xFF; // inc DIV + } + //LCD Controller Ticks + $timedTicks = $this->CPUTicks / $this->multiplier; + // LCD Timing + $this->LCDTicks += $timedTicks; //LCD timing + $this->LCDCONTROL[$this->actualScanLine]($this); //Scan Line and STAT Mode Control + + //Audio Timing + $this->audioTicks += $timedTicks; //Not the same as the LCD timing (Cannot be altered by display on/off changes!!!). + + if ($this->audioTicks >= Settings::$settings[11]) { //Are we past the granularity setting? + //Emulator Timing (Timed against audio for optimization): + $this->emulatorTicks += $this->audioTicks; + if ($this->emulatorTicks >= Settings::$settings[13]) { + if (($this->stopEmulator & 1) == 0) { //Make sure we don't overdo the audio. + if ($this->drewBlank == 0) { //LCD off takes at least 2 frames. + $this->drawToCanvas(); //Display frame + } + } + $this->stopEmulator |= 1; //End current loop. + $this->emulatorTicks = 0; + } + $this->audioTicks = 0; + } + + // Internal Timer + if ($this->TIMAEnabled) { + $this->timerTicks += $this->CPUTicks; + while ($this->timerTicks >= $this->TACClocker) { + $this->timerTicks -= $this->TACClocker; + if ($this->memory[0xFF05] == 0xFF) { + $this->memory[0xFF05] = $this->memory[0xFF06]; + $this->memory[0xFF0F] |= 0x4; // set IF bit 2 + } + else { + $this->memory[0xFF05]++; + } + } + } + } + + public function initializeLCDController() { + //Display on hanlding: + $line = 0; + + while ($line < 154) { + if ($line < 143) { + //We're on a normal scan line: + $this->LINECONTROL[$line] = function ($parentObj) { + if ($parentObj->LCDTicks < 20) { + $parentObj->scanLineMode2(); // mode2: 80 cycles + } + else if ($parentObj->LCDTicks < 63) { + $parentObj->scanLineMode3(); // mode3: 172 cycles + } + else if ($parentObj->LCDTicks < 114) { + $parentObj->scanLineMode0(); // mode0: 204 cycles + } + else { + //We're on a new scan line: + $parentObj->LCDTicks -= 114; + $parentObj->actualScanLine = ++$parentObj->memory[0xFF44]; + $parentObj->matchLYC(); + if ($parentObj->STATTracker != 2) { + if ($parentObj->hdmaRunning && !$parentObj->halt && $parentObj->LCDisOn) { + $parentObj->performHdma(); //H-Blank DMA + } + if ($parentObj->mode0TriggerSTAT) { + $parentObj->memory[0xFF0F] |= 0x2;// set IF bit 1 + } + } + $parentObj->STATTracker = 0; + $parentObj->scanLineMode2(); // mode2: 80 cycles + if ($parentObj->LCDTicks >= 114) { + //We need to skip 1 or more scan lines: + $parentObj->notifyScanline(); + $parentObj->LCDCONTROL[$parentObj->actualScanLine]($parentObj); //Scan Line and STAT Mode Control + } + } + }; + } else if ($line == 143) { + //We're on the last visible scan line of the LCD screen: + $this->LINECONTROL[143] = function ($parentObj) { + if ($parentObj->LCDTicks < 20) { + $parentObj->scanLineMode2(); // mode2: 80 cycles + } + else if ($parentObj->LCDTicks < 63) { + $parentObj->scanLineMode3(); // mode3: 172 cycles + } + else if ($parentObj->LCDTicks < 114) { + $parentObj->scanLineMode0(); // mode0: 204 cycles + } + else { + //Starting V-Blank: + //Just finished the last visible scan line: + $parentObj->LCDTicks -= 114; + $parentObj->actualScanLine = ++$parentObj->memory[0xFF44]; + $parentObj->matchLYC(); + if ($parentObj->mode1TriggerSTAT) { + $parentObj->memory[0xFF0F] |= 0x2;// set IF bit 1 + } + if ($parentObj->STATTracker != 2) { + if ($parentObj->hdmaRunning && !$parentObj->halt && $parentObj->LCDisOn) { + $parentObj->performHdma(); //H-Blank DMA + } + if ($parentObj->mode0TriggerSTAT) { + $parentObj->memory[0xFF0F] |= 0x2;// set IF bit 1 + } + } + $parentObj->STATTracker = 0; + $parentObj->modeSTAT = 1; + $parentObj->memory[0xFF0F] |= 0x1; // set IF flag 0 + if ($parentObj->drewBlank > 0) { //LCD off takes at least 2 frames. + $parentObj->drewBlank--; + } + if ($parentObj->LCDTicks >= 114) { + //We need to skip 1 or more scan lines: + $parentObj->LCDCONTROL[$parentObj->actualScanLine]($parentObj); //Scan Line and STAT Mode Control + } + } + }; + } else if ($line < 153) { + //In VBlank + $this->LINECONTROL[$line] = function ($parentObj) { + if ($parentObj->LCDTicks >= 114) { + //We're on a new scan line: + $parentObj->LCDTicks -= 114; + $parentObj->actualScanLine = ++$parentObj->memory[0xFF44]; + $parentObj->matchLYC(); + if ($parentObj->LCDTicks >= 114) { + //We need to skip 1 or more scan lines: + $parentObj->LCDCONTROL[$parentObj->actualScanLine]($parentObj); //Scan Line and STAT Mode Control + } + } + }; + } + else { + //VBlank Ending (We're on the last actual scan line) + $this->LINECONTROL[153] = function ($parentObj) { + if ($parentObj->memory[0xFF44] == 153) { + $parentObj->memory[0xFF44] = 0; //LY register resets to 0 early. + $parentObj->matchLYC(); //LY==LYC Test is early here (Fixes specific one-line glitches (example: Kirby2 intro)). + } + if ($parentObj->LCDTicks >= 114) { + //We reset back to the beginning: + $parentObj->LCDTicks -= 114; + $parentObj->actualScanLine = 0; + $parentObj->scanLineMode2(); // mode2: 80 cycles + if ($parentObj->LCDTicks >= 114) { + //We need to skip 1 or more scan lines: + $parentObj->LCDCONTROL[$parentObj->actualScanLine]($parentObj); //Scan Line and STAT Mode Control + } + } + }; + } + $line++; + } + $this->LCDCONTROL = ($this->LCDisOn) ? $this->LINECONTROL : $this->DISPLAYOFFCONTROL; + } + + public function DisplayShowOff() { + if ($this->drewBlank == 0) { + $this->canvasBuffer = array_fill(0, 4 * $this->width * $this->height, 255); + $this->drawContext->draw($this->canvasBuffer, 0, 0); + $this->drewBlank = 2; + } + } + + public function performHdma() { + $this->CPUTicks += 1 + (8 * $this->multiplier); + + $dmaSrc = ($this->memory[0xFF51] << 8) + $this->memory[0xFF52]; + $dmaDstRelative = ($this->memory[0xFF53] << 8) + $this->memory[0xFF54]; + $dmaDstFinal = $dmaDstRelative + 0x10; + $tileRelative = $this->tileData->length - $this->tileCount; + + if ($this->currVRAMBank == 1) { + while ($dmaDstRelative < $dmaDstFinal) { + if ($dmaDstRelative < 0x1800) { // Bkg Tile data area + $tileIndex = ($dmaDstRelative >> 4) + 384; + if ($this->tileReadState[$tileIndex] == 1) { + $r = $tileRelative + $tileIndex; + do { + $this->tileData[$r] = null; + $r -= $this->tileCount; + } while ($r >= 0); + $this->tileReadState[$tileIndex] = 0; + } + } + $this->VRAM[$dmaDstRelative++] = $this->memoryRead($dmaSrc++); + } + } else { + while ($dmaDstRelative < $dmaDstFinal) { + if ($dmaDstRelative < 0x1800) { // Bkg Tile data area + $tileIndex = $dmaDstRelative >> 4; + if ($this->tileReadState[$tileIndex] == 1) { + $r = $tileRelative + $tileIndex; + + do { + $this->tileData[$r] = null; + $r -= $this->tileCount; + } while ($r >= 0); + + $this->tileReadState[$tileIndex] = 0; + } + } + $this->memory[0x8000 + $dmaDstRelative++] = $this->memoryRead($dmaSrc++); + } + } + + $this->memory[0xFF51] = (($dmaSrc & 0xFF00) >> 8); + $this->memory[0xFF52] = ($dmaSrc & 0x00F0); + $this->memory[0xFF53] = (($dmaDstFinal & 0x1F00) >> 8); + $this->memory[0xFF54] = ($dmaDstFinal & 0x00F0); + if ($this->memory[0xFF55] == 0) { + $this->hdmaRunning = false; + $this->memory[0xFF55] = 0xFF; //Transfer completed ("Hidden last step," since some ROMs don't imply this, but most do). + } + else { + $this->memory[0xFF55]--; + } + } + + public function clockUpdate() { + //We're tying in the same timer for RTC and frame skipping, since we can and this reduces load. + if (Settings::$settings[7] || $this->cTIMER) { + $timeElapsed = ((int) (microtime(true) * 1000)) - $this->lastIteration; //Get the numnber of milliseconds since this last executed. + if ($this->cTIMER && !$this->RTCHALT) { + //Update the MBC3 RTC: + $this->RTCSeconds += $timeElapsed / 1000; + while ($this->RTCSeconds >= 60) { //System can stutter, so the seconds difference can get large, thus the "while". + $this->RTCSeconds -= 60; + $this->RTCMinutes++; + if ($this->RTCMinutes >= 60) { + $this->RTCMinutes -= 60; + $this->RTCHours++; + if ($this->RTCHours >= 24) { + $this->RTCHours -= 24; + $this->RTCDays++; + if ($this->RTCDays >= 512) { + $this->RTCDays -= 512; + $this->RTCDayOverFlow = true; + } + } + } + } + } + if (Settings::$settings[7]) { + //Auto Frame Skip: + if ($timeElapsed > Settings::$settings[20]) { + //Did not finish in time... + if (Settings::$settings[4] < Settings::$settings[8]) { + Settings::$settings[4]++; + } + } + else if (Settings::$settings[4] > 0) { + //We finished on time, decrease frame skipping (throttle to somewhere just below full speed)... + Settings::$settings[4]--; + } + } + $this->lastIteration = (int) (microtime(true) * 1000); + } + } + + public function drawToCanvas() { + //Draw the frame buffer to the canvas: + if (Settings::$settings[4] == 0 || $this->frameCount > 0) { + //Copy and convert the framebuffer data to the CanvasPixelArray format. + $bufferIndex = $this->pixelCount; + $canvasIndex = $this->rgbCount; + + while ($canvasIndex > 3) { + //Red + $this->canvasBuffer[$canvasIndex -= 4] = ($this->frameBuffer[--$bufferIndex] >> 16) & 0xFF; + //Green + $this->canvasBuffer[$canvasIndex + 1] = ($this->frameBuffer[$bufferIndex] >> 8) & 0xFF; + //Blue + $this->canvasBuffer[$canvasIndex + 2] = $this->frameBuffer[$bufferIndex] & 0xFF; + } + + //Draw out the CanvasPixelArray data: + $this->drawContext->draw($this->canvasBuffer, 0, 0); + + if (Settings::$settings[4] > 0) { + //Decrement the frameskip counter: + $this->frameCount -= Settings::$settings[4]; + } + } else { + //Reset the frameskip counter: + $this->frameCount += Settings::$settings[12]; + } + } + + public function invalidateAll($pal) { + $stop = ($pal + 1) * $this->tileCountInvalidator; + for ($r = $pal * $this->tileCountInvalidator; $r < $stop; $r++) { + $this->tileData[$r] = null; + } + } + + public function setGBCPalettePre($index_, $data) { + if ($this->gbcRawPalette[$index_] == $data) { + return; + } + $this->gbcRawPalette[$index_] = $data; + if ($index_ >= 0x40 && ($index_ & 0x6) == 0) { + // stay transparent + return; + } + $value = ($this->gbcRawPalette[$index_ | 1] << 8) + $this->gbcRawPalette[$index_ & -2]; + $this->gbcPalette[$index_ >> 1] = 0x80000000 + (($value & 0x1F) << 19) + (($value & 0x3E0) << 6) + (($value & 0x7C00) >> 7); + $this->invalidateAll($index_ >> 3); + } + + public function setGBCPalette($index_, $data) { + $this->setGBCPalettePre($index_, $data); + if (($index_ & 0x6) == 0) { + $this->gbcPalette[$index_ >> 1] &= 0x00FFFFFF; + } + } + + public function decodePalette($startIndex, $data) { + if (!$this->cGBC) { + $this->gbPalette[$startIndex] = $this->colors[$data & 0x03] & 0x00FFFFFF; // color 0: transparent + $this->gbPalette[$startIndex + 1] = $this->colors[($data >> 2) & 0x03]; + $this->gbPalette[$startIndex + 2] = $this->colors[($data >> 4) & 0x03]; + $this->gbPalette[$startIndex + 3] = $this->colors[$data >> 6]; + + if ($this->usedBootROM) { //Do palette conversions if we did the GBC bootup: + //GB colorization: + $startOffset = ($startIndex >= 4) ? 0x20 : 0; + $pal2 = $this->gbcPalette[$startOffset + (($data >> 2) & 0x03)]; + $pal3 = $this->gbcPalette[$startOffset + (($data >> 4) & 0x03)]; + $pal4 = $this->gbcPalette[$startOffset + ($data >> 6)]; + $this->gbColorizedPalette[$startIndex] = $this->gbcPalette[$startOffset + ($data & 0x03)] & 0x00FFFFFF; + $this->gbColorizedPalette[$startIndex + 1] = ($pal2 >= 0x80000000) ? $pal2 : 0xFFFFFFFF; + $this->gbColorizedPalette[$startIndex + 2] = ($pal3 >= 0x80000000) ? $pal3 : 0xFFFFFFFF; + $this->gbColorizedPalette[$startIndex + 3] = ($pal4 >= 0x80000000) ? $pal4 : 0xFFFFFFFF; + } + + //@PHP - Need to copy the new palette + $this->checkPaletteType(); + } + } + + public function notifyScanline() { + if ($this->actualScanLine == 0) { + $this->windowSourceLine = 0; + } + // determine the left edge of the window (160 if window is inactive) + $windowLeft = ($this->gfxWindowDisplay && $this->memory[0xFF4A] <= $this->actualScanLine) ? min(160, $this->memory[0xFF4B] - 7) : 160; + // step 1: background+window + $skippedAnything = $this->drawBackgroundForLine($this->actualScanLine, $windowLeft, 0); + // At this point, the high (alpha) byte in the frameBuffer is 0xff for colors 1,2,3 and + // 0x00 for color 0. Foreground sprites draw on all colors, background sprites draw on + // top of color 0 only. + // step 2: sprites + $this->drawSpritesForLine($this->actualScanLine); + // step 3: prio tiles+window + if ($skippedAnything) { + $this->drawBackgroundForLine($this->actualScanLine, $windowLeft, 0x80); + } + if ($windowLeft < 160) { + $this->windowSourceLine++; + } + } + + public function drawBackgroundForLine($line, $windowLeft, $priority) { + $skippedTile = false; + $tileNum = 0; + $tileXCoord = 0; + $tileAttrib = 0; + $sourceY = $line + $this->memory[0xFF42]; + $sourceImageLine = $sourceY & 0x7; + $tileX = $this->memory[0xFF43] >> 3; + $memStart = (($this->gfxBackgroundY) ? 0x1C00 : 0x1800) + (($sourceY & 0xF8) << 2); + $screenX = -($this->memory[0xFF43] & 7); + + for (; $screenX < $windowLeft; $tileX++, $screenX += 8) { + $tileXCoord = ($tileX & 0x1F); + $baseaddr = $this->memory[0x8000 + $memStart + $tileXCoord]; + $tileNum = ($this->gfxBackgroundX) ? $baseaddr : (($baseaddr > 0x7F) ? (($baseaddr & 0x7F) + 0x80) : ($baseaddr + 0x100)); + if ($this->cGBC) { + $mapAttrib = $this->VRAM[$memStart + $tileXCoord]; + if (($mapAttrib & 0x80) != $priority) { + $skippedTile = true; + continue; + } + $tileAttrib = (($mapAttrib & 0x07) << 2) + (($mapAttrib >> 5) & 0x03); + $tileNum += 384 * (($mapAttrib >> 3) & 0x01); // tile vram bank + } + $this->drawPartCopy($tileNum, $screenX, $line, $sourceImageLine, $tileAttrib); + } + + if ($windowLeft < 160) { + // window! + $windowStartAddress = ($this->gfxWindowY) ? 0x1C00 : 0x1800; + $windowSourceTileY = $this->windowSourceLine >> 3; + $tileAddress = $windowStartAddress + ($windowSourceTileY * 0x20); + $windowSourceTileLine = $this->windowSourceLine & 0x7; + for ($screenX = $windowLeft; $screenX < 160; $tileAddress++, $screenX += 8) { + $baseaddr = $this->memory[0x8000 + $tileAddress]; + $tileNum = ($this->gfxBackgroundX) ? $baseaddr : (($baseaddr > 0x7F) ? (($baseaddr & 0x7F) + 0x80) : ($baseaddr + 0x100)); + if ($this->cGBC) { + $mapAttrib = $this->VRAM[$tileAddress]; + if (($mapAttrib & 0x80) != $priority) { + $skippedTile = true; + continue; + } + $tileAttrib = (($mapAttrib & 0x07) << 2) + (($mapAttrib >> 5) & 0x03); // mirroring + $tileNum += 384 * (($mapAttrib >> 3) & 0x01); // tile vram bank + } + $this->drawPartCopy($tileNum, $screenX, $line, $windowSourceTileLine, $tileAttrib); + } + } + return $skippedTile; + } + + public function drawPartCopy($tileIndex, $x, $y, $sourceLine, $attribs) { + $image = $this->tileData[$tileIndex + $this->tileCount * $attribs] ? $this->tileData[$tileIndex + $this->tileCount * $attribs] : $this->updateImage($tileIndex, $attribs); + $dst = $x + $y * 160; + $src = $sourceLine * 8; + $dstEnd = ($x > 152) ? (($y + 1) * 160) : ($dst + 8); + if ($x < 0) { // adjust left + $dst -= $x; + $src -= $x; + } + + while ($dst < $dstEnd) { + $this->frameBuffer[$dst++] = $image[$src++]; + } + } + + public function checkPaletteType() { + //Reference the correct palette ahead of time... + $this->palette = ($this->cGBC) ? $this->gbcPalette : (($this->usedBootROM && Settings::$settings[17]) ? $this->gbColorizedPalette : $this->gbPalette); + } + + public function updateImage($tileIndex, $attribs) { + $index_ = $tileIndex + $this->tileCount * $attribs; + $otherBank = ($tileIndex >= 384); + $offset = $otherBank ? (($tileIndex - 384) << 4) : ($tileIndex << 4); + $paletteStart = $attribs & 0xFC; + $transparent = $attribs >= $this->transparentCutoff; + $pixix = 0; + $pixixdx = 1; + $pixixdy = 0; + $tempPix = $this->getTypedArray(64, 0, "int32"); + if (($attribs & 2) != 0) { + $pixixdy = -16; + $pixix = 56; + } + if (($attribs & 1) == 0) { + $pixixdx = -1; + $pixix += 7; + $pixixdy += 16; + } + for ($y = 8; --$y >= 0;) { + $num = $this->weaveLookup[$this->VRAMReadGFX($offset++, $otherBank)] + ($this->weaveLookup[$this->VRAMReadGFX($offset++, $otherBank)] << 1); + if ($num != 0) { + $transparent = false; + } + for ($x = 8; --$x >= 0;) { + $tempPix[$pixix] = $this->palette[$paletteStart + ($num & 3)] & -1; + $pixix += $pixixdx; + $num >>= 2; + } + $pixix += $pixixdy; + } + $this->tileData[$index_] = ($transparent) ? true : $tempPix; + + $this->tileReadState[$tileIndex] = 1; + return $this->tileData[$index_]; + } + + public function drawSpritesForLine($line) { + if (!$this->gfxSpriteShow) { + return; + } + $minSpriteY = $line - (($this->gfxSpriteDouble) ? 15 : 7); + // either only do priorityFlag == 0 (all foreground), + // or first 0x80 (background) and then 0 (foreground) + $priorityFlag = $this->spritePriorityEnabled ? 0x80 : 0; + for (; $priorityFlag >= 0; $priorityFlag -= 0x80) { + $oamIx = 159; + while ($oamIx >= 0) { + $attributes = 0xFF & $this->memory[0xFE00 + $oamIx--]; + if (($attributes & 0x80) == $priorityFlag || !$this->spritePriorityEnabled) { + $tileNum = (0xFF & $this->memory[0xFE00 + $oamIx--]); + $spriteX = (0xFF & $this->memory[0xFE00 + $oamIx--]) - 8; + $spriteY = (0xFF & $this->memory[0xFE00 + $oamIx--]) - 16; + $offset = $line - $spriteY; + if ($spriteX >= 160 || $spriteY < $minSpriteY || $offset < 0) { + continue; + } + if ($this->gfxSpriteDouble) { + $tileNum = $tileNum & 0xFE; + } + $spriteAttrib = ($attributes >> 5) & 0x03; // flipx: from bit 0x20 to 0x01, flipy: from bit 0x40 to 0x02 + if ($this->cGBC) { + $spriteAttrib += 0x20 + (($attributes & 0x07) << 2); // palette + $tileNum += (384 >> 3) * ($attributes & 0x08); // tile vram bank + } + else { + // attributes 0x10: 0x00 = OBJ1 palette, 0x10 = OBJ2 palette + // spriteAttrib: 0x04: OBJ1 palette, 0x08: OBJ2 palette + $spriteAttrib += 0x4 + (($attributes & 0x10) >> 2); + } + if ($priorityFlag == 0x80) { + // background + if ($this->gfxSpriteDouble) { + if (($spriteAttrib & 2) != 0) { + $this->drawPartBgSprite(($tileNum | 1) - ($offset >> 3), $spriteX, $line, $offset & 7, $spriteAttrib); + } + else { + $this->drawPartBgSprite(($tileNum & -2) + ($offset >> 3), $spriteX, $line, $offset & 7, $spriteAttrib); + } + } + else { + $this->drawPartBgSprite($tileNum, $spriteX, $line, $offset, $spriteAttrib); + } + } + else { + // foreground + if ($this->gfxSpriteDouble) { + if (($spriteAttrib & 2) != 0) { + $this->drawPartFgSprite(($tileNum | 1) - ($offset >> 3), $spriteX, $line, $offset & 7, $spriteAttrib); + } + else { + $this->drawPartFgSprite(($tileNum & -2) + ($offset >> 3), $spriteX, $line, $offset & 7, $spriteAttrib); + } + } + else { + $this->drawPartFgSprite($tileNum, $spriteX, $line, $offset, $spriteAttrib); + } + } + } + else { + $oamIx -= 3; + } + } + } + } + + public function drawPartFgSprite($tileIndex, $x, $y, $sourceLine, $attribs) { + $im = $this->tileData[$tileIndex + $this->tileCount * $attribs] ? $this->tileData[$tileIndex + $this->tileCount * $attribs] : $this->updateImage($tileIndex, $attribs); + if ($im === true) { + return; + } + $dst = $x + $y * 160; + $src = $sourceLine * 8; + $dstEnd = ($x > 152) ? (($y + 1) * 160) : ($dst + 8); + if ($x < 0) { // adjust left + $dst -= $x; + $src -= $x; + } + + while ($dst < $dstEnd) { + $this->frameBuffer[$dst] = $im[$src]; + $dst++; + $src++; + } + } + + public function drawPartBgSprite($tileIndex, $x, $y, $sourceLine, $attribs) { + $im = $this->tileData[$tileIndex + $this->tileCount * $attribs] ? $this->tileData[$tileIndex + $this->tileCount * $attribs] : $this->updateImage($tileIndex, $attribs); + if ($im === true) { + return; + } + $dst = $x + $y * 160; + $src = $sourceLine * 8; + $dstEnd = ($x > 152) ? (($y + 1) * 160) : ($dst + 8); + if ($x < 0) { // adjust left + $dst -= $x; + $src -= $x; + } + while ($dst < $dstEnd) { + //if ($im[$src] < 0 && $this->frameBuffer[$dst] >= 0) { + $this->frameBuffer[$dst] = $im[$src]; + // } + $dst++; + $src++; + } + } + + //Memory Reading: + public function memoryRead($address) { + //Act as a wrapper for reading the returns from the compiled jumps to memory. + return $this->memoryReader[$address]($this, $address); //This seems to be faster than the usual if/else. + } + + public function memoryReadJumpCompile() { + //Faster in some browsers, since we are doing less conditionals overall by implementing them in advance. + for ($index = 0x0000; $index <= 0xFFFF; $index++) { + if ($index < 0x4000) { + $this->memoryReader[$index] = function ($parentObj, $address) { //memoryReadNormal + return $parentObj->memory[$address]; + }; + } + else if ($index < 0x8000) { + $this->memoryReader[$index] = function ($parentObj, $address) { //memoryReadROM + return $parentObj->ROM[$parentObj->currentROMBank + $address]; + }; + } + else if ($index >= 0x8000 && $index < 0xA000) { + $VRAMReadCGBCPU = function ($parentObj, $address) { + //CPU Side Reading The VRAM (Optimized for GameBoy Color) + return ($parentObj->modeSTAT > 2) ? 0xFF : (($parentObj->currVRAMBank == 0) ? $parentObj->memory[$address] : $parentObj->VRAM[$address - 0x8000]); + }; + + $VRAMReadDMGCPU = function ($parentObj, $address) { + //CPU Side Reading The VRAM (Optimized for classic GameBoy) + return ($parentObj->modeSTAT > 2) ? 0xFF : $parentObj->memory[$address]; + }; + + $this->memoryReader[$index] = ($this->cGBC) ? $VRAMReadCGBCPU : $VRAMReadDMGCPU; + } + else if ($index >= 0xA000 && $index < 0xC000) { + if (($this->numRAMBanks == 1 / 16 && $index < 0xA200) || $this->numRAMBanks >= 1) { + if (!$this->cMBC3) { + $this->memoryReader[$index] = function ($parentObj, $address) { //memoryReadMBC + //Switchable RAM + if ($parentObj->MBCRAMBanksEnabled || Settings::$settings[10]) { + return $parentObj->MBCRam[$address + $parentObj->currMBCRAMBankPosition]; + } + //cout("Reading from disabled RAM.", 1); + return 0xFF; + }; + } + else { + //MBC3 RTC + RAM: + $this->memoryReader[$index] = function ($parentObj, $address) { //memoryReadMBC3 + //Switchable RAM + if ($parentObj->MBCRAMBanksEnabled || Settings::$settings[10]) { + switch ($parentObj->currMBCRAMBank) { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + return $parentObj->MBCRam[$address + $parentObj->currMBCRAMBankPosition]; + break; + case 0x08: + return $parentObj->latchedSeconds; + break; + case 0x09: + return $parentObj->latchedMinutes; + break; + case 0x0A: + return $parentObj->latchedHours; + break; + case 0x0B: + return $parentObj->latchedLDays; + break; + case 0x0C: + return ((($parentObj->RTCDayOverFlow) ? 0x80 : 0) + (($parentObj->RTCHALT) ? 0x40 : 0)) + $parentObj->latchedHDays; + } + } + //cout("Reading from invalid or disabled RAM.", 1); + return 0xFF; + }; + } + } + else { + $this->memoryReader[$index] = function ($parentObj, $address) { //memoryReadBAD + return 0xFF; + }; + } + } + else if ($index >= 0xC000 && $index < 0xE000) { + if (!$this->cGBC || $index < 0xD000) { + $this->memoryReader[$index] = function ($parentObj, $address) { //memoryReadNormal + return $parentObj->memory[$address]; + }; + } + else { + $this->memoryReader[$index] = function ($parentObj, $address) { //memoryReadGBCMemory + return $parentObj->GBCMemory[$address + $parentObj->gbcRamBankPosition]; + }; + } + } + else if ($index >= 0xE000 && $index < 0xFE00) { + if (!$this->cGBC || $index < 0xF000) { + $this->memoryReader[$index] = function ($parentObj, $address) { //memoryReadECHONormal + return $parentObj->memory[$address - 0x2000]; + }; + } + else { + $this->memoryReader[$index] = function ($parentObj, $address) { //memoryReadECHOGBCMemory + return $parentObj->GBCMemory[$address + $parentObj->gbcRamBankPositionECHO]; + }; + } + } + else if ($index < 0xFEA0) { + $this->memoryReader[$index] = function ($parentObj, $address) { //memoryReadOAM + return ($parentObj->modeSTAT > 1) ? 0xFF : $parentObj->memory[$address]; + }; + } + else if ($this->cGBC && $index >= 0xFEA0 && $index < 0xFF00) { + $this->memoryReader[$index] = function ($parentObj, $address) { //memoryReadNormal + return $parentObj->memory[$address]; + }; + } + else if ($index >= 0xFF00) { + switch ($index) { + case 0xFF00: + $this->memoryReader[0xFF00] = function ($parentObj, $address) { + return 0xC0 | $parentObj->memory[0xFF00]; //Top nibble returns as set. + }; + break; + case 0xFF01: + $this->memoryReader[0xFF01] = function ($parentObj, $address) { + return (($parentObj->memory[0xFF02] & 0x1) == 0x1) ? 0xFF : $parentObj->memory[0xFF01]; + }; + break; + case 0xFF02: + if ($this->cGBC) { + $this->memoryReader[0xFF02] = function ($parentObj, $address) { + return 0x7C | $parentObj->memory[0xFF02]; + }; + } + else { + $this->memoryReader[0xFF02] = function ($parentObj, $address) { + return 0x7E | $parentObj->memory[0xFF02]; + }; + } + break; + case 0xFF07: + $this->memoryReader[0xFF07] = function ($parentObj, $address) { + return 0xF8 | $parentObj->memory[0xFF07]; + }; + break; + case 0xFF0F: + $this->memoryReader[0xFF0F] = function ($parentObj, $address) { + return 0xE0 | $parentObj->memory[0xFF0F]; + }; + break; + case 0xFF10: + $this->memoryReader[0xFF10] = function ($parentObj, $address) { + return 0x80 | $parentObj->memory[0xFF10]; + }; + break; + case 0xFF11: + $this->memoryReader[0xFF11] = function ($parentObj, $address) { + return 0x3F | $parentObj->memory[0xFF11]; + }; + break; + case 0xFF14: + $this->memoryReader[0xFF14] = function ($parentObj, $address) { + return 0xBF | $parentObj->memory[0xFF14]; + }; + break; + case 0xFF16: + $this->memoryReader[0xFF16] = function ($parentObj, $address) { + return 0x3F | $parentObj->memory[0xFF16]; + }; + break; + case 0xFF19: + $this->memoryReader[0xFF19] = function ($parentObj, $address) { + return 0xBF | $parentObj->memory[0xFF19]; + }; + break; + case 0xFF1A: + $this->memoryReader[0xFF1A] = function ($parentObj, $address) { + return 0x7F | $parentObj->memory[0xFF1A]; + }; + break; + case 0xFF1B: + $this->memoryReader[0xFF1B] = function ($parentObj, $address) { + return 0xFF; + }; + break; + case 0xFF1C: + $this->memoryReader[0xFF1C] = function ($parentObj, $address) { + return 0x9F | $parentObj->memory[0xFF1C]; + }; + break; + case 0xFF1E: + $this->memoryReader[0xFF1E] = function ($parentObj, $address) { + return 0xBF | $parentObj->memory[0xFF1E]; + }; + break; + case 0xFF20: + $this->memoryReader[0xFF20] = function ($parentObj, $address) { + return 0xFF; + }; + break; + case 0xFF23: + $this->memoryReader[0xFF23] = function ($parentObj, $address) { + return 0xBF | $parentObj->memory[0xFF23]; + }; + break; + case 0xFF26: + $this->memoryReader[0xFF26] = function ($parentObj, $address) { + return 0x70 | $parentObj->memory[0xFF26]; + }; + break; + case 0xFF30: + case 0xFF31: + case 0xFF32: + case 0xFF33: + case 0xFF34: + case 0xFF35: + case 0xFF36: + case 0xFF37: + case 0xFF38: + case 0xFF39: + case 0xFF3A: + case 0xFF3B: + case 0xFF3C: + case 0xFF3D: + case 0xFF3E: + case 0xFF3F: + $this->memoryReader[$index] = function ($parentObj, $address) { + return (($parentObj->memory[0xFF26] & 0x4) == 0x4) ? 0xFF : $parentObj->memory[$address]; + }; + break; + case 0xFF41: + $this->memoryReader[0xFF41] = function ($parentObj, $address) { + return 0x80 | $parentObj->memory[0xFF41] | $parentObj->modeSTAT; + }; + break; + case 0xFF44: + $this->memoryReader[0xFF44] = function ($parentObj, $address) { + return (($parentObj->LCDisOn) ? $parentObj->memory[0xFF44] : 0); + }; + break; + case 0xFF4F: + $this->memoryReader[0xFF4F] = function ($parentObj, $address) { + return $parentObj->currVRAMBank; + }; + break; + default: + $this->memoryReader[$index] = function ($parentObj, $address) { //memoryReadNormal + return $parentObj->memory[$address]; + }; + } + } + else { + $this->memoryReader[$index] = function ($parentObj, $address) { //memoryReadBAD + return 0xFF; + }; + } + } + } + + public function VRAMReadGFX($address, $gbcBank) { + //Graphics Side Reading The VRAM + return ((!$gbcBank) ? $this->memory[0x8000 + $address] : $this->VRAM[$address]); + } + + public function setCurrentMBC1ROMBank() { + //Read the cartridge ROM data from RAM memory: + switch ($this->ROMBank1offs) { + case 0x00: + case 0x20: + case 0x40: + case 0x60: + //Bank calls for 0x00, 0x20, 0x40, and 0x60 are really for 0x01, 0x21, 0x41, and 0x61. + $this->currentROMBank = $this->ROMBank1offs * 0x4000; + break; + default: + $this->currentROMBank = ($this->ROMBank1offs - 1) * 0x4000; + } + while ($this->currentROMBank + 0x4000 >= count($this->ROM)) { + $this->currentROMBank -= count($this->ROM); + } + } + + public function setCurrentMBC2AND3ROMBank() { + //Read the cartridge ROM data from RAM memory: + //Only map bank 0 to bank 1 here (MBC2 is like MBC1, but can only do 16 banks, so only the bank 0 quirk appears for MBC2): + $this->currentROMBank = max($this->ROMBank1offs - 1, 0) * 0x4000; + while ($this->currentROMBank + 0x4000 >= count($this->ROM)) { + $this->currentROMBank -= count($this->ROM); + } + } + public function setCurrentMBC5ROMBank() { + //Read the cartridge ROM data from RAM memory: + $this->currentROMBank = ($this->ROMBank1offs - 1) * 0x4000; + while ($this->currentROMBank + 0x4000 >= count($this->ROM)) { + $this->currentROMBank -= count($this->ROM); + } + } + + //Memory Writing: + public function memoryWrite($address, $data) { + //Act as a wrapper for writing by compiled jumps to specific memory writing functions. + $this->memoryWriter[$address]($this, $address, $data); + } + + public function memoryWriteJumpCompile() { + $MBCWriteEnable = function ($parentObj, $address, $data) { + //MBC RAM Bank Enable/Disable: + $parentObj->MBCRAMBanksEnabled = (($data & 0x0F) == 0x0A); //If lower nibble is 0x0A, then enable, otherwise disable. + }; + + $MBC3WriteROMBank = function ($parentObj, $address, $data) { + //MBC3 ROM bank switching: + $parentObj->ROMBank1offs = $data & 0x7F; + $parentObj->setCurrentMBC2AND3ROMBank(); + }; + + $cartIgnoreWrite = function ($parentObj, $address, $data) { + //We might have encountered illegal RAM writing or such, so just do nothing... + }; + + //Faster in some browsers, since we are doing less conditionals overall by implementing them in advance. + for ($index = 0x0000; $index <= 0xFFFF; $index++) { + if ($index < 0x8000) { + if ($this->cMBC1) { + if ($index < 0x2000) { + $this->memoryWriter[$index] = $MBCWriteEnable; + } + else if ($index < 0x4000) { + $this->memoryWriter[$index] = function ($parentObj, $address, $data) { // MBC1WriteROMBank + //MBC1 ROM bank switching: + $parentObj->ROMBank1offs = ($parentObj->ROMBank1offs & 0x60) | ($data & 0x1F); + $parentObj->setCurrentMBC1ROMBank(); + }; + } + else if ($index < 0x6000) { + $this->memoryWriter[$index] = function ($parentObj, $address, $data) { //MBC1WriteRAMBank + //MBC1 RAM bank switching + if ($parentObj->MBC1Mode) { + //4/32 Mode + $parentObj->currMBCRAMBank = $data & 0x3; + $parentObj->currMBCRAMBankPosition = ($parentObj->currMBCRAMBank << 13) - 0xA000; + } + else { + //16/8 Mode + $parentObj->ROMBank1offs = (($data & 0x03) << 5) | ($parentObj->ROMBank1offs & 0x1F); + $parentObj->setCurrentMBC1ROMBank(); + } + }; + } + else { + $this->memoryWriter[$index] = function ($parentObj, $address, $data) { //MBC1WriteType + //MBC1 mode setting: + $parentObj->MBC1Mode = (($data & 0x1) == 0x1); + }; + } + } + else if ($this->cMBC2) { + if ($index < 0x1000) { + $this->memoryWriter[$index] = $MBCWriteEnable; + } + else if ($index >= 0x2100 && $index < 0x2200) { + $this->memoryWriter[$index] = function ($parentObj, $address, $data) { //MBC2WriteROMBank + //MBC2 ROM bank switching: + $parentObj->ROMBank1offs = $data & 0x0F; + $parentObj->setCurrentMBC2AND3ROMBank(); + }; + } + else { + $this->memoryWriter[$index] = $cartIgnoreWrite; + } + } + else if ($this->cMBC3) { + if ($index < 0x2000) { + $this->memoryWriter[$index] = $MBCWriteEnable; + } + else if ($index < 0x4000) { + $this->memoryWriter[$index] = $MBC3WriteROMBank; + } + else if ($index < 0x6000) { + $this->memoryWriter[$index] = function ($parentObj, $address, $data) { //MBC3WriteRAMBank + $parentObj->currMBCRAMBank = $data; + if ($data < 4) { + //MBC3 RAM bank switching + $parentObj->currMBCRAMBankPosition = ($parentObj->currMBCRAMBank << 13) - 0xA000; + } + }; + } + else { + $this->memoryWriter[$index] = function ($parentObj, $address, $data) { //MBC3WriteRTCLatch + if ($data == 0) { + $parentObj->RTCisLatched = false; + } + else if (!$parentObj->RTCisLatched) { + //Copy over the current RTC time for reading. + $parentObj->RTCisLatched = true; + $parentObj->latchedSeconds = floor($parentObj->RTCSeconds); + $parentObj->latchedMinutes = $parentObj->RTCMinutes; + $parentObj->latchedHours = $parentObj->RTCHours; + $parentObj->latchedLDays = ($parentObj->RTCDays & 0xFF); + $parentObj->latchedHDays = $parentObj->RTCDays >> 8; + } + }; + } + } + else if ($this->cMBC5 || $this->cRUMBLE) { + if ($index < 0x2000) { + $this->memoryWriter[$index] = $MBCWriteEnable; + } + else if ($index < 0x3000) { + $this->memoryWriter[$index] = function ($parentObj, $address, $data) { //MBC5WriteROMBankLow + //MBC5 ROM bank switching: + $parentObj->ROMBank1offs = ($parentObj->ROMBank1offs & 0x100) | $data; + $parentObj->setCurrentMBC5ROMBank(); + }; + } + else if ($index < 0x4000) { + $this->memoryWriter[$index] = function ($parentObj, $address, $data) { //MBC5WriteROMBankHigh + //MBC5 ROM bank switching (by least significant bit): + $parentObj->ROMBank1offs = (($data & 0x01) << 8) | ($parentObj->ROMBank1offs & 0xFF); + $parentObj->setCurrentMBC5ROMBank(); + }; + } + else if ($index < 0x6000) { + $RUMBLEWriteRAMBank = function ($parentObj, $address, $data) { + //MBC5 RAM bank switching + //Like MBC5, but bit 3 of the lower nibble is used for rumbling and bit 2 is ignored. + $parentObj->currMBCRAMBank = $data & 0x3; + $parentObj->currMBCRAMBankPosition = ($parentObj->currMBCRAMBank << 13) - 0xA000; + }; + + $MBC5WriteRAMBank = function ($parentObj, $address, $data) { + //MBC5 RAM bank switching + $parentObj->currMBCRAMBank = $data & 0xF; + $parentObj->currMBCRAMBankPosition = ($parentObj->currMBCRAMBank << 13) - 0xA000; + }; + + $this->memoryWriter[$index] = ($this->cRUMBLE) ? $RUMBLEWriteRAMBank : $MBC5WriteRAMBank; + } + else { + $this->memoryWriter[$index] = $cartIgnoreWrite; + } + } + else if ($this->cHuC3) { + if ($index < 0x2000) { + $this->memoryWriter[$index] = $MBCWriteEnable; + } + else if ($index < 0x4000) { + $this->memoryWriter[$index] = $MBC3WriteROMBank; + } + else if ($index < 0x6000) { + $this->memoryWriter[$index] = function ($parentObj, $address, $data) { //HuC3WriteRAMBank + //HuC3 RAM bank switching + $parentObj->currMBCRAMBank = $data & 0x03; + $parentObj->currMBCRAMBankPosition = ($parentObj->currMBCRAMBank << 13) - 0xA000; + }; + } + else { + $this->memoryWriter[$index] = $cartIgnoreWrite; + } + } + else { + $this->memoryWriter[$index] = $cartIgnoreWrite; + } + } + else if ($index < 0xA000) { + $this->memoryWriter[$index] = function ($parentObj, $address, $data) { // VRAMWrite + if ($parentObj->modeSTAT < 3) { //VRAM cannot be written to during mode 3 + if ($address < 0x9800) { // Bkg Tile data area + $tileIndex = (($address - 0x8000) >> 4) + (384 * $parentObj->currVRAMBank); + if ($parentObj->tileReadState[$tileIndex] == 1) { + $r = count($parentObj->tileData) - $parentObj->tileCount + $tileIndex; + do { + $parentObj->tileData[$r] = null; + $r -= $parentObj->tileCount; + } while ($r >= 0); + $parentObj->tileReadState[$tileIndex] = 0; + } + } + if ($parentObj->currVRAMBank == 0) { + $parentObj->memory[$address] = $data; + } + else { + $parentObj->VRAM[$address - 0x8000] = $data; + } + } + }; + } + else if ($index < 0xC000) { + if (($this->numRAMBanks == 1 / 16 && $index < 0xA200) || $this->numRAMBanks >= 1) { + if (!$this->cMBC3) { + $this->memoryWriter[$index] = function ($parentObj, $address, $data) { //memoryWriteMBCRAM + if ($parentObj->MBCRAMBanksEnabled || Settings::$settings[10]) { + $parentObj->MBCRam[$address + $parentObj->currMBCRAMBankPosition] = $data; + } + }; + } + else { + //MBC3 RTC + RAM: + $this->memoryWriter[$index] = function ($parentObj, $address, $data) { //memoryWriteMBC3RAM + if ($parentObj->MBCRAMBanksEnabled || Settings::$settings[10]) { + switch ($parentObj->currMBCRAMBank) { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + $parentObj->MBCRam[$address + $parentObj->currMBCRAMBankPosition] = $data; + break; + case 0x08: + if ($data < 60) { + $parentObj->RTCSeconds = $data; + } + else { + echo "(Bank #" + $parentObj->currMBCRAMBank + ") RTC write out of range: " + $data . PHP_EOL; + } + break; + case 0x09: + if ($data < 60) { + $parentObj->RTCMinutes = $data; + } + else { + echo "(Bank #" + $parentObj->currMBCRAMBank + ") RTC write out of range: " + $data . PHP_EOL; + } + break; + case 0x0A: + if ($data < 24) { + $parentObj->RTCHours = $data; + } + else { + echo "(Bank #" + $parentObj->currMBCRAMBank + ") RTC write out of range: " + $data . PHP_EOL; + } + break; + case 0x0B: + $parentObj->RTCDays = ($data & 0xFF) | ($parentObj->RTCDays & 0x100); + break; + case 0x0C: + $parentObj->RTCDayOverFlow = ($data & 0x80) == 0x80; + $parentObj->RTCHalt = ($data & 0x40) == 0x40; + $parentObj->RTCDays = (($data & 0x1) << 8) | ($parentObj->RTCDays & 0xFF); + break; + default: + echo "Invalid MBC3 bank address selected: " + $parentObj->currMBCRAMBank . PHP_EOL; + } + } + }; + } + } + else { + $this->memoryWriter[$index] = $cartIgnoreWrite; + } + } + else if ($index < 0xE000) { + if ($this->cGBC && $index >= 0xD000) { + $this->memoryWriter[$index] = function ($parentObj, $address, $data) { //memoryWriteGBCRAM + $parentObj->GBCMemory[$address + $parentObj->gbcRamBankPosition] = $data; + }; + } + else { + $this->memoryWriter[$index] = function ($parentObj, $address, $data) { //memoryWriteNormal + $parentObj->memory[$address] = $data; + }; + } + } + else if ($index < 0xFE00) { + if ($this->cGBC && $index >= 0xF000) { + $this->memoryWriter[$index] = function ($parentObj, $address, $data) { //memoryWriteECHOGBCRAM + $parentObj->GBCMemory[$address + $parentObj->gbcRamBankPositionECHO] = $data; + }; + } + else { + $this->memoryWriter[$index] = function ($parentObj, $address, $data) { //memoryWriteECHONormal + $parentObj->memory[$address - 0x2000] = $data; + }; + } + } + else if ($index <= 0xFEA0) { + $this->memoryWriter[$index] = function ($parentObj, $address, $data) { //memoryWriteOAMRAM + if ($parentObj->modeSTAT < 2) { //OAM RAM cannot be written to in mode 2 & 3 + $parentObj->memory[$address] = $data; + } + }; + } + else if ($index < 0xFF00) { + if ($this->cGBC) { //Only GBC has access to this RAM. + $this->memoryWriter[$index] = function ($parentObj, $address, $data) { //memoryWriteNormal + $parentObj->memory[$address] = $data; + }; + } + else { + $this->memoryWriter[$index] = $cartIgnoreWrite; + } + } + else { + //Start the I/O initialization by filling in the slots as normal memory: + $this->memoryWriter[$index] = function ($parentObj, $address, $data) { //memoryWriteNormal + $parentObj->memory[$address] = $data; + }; + } + } + $this->registerWriteJumpCompile(); //Compile the I/O write functions separately... + } + + public function registerWriteJumpCompile() { + //I/O Registers (GB + GBC): + $this->memoryWriter[0xFF00] = function ($parentObj, $address, $data) { + $parentObj->memory[0xFF00] = ($data & 0x30) | (((($data & 0x20) == 0) ? ($parentObj->JoyPad >> 4) : 0xF) & ((($data & 0x10) == 0) ? ($parentObj->JoyPad & 0xF) : 0xF)); + }; + $this->memoryWriter[0xFF02] = function ($parentObj, $address, $data) { + if ((($data & 0x1) == 0x1)) { + //Internal clock: + $parentObj->memory[0xFF02] = ($data & 0x7F); + $parentObj->memory[0xFF0F] |= 0x8; //Get this time delayed... + } + else { + //External clock: + $parentObj->memory[0xFF02] = $data; + //No connected serial device, so don't trigger interrupt... + } + }; + $this->memoryWriter[0xFF04] = function ($parentObj, $address, $data) { + $parentObj->memory[0xFF04] = 0; + }; + $this->memoryWriter[0xFF07] = function ($parentObj, $address, $data) { + $parentObj->memory[0xFF07] = $data & 0x07; + $parentObj->TIMAEnabled = ($data & 0x04) == 0x04; + $parentObj->TACClocker = pow(4, (($data & 0x3) != 0) ? ($data & 0x3) : 4); //TODO: Find a way to not make a conditional in here... + }; + + // BEGIN - Audio Writers + $this->memoryWriter[0xFF10] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF11] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF12] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF13] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF14] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF16] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF17] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF18] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF19] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF1A] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF1B] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF1C] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF1D] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF1E] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF20] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF21] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF22] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF23] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF24] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF25] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF26] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF30] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF31] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF32] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF33] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF34] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF35] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF36] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF37] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF38] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF39] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF3A] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF3B] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF3C] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF3D] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF3E] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF3F] = function ($parentObj, $address, $data) { + }; + $this->memoryWriter[0xFF44] = function ($parentObj, $address, $data) { + //Read only + }; + // END - Audio Writers + // + $this->memoryWriter[0xFF45] = function ($parentObj, $address, $data) { + $parentObj->memory[0xFF45] = $data; + if ($parentObj->LCDisOn) { + $parentObj->matchLYC(); //Get the compare of the first scan line. + } + }; + $this->memoryWriter[0xFF46] = function ($parentObj, $address, $data) { + $parentObj->memory[0xFF46] = $data; + if ($parentObj->cGBC || $data > 0x7F) { //DMG cannot DMA from the ROM banks. + $data <<= 8; + $address = 0xFE00; + while ($address < 0xFEA0) { + $parentObj->memory[$address++] = $parentObj->memoryReader[$data]($parentObj, $data++); + } + } + }; + $this->memoryWriter[0xFF47] = function ($parentObj, $address, $data) { + $parentObj->decodePalette(0, $data); + if ($parentObj->memory[0xFF47] != $data) { + $parentObj->memory[0xFF47] = $data; + $parentObj->invalidateAll(0); + } + }; + $this->memoryWriter[0xFF48] = function ($parentObj, $address, $data) { + $parentObj->decodePalette(4, $data); + if ($parentObj->memory[0xFF48] != $data) { + $parentObj->memory[0xFF48] = $data; + $parentObj->invalidateAll(1); + } + }; + $this->memoryWriter[0xFF49] = function ($parentObj, $address, $data) { + $parentObj->decodePalette(8, $data); + if ($parentObj->memory[0xFF49] != $data) { + $parentObj->memory[0xFF49] = $data; + $parentObj->invalidateAll(2); + } + }; + if ($this->cGBC) { + //GameBoy Color Specific I/O: + $this->memoryWriter[0xFF40] = function ($parentObj, $address, $data) { + $temp_var = ($data & 0x80) == 0x80; + if ($temp_var != $parentObj->LCDisOn) { + //When the display mode changes... + $parentObj->LCDisOn = $temp_var; + $parentObj->memory[0xFF41] &= 0xF8; + $parentObj->STATTracker = $parentObj->modeSTAT = $parentObj->LCDTicks = $parentObj->actualScanLine = $parentObj->memory[0xFF44] = 0; + if ($parentObj->LCDisOn) { + $parentObj->matchLYC(); //Get the compare of the first scan line. + $parentObj->LCDCONTROL = $parentObj->LINECONTROL; + } + else { + $parentObj->LCDCONTROL = $parentObj->DISPLAYOFFCONTROL; + $parentObj->DisplayShowOff(); + } + $parentObj->memory[0xFF0F] &= 0xFD; + } + $parentObj->gfxWindowY = ($data & 0x40) == 0x40; + $parentObj->gfxWindowDisplay = ($data & 0x20) == 0x20; + $parentObj->gfxBackgroundX = ($data & 0x10) == 0x10; + $parentObj->gfxBackgroundY = ($data & 0x08) == 0x08; + $parentObj->gfxSpriteDouble = ($data & 0x04) == 0x04; + $parentObj->gfxSpriteShow = ($data & 0x02) == 0x02; + $parentObj->spritePriorityEnabled = ($data & 0x01) == 0x01; + $parentObj->memory[0xFF40] = $data; + }; + $this->memoryWriter[0xFF41] = function ($parentObj, $address, $data) { + $parentObj->LYCMatchTriggerSTAT = (($data & 0x40) == 0x40); + $parentObj->mode2TriggerSTAT = (($data & 0x20) == 0x20); + $parentObj->mode1TriggerSTAT = (($data & 0x10) == 0x10); + $parentObj->mode0TriggerSTAT = (($data & 0x08) == 0x08); + $parentObj->memory[0xFF41] = ($data & 0xF8); + }; + $this->memoryWriter[0xFF4D] = function ($parentObj, $address, $data) { + $parentObj->memory[0xFF4D] = ($data & 0x7F) + ($parentObj->memory[0xFF4D] & 0x80); + }; + $this->memoryWriter[0xFF4F] = function ($parentObj, $address, $data) { + $parentObj->currVRAMBank = $data & 0x01; + //Only writable by GBC. + }; + $this->memoryWriter[0xFF51] = function ($parentObj, $address, $data) { + if (!$parentObj->hdmaRunning) { + $parentObj->memory[0xFF51] = $data; + } + }; + $this->memoryWriter[0xFF52] = function ($parentObj, $address, $data) { + if (!$parentObj->hdmaRunning) { + $parentObj->memory[0xFF52] = $data & 0xF0; + } + }; + $this->memoryWriter[0xFF53] = function ($parentObj, $address, $data) { + if (!$parentObj->hdmaRunning) { + $parentObj->memory[0xFF53] = $data & 0x1F; + } + }; + $this->memoryWriter[0xFF54] = function ($parentObj, $address, $data) { + if (!$parentObj->hdmaRunning) { + $parentObj->memory[0xFF54] = $data & 0xF0; + } + }; + $this->memoryWriter[0xFF55] = function ($parentObj, $address, $data) { + if (!$parentObj->hdmaRunning) { + if (($data & 0x80) == 0) { + //DMA + $parentObj->CPUTicks += 1 + ((8 * (($data & 0x7F) + 1)) * $parentObj->multiplier); + $dmaSrc = ($parentObj->memory[0xFF51] << 8) + $parentObj->memory[0xFF52]; + $dmaDst = 0x8000 + ($parentObj->memory[0xFF53] << 8) + $parentObj->memory[0xFF54]; + $endAmount = ((($data & 0x7F) * 0x10) + 0x10); + for ($loopAmount = 0; $loopAmount < $endAmount; $loopAmount++) { + $parentObj->memoryWrite($dmaDst++, $parentObj->memoryRead($dmaSrc++)); + } + $parentObj->memory[0xFF51] = (($dmaSrc & 0xFF00) >> 8); + $parentObj->memory[0xFF52] = ($dmaSrc & 0x00F0); + $parentObj->memory[0xFF53] = (($dmaDst & 0x1F00) >> 8); + $parentObj->memory[0xFF54] = ($dmaDst & 0x00F0); + $parentObj->memory[0xFF55] = 0xFF; //Transfer completed. + } + else { + //H-Blank DMA + if ($data > 0x80) { + $parentObj->hdmaRunning = true; + $parentObj->memory[0xFF55] = $data & 0x7F; + } + else { + $parentObj->memory[0xFF55] = 0xFF; + } + } + } + else if (($data & 0x80) == 0) { + //Stop H-Blank DMA + $parentObj->hdmaRunning = false; + $parentObj->memory[0xFF55] |= 0x80; + } + }; + $this->memoryWriter[0xFF68] = function ($parentObj, $address, $data) { + $parentObj->memory[0xFF69] = 0xFF & $parentObj->gbcRawPalette[$data & 0x3F]; + $parentObj->memory[0xFF68] = $data; + }; + $this->memoryWriter[0xFF69] = function ($parentObj, $address, $data) { + $parentObj->setGBCPalette($parentObj->memory[0xFF68] & 0x3F, $data); + if ($parentObj->usbtsb($parentObj->memory[0xFF68]) < 0) { // high bit = autoincrement + $next = (($parentObj->usbtsb($parentObj->memory[0xFF68]) + 1) & 0x3F); + $parentObj->memory[0xFF68] = ($next | 0x80); + $parentObj->memory[0xFF69] = 0xFF & $parentObj->gbcRawPalette[$next]; + } + else { + $parentObj->memory[0xFF69] = $data; + } + }; + $this->memoryWriter[0xFF6A] = function ($parentObj, $address, $data) { + $parentObj->memory[0xFF6B] = 0xFF & $parentObj->gbcRawPalette[($data & 0x3F) | 0x40]; + $parentObj->memory[0xFF6A] = $data; + }; + $this->memoryWriter[0xFF6B] = function ($parentObj, $address, $data) { + $parentObj->setGBCPalette(($parentObj->memory[0xFF6A] & 0x3F) + 0x40, $data); + if ($parentObj->usbtsb($parentObj->memory[0xFF6A]) < 0) { // high bit = autoincrement + $next = (($parentObj->memory[0xFF6A] + 1) & 0x3F); + $parentObj->memory[0xFF6A] = ($next | 0x80); + $parentObj->memory[0xFF6B] = 0xFF & $parentObj->gbcRawPalette[$next | 0x40]; + } + else { + $parentObj->memory[0xFF6B] = $data; + } + }; + $this->memoryWriter[0xFF70] = function ($parentObj, $address, $data) { + $addressCheck = ($parentObj->memory[0xFF51] << 8) | $parentObj->memory[0xFF52]; //Cannot change the RAM bank while WRAM is the source of a running HDMA. + if (!$parentObj->hdmaRunning || $addressCheck < 0xD000 || $addressCheck >= 0xE000) { + $parentObj->gbcRamBank = max($data & 0x07, 1); //Bank range is from 1-7 + $parentObj->gbcRamBankPosition = (($parentObj->gbcRamBank - 1) * 0x1000) - 0xD000; + $parentObj->gbcRamBankPositionECHO = (($parentObj->gbcRamBank - 1) * 0x1000) - 0xF000; + } + $parentObj->memory[0xFF70] = ($data | 0x40); //Bit 6 cannot be written to. + }; + } + else { + //Fill in the GameBoy Color I/O registers as normal RAM for GameBoy compatibility: + $this->memoryWriter[0xFF40] = function ($parentObj, $address, $data) { + $temp_var = ($data & 0x80) == 0x80; + if ($temp_var != $parentObj->LCDisOn) { + //When the display mode changes... + $parentObj->LCDisOn = $temp_var; + $parentObj->memory[0xFF41] &= 0xF8; + $parentObj->STATTracker = $parentObj->modeSTAT = $parentObj->LCDTicks = $parentObj->actualScanLine = $parentObj->memory[0xFF44] = 0; + if ($parentObj->LCDisOn) { + $parentObj->matchLYC(); //Get the compare of the first scan line. + $parentObj->LCDCONTROL = $parentObj->LINECONTROL; + } + else { + $parentObj->LCDCONTROL = $parentObj->DISPLAYOFFCONTROL; + $parentObj->DisplayShowOff(); + } + $parentObj->memory[0xFF0F] &= 0xFD; + } + $parentObj->gfxWindowY = ($data & 0x40) == 0x40; + $parentObj->gfxWindowDisplay = ($data & 0x20) == 0x20; + $parentObj->gfxBackgroundX = ($data & 0x10) == 0x10; + $parentObj->gfxBackgroundY = ($data & 0x08) == 0x08; + $parentObj->gfxSpriteDouble = ($data & 0x04) == 0x04; + $parentObj->gfxSpriteShow = ($data & 0x02) == 0x02; + if (($data & 0x01) == 0) { + // this emulates the gbc-in-gb-mode, not the original gb-mode + $parentObj->bgEnabled = false; + $parentObj->gfxWindowDisplay = false; + } + else { + $parentObj->bgEnabled = true; + } + $parentObj->memory[0xFF40] = $data; + }; + $this->memoryWriter[0xFF41] = function ($parentObj, $address, $data) { + $parentObj->LYCMatchTriggerSTAT = (($data & 0x40) == 0x40); + $parentObj->mode2TriggerSTAT = (($data & 0x20) == 0x20); + $parentObj->mode1TriggerSTAT = (($data & 0x10) == 0x10); + $parentObj->mode0TriggerSTAT = (($data & 0x08) == 0x08); + $parentObj->memory[0xFF41] = ($data & 0xF8); + if ($parentObj->LCDisOn && $parentObj->modeSTAT < 2) { + $parentObj->memory[0xFF0F] |= 0x2; + } + }; + $this->memoryWriter[0xFF4D] = function ($parentObj, $address, $data) { + $parentObj->memory[0xFF4D] = $data; + }; + $this->memoryWriter[0xFF4F] = function ($parentObj, $address, $data) { + //Not writable in DMG mode. + }; + $this->memoryWriter[0xFF55] = function ($parentObj, $address, $data) { + $parentObj->memory[0xFF55] = $data; + }; + $this->memoryWriter[0xFF68] = function ($parentObj, $address, $data) { + $parentObj->memory[0xFF68] = $data; + }; + $this->memoryWriter[0xFF69] = function ($parentObj, $address, $data) { + $parentObj->memory[0xFF69] = $data; + }; + $this->memoryWriter[0xFF6A] = function ($parentObj, $address, $data) { + $parentObj->memory[0xFF6A] = $data; + }; + $this->memoryWriter[0xFF6B] = function ($parentObj, $address, $data) { + $parentObj->memory[0xFF6B] = $data; + }; + $this->memoryWriter[0xFF70] = function ($parentObj, $address, $data) { + $parentObj->memory[0xFF70] = $data; + }; + } + //Boot I/O Registers: + if ($this->inBootstrap) { + $this->memoryWriter[0xFF50] = function ($parentObj, $address, $data) { + echo "Boot ROM reads blocked: Bootstrap process has ended." . PHP_EOL; + $parentObj->inBootstrap = false; + $parentObj->disableBootROM(); //Fill in the boot ROM ranges with ROM bank 0 ROM ranges + $parentObj->memory[0xFF50] = $data; //Bits are sustained in memory? + }; + $this->memoryWriter[0xFF6C] = function ($parentObj, $address, $data) { + if ($parentObj->inBootstrap) { + $parentObj->cGBC = ($data == 0x80); + echo "Booted to GBC Mode: " + $parentObj->cGBC . PHP_EOL; + } + $parentObj->memory[0xFF6C] = $data; + }; + } + else { + //Lockout the ROMs from accessing the BOOT ROM control register: + $this->memoryWriter[0xFF6C] = $this->memoryWriter[0xFF50] = function ($parentObj, $address, $data) { }; + } + } + //Helper Functions + public function usbtsb($ubyte) { + //Unsigned byte to signed byte: + return ($ubyte > 0x7F) ? (($ubyte & 0x7F) - 0x80) : $ubyte; + } + + public function unsbtub($ubyte) { + //Keep an unsigned byte unsigned: + if ($ubyte < 0) { + $ubyte += 0x100; + } + return $ubyte; //If this function is called, no wrapping requested. + } + + public function nswtuw($uword) { + //Keep an unsigned word unsigned: + if ($uword < 0) { + $uword += 0x10000; + } + return $uword & 0xFFFF; //Wrap also... + } + + public function unswtuw($uword) { + //Keep an unsigned word unsigned: + if ($uword < 0) { + $uword += 0x10000; + } + return $uword; //If this function is called, no wrapping requested. + } + + public function toTypedArray($baseArray, $bit32, $unsigned) { + try { + $typedArrayTemp = ($bit32) ? (($unsigned) ? new Uint32Array(count($baseArray)) : new Int32Array(count($baseArray))) : new Uint8Array(count($baseArray)); + for ($index = 0; $index < count($baseArray); $index++) { + $typedArrayTemp[$index] = $baseArray[$index]; + } + return $typedArrayTemp; + } + catch (\Exception $error) { + echo "Could not convert an array to a typed array" . PHP_EOL; + return $baseArray; + } + } + + public function fromTypedArray($baseArray) { + try { + $arrayTemp = array_fill(0, count($baseArray), 0); + for ($index = 0; $index < count($baseArray); $index++) { + $arrayTemp[$index] = $baseArray[$index]; + } + return $arrayTemp; + } + catch (\Exception $error) { + return $baseArray; + } + } + + public function getTypedArray($length, $defaultValue, $numberType) { + // @PHP - We dont have typed arrays and unsigned int in PHP + // This function just creates an array and initialize with a value + $arrayHandle = array_fill(0, $length, $defaultValue); + + return $arrayHandle; + } + + public function ArrayPad($length, $defaultValue) { + $arrayHandle = array_fill(0, $length, $defaultValue); + return $arrayHandle; + } +}
\ No newline at end of file diff --git a/src/GameBoy/Keyboard.php b/src/GameBoy/Keyboard.php new file mode 100644 index 0000000..8ad007f --- /dev/null +++ b/src/GameBoy/Keyboard.php @@ -0,0 +1,63 @@ +<?php +namespace GameBoy; + +class Keyboard +{ + public $core; + public $file; + public $keyPressing = null; + public $started = false; + + public function __construct(Core $core) + { + $this->core = $core; + exec('stty -icanon'); + $this->file = fopen('php://stdin', 'r'); + stream_set_blocking($this->file, false); + } + + public function check() + { + $key = fread($this->file, 1); + + if (! empty($key)) { + $this->keyDown($key); + } else if (! empty($this->keyPressing)) { + $this->keyUp($this->keyPressing); + } + + $this->keyPressing = $key; + } + + public function matchKey($key) + { + //Maps a keyboard key to a gameboy key. + //Order: Right, Left, Up, Down, A, B, Select, Start + + $keyIndex = array_search($key, Settings::$settings[3]); + + if ($keyIndex === false) { + return -1; + } + + return $keyIndex; + } + + public function keyDown($key) + { + $keyCode = $this->matchKey($key); + + if ($keyCode > -1) { + $this->core->JoyPadEvent($keyCode, true); + } + } + + public function keyUp($key) + { + $keyCode = $this->matchKey($key); + + if ($keyCode > -1) { + $this->core->JoyPadEvent($keyCode, false); + } + } +}
\ No newline at end of file diff --git a/src/GameBoy/Opcode.php b/src/GameBoy/Opcode.php new file mode 100644 index 0000000..80c82a7 --- /dev/null +++ b/src/GameBoy/Opcode.php @@ -0,0 +1,1985 @@ +<?php +namespace GameBoy; + +class Opcode +{ + public $functionsArray = []; + + public function __construct() + { + //NOP + //#0x00: + $this->functionsArray[] = function ($parentObj) { + //Do Nothing... + }; + //LD BC, nn + //#0x01: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC = $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->registerB = $parentObj->memoryRead(($parentObj->programCounter + 1) & 0xFFFF); + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + }; + //LD (BC), A + //#0x02: + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite(($parentObj->registerB << 8) + $parentObj->registerC, $parentObj->registerA); + }; + //INC BC + //#0x03: + $this->functionsArray[] = function ($parentObj) { + $temp_var = ((($parentObj->registerB << 8) + $parentObj->registerC) + 1); + $parentObj->registerB = (($temp_var >> 8) & 0xFF); + $parentObj->registerC = ($temp_var & 0xFF); + }; + //INC B + //#0x04: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB = (($parentObj->registerB + 1) & 0xFF); + $parentObj->FZero = ($parentObj->registerB == 0); + $parentObj->FHalfCarry = (($parentObj->registerB & 0xF) == 0); + $parentObj->FSubtract = false; + }; + //DEC B + //#0x05: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB = $parentObj->unsbtub($parentObj->registerB - 1); + $parentObj->FZero = ($parentObj->registerB == 0); + $parentObj->FHalfCarry = (($parentObj->registerB & 0xF) == 0xF); + $parentObj->FSubtract = true; + }; + //LD B, n + //#0x06: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB = $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + }; + //RLCA + //#0x07: + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerA & 0x80) == 0x80); + $parentObj->registerA = (($parentObj->registerA << 1) & 0xFF) | ($parentObj->registerA >> 7); + $parentObj->FZero = $parentObj->FSubtract = $parentObj->FHalfCarry = false; + }; + //LD (nn), SP + //#0x08: + $this->functionsArray[] = function ($parentObj) { + $temp_var = ($parentObj->memoryRead(($parentObj->programCounter + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->memoryWrite($temp_var, $parentObj->stackPointer & 0xFF); + $parentObj->memoryWrite(($temp_var + 1) & 0xFFFF, $parentObj->stackPointer >> 8); + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + }; + //ADD HL, BC + //#0x09: + $this->functionsArray[] = function ($parentObj) { + $n2 = ($parentObj->registerB << 8) + $parentObj->registerC; + $dirtySum = $parentObj->registersHL + $n2; + $parentObj->FHalfCarry = (($parentObj->registersHL & 0xFFF) + ($n2 & 0xFFF) > 0xFFF); + $parentObj->FCarry = ($dirtySum > 0xFFFF); + $parentObj->registersHL = ($dirtySum & 0xFFFF); + $parentObj->FSubtract = false; + }; + //LD A, (BC) + //#0x0A: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA = $parentObj->memoryRead(($parentObj->registerB << 8) + $parentObj->registerC); + }; + //DEC BC + //#0x0B: + $this->functionsArray[] = function ($parentObj) { + $temp_var = $parentObj->unswtuw((($parentObj->registerB << 8) + $parentObj->registerC) - 1); + $parentObj->registerB = ($temp_var >> 8); + $parentObj->registerC = ($temp_var & 0xFF); + }; + //INC C + //#0x0C: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC = (($parentObj->registerC + 1) & 0xFF); + $parentObj->FZero = ($parentObj->registerC == 0); + $parentObj->FHalfCarry = (($parentObj->registerC & 0xF) == 0); + $parentObj->FSubtract = false; + }; + //DEC C + //#0x0D: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC = $parentObj->unsbtub($parentObj->registerC - 1); + $parentObj->FZero = ($parentObj->registerC == 0); + $parentObj->FHalfCarry = (($parentObj->registerC & 0xF) == 0xF); + $parentObj->FSubtract = true; + }; + //LD C, n + //#0x0E: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC = $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + }; + //RRCA + //#0x0F: + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = (($parentObj->registerA & 1) == 1); + $parentObj->registerA = ($parentObj->registerA >> 1) + (($parentObj->registerA & 1) << 7); + $parentObj->FZero = $parentObj->FSubtract = $parentObj->FHalfCarry = false; + }; + //STOP + //#0x10: + $this->functionsArray[] = function ($parentObj) { + if ($parentObj->cGBC) { + /*TODO: Emulate the speed switch delay: + Delay Amount: + 16 ms when going to double-speed. + 32 ms when going to single-speed. + Also, bits 4 and 5 of 0xFF00 should read as set (1), while the switch is in process. + */ + if (($parentObj->memory[0xFF4D] & 0x01) == 0x01) { //Speed change requested. + if (($parentObj->memory[0xFF4D] & 0x80) == 0x80) { //Go back to single speed mode. + // cout("Going into single clock speed mode.", 0); + $parentObj->multiplier = 1; //TODO: Move this into the delay done code. + $parentObj->memory[0xFF4D] &= 0x7F; //Clear the double speed mode flag. + } + else { //Go to double speed mode. + // cout("Going into double clock speed mode.", 0); + $parentObj->multiplier = 2; //TODO: Move this into the delay done code. + $parentObj->memory[0xFF4D] |= 0x80; //Set the double speed mode flag. + } + $parentObj->memory[0xFF4D] &= 0xFE; //Reset the request bit. + } + } + }; + //LD DE, nn + //#0x11: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE = $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->registerD = $parentObj->memoryRead(($parentObj->programCounter + 1) & 0xFFFF); + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + }; + //LD (DE), A + //#0x12: + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite(($parentObj->registerD << 8) + $parentObj->registerE, $parentObj->registerA); + }; + //INC DE + //#0x13: + $this->functionsArray[] = function ($parentObj) { + $temp_var = ((($parentObj->registerD << 8) + $parentObj->registerE) + 1); + $parentObj->registerD = (($temp_var >> 8) & 0xFF); + $parentObj->registerE = ($temp_var & 0xFF); + }; + //INC D + //#0x14: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD = (($parentObj->registerD + 1) & 0xFF); + $parentObj->FZero = ($parentObj->registerD == 0); + $parentObj->FHalfCarry = (($parentObj->registerD & 0xF) == 0); + $parentObj->FSubtract = false; + }; + //DEC D + //#0x15: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD = $parentObj->unsbtub($parentObj->registerD - 1); + $parentObj->FZero = ($parentObj->registerD == 0); + $parentObj->FHalfCarry = (($parentObj->registerD & 0xF) == 0xF); + $parentObj->FSubtract = true; + }; + //LD D, n + //#0x16: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD = $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + }; + //RLA + //#0x17: + $this->functionsArray[] = function ($parentObj) { + $carry_flag = ($parentObj->FCarry) ? 1 : 0; + $parentObj->FCarry = (($parentObj->registerA & 0x80) == 0x80); + $parentObj->registerA = (($parentObj->registerA << 1) & 0xFF) | $carry_flag; + $parentObj->FZero = $parentObj->FSubtract = $parentObj->FHalfCarry = false; + }; + //JR n + //#0x18: + $this->functionsArray[] = function ($parentObj) { + $parentObj->programCounter = $parentObj->nswtuw($parentObj->programCounter + $parentObj->usbtsb($parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter)) + 1); + }; + //ADD HL, DE + //#0x19: + $this->functionsArray[] = function ($parentObj) { + $n2 = ($parentObj->registerD << 8) + $parentObj->registerE; + $dirtySum = $parentObj->registersHL + $n2; + $parentObj->FHalfCarry = (($parentObj->registersHL & 0xFFF) + ($n2 & 0xFFF) > 0xFFF); + $parentObj->FCarry = ($dirtySum > 0xFFFF); + $parentObj->registersHL = ($dirtySum & 0xFFFF); + $parentObj->FSubtract = false; + }; + //LD A, (DE) + //#0x1A: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA = $parentObj->memoryRead(($parentObj->registerD << 8) + $parentObj->registerE); + }; + //DEC DE + //#0x1B: + $this->functionsArray[] = function ($parentObj) { + $temp_var = $parentObj->unswtuw((($parentObj->registerD << 8) + $parentObj->registerE) - 1); + $parentObj->registerD = ($temp_var >> 8); + $parentObj->registerE = ($temp_var & 0xFF); + }; + //INC E + //#0x1C: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE = (($parentObj->registerE + 1) & 0xFF); + $parentObj->FZero = ($parentObj->registerE == 0); + $parentObj->FHalfCarry = (($parentObj->registerE & 0xF) == 0); + $parentObj->FSubtract = false; + }; + //DEC E + //#0x1D: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE = $parentObj->unsbtub($parentObj->registerE - 1); + $parentObj->FZero = ($parentObj->registerE == 0); + $parentObj->FHalfCarry = (($parentObj->registerE & 0xF) == 0xF); + $parentObj->FSubtract = true; + }; + //LD E, n + //#0x1E: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE = $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + }; + //RRA + //#0x1F: + $this->functionsArray[] = function ($parentObj) { + $carry_flag = ($parentObj->FCarry) ? 0x80 : 0; + $parentObj->FCarry = (($parentObj->registerA & 1) == 1); + $parentObj->registerA = ($parentObj->registerA >> 1) + $carry_flag; + $parentObj->FZero = $parentObj->FSubtract = $parentObj->FHalfCarry = false; + }; + //JR cc, n + //#0x20: + $this->functionsArray[] = function ($parentObj) { + if (!$parentObj->FZero) { + $parentObj->programCounter = $parentObj->nswtuw($parentObj->programCounter + $parentObj->usbtsb($parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter)) + 1); + $parentObj->CPUTicks++; + } + else { + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + } + }; + //LD HL, nn + //#0x21: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = ($parentObj->memoryRead(($parentObj->programCounter + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + }; + //LDI (HL), A + //#0x22: + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->registerA); + $parentObj->registersHL = (($parentObj->registersHL + 1) & 0xFFFF); + }; + //INC HL + //#0x23: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = (($parentObj->registersHL + 1) & 0xFFFF); + }; + //INC H + //#0x24: + $this->functionsArray[] = function ($parentObj) { + $H = ((($parentObj->registersHL >> 8) + 1) & 0xFF); + $parentObj->FZero = ($H == 0); + $parentObj->FHalfCarry = (($H & 0xF) == 0); + $parentObj->FSubtract = false; + $parentObj->registersHL = ($H << 8) + ($parentObj->registersHL & 0xFF); + }; + //DEC H + //#0x25: + $this->functionsArray[] = function ($parentObj) { + $H = $parentObj->unsbtub(($parentObj->registersHL >> 8) - 1); + $parentObj->FZero = ($H == 0); + $parentObj->FHalfCarry = (($H & 0xF) == 0xF); + $parentObj->FSubtract = true; + $parentObj->registersHL = ($H << 8) + ($parentObj->registersHL & 0xFF); + }; + //LD H, n + //#0x26: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = ($parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter) << 8) + ($parentObj->registersHL & 0xFF); + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + }; + //DAA + //#0x27: + $this->functionsArray[] = function ($parentObj) { + $temp_var = $parentObj->registerA; + if ($parentObj->FCarry) { + $temp_var |= 0x100; + } + if ($parentObj->FHalfCarry) { + $temp_var |= 0x200; + } + if ($parentObj->FSubtract) { + $temp_var |= 0x400; + } + $parentObj->registerA = ($temp_var = $parentObj->DAATable[$temp_var]) >> 8; + $parentObj->FZero = (($temp_var & 0x80) == 0x80); + $parentObj->FSubtract = (($temp_var & 0x40) == 0x40); + $parentObj->FHalfCarry = (($temp_var & 0x20) == 0x20); + $parentObj->FCarry = (($temp_var & 0x10) == 0x10); + }; + //JR cc, n + //#0x28: + $this->functionsArray[] = function ($parentObj) { + if ($parentObj->FZero) { + $parentObj->programCounter = $parentObj->nswtuw($parentObj->programCounter + $parentObj->usbtsb($parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter)) + 1); + $parentObj->CPUTicks++; + } + else { + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + } + }; + //ADD HL, HL + //#0x29: + $this->functionsArray[] = function ($parentObj) {; + $parentObj->FHalfCarry = (($parentObj->registersHL & 0xFFF) > 0x7FF); + $parentObj->FCarry = ($parentObj->registersHL > 0x7FFF); + $parentObj->registersHL = ((2 * $parentObj->registersHL) & 0xFFFF); + $parentObj->FSubtract = false; + }; + //LDI A, (HL) + //#0x2A: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA = $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + $parentObj->registersHL = (($parentObj->registersHL + 1) & 0xFFFF); + }; + //DEC HL + //#0x2B: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = $parentObj->unswtuw($parentObj->registersHL - 1); + }; + //INC L + //#0x2C: + $this->functionsArray[] = function ($parentObj) { + $L = (($parentObj->registersHL + 1) & 0xFF); + $parentObj->FZero = ($L == 0); + $parentObj->FHalfCarry = (($L & 0xF) == 0); + $parentObj->FSubtract = false; + $parentObj->registersHL = ($parentObj->registersHL & 0xFF00) + $L; + }; + //DEC L + //#0x2D: + $this->functionsArray[] = function ($parentObj) { + $L = $parentObj->unsbtub(($parentObj->registersHL & 0xFF) - 1); + $parentObj->FZero = ($L == 0); + $parentObj->FHalfCarry = (($L & 0xF) == 0xF); + $parentObj->FSubtract = true; + $parentObj->registersHL = ($parentObj->registersHL & 0xFF00) + $L; + }; + //LD L, n + //#0x2E: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = ($parentObj->registersHL & 0xFF00) + $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + }; + //CPL + //#0x2F: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA ^= 0xFF; + $parentObj->FSubtract = $parentObj->FHalfCarry = true; + }; + //JR cc, n + //#0x30: + $this->functionsArray[] = function ($parentObj) { + if (!$parentObj->FCarry) { + $parentObj->programCounter = $parentObj->nswtuw($parentObj->programCounter + $parentObj->usbtsb($parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter)) + 1); + $parentObj->CPUTicks++; + } + else { + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + } + }; + //LD SP, nn + //#0x31: + $this->functionsArray[] = function ($parentObj) { + $parentObj->stackPointer = ($parentObj->memoryRead(($parentObj->programCounter + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + }; + //LDD (HL), A + //#0x32: + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->registerA); + $parentObj->registersHL = $parentObj->unswtuw($parentObj->registersHL - 1); + }; + //INC SP + //#0x33: + $this->functionsArray[] = function ($parentObj) { + $parentObj->stackPointer = ($parentObj->stackPointer + 1) & 0xFFFF; + }; + //INC (HL) + //#0x34: + $this->functionsArray[] = function ($parentObj) { + $temp_var = (($parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) + 1) & 0xFF); + $parentObj->FZero = ($temp_var == 0); + $parentObj->FHalfCarry = (($temp_var & 0xF) == 0); + $parentObj->FSubtract = false; + $parentObj->memoryWrite($parentObj->registersHL, $temp_var); + }; + //DEC (HL) + //#0x35: + $this->functionsArray[] = function ($parentObj) { + $temp_var = $parentObj->unsbtub($parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) - 1); + $parentObj->FZero = ($temp_var == 0); + $parentObj->FHalfCarry = (($temp_var & 0xF) == 0xF); + $parentObj->FSubtract = true; + $parentObj->memoryWrite($parentObj->registersHL, $temp_var); + }; + //LD (HL), n + //#0x36: + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter)); + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + }; + //SCF + //#0x37: + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = true; + $parentObj->FSubtract = $parentObj->FHalfCarry = false; + }; + //JR cc, n + //#0x38: + $this->functionsArray[] = function ($parentObj) { + if ($parentObj->FCarry) { + $parentObj->programCounter = $parentObj->nswtuw($parentObj->programCounter + $parentObj->usbtsb($parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter)) + 1); + $parentObj->CPUTicks++; + } + else { + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + } + }; + //ADD HL, SP + //#0x39: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registersHL + $parentObj->stackPointer; + $parentObj->FHalfCarry = (($parentObj->registersHL & 0xFFF) + ($parentObj->stackPointer & 0xFFF) > 0xFFF); + $parentObj->FCarry = ($dirtySum > 0xFFFF); + $parentObj->registersHL = ($dirtySum & 0xFFFF); + $parentObj->FSubtract = false; + }; + // LDD A, (HL) + //#0x3A: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA = $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + $parentObj->registersHL = $parentObj->unswtuw($parentObj->registersHL - 1); + }; + //DEC SP + //#0x3B: + $this->functionsArray[] = function ($parentObj) { + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + }; + //INC A + //#0x3C: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA = (($parentObj->registerA + 1) & 0xFF); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FHalfCarry = (($parentObj->registerA & 0xF) == 0); + $parentObj->FSubtract = false; + }; + //DEC A + //#0x3D: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA = $parentObj->unsbtub($parentObj->registerA - 1); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FHalfCarry = (($parentObj->registerA & 0xF) == 0xF); + $parentObj->FSubtract = true; + }; + //LD A, n + //#0x3E: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA = $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + }; + //CCF + //#0x3F: + $this->functionsArray[] = function ($parentObj) { + $parentObj->FCarry = !$parentObj->FCarry; + $parentObj->FSubtract = $parentObj->FHalfCarry = false; + }; + //LD B, B + //#0x40: + $this->functionsArray[] = function ($parentObj) { + //Do nothing... + }; + //LD B, C + //#0x41: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB = $parentObj->registerC; + }; + //LD B, D + //#0x42: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB = $parentObj->registerD; + }; + //LD B, E + //#0x43: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB = $parentObj->registerE; + }; + //LD B, H + //#0x44: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB = ($parentObj->registersHL >> 8); + }; + //LD B, L + //#0x45: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB = ($parentObj->registersHL & 0xFF); + }; + //LD B, (HL) + //#0x46: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB = $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + }; + //LD B, A + //#0x47: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerB = $parentObj->registerA; + }; + //LD C, B + //#0x48: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC = $parentObj->registerB; + }; + //LD C, C + //#0x49: + $this->functionsArray[] = function ($parentObj) { + //Do nothing... + }; + //LD C, D + //#0x4A: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC = $parentObj->registerD; + }; + //LD C, E + //#0x4B: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC = $parentObj->registerE; + }; + //LD C, H + //#0x4C: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC = ($parentObj->registersHL >> 8); + }; + //LD C, L + //#0x4D: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC = ($parentObj->registersHL & 0xFF); + }; + //LD C, (HL) + //#0x4E: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC = $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + }; + //LD C, A + //#0x4F: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC = $parentObj->registerA; + }; + //LD D, B + //#0x50: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD = $parentObj->registerB; + }; + //LD D, C + //#0x51: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD = $parentObj->registerC; + }; + //LD D, D + //#0x52: + $this->functionsArray[] = function ($parentObj) { + //Do nothing... + }; + //LD D, E + //#0x53: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD = $parentObj->registerE; + }; + //LD D, H + //#0x54: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD = ($parentObj->registersHL >> 8); + }; + //LD D, L + //#0x55: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD = ($parentObj->registersHL & 0xFF); + }; + //LD D, (HL) + //#0x56: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD = $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + }; + //LD D, A + //#0x57: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerD = $parentObj->registerA; + }; + //LD E, B + //#0x58: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE = $parentObj->registerB; + }; + //LD E, C + //#0x59: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE = $parentObj->registerC; + }; + //LD E, D + //#0x5A: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE = $parentObj->registerD; + }; + //LD E, E + //#0x5B: + $this->functionsArray[] = function ($parentObj) { + //Do nothing... + }; + //LD E, H + //#0x5C: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE = ($parentObj->registersHL >> 8); + }; + //LD E, L + //#0x5D: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE = ($parentObj->registersHL & 0xFF); + }; + //LD E, (HL) + //#0x5E: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE = $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + }; + //LD E, A + //#0x5F: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE = $parentObj->registerA; + }; + //LD H, B + //#0x60: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = ($parentObj->registerB << 8) + ($parentObj->registersHL & 0xFF); + }; + //LD H, C + //#0x61: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = ($parentObj->registerC << 8) + ($parentObj->registersHL & 0xFF); + }; + //LD H, D + //#0x62: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = ($parentObj->registerD << 8) + ($parentObj->registersHL & 0xFF); + }; + //LD H, E + //#0x63: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = ($parentObj->registerE << 8) + ($parentObj->registersHL & 0xFF); + }; + //LD H, H + //#0x64: + $this->functionsArray[] = function ($parentObj) { + //Do nothing... + }; + //LD H, L + //#0x65: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = (($parentObj->registersHL & 0xFF) << 8) + ($parentObj->registersHL & 0xFF); + }; + //LD H, (HL) + //#0x66: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = ($parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL) << 8) + ($parentObj->registersHL & 0xFF); + }; + //LD H, A + //#0x67: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = ($parentObj->registerA << 8) + ($parentObj->registersHL & 0xFF); + }; + //LD L, B + //#0x68: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = ($parentObj->registersHL & 0xFF00) + $parentObj->registerB; + }; + //LD L, C + //#0x69: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = ($parentObj->registersHL & 0xFF00) + $parentObj->registerC; + }; + //LD L, D + //#0x6A: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = ($parentObj->registersHL & 0xFF00) + $parentObj->registerD; + }; + //LD L, E + //#0x6B: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = ($parentObj->registersHL & 0xFF00) + $parentObj->registerE; + }; + //LD L, H + //#0x6C: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = ($parentObj->registersHL & 0xFF00) + ($parentObj->registersHL >> 8); + }; + //LD L, L + //#0x6D: + $this->functionsArray[] = function ($parentObj) { + //Do nothing... + }; + //LD L, (HL) + //#0x6E: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = ($parentObj->registersHL & 0xFF00) + $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + }; + //LD L, A + //#0x6F: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = ($parentObj->registersHL & 0xFF00) + $parentObj->registerA; + }; + //LD (HL), B + //#0x70: + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->registerB); + }; + //LD (HL), C + //#0x71: + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->registerC); + }; + //LD (HL), D + //#0x72: + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->registerD); + }; + //LD (HL), E + //#0x73: + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->registerE); + }; + //LD (HL), H + //#0x74: + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, ($parentObj->registersHL >> 8)); + }; + //LD (HL), L + //#0x75: + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, ($parentObj->registersHL & 0xFF)); + }; + //HALT + //#0x76: + $this->functionsArray[] = function ($parentObj) { + if ($parentObj->untilEnable == 1) { + /*VBA-M says this fixes Torpedo Range (Seems to work): + Involves an edge case where an EI is placed right before a HALT. + EI in this case actually is immediate, so we adjust (Hacky?).*/ + $parentObj->programCounter = $parentObj->nswtuw($parentObj->programCounter - 1); + } + else { + if (!$parentObj->halt && !$parentObj->IME && !$parentObj->cGBC && !$parentObj->usedBootROM && ($parentObj->memory[0xFF0F] & $parentObj->memory[0xFFFF] & 0x1F) > 0) { + $parentObj->skipPCIncrement = true; + } + $parentObj->halt = true; + while ($parentObj->halt && ($parentObj->stopEmulator & 1) == 0) { + /*We're hijacking the main interpreter loop to do this dirty business + in order to not slow down the main interpreter loop code with halt state handling.*/ + $bitShift = 0; + $testbit = 1; + $interrupts = $parentObj->memory[0xFFFF] & $parentObj->memory[0xFF0F]; + while ($bitShift < 5) { + //Check to see if an interrupt is enabled AND requested. + if (($testbit & $interrupts) == $testbit) { + $parentObj->halt = false; //Get out of halt state if in halt state. + return; //Let the main interrupt handler compute the interrupt. + } + $testbit = 1 << ++$bitShift; + } + $parentObj->CPUTicks = 1; //1 machine cycle under HALT... + //Timing: + $parentObj->updateCore(); + } + + //Throw an error on purpose to exit out of the loop. + throw new \Exception("HALT_OVERRUN"); + } + }; + //LD (HL), A + //#0x77: + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite($parentObj->registersHL, $parentObj->registerA); + }; + //LD A, B + //#0x78: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA = $parentObj->registerB; + }; + //LD A, C + //#0x79: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA = $parentObj->registerC; + }; + //LD A, D + //#0x7A: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA = $parentObj->registerD; + }; + //LD A, E + //#0x7B: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA = $parentObj->registerE; + }; + //LD A, H + //#0x7C: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA = ($parentObj->registersHL >> 8); + }; + //LD A, L + //#0x7D: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA = ($parentObj->registersHL & 0xFF); + }; + //LD, A, (HL) + //#0x7E: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA = $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + }; + //LD A, A + //#0x7F: + $this->functionsArray[] = function ($parentObj) { + //Do Nothing... + }; + //ADD A, B + //#0x80: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA + $parentObj->registerB; + $parentObj->FHalfCarry = ($dirtySum & 0xF) < ($parentObj->registerA & 0xF); + $parentObj->FCarry = ($dirtySum > 0xFF); + $parentObj->registerA = $dirtySum & 0xFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = false; + }; + //ADD A, C + //#0x81: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA + $parentObj->registerC; + $parentObj->FHalfCarry = ($dirtySum & 0xF) < ($parentObj->registerA & 0xF); + $parentObj->FCarry = ($dirtySum > 0xFF); + $parentObj->registerA = $dirtySum & 0xFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = false; + }; + //ADD A, D + //#0x82: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA + $parentObj->registerD; + $parentObj->FHalfCarry = ($dirtySum & 0xF) < ($parentObj->registerA & 0xF); + $parentObj->FCarry = ($dirtySum > 0xFF); + $parentObj->registerA = $dirtySum & 0xFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = false; + }; + //ADD A, E + //#0x83: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA + $parentObj->registerE; + $parentObj->FHalfCarry = ($dirtySum & 0xF) < ($parentObj->registerA & 0xF); + $parentObj->FCarry = ($dirtySum > 0xFF); + $parentObj->registerA = $dirtySum & 0xFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = false; + }; + //ADD A, H + //#0x84: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA + ($parentObj->registersHL >> 8); + $parentObj->FHalfCarry = ($dirtySum & 0xF) < ($parentObj->registerA & 0xF); + $parentObj->FCarry = ($dirtySum > 0xFF); + $parentObj->registerA = $dirtySum & 0xFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = false; + }; + //ADD A, L + //#0x85: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA + ($parentObj->registersHL & 0xFF); + $parentObj->FHalfCarry = ($dirtySum & 0xF) < ($parentObj->registerA & 0xF); + $parentObj->FCarry = ($dirtySum > 0xFF); + $parentObj->registerA = $dirtySum & 0xFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = false; + }; + //ADD A, (HL) + //#0x86: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA + $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + $parentObj->FHalfCarry = ($dirtySum & 0xF) < ($parentObj->registerA & 0xF); + $parentObj->FCarry = ($dirtySum > 0xFF); + $parentObj->registerA = $dirtySum & 0xFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = false; + }; + //ADD A, A + //#0x87: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA * 2; + $parentObj->FHalfCarry = ($dirtySum & 0xF) < ($parentObj->registerA & 0xF); + $parentObj->FCarry = ($dirtySum > 0xFF); + $parentObj->registerA = $dirtySum & 0xFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = false; + }; + //ADC A, B + //#0x88: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA + $parentObj->registerB + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = (($parentObj->registerA & 0xF) + ($parentObj->registerB & 0xF) + (($parentObj->FCarry) ? 1 : 0) > 0xF); + $parentObj->FCarry = ($dirtySum > 0xFF); + $parentObj->registerA = $dirtySum & 0xFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = false; + }; + //ADC A, C + //#0x89: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA + $parentObj->registerC + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = (($parentObj->registerA & 0xF) + ($parentObj->registerC & 0xF) + (($parentObj->FCarry) ? 1 : 0) > 0xF); + $parentObj->FCarry = ($dirtySum > 0xFF); + $parentObj->registerA = $dirtySum & 0xFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = false; + }; + //ADC A, D + //#0x8A: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA + $parentObj->registerD + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = (($parentObj->registerA & 0xF) + ($parentObj->registerD & 0xF) + (($parentObj->FCarry) ? 1 : 0) > 0xF); + $parentObj->FCarry = ($dirtySum > 0xFF); + $parentObj->registerA = $dirtySum & 0xFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = false; + }; + //ADC A, E + //#0x8B: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA + $parentObj->registerE + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = (($parentObj->registerA & 0xF) + ($parentObj->registerE & 0xF) + (($parentObj->FCarry) ? 1 : 0) > 0xF); + $parentObj->FCarry = ($dirtySum > 0xFF); + $parentObj->registerA = $dirtySum & 0xFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = false; + }; + //ADC A, H + //#0x8C: + $this->functionsArray[] = function ($parentObj) { + $tempValue = ($parentObj->registersHL >> 8); + $dirtySum = $parentObj->registerA + $tempValue + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = (($parentObj->registerA & 0xF) + ($tempValue & 0xF) + (($parentObj->FCarry) ? 1 : 0) > 0xF); + $parentObj->FCarry = ($dirtySum > 0xFF); + $parentObj->registerA = $dirtySum & 0xFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = false; + }; + //ADC A, L + //#0x8D: + $this->functionsArray[] = function ($parentObj) { + $tempValue = ($parentObj->registersHL & 0xFF); + $dirtySum = $parentObj->registerA + $tempValue + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = (($parentObj->registerA & 0xF) + ($tempValue & 0xF) + (($parentObj->FCarry) ? 1 : 0) > 0xF); + $parentObj->FCarry = ($dirtySum > 0xFF); + $parentObj->registerA = $dirtySum & 0xFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = false; + }; + //ADC A, (HL) + //#0x8E: + $this->functionsArray[] = function ($parentObj) { + $tempValue = $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + $dirtySum = $parentObj->registerA + $tempValue + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = (($parentObj->registerA & 0xF) + ($tempValue & 0xF) + (($parentObj->FCarry) ? 1 : 0) > 0xF); + $parentObj->FCarry = ($dirtySum > 0xFF); + $parentObj->registerA = $dirtySum & 0xFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = false; + }; + //ADC A, A + //#0x8F: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = ($parentObj->registerA * 2) + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = (($parentObj->registerA & 0xF) + ($parentObj->registerA & 0xF) + (($parentObj->FCarry) ? 1 : 0) > 0xF); + $parentObj->FCarry = ($dirtySum > 0xFF); + $parentObj->registerA = $dirtySum & 0xFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = false; + }; + //SUB A, B + //#0x90: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA - $parentObj->registerB; + $parentObj->FHalfCarry = ($parentObj->registerA & 0xF) < ($parentObj->registerB & 0xF); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->registerA = $parentObj->unsbtub($dirtySum); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = true; + }; + //SUB A, C + //#0x91: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA - $parentObj->registerC; + $parentObj->FHalfCarry = ($parentObj->registerA & 0xF) < ($parentObj->registerC & 0xF); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->registerA = $parentObj->unsbtub($dirtySum); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = true; + }; + //SUB A, D + //#0x92: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA - $parentObj->registerD; + $parentObj->FHalfCarry = ($parentObj->registerA & 0xF) < ($parentObj->registerD & 0xF); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->registerA = $parentObj->unsbtub($dirtySum); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = true; + }; + //SUB A, E + //#0x93: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA - $parentObj->registerE; + $parentObj->FHalfCarry = ($parentObj->registerA & 0xF) < ($parentObj->registerE & 0xF); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->registerA = $parentObj->unsbtub($dirtySum); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = true; + }; + //SUB A, H + //#0x94: + $this->functionsArray[] = function ($parentObj) { + $temp_var = $parentObj->registersHL >> 8; + $dirtySum = $parentObj->registerA - $temp_var; + $parentObj->FHalfCarry = ($parentObj->registerA & 0xF) < ($temp_var & 0xF); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->registerA = $parentObj->unsbtub($dirtySum); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = true; + }; + //SUB A, L + //#0x95: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA - ($parentObj->registersHL & 0xFF); + $parentObj->FHalfCarry = ($parentObj->registerA & 0xF) < ($parentObj->registersHL & 0xF); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->registerA = $parentObj->unsbtub($dirtySum); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = true; + }; + //SUB A, (HL) + //#0x96: + $this->functionsArray[] = function ($parentObj) { + $temp_var = $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + $dirtySum = $parentObj->registerA - $temp_var; + $parentObj->FHalfCarry = ($parentObj->registerA & 0xF) < ($temp_var & 0xF); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->registerA = $parentObj->unsbtub($dirtySum); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = true; + }; + //SUB A, A + //#0x97: + $this->functionsArray[] = function ($parentObj) { + //number - same number == 0 + $parentObj->registerA = 0; + $parentObj->FHalfCarry = $parentObj->FCarry = false; + $parentObj->FZero = $parentObj->FSubtract = true; + }; + //SBC A, B + //#0x98: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA - $parentObj->registerB - (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = (($parentObj->registerA & 0xF) - ($parentObj->registerB & 0xF) - (($parentObj->FCarry) ? 1 : 0) < 0); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->registerA = $parentObj->unsbtub($dirtySum); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = true; + }; + //SBC A, C + //#0x99: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA - $parentObj->registerC - (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = (($parentObj->registerA & 0xF) - ($parentObj->registerC & 0xF) - (($parentObj->FCarry) ? 1 : 0) < 0); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->registerA = $parentObj->unsbtub($dirtySum); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = true; + }; + //SBC A, D + //#0x9A: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA - $parentObj->registerD - (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = (($parentObj->registerA & 0xF) - ($parentObj->registerD & 0xF) - (($parentObj->FCarry) ? 1 : 0) < 0); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->registerA = $parentObj->unsbtub($dirtySum); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = true; + }; + //SBC A, E + //#0x9B: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA - $parentObj->registerE - (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = (($parentObj->registerA & 0xF) - ($parentObj->registerE & 0xF) - (($parentObj->FCarry) ? 1 : 0) < 0); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->registerA = $parentObj->unsbtub($dirtySum); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = true; + }; + //SBC A, H + //#0x9C: + $this->functionsArray[] = function ($parentObj) { + $temp_var = $parentObj->registersHL >> 8; + $dirtySum = $parentObj->registerA - $temp_var - (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = (($parentObj->registerA & 0xF) - ($temp_var & 0xF) - (($parentObj->FCarry) ? 1 : 0) < 0); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->registerA = $parentObj->unsbtub($dirtySum); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = true; + }; + //SBC A, L + //#0x9D: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA - ($parentObj->registersHL & 0xFF) - (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = (($parentObj->registerA & 0xF) - ($parentObj->registersHL & 0xF) - (($parentObj->FCarry) ? 1 : 0) < 0); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->registerA = $parentObj->unsbtub($dirtySum); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = true; + }; + //SBC A, (HL) + //#0x9E: + $this->functionsArray[] = function ($parentObj) { + $temp_var = $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + $dirtySum = $parentObj->registerA - $temp_var - (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = (($parentObj->registerA & 0xF) - ($temp_var & 0xF) - (($parentObj->FCarry) ? 1 : 0) < 0); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->registerA = $parentObj->unsbtub($dirtySum); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = true; + }; + //SBC A, A + //#0x9F: + $this->functionsArray[] = function ($parentObj) { + //Optimized SBC A: + if ($parentObj->FCarry) { + $parentObj->FZero = false; + $parentObj->FSubtract = $parentObj->FHalfCarry = $parentObj->FCarry = true; + $parentObj->registerA = 0xFF; + } + else { + $parentObj->FHalfCarry = $parentObj->FCarry = false; + $parentObj->FSubtract = $parentObj->FZero = true; + $parentObj->registerA = 0; + } + }; + //AND B + //#0xA0: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA &= $parentObj->registerB; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = $parentObj->FCarry = false; + }; + //AND C + //#0xA1: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA &= $parentObj->registerC; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = $parentObj->FCarry = false; + }; + //AND D + //#0xA2: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA &= $parentObj->registerD; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = $parentObj->FCarry = false; + }; + //AND E + //#0xA3: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA &= $parentObj->registerE; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = $parentObj->FCarry = false; + }; + //AND H + //#0xA4: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA &= ($parentObj->registersHL >> 8); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = $parentObj->FCarry = false; + }; + //AND L + //#0xA5: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA &= ($parentObj->registersHL & 0xFF); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = $parentObj->FCarry = false; + }; + //AND (HL) + //#0xA6: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA &= $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = $parentObj->FCarry = false; + }; + //AND A + //#0xA7: + $this->functionsArray[] = function ($parentObj) { + //number & same number = same number + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = $parentObj->FCarry = false; + }; + //XOR B + //#0xA8: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA ^= $parentObj->registerB; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = $parentObj->FHalfCarry = $parentObj->FCarry = false; + }; + //XOR C + //#0xA9: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA ^= $parentObj->registerC; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = $parentObj->FHalfCarry = $parentObj->FCarry = false; + }; + //XOR D + //#0xAA: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA ^= $parentObj->registerD; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = $parentObj->FHalfCarry = $parentObj->FCarry = false; + }; + //XOR E + //#0xAB: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA ^= $parentObj->registerE; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = $parentObj->FHalfCarry = $parentObj->FCarry = false; + }; + //XOR H + //#0xAC: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA ^= ($parentObj->registersHL >> 8); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = $parentObj->FHalfCarry = $parentObj->FCarry = false; + }; + //XOR L + //#0xAD: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA ^= ($parentObj->registersHL & 0xFF); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = $parentObj->FHalfCarry = $parentObj->FCarry = false; + }; + //XOR (HL) + //#0xAE: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA ^= $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = $parentObj->FHalfCarry = $parentObj->FCarry = false; + }; + //XOR A + //#0xAF: + $this->functionsArray[] = function ($parentObj) { + //number ^ same number == 0 + $parentObj->registerA = 0; + $parentObj->FZero = true; + $parentObj->FSubtract = $parentObj->FHalfCarry = $parentObj->FCarry = false; + }; + //OR B + //#0xB0: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA |= $parentObj->registerB; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = $parentObj->FCarry = $parentObj->FHalfCarry = false; + }; + //OR C + //#0xB1: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA |= $parentObj->registerC; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = $parentObj->FCarry = $parentObj->FHalfCarry = false; + }; + //OR D + //#0xB2: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA |= $parentObj->registerD; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = $parentObj->FCarry = $parentObj->FHalfCarry = false; + }; + //OR E + //#0xB3: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA |= $parentObj->registerE; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = $parentObj->FCarry = $parentObj->FHalfCarry = false; + }; + //OR H + //#0xB4: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA |= ($parentObj->registersHL >> 8); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = $parentObj->FCarry = $parentObj->FHalfCarry = false; + }; + //OR L + //#0xB5: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA |= ($parentObj->registersHL & 0xFF); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = $parentObj->FCarry = $parentObj->FHalfCarry = false; + }; + //OR (HL) + //#0xB6: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA |= $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = $parentObj->FCarry = $parentObj->FHalfCarry = false; + }; + //OR A + //#0xB7: + $this->functionsArray[] = function ($parentObj) { + //number | same number == same number + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = $parentObj->FCarry = $parentObj->FHalfCarry = false; + }; + //CP B + //#0xB8: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA - $parentObj->registerB; + $parentObj->FHalfCarry = ($parentObj->unsbtub($dirtySum) & 0xF) > ($parentObj->registerA & 0xF); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->FZero = ($dirtySum == 0); + $parentObj->FSubtract = true; + }; + //CP C + //#0xB9: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA - $parentObj->registerC; + $parentObj->FHalfCarry = ($parentObj->unsbtub($dirtySum) & 0xF) > ($parentObj->registerA & 0xF); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->FZero = ($dirtySum == 0); + $parentObj->FSubtract = true; + }; + //CP D + //#0xBA: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA - $parentObj->registerD; + $parentObj->FHalfCarry = ($parentObj->unsbtub($dirtySum) & 0xF) > ($parentObj->registerA & 0xF); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->FZero = ($dirtySum == 0); + $parentObj->FSubtract = true; + }; + //CP E + //#0xBB: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA - $parentObj->registerE; + $parentObj->FHalfCarry = ($parentObj->unsbtub($dirtySum) & 0xF) > ($parentObj->registerA & 0xF); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->FZero = ($dirtySum == 0); + $parentObj->FSubtract = true; + }; + //CP H + //#0xBC: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA - ($parentObj->registersHL >> 8); + $parentObj->FHalfCarry = ($parentObj->unsbtub($dirtySum) & 0xF) > ($parentObj->registerA & 0xF); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->FZero = ($dirtySum == 0); + $parentObj->FSubtract = true; + }; + //CP L + //#0xBD: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA - ($parentObj->registersHL & 0xFF); + $parentObj->FHalfCarry = ($parentObj->unsbtub($dirtySum) & 0xF) > ($parentObj->registerA & 0xF); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->FZero = ($dirtySum == 0); + $parentObj->FSubtract = true; + }; + //CP (HL) + //#0xBE: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA - $parentObj->memoryReader[$parentObj->registersHL]($parentObj, $parentObj->registersHL); + $parentObj->FHalfCarry = ($parentObj->unsbtub($dirtySum) & 0xF) > ($parentObj->registerA & 0xF); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->FZero = ($dirtySum == 0); + $parentObj->FSubtract = true; + }; + //CP A + //#0xBF: + $this->functionsArray[] = function ($parentObj) { + $parentObj->FHalfCarry = $parentObj->FCarry = false; + $parentObj->FZero = $parentObj->FSubtract = true; + }; + //RET !FZ + //#0xC0: + $this->functionsArray[] = function ($parentObj) { + if (!$parentObj->FZero) { + $parentObj->programCounter = ($parentObj->memoryRead(($parentObj->stackPointer + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->stackPointer]($parentObj, $parentObj->stackPointer); + $parentObj->stackPointer = ($parentObj->stackPointer + 2) & 0xFFFF; + $parentObj->CPUTicks += 3; + } + }; + //POP BC + //#0xC1: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerC = $parentObj->memoryReader[$parentObj->stackPointer]($parentObj, $parentObj->stackPointer); + $parentObj->registerB = $parentObj->memoryRead(($parentObj->stackPointer + 1) & 0xFFFF); + $parentObj->stackPointer = ($parentObj->stackPointer + 2) & 0xFFFF; + }; + //JP !FZ, nn + //#0xC2: + $this->functionsArray[] = function ($parentObj) { + if (!$parentObj->FZero) { + $parentObj->programCounter = ($parentObj->memoryRead(($parentObj->programCounter + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->CPUTicks++; + } + else { + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + } + }; + //JP nn + //#0xC3: + $this->functionsArray[] = function ($parentObj) { + $parentObj->programCounter = ($parentObj->memoryRead(($parentObj->programCounter + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + }; + //CALL !FZ, nn + //#0xC4: + $this->functionsArray[] = function ($parentObj) { + if (!$parentObj->FZero) { + $temp_pc = ($parentObj->memoryRead(($parentObj->programCounter + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter >> 8); + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter & 0xFF); + $parentObj->programCounter = $temp_pc; + $parentObj->CPUTicks += 3; + } + else { + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + } + }; + //PUSH BC + //#0xC5: + $this->functionsArray[] = function ($parentObj) { + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->registerB); + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->registerC); + }; + //ADD, n + //#0xC6: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA + $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->FHalfCarry = ($dirtySum & 0xF) < ($parentObj->registerA & 0xF); + $parentObj->FCarry = ($dirtySum > 0xFF); + $parentObj->registerA = $dirtySum & 0xFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = false; + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + }; + //RST 0 + //#0xC7: + $this->functionsArray[] = function ($parentObj) { + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter >> 8); + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter & 0xFF); + $parentObj->programCounter = 0; + }; + //RET FZ + //#0xC8: + $this->functionsArray[] = function ($parentObj) { + if ($parentObj->FZero) { + $parentObj->programCounter = ($parentObj->memoryRead(($parentObj->stackPointer + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->stackPointer]($parentObj, $parentObj->stackPointer); + $parentObj->stackPointer = ($parentObj->stackPointer + 2) & 0xFFFF; + $parentObj->CPUTicks += 3; + } + }; + //RET + //#0xC9: + $this->functionsArray[] = function ($parentObj) { + $parentObj->programCounter = ($parentObj->memoryRead(($parentObj->stackPointer + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->stackPointer]($parentObj, $parentObj->stackPointer); + $parentObj->stackPointer = ($parentObj->stackPointer + 2) & 0xFFFF; + }; + //JP FZ, nn + //#0xCA: + $this->functionsArray[] = function ($parentObj) { + if ($parentObj->FZero) { + $parentObj->programCounter = ($parentObj->memoryRead(($parentObj->programCounter + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->CPUTicks++; + } + else { + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + } + }; + //Secondary OP Code Set: + //#0xCB: + $this->functionsArray[] = function ($parentObj) { + $opcode = $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + //Increment the program counter to the next instruction: + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + //Get how many CPU cycles the current 0xCBXX op code counts for: + $parentObj->CPUTicks = $parentObj->SecondaryTICKTable[$opcode]; + //Execute secondary OP codes for the 0xCB OP code call. + $parentObj->CBOPCODE[$opcode]($parentObj); + }; + //CALL FZ, nn + //#0xCC: + $this->functionsArray[] = function ($parentObj) { + if ($parentObj->FZero) { + $temp_pc = ($parentObj->memoryRead(($parentObj->programCounter + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter >> 8); + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter & 0xFF); + $parentObj->programCounter = $temp_pc; + $parentObj->CPUTicks += 3; + } + else { + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + } + }; + //CALL nn + //#0xCD: + $this->functionsArray[] = function ($parentObj) { + $temp_pc = ($parentObj->memoryRead(($parentObj->programCounter + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter >> 8); + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter & 0xFF); + $parentObj->programCounter = $temp_pc; + }; + //ADC A, n + //#0xCE: + $this->functionsArray[] = function ($parentObj) { + $tempValue = $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $dirtySum = $parentObj->registerA + $tempValue + (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = (($parentObj->registerA & 0xF) + ($tempValue & 0xF) + (($parentObj->FCarry) ? 1 : 0) > 0xF); + $parentObj->FCarry = ($dirtySum > 0xFF); + $parentObj->registerA = $dirtySum & 0xFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = false; + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + }; + //RST 0x8 + //#0xCF: + $this->functionsArray[] = function ($parentObj) { + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter >> 8); + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter & 0xFF); + $parentObj->programCounter = 0x8; + }; + //RET !FC + //#0xD0: + $this->functionsArray[] = function ($parentObj) { + if (!$parentObj->FCarry) { + $parentObj->programCounter = ($parentObj->memoryRead(($parentObj->stackPointer + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->stackPointer]($parentObj, $parentObj->stackPointer); + $parentObj->stackPointer = ($parentObj->stackPointer + 2) & 0xFFFF; + $parentObj->CPUTicks += 3; + } + }; + //POP DE + //#0xD1: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerE = $parentObj->memoryReader[$parentObj->stackPointer]($parentObj, $parentObj->stackPointer); + $parentObj->registerD = $parentObj->memoryRead(($parentObj->stackPointer + 1) & 0xFFFF); + $parentObj->stackPointer = ($parentObj->stackPointer + 2) & 0xFFFF; + }; + //JP !FC, nn + //#0xD2: + $this->functionsArray[] = function ($parentObj) { + if (!$parentObj->FCarry) { + $parentObj->programCounter = ($parentObj->memoryRead(($parentObj->programCounter + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->CPUTicks++; + } + else { + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + } + }; + //0xD3 - Illegal + //#0xD3: + $this->functionsArray[] = function ($parentObj) { + // @TODO + // cout("Illegal op code 0xD3 called, pausing emulation.", 2); + // pause(); + }; + //CALL !FC, nn + //#0xD4: + $this->functionsArray[] = function ($parentObj) { + if (!$parentObj->FCarry) { + $temp_pc = ($parentObj->memoryRead(($parentObj->programCounter + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter >> 8); + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter & 0xFF); + $parentObj->programCounter = $temp_pc; + $parentObj->CPUTicks += 3; + } + else { + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + } + }; + //PUSH DE + //#0xD5: + $this->functionsArray[] = function ($parentObj) { + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->registerD); + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->registerE); + }; + //SUB A, n + //#0xD6: + $this->functionsArray[] = function ($parentObj) { + $temp_var = $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $dirtySum = $parentObj->registerA - $temp_var; + $parentObj->FHalfCarry = ($parentObj->registerA & 0xF) < ($temp_var & 0xF); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->registerA = $parentObj->unsbtub($dirtySum); + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = true; + }; + //RST 0x10 + //#0xD7: + $this->functionsArray[] = function ($parentObj) { + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter >> 8); + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter & 0xFF); + $parentObj->programCounter = 0x10; + }; + //RET FC + //#0xD8: + $this->functionsArray[] = function ($parentObj) { + if ($parentObj->FCarry) { + $parentObj->programCounter = ($parentObj->memoryRead(($parentObj->stackPointer + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->stackPointer]($parentObj, $parentObj->stackPointer); + $parentObj->stackPointer = ($parentObj->stackPointer + 2) & 0xFFFF; + $parentObj->CPUTicks += 3; + } + }; + //RETI + //#0xD9: + $this->functionsArray[] = function ($parentObj) { + $parentObj->programCounter = ($parentObj->memoryRead(($parentObj->stackPointer + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->stackPointer]($parentObj, $parentObj->stackPointer); + $parentObj->stackPointer = ($parentObj->stackPointer + 2) & 0xFFFF; + //$parentObj->IME = true; + $parentObj->untilEnable = 2; + }; + //JP FC, nn + //#0xDA: + $this->functionsArray[] = function ($parentObj) { + if ($parentObj->FCarry) { + $parentObj->programCounter = ($parentObj->memoryRead(($parentObj->programCounter + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->CPUTicks++; + } + else { + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + } + }; + //0xDB - Illegal + //#0xDB: + $this->functionsArray[] = function ($parentObj) { + echo "Illegal op code 0xDB called, pausing emulation."; + exit(); + }; + //CALL FC, nn + //#0xDC: + $this->functionsArray[] = function ($parentObj) { + if ($parentObj->FCarry) { + $temp_pc = ($parentObj->memoryRead(($parentObj->programCounter + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter >> 8); + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter & 0xFF); + $parentObj->programCounter = $temp_pc; + $parentObj->CPUTicks += 3; + } + else { + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + } + }; + //0xDD - Illegal + //#0xDD: + $this->functionsArray[] = function ($parentObj) { + echo "Illegal op code 0xDD called, pausing emulation."; + exit(); + }; + //SBC A, n + //#0xDE: + $this->functionsArray[] = function ($parentObj) { + $temp_var = $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $dirtySum = $parentObj->registerA - $temp_var - (($parentObj->FCarry) ? 1 : 0); + $parentObj->FHalfCarry = (($parentObj->registerA & 0xF) - ($temp_var & 0xF) - (($parentObj->FCarry) ? 1 : 0) < 0); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->registerA = $parentObj->unsbtub($dirtySum); + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FSubtract = true; + }; + //RST 0x18 + //#0xDF: + $this->functionsArray[] = function ($parentObj) { + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter >> 8); + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter & 0xFF); + $parentObj->programCounter = 0x18; + }; + //LDH (n), A + //#0xE0: + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite(0xFF00 + $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter), $parentObj->registerA); + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + }; + //POP HL + //#0xE1: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registersHL = ($parentObj->memoryRead(($parentObj->stackPointer + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->stackPointer]($parentObj, $parentObj->stackPointer); + $parentObj->stackPointer = ($parentObj->stackPointer + 2) & 0xFFFF; + }; + //LD (C), A + //#0xE2: + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite(0xFF00 + $parentObj->registerC, $parentObj->registerA); + }; + //0xE3 - Illegal + //#0xE3: + $this->functionsArray[] = function ($parentObj) { + echo "Illegal op code 0xE3 called, pausing emulation."; + exit(); + }; + //0xE4 - Illegal + //#0xE4: + $this->functionsArray[] = function ($parentObj) { + echo "Illegal op code 0xE4 called, pausing emulation."; + exit(); + }; + //PUSH HL + //#0xE5: + $this->functionsArray[] = function ($parentObj) { + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->registersHL >> 8); + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->registersHL & 0xFF); + }; + //AND n + //#0xE6: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA &= $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->FHalfCarry = true; + $parentObj->FSubtract = $parentObj->FCarry = false; + }; + //RST 0x20 + //#0xE7: + $this->functionsArray[] = function ($parentObj) { + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter >> 8); + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter & 0xFF); + $parentObj->programCounter = 0x20; + }; + //ADD SP, n + //#0xE8: + $this->functionsArray[] = function ($parentObj) { + $signedByte = $parentObj->usbtsb($parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter)); + $temp_value = $parentObj->nswtuw($parentObj->stackPointer + $signedByte); + $parentObj->FCarry = ((($parentObj->stackPointer ^ $signedByte ^ $temp_value) & 0x100) == 0x100); + $parentObj->FHalfCarry = ((($parentObj->stackPointer ^ $signedByte ^ $temp_value) & 0x10) == 0x10); + $parentObj->stackPointer = $temp_value; + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + $parentObj->FZero = $parentObj->FSubtract = false; + }; + //JP, (HL) + //#0xE9: + $this->functionsArray[] = function ($parentObj) { + $parentObj->programCounter = $parentObj->registersHL; + }; + //LD n, A + //#0xEA: + $this->functionsArray[] = function ($parentObj) { + $parentObj->memoryWrite(($parentObj->memoryRead(($parentObj->programCounter + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter), $parentObj->registerA); + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + }; + //0xEB - Illegal + //#0xEB: + $this->functionsArray[] = function ($parentObj) { + echo "Illegal op code 0xEB called, pausing emulation."; + exit(); + }; + //0xEC - Illegal + //#0xEC: + $this->functionsArray[] = function ($parentObj) { + echo "Illegal op code 0xEC called, pausing emulation."; + exit(); + }; + //0xED - Illegal + //#0xED: + $this->functionsArray[] = function ($parentObj) { + echo "Illegal op code 0xED called, pausing emulation."; + exit(); + }; + //XOR n + //#0xEE: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA ^= $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + $parentObj->FSubtract = $parentObj->FHalfCarry = $parentObj->FCarry = false; + }; + //RST 0x28 + //#0xEF: + $this->functionsArray[] = function ($parentObj) { + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter >> 8); + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter & 0xFF); + $parentObj->programCounter = 0x28; + }; + //LDH A, (n) + //#0xF0: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA = $parentObj->memoryRead(0xFF00 + $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter)); + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + }; + //POP AF + //#0xF1: + $this->functionsArray[] = function ($parentObj) { + $temp_var = $parentObj->memoryReader[$parentObj->stackPointer]($parentObj, $parentObj->stackPointer); + $parentObj->FZero = (($temp_var & 0x80) == 0x80); + $parentObj->FSubtract = (($temp_var & 0x40) == 0x40); + $parentObj->FHalfCarry = (($temp_var & 0x20) == 0x20); + $parentObj->FCarry = (($temp_var & 0x10) == 0x10); + $parentObj->registerA = $parentObj->memoryRead(($parentObj->stackPointer + 1) & 0xFFFF); + $parentObj->stackPointer = ($parentObj->stackPointer + 2) & 0xFFFF; + }; + //LD A, (C) + //#0xF2: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA = $parentObj->memoryRead(0xFF00 + $parentObj->registerC); + }; + //DI + //#0xF3: + $this->functionsArray[] = function ($parentObj) { + $parentObj->IME = false; + $parentObj->untilEnable = 0; + }; + //0xF4 - Illegal + //#0xF4: + $this->functionsArray[] = function ($parentObj) { + // @TODO + // cout("Illegal op code 0xF4 called, pausing emulation.", 2); + // pause(); + }; + //PUSH AF + //#0xF5: + $this->functionsArray[] = function ($parentObj) { + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->registerA); + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, (($parentObj->FZero) ? 0x80 : 0) + (($parentObj->FSubtract) ? 0x40 : 0) + (($parentObj->FHalfCarry) ? 0x20 : 0) + (($parentObj->FCarry) ? 0x10 : 0)); + }; + //OR n + //#0xF6: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA |= $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->FZero = ($parentObj->registerA == 0); + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + $parentObj->FSubtract = $parentObj->FCarry = $parentObj->FHalfCarry = false; + }; + //RST 0x30 + //#0xF7: + $this->functionsArray[] = function ($parentObj) { + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter >> 8); + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter & 0xFF); + $parentObj->programCounter = 0x30; + }; + //LDHL SP, n + //#0xF8: + $this->functionsArray[] = function ($parentObj) { + $signedByte = $parentObj->usbtsb($parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter)); + $parentObj->registersHL = $parentObj->nswtuw($parentObj->stackPointer + $signedByte); + $parentObj->FCarry = ((($parentObj->stackPointer ^ $signedByte ^ $parentObj->registersHL) & 0x100) == 0x100); + $parentObj->FHalfCarry = ((($parentObj->stackPointer ^ $signedByte ^ $parentObj->registersHL) & 0x10) == 0x10); + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + $parentObj->FZero = $parentObj->FSubtract = false; + }; + //LD SP, HL + //#0xF9: + $this->functionsArray[] = function ($parentObj) { + $parentObj->stackPointer = $parentObj->registersHL; + }; + //LD A, (nn) + //#0xFA: + $this->functionsArray[] = function ($parentObj) { + $parentObj->registerA = $parentObj->memoryRead(($parentObj->memoryRead(($parentObj->programCounter + 1) & 0xFFFF) << 8) + $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter)); + $parentObj->programCounter = ($parentObj->programCounter + 2) & 0xFFFF; + }; + //EI + //#0xFB: + $this->functionsArray[] = function ($parentObj) { + $parentObj->untilEnable = 2; + }; + //0xFC - Illegal + //#0xFC: + $this->functionsArray[] = function ($parentObj) { + echo "Illegal op code 0xFC called, pausing emulation."; + exit(); + }; + //0xFD - Illegal + //#0xFD: + $this->functionsArray[] = function ($parentObj) { + echo "Illegal op code 0xFD called, pausing emulation."; + exit(); + }; + //CP n + //#0xFE: + $this->functionsArray[] = function ($parentObj) { + $dirtySum = $parentObj->registerA - $parentObj->memoryReader[$parentObj->programCounter]($parentObj, $parentObj->programCounter); + $parentObj->FHalfCarry = ($parentObj->unsbtub($dirtySum) & 0xF) > ($parentObj->registerA & 0xF); + $parentObj->FCarry = ($dirtySum < 0); + $parentObj->FZero = ($dirtySum == 0); + $parentObj->programCounter = ($parentObj->programCounter + 1) & 0xFFFF; + $parentObj->FSubtract = true; + }; + //RST 0x38 + //#0xFF: + $this->functionsArray[] = function ($parentObj) { + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter >> 8); + $parentObj->stackPointer = $parentObj->unswtuw($parentObj->stackPointer - 1); + $parentObj->memoryWrite($parentObj->stackPointer, $parentObj->programCounter & 0xFF); + $parentObj->programCounter = 0x38; + }; + } + + public function get() + { + return $this->functionsArray; + } +}
\ No newline at end of file diff --git a/src/GameBoy/Settings.php b/src/GameBoy/Settings.php new file mode 100644 index 0000000..ba9adab --- /dev/null +++ b/src/GameBoy/Settings.php @@ -0,0 +1,72 @@ +<?php +namespace GameBoy; + +class Settings +{ + //Some settings. + public static $settings = [ + //[0] - Turn on sound. + false, + + //[1] - Force Mono sound. + false, + + //[2] - Give priority to GameBoy mode + true, + + //[3] - Keyboard button map. + //Order: Right, Left, Up, Down, A, B, Select, Start + ['d', 'a', 'w', 's', ',', '.', 'n', 'm'], + + //[4] - Frameskip Amount (Auto frameskip setting allows the script to change this.) + 0, + + //[5] - Use the data URI BMP method over the canvas tag method? + false, + + //[6] - How many tiles in each direction when using the BMP method (width * height). + [16, 12], + + //[7] - Auto Frame Skip + true, + + //[8] - Maximum Frame Skip + 29, + + //[9] - Override to allow for MBC1 instead of ROM only (compatibility for broken 3rd-party cartridges). + true, + + //[10] - Override MBC RAM disabling and always allow reading and writing to the banks. + true, + + //[11] - Audio granularity setting (Sampling of audio every x many machine cycles) + 20, + + //[12] - Frameskip base factor + 10, + + //[13] - Target number of machine cycles per loop. (4,194,300 / 1000 * 17) + 17826, + + //[14] - Sample Rate + 70000, + + //[15] - How many bits per WAV PCM sample (For browsers that fall back to WAV PCM generation) + 0x10, + + //[16] - Use the GBC BIOS? + false, + + //[17] - Colorize GB mode? + false, + + //[18] - Sample size for webkit audio. + 512, + + //[19] - Whether to display the canvas at 144x160 on fullscreen or as stretched. + false, + + //[20] - Interval for the emulator loop. + 17, + ]; +}
\ No newline at end of file diff --git a/src/GameBoy/TICKTables.php b/src/GameBoy/TICKTables.php new file mode 100644 index 0000000..d6b0824 --- /dev/null +++ b/src/GameBoy/TICKTables.php @@ -0,0 +1,55 @@ +<?php +namespace GameBoy; + +class TickTables +{ + public static $primary = [ + //Number of machine cycles for each instruction: + /* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F*/ + 1, 3, 2, 2, 1, 1, 2, 1, 5, 2, 2, 2, 1, 1, 2, 1, //0 + 1, 3, 2, 2, 1, 1, 2, 1, 3, 2, 2, 2, 1, 1, 2, 1, //1 + 2, 3, 2, 2, 1, 1, 2, 1, 2, 2, 2, 2, 1, 1, 2, 1, //2 + 2, 3, 2, 2, 3, 3, 3, 1, 2, 2, 2, 2, 1, 1, 2, 1, //3 + + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, //4 + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, //5 + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, //6 + 2, 2, 2, 2, 2, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, //7 + + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, //8 + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, //9 + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, //A + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, //B + + 2, 3, 3, 4, 3, 4, 2, 4, 2, 4, 3, 2, 3, 6, 2, 4, //C + 2, 3, 3, 1, 3, 4, 2, 4, 2, 4, 3, 1, 3, 1, 2, 4, //D + 3, 3, 2, 1, 1, 4, 2, 4, 4, 1, 4, 1, 1, 1, 2, 4, //E + 3, 3, 2, 1, 1, 4, 2, 4, 3, 2, 4, 1, 0, 1, 2, 4 //F + + ]; + + public static $secondary = [ + //Number of machine cycles for each 0xCBXX instruction: + /* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F*/ + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, //0 + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, //1 + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, //2 + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, //3 + + 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, //4 + 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, //5 + 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, //6 + 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, //7 + + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, //8 + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, //9 + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, //A + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, //B + + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, //C + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, //D + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, //E + 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2 //F + ]; +} + |