summaryrefslogtreecommitdiffstats
path: root/src/GameBoy
diff options
context:
space:
mode:
Diffstat (limited to 'src/GameBoy')
-rw-r--r--src/GameBoy/Canvas/DrawContextInterface.php18
-rw-r--r--src/GameBoy/Canvas/TerminalCanvas.php58
-rw-r--r--src/GameBoy/Cbopcode.php1145
-rw-r--r--src/GameBoy/Core.php2860
-rw-r--r--src/GameBoy/Keyboard.php63
-rw-r--r--src/GameBoy/Opcode.php1985
-rw-r--r--src/GameBoy/Settings.php72
-rw-r--r--src/GameBoy/TICKTables.php55
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
+ ];
+}
+