mirror of
https://github.com/gabrielrcouto/php-terminal-gameboy-emulator.git
synced 2025-03-13 20:23:43 +01:00
Compare commits
32 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e3fb698a7a | ||
|
fc2c91acfe | ||
|
3fa8a25142 | ||
|
89e12a4415 | ||
|
adffbf649d | ||
|
d894d500df | ||
|
43830cd31d | ||
|
dca580237a | ||
|
afdbfae33b | ||
|
f5c638e243 | ||
|
2a05514b8a | ||
|
785ba4e571 | ||
|
f897b90064 | ||
|
a46af4b8de | ||
|
ff7b65b719 | ||
|
765a7b77dd | ||
|
cc403325cf | ||
|
4620492b89 | ||
|
c2c6a6ca84 | ||
|
25b72dfcc9 | ||
|
790f1298c5 | ||
|
de77ce982b | ||
|
35f9c92378 | ||
|
a189df7fd5 | ||
|
e73dc3d5c5 | ||
|
eb0faf9dbe | ||
|
e0a2b96088 | ||
|
0c8f6fa8ee | ||
|
b865e6dc03 | ||
|
1b30bc7eb0 | ||
|
45454e3377 | ||
|
15d04a5ca4 |
23
.gitignore
vendored
23
.gitignore
vendored
@ -1,4 +1,21 @@
|
||||
material/
|
||||
vendor/
|
||||
roms/*rom
|
||||
# root
|
||||
cache.properties
|
||||
|
||||
# bin
|
||||
bin/*
|
||||
!bin/php-gameboy
|
||||
!bin/php-gameboy.phar
|
||||
|
||||
# material
|
||||
material/
|
||||
|
||||
# vendor
|
||||
vendor/
|
||||
|
||||
# roms
|
||||
roms/
|
||||
|
||||
# global
|
||||
*.gb
|
||||
*.gbc
|
||||
*.rom
|
||||
|
44
README.md
44
README.md
@ -13,7 +13,7 @@ Want to play Dr Mario or Pokémon on your server terminal? That's for you!
|
||||
+ [Running](#running)
|
||||
+ [Controls](#controls)
|
||||
+ [Tests](#tests)
|
||||
+ [TO-DO](#todo)
|
||||
+ [TO-DO](#to-do)
|
||||
+ [Credits](#credits)
|
||||
+ [Legal](#legal)
|
||||
|
||||
@ -38,14 +38,44 @@ The following PHP versions are supported:
|
||||
You will need a good terminal! I've tested only on MacOSX and Linux. I'm sorry
|
||||
about that Windows guys :disappointed:
|
||||
|
||||
## Running
|
||||
## Installation
|
||||
|
||||
Before: Put your ROMs files (.gb or .gbc) on "roms/" folder.
|
||||
Using composer:
|
||||
|
||||
```bash
|
||||
$ composer g require gabrielrcouto/php-terminal-gameboy-emulator:dev-master
|
||||
```
|
||||
|
||||
Using PHAR:
|
||||
|
||||
```bash
|
||||
$ wget https://raw.githubusercontent.com/gabrielrcouto/php-terminal-gameboy-emulator/master/bin/php-gameboy.phar
|
||||
$ chmod +x php-gameboy.phar
|
||||
$ mv php-gameboy.phar /usr/local/bin/php-gameboy
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
Your roms are loaded from the directory you are running the `php-gameboy` command.
|
||||
|
||||
```bash
|
||||
$ php-gameboy drmario.gb
|
||||
$ php-gameboy pokemon.gbc
|
||||
```
|
||||
|
||||
If you like to run this emulator locally, simple clone the repository:
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/gabrielrcouto/php-terminal-gameboy-emulator.git
|
||||
$ cd php-terminal-gameboy-emulator
|
||||
$ composer install -o
|
||||
$ bin/php-gameboy drmario.gb
|
||||
```
|
||||
|
||||
For running roms, pass the full path to your rom or put then in the `php-terminal-gameboy-emulator` folder:
|
||||
|
||||
```bash
|
||||
$ bin/php-gameboy pokemon.gbc
|
||||
$ bin/php-gameboy /full/path/to/your/rom/drmario.gb
|
||||
```
|
||||
|
||||
## Controls
|
||||
@ -93,8 +123,8 @@ You can use the following command to run the most common checks, such as `php -l
|
||||
Converting from the JS paradigm was a lot of work, and I still need to adapt somethings like:
|
||||
|
||||
- [x] Code standard - PSRs, please!
|
||||
- [ ] Array of functions - Maybe in PHP it's not the best approach
|
||||
- [ ] Pixel auxiliary array - Very CPU intersive to convert RGBA every time
|
||||
- [x] Array of functions - Maybe in PHP it's not the best approach
|
||||
- [x] Pixel auxiliary array - Very CPU intensive to convert RGBA every time
|
||||
- [ ] Classes - Core is too big!
|
||||
- [ ] Profiling and otimizing - XHProf to find the most intensive functions
|
||||
- [ ] Save/Restore - I need to save my Pokémon, please!
|
||||
@ -112,3 +142,5 @@ It does not have any commercial or profitable intentions.
|
||||
The user is responsible to use this code and its content in the terms of the law.
|
||||
|
||||
The author is completely against piracy and respects all the copyrights, trademarks and patents of Nintendo.
|
||||
|
||||
|
||||
|
74
bin/build
Executable file
74
bin/build
Executable file
@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Finder\SplFileInfo;
|
||||
|
||||
require_once(__DIR__ . '/../vendor/autoload.php');
|
||||
|
||||
$rootDir = __DIR__ . '/..';
|
||||
|
||||
$filename = 'php-gameboy.phar';
|
||||
$filePath = $rootDir . '/bin/' . $filename;
|
||||
$stub = <<<STUB
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
Phar::mapPhar("php-gameboy.phar");
|
||||
|
||||
require_once("phar://php-gameboy.phar/php-terminal-gameboy-emulator/boot.php");
|
||||
|
||||
__HALT_COMPILER();
|
||||
|
||||
STUB;
|
||||
|
||||
if (file_exists($filePath)) {
|
||||
unlink($filePath);
|
||||
}
|
||||
|
||||
$finderSort = function ($a, $b) {
|
||||
return strcmp(strtr($a->getRealPath(), '\\', '/'), strtr($b->getRealPath(), '\\', '/'));
|
||||
};
|
||||
|
||||
function addFile($phar, $file)
|
||||
{
|
||||
$path = strtr(str_replace(dirname(dirname(__DIR__)).DIRECTORY_SEPARATOR, '', $file->getRealPath()), '\\', '/');
|
||||
|
||||
$content = file_get_contents($file);
|
||||
|
||||
$phar->addFromString($path, $content);
|
||||
}
|
||||
|
||||
$phar = new Phar(
|
||||
$filePath,
|
||||
FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::KEY_AS_FILENAME,
|
||||
$filename
|
||||
);
|
||||
$phar->setSignatureAlgorithm(\Phar::SHA1);
|
||||
$phar->startBuffering();
|
||||
|
||||
addFile($phar, new SplFileInfo('boot.php', $rootDir . '/boot.php', 'boot.php'));
|
||||
addFile($phar, new SplFileInfo('composer.json', $rootDir . '/composer.json', 'composer.json'));
|
||||
addFile($phar, new SplFileInfo('vendor/autoload.php', $rootDir . '/vendor/autoload.php', 'vendor/autoload.php'));
|
||||
|
||||
$finder = new Finder();
|
||||
$finder->files()
|
||||
->ignoreVCS(true)
|
||||
->name('*.php')
|
||||
->in([
|
||||
$rootDir . '/src/',
|
||||
$rootDir . '/vendor/composer',
|
||||
$rootDir . '/vendor/whatthejeff',
|
||||
])
|
||||
->sort($finderSort)
|
||||
;
|
||||
|
||||
foreach ($finder as $file) {
|
||||
addFile($phar, $file);
|
||||
}
|
||||
|
||||
$phar->setStub($stub);
|
||||
|
||||
$phar->stopBuffering();
|
||||
|
||||
chmod($filePath, 0775);
|
BIN
bin/php-gameboy.phar
Executable file
BIN
bin/php-gameboy.phar
Executable file
Binary file not shown.
40
boot.php
40
boot.php
@ -1,23 +1,39 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__.'/vendor/autoload.php';
|
||||
|
||||
foreach (['../../autoload.php', '../vendor/autoload.php', 'vendor/autoload.php'] as $autoload) {
|
||||
$autoload = __DIR__.'/'.$autoload;
|
||||
if (file_exists($autoload)) {
|
||||
require $autoload;
|
||||
break;
|
||||
}
|
||||
}
|
||||
unset($autoload);
|
||||
|
||||
use GameBoy\Canvas\TerminalCanvas;
|
||||
use GameBoy\Core;
|
||||
use GameBoy\Keyboard;
|
||||
|
||||
set_exception_handler(function (Exception $exception) {
|
||||
fwrite(STDERR, $exception->getMessage().PHP_EOL);
|
||||
exit(254);
|
||||
});
|
||||
|
||||
if (count($argv) < 2) {
|
||||
throw new RuntimeException('You need to pass the ROM file name (Ex: drmario.rom)');
|
||||
if (PHP_VERSION_ID >= 70000) {
|
||||
set_exception_handler(function (\Throwable $exception) {
|
||||
fwrite(STDERR, $exception->getMessage() . PHP_EOL);
|
||||
exit(254);
|
||||
});
|
||||
} else {
|
||||
set_exception_handler(function (Exception $exception) {
|
||||
fwrite(STDERR, $exception->getMessage() . PHP_EOL);
|
||||
exit(254);
|
||||
});
|
||||
}
|
||||
|
||||
$filename = 'roms/'.$argv[1];
|
||||
if (count($argv) < 2) {
|
||||
throw new \RuntimeException('You need to pass the ROM file name (Ex: drmario.rom)');
|
||||
}
|
||||
|
||||
$filename = $argv[1];
|
||||
|
||||
if (!file_exists($filename)) {
|
||||
throw new RuntimeException(sprintf('"%s" does not exist', $filename));
|
||||
throw new \RuntimeException(sprintf('"%s" does not exist', $filename));
|
||||
}
|
||||
|
||||
if (extension_loaded('xdebug')) {
|
||||
@ -35,11 +51,11 @@ $keyboard = new Keyboard($core);
|
||||
$core->start();
|
||||
|
||||
if (($core->stopEmulator & 2) == 0) {
|
||||
throw new RuntimeException('The GameBoy core is already running.');
|
||||
throw new \RuntimeException('The GameBoy core is already running.');
|
||||
}
|
||||
|
||||
if ($core->stopEmulator & 2 != 2) {
|
||||
throw new RuntimeException('GameBoy core cannot run while it has not been initialized.');
|
||||
throw new \RuntimeException('GameBoy core cannot run while it has not been initialized.');
|
||||
}
|
||||
|
||||
$core->stopEmulator &= 1;
|
||||
|
@ -11,12 +11,12 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.6.0",
|
||||
"whatthejeff/drawille": "^1.0"
|
||||
"php": ">=5.6.0"
|
||||
},
|
||||
"bin": ["bin/php-gameboy"],
|
||||
"require-dev": {
|
||||
"squizlabs/php_codesniffer": "2.0.*@dev"
|
||||
"squizlabs/php_codesniffer": "2.0.*@dev",
|
||||
"symfony/finder" : "*"
|
||||
},
|
||||
"config": {
|
||||
"bin-dir": "bin"
|
||||
|
108
composer.lock
generated
108
composer.lock
generated
@ -4,62 +4,9 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"hash": "6f5eed549688ca9a4eb124eae054ffce",
|
||||
"content-hash": "ccc583d4e7fb658a3842017f2fc17e17",
|
||||
"packages": [
|
||||
{
|
||||
"name": "whatthejeff/drawille",
|
||||
"version": "v1.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/whatthejeff/php-drawille.git",
|
||||
"reference": "bafea427f5bf2445413f6807000a95f70cc83bfd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/whatthejeff/php-drawille/zipball/bafea427f5bf2445413f6807000a95f70cc83bfd",
|
||||
"reference": "bafea427f5bf2445413f6807000a95f70cc83bfd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.1.*"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Drawille": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jeff Welch",
|
||||
"email": "whatthejeff@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Terminal drawing with braille",
|
||||
"homepage": "http://github.com/whatthejeff/php-drawille",
|
||||
"keywords": [
|
||||
"Turtle",
|
||||
"braille",
|
||||
"console",
|
||||
"drawing",
|
||||
"terminal"
|
||||
],
|
||||
"time": "2014-05-26 13:45:31"
|
||||
}
|
||||
],
|
||||
"hash": "1d43205f748238a612e736313fb85ead",
|
||||
"content-hash": "650da37ee06349860cc3ce6988504476",
|
||||
"packages": [],
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "squizlabs/php_codesniffer",
|
||||
@ -133,6 +80,55 @@
|
||||
"standards"
|
||||
],
|
||||
"time": "2014-12-05 00:14:12"
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v3.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/finder.git",
|
||||
"reference": "8201978de88a9fa0923e18601bb17f1df9c721e7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/8201978de88a9fa0923e18601bb17f1df9c721e7",
|
||||
"reference": "8201978de88a9fa0923e18601bb17f1df9c721e7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.5.9"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.1-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Finder\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony Finder Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2016-06-29 05:41:56"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
|
2
roms/.gitignore
vendored
2
roms/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
*.gb
|
||||
*.gbc
|
@ -11,9 +11,7 @@ interface DrawContextInterface
|
||||
/**
|
||||
* Draw image on canvas.
|
||||
*
|
||||
* @param array $canvasBuffer Each pixel => 4 items on array (RGBA)
|
||||
* @param int $left
|
||||
* @param int $top
|
||||
* @param array $canvasBuffer If colored, each pixel => 4 items on array (RGBA)
|
||||
*/
|
||||
public function draw($canvasBuffer, $left, $top);
|
||||
public function draw($canvasBuffer);
|
||||
}
|
||||
|
@ -2,50 +2,59 @@
|
||||
|
||||
namespace GameBoy\Canvas;
|
||||
|
||||
use Drawille\Canvas;
|
||||
use GameBoy\Settings;
|
||||
|
||||
class TerminalCanvas implements DrawContextInterface
|
||||
{
|
||||
/**
|
||||
* The blank brailler char
|
||||
* @var String
|
||||
*/
|
||||
protected $brailleCharOffset;
|
||||
protected $canvas;
|
||||
/**
|
||||
* If is a color enabled canvas, set to true
|
||||
* @var Boolean
|
||||
*/
|
||||
public $colorEnabled = false;
|
||||
protected $currentSecond = 0;
|
||||
protected $framesInSecond = 0;
|
||||
protected $fps = 0;
|
||||
|
||||
private $width = 0;
|
||||
private $height = 0;
|
||||
protected $height = 0;
|
||||
protected $lastFrame;
|
||||
protected $lastFrameCanvasBuffer;
|
||||
/**
|
||||
* Braille Pixel Matrix
|
||||
* ,___,
|
||||
* |1 4|
|
||||
* |2 5|
|
||||
* |3 6|
|
||||
* |7 8|
|
||||
* `````
|
||||
* @var Array
|
||||
*/
|
||||
protected $pixelMap;
|
||||
protected $width = 0;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->canvas = new Canvas();
|
||||
$this->brailleCharOffset = html_entity_decode('&#' . (0x2800) . ';', ENT_NOQUOTES, 'UTF-8');
|
||||
$this->pixelMap = [
|
||||
[html_entity_decode('&#' . (0x2801) . ';', ENT_NOQUOTES, 'UTF-8'), html_entity_decode('&#' . (0x2808) . ';', ENT_NOQUOTES, 'UTF-8')],
|
||||
[html_entity_decode('&#' . (0x2802) . ';', ENT_NOQUOTES, 'UTF-8'), html_entity_decode('&#' . (0x2810) . ';', ENT_NOQUOTES, 'UTF-8')],
|
||||
[html_entity_decode('&#' . (0x2804) . ';', ENT_NOQUOTES, 'UTF-8'), html_entity_decode('&#' . (0x2820) . ';', ENT_NOQUOTES, 'UTF-8')],
|
||||
[html_entity_decode('&#' . (0x2840) . ';', ENT_NOQUOTES, 'UTF-8'), html_entity_decode('&#' . (0x2880) . ';', ENT_NOQUOTES, 'UTF-8')],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw image on canvas using braille font.
|
||||
*
|
||||
* @param object $canvasBuffer $data = Each pixel = 4 items on array (RGBA)
|
||||
* @param int $left
|
||||
* @param int $top
|
||||
* @param object $canvasBuffer $data = Each pixel (true/false)
|
||||
*/
|
||||
public function draw($canvasBuffer, $left, $top)
|
||||
public function draw($canvasBuffer)
|
||||
{
|
||||
//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);
|
||||
}
|
||||
}
|
||||
|
||||
//Calculate current FPS
|
||||
if ($this->currentSecond != time()) {
|
||||
$this->fps = $this->framesInSecond;
|
||||
$this->currentSecond = time();
|
||||
@ -54,21 +63,56 @@ class TerminalCanvas implements DrawContextInterface
|
||||
++$this->framesInSecond;
|
||||
}
|
||||
|
||||
$frame = $this->canvas->frame();
|
||||
$content = "\e[H\e[2J";
|
||||
//If the last frame changed, we draw
|
||||
// @TODO - The FPS will be wrong, need to find a way to update
|
||||
// without redraw
|
||||
if ($canvasBuffer != $this->lastFrameCanvasBuffer) {
|
||||
// Array with all braille chars of the frame, filled with the blank char
|
||||
// 2880 = total braille chars per frame
|
||||
$chars = array_fill(0, 2880, $this->brailleCharOffset);
|
||||
|
||||
if ($this->height > 0 && $this->width > 0) {
|
||||
$content = "\e[{$this->height}A\e[{$this->width}D";
|
||||
// Turn on the first and last pixels
|
||||
$chars[0] |= $this->pixelMap[0][0];
|
||||
$chars[2879] |= $this->pixelMap[0][0];
|
||||
|
||||
// Frame string - It's a big braille chars string
|
||||
$frame = '';
|
||||
|
||||
for ($y = 0; $y < 144; $y++) {
|
||||
for ($x = 0; $x < 160; $x++) {
|
||||
$pixelCanvasNumber = $x + (160 * $y);
|
||||
$charPosition = floor($x / 2) + (floor($y / 4) * 80);
|
||||
|
||||
if (isset($canvasBuffer[$pixelCanvasNumber]) && $canvasBuffer[$pixelCanvasNumber]) {
|
||||
$chars[$charPosition] |= $this->pixelMap[$y % 4][$x % 2];
|
||||
}
|
||||
|
||||
// Each braille frame has 8 pixels, when we reach the last pixel,
|
||||
// we can append to the frame string
|
||||
if ($x % 2 === 1 && $y % 4 === 3) {
|
||||
$frame .= $chars[$charPosition];
|
||||
|
||||
if ($x % 159 === 0) {
|
||||
$frame .= PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->lastFrame = $frame;
|
||||
$this->lastFrameCanvasBuffer = $canvasBuffer;
|
||||
|
||||
$content = "\e[H\e[2J";
|
||||
|
||||
if ($this->height > 0 && $this->width > 0) {
|
||||
$content = "\e[{$this->height}A\e[{$this->width}D";
|
||||
}
|
||||
|
||||
$content .= sprintf('FPS: %3d - Frame Skip: %3d' . PHP_EOL, $this->fps, Settings::$frameskipAmout) . $frame;
|
||||
echo $content;
|
||||
|
||||
$this->height = 37;
|
||||
$this->width = 80;
|
||||
}
|
||||
|
||||
$content .= sprintf('FPS: %d - Frame Skip: %s'.PHP_EOL, $this->fps, Settings::$settings[4]);
|
||||
$content .= $frame;
|
||||
|
||||
echo $content;
|
||||
|
||||
$this->canvas->clear();
|
||||
|
||||
$this->height = substr_count($frame, PHP_EOL) + 1;
|
||||
$this->width = strpos($frame, PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
4045
src/Cbopcode.php
4045
src/Cbopcode.php
File diff suppressed because it is too large
Load Diff
2144
src/Core.php
2144
src/Core.php
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,7 @@ class Keyboard
|
||||
public function __construct(Core $core)
|
||||
{
|
||||
$this->core = $core;
|
||||
exec('stty -icanon');
|
||||
exec('stty -icanon -echo');
|
||||
$this->file = fopen('php://stdin', 'r');
|
||||
stream_set_blocking($this->file, false);
|
||||
}
|
||||
@ -35,7 +35,7 @@ class Keyboard
|
||||
//Maps a keyboard key to a gameboy key.
|
||||
//Order: Right, Left, Up, Down, A, B, Select, Start
|
||||
|
||||
$keyIndex = array_search($key, Settings::$settings[3]);
|
||||
$keyIndex = array_search($key, Settings::$keyboardButtonMap);
|
||||
|
||||
if ($keyIndex === false) {
|
||||
return -1;
|
||||
|
220
src/LcdController.php
Normal file
220
src/LcdController.php
Normal file
@ -0,0 +1,220 @@
|
||||
<?php
|
||||
|
||||
namespace GameBoy;
|
||||
|
||||
class LcdController
|
||||
{
|
||||
//Actual scan line...
|
||||
public $actualScanLine = 0;
|
||||
|
||||
protected $core;
|
||||
|
||||
//Is the emulated LCD controller on?
|
||||
public $LCDisOn = false;
|
||||
|
||||
//Should we trigger an interrupt if LY==LYC?
|
||||
public $LYCMatchTriggerSTAT = false;
|
||||
|
||||
//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 in mode 0?
|
||||
public $mode0TriggerSTAT = false;
|
||||
|
||||
//Should we trigger an interrupt if in mode 1?
|
||||
public $mode1TriggerSTAT = false;
|
||||
|
||||
//Should we trigger an interrupt if in mode 2?
|
||||
public $mode2TriggerSTAT = false;
|
||||
|
||||
//Tracker for STAT triggering.
|
||||
public $STATTracker = 0;
|
||||
|
||||
public function __construct($core)
|
||||
{
|
||||
$this->core = $core;
|
||||
}
|
||||
|
||||
public function matchLYC()
|
||||
{
|
||||
// LY - LYC Compare
|
||||
// If LY==LCY
|
||||
if ($this->core->memory[0xFF44] == $this->core->memory[0xFF45]) {
|
||||
$this->core->memory[0xFF41] |= 0x04; // set STAT bit 2: LY-LYC coincidence flag
|
||||
if ($this->LYCMatchTriggerSTAT) {
|
||||
$this->core->memory[0xFF0F] |= 0x2; // set IF bit 1
|
||||
}
|
||||
} else {
|
||||
$this->core->memory[0xFF41] &= 0xFB; // reset STAT bit 2 (LY!=LYC)
|
||||
}
|
||||
}
|
||||
|
||||
public function notifyScanline()
|
||||
{
|
||||
if ($this->actualScanLine == 0) {
|
||||
$this->core->windowSourceLine = 0;
|
||||
}
|
||||
// determine the left edge of the window (160 if window is inactive)
|
||||
$windowLeft = ($this->core->gfxWindowDisplay && $this->core->memory[0xFF4A] <= $this->actualScanLine) ? min(160, $this->core->memory[0xFF4B] - 7) : 160;
|
||||
// step 1: background+window
|
||||
$skippedAnything = $this->core->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->core->drawSpritesForLine($this->actualScanLine);
|
||||
// step 3: prio tiles+window
|
||||
if ($skippedAnything) {
|
||||
$this->core->drawBackgroundForLine($this->actualScanLine, $windowLeft, 0x80);
|
||||
}
|
||||
if ($windowLeft < 160) {
|
||||
++$this->core->windowSourceLine;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan Line and STAT Mode Control
|
||||
* @param int $line Memory Scanline
|
||||
*/
|
||||
public function scanLine($line)
|
||||
{
|
||||
//When turned off = Do nothing!
|
||||
if ($this->LCDisOn) {
|
||||
if ($line < 143) {
|
||||
//We're on a normal scan line:
|
||||
if ($this->core->LCDTicks < 20) {
|
||||
$this->scanLineMode2(); // mode2: 80 cycles
|
||||
} elseif ($this->core->LCDTicks < 63) {
|
||||
$this->scanLineMode3(); // mode3: 172 cycles
|
||||
} elseif ($this->core->LCDTicks < 114) {
|
||||
$this->scanLineMode0(); // mode0: 204 cycles
|
||||
} else {
|
||||
//We're on a new scan line:
|
||||
$this->core->LCDTicks -= 114;
|
||||
$this->actualScanLine = ++$this->core->memory[0xFF44];
|
||||
$this->matchLYC();
|
||||
if ($this->STATTracker != 2) {
|
||||
if ($this->core->hdmaRunning && !$this->core->halt && $this->LCDisOn) {
|
||||
$this->core->performHdma(); //H-Blank DMA
|
||||
}
|
||||
if ($this->mode0TriggerSTAT) {
|
||||
$this->core->memory[0xFF0F] |= 0x2; // set IF bit 1
|
||||
}
|
||||
}
|
||||
$this->STATTracker = 0;
|
||||
$this->scanLineMode2(); // mode2: 80 cycles
|
||||
if ($this->core->LCDTicks >= 114) {
|
||||
//We need to skip 1 or more scan lines:
|
||||
$this->core->notifyScanline();
|
||||
$this->scanLine($this->actualScanLine); //Scan Line and STAT Mode Control
|
||||
}
|
||||
}
|
||||
} elseif ($line == 143) {
|
||||
//We're on the last visible scan line of the LCD screen:
|
||||
if ($this->core->LCDTicks < 20) {
|
||||
$this->scanLineMode2(); // mode2: 80 cycles
|
||||
} elseif ($this->core->LCDTicks < 63) {
|
||||
$this->scanLineMode3(); // mode3: 172 cycles
|
||||
} elseif ($this->core->LCDTicks < 114) {
|
||||
$this->scanLineMode0(); // mode0: 204 cycles
|
||||
} else {
|
||||
//Starting V-Blank:
|
||||
//Just finished the last visible scan line:
|
||||
$this->core->LCDTicks -= 114;
|
||||
$this->actualScanLine = ++$this->core->memory[0xFF44];
|
||||
$this->matchLYC();
|
||||
if ($this->mode1TriggerSTAT) {
|
||||
$this->core->memory[0xFF0F] |= 0x2; // set IF bit 1
|
||||
}
|
||||
if ($this->STATTracker != 2) {
|
||||
if ($this->core->hdmaRunning && !$this->core->halt && $this->LCDisOn) {
|
||||
$this->core->performHdma(); //H-Blank DMA
|
||||
}
|
||||
if ($this->mode0TriggerSTAT) {
|
||||
$this->core->memory[0xFF0F] |= 0x2; // set IF bit 1
|
||||
}
|
||||
}
|
||||
$this->STATTracker = 0;
|
||||
$this->modeSTAT = 1;
|
||||
$this->core->memory[0xFF0F] |= 0x1; // set IF flag 0
|
||||
//LCD off takes at least 2 frames.
|
||||
if ($this->core->drewBlank > 0) {
|
||||
--$this->core->drewBlank;
|
||||
}
|
||||
if ($this->core->LCDTicks >= 114) {
|
||||
//We need to skip 1 or more scan lines:
|
||||
$this->scanLine($this->actualScanLine); //Scan Line and STAT Mode Control
|
||||
}
|
||||
}
|
||||
} elseif ($line < 153) {
|
||||
//In VBlank
|
||||
if ($this->core->LCDTicks >= 114) {
|
||||
//We're on a new scan line:
|
||||
$this->core->LCDTicks -= 114;
|
||||
$this->actualScanLine = ++$this->core->memory[0xFF44];
|
||||
$this->matchLYC();
|
||||
if ($this->core->LCDTicks >= 114) {
|
||||
//We need to skip 1 or more scan lines:
|
||||
$this->scanLine($this->actualScanLine); //Scan Line and STAT Mode Control
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//VBlank Ending (We're on the last actual scan line)
|
||||
if ($this->core->memory[0xFF44] == 153) {
|
||||
$this->core->memory[0xFF44] = 0; //LY register resets to 0 early.
|
||||
$this->matchLYC(); //LY==LYC Test is early here (Fixes specific one-line glitches (example: Kirby2 intro)).
|
||||
}
|
||||
if ($this->core->LCDTicks >= 114) {
|
||||
//We reset back to the beginning:
|
||||
$this->core->LCDTicks -= 114;
|
||||
$this->actualScanLine = 0;
|
||||
$this->scanLineMode2(); // mode2: 80 cycles
|
||||
if ($this->core->LCDTicks >= 114) {
|
||||
//We need to skip 1 or more scan lines:
|
||||
$this->scanLine($this->actualScanLine); //Scan Line and STAT Mode Control
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function scanLineMode0()
|
||||
{
|
||||
// H-Blank
|
||||
if ($this->modeSTAT != 0) {
|
||||
if ($this->core->hdmaRunning && !$this->core->halt && $this->LCDisOn) {
|
||||
$this->performHdma(); //H-Blank DMA
|
||||
}
|
||||
if ($this->mode0TriggerSTAT || ($this->mode2TriggerSTAT && $this->STATTracker == 0)) {
|
||||
$this->core->memory[0xFF0F] |= 0x2; // if STAT bit 3 -> set IF bit1
|
||||
}
|
||||
$this->notifyScanline();
|
||||
$this->STATTracker = 2;
|
||||
$this->modeSTAT = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public function scanLineMode2()
|
||||
{
|
||||
// OAM in use
|
||||
if ($this->modeSTAT != 2) {
|
||||
if ($this->mode2TriggerSTAT) {
|
||||
$this->core->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->core->memory[0xFF0F] |= 0x2; // set IF bit 1
|
||||
}
|
||||
$this->STATTracker = 1;
|
||||
$this->modeSTAT = 3;
|
||||
}
|
||||
}
|
||||
}
|
5724
src/Opcode.php
5724
src/Opcode.php
File diff suppressed because it is too large
Load Diff
@ -4,70 +4,43 @@ namespace GameBoy;
|
||||
|
||||
class Settings
|
||||
{
|
||||
//Some settings.
|
||||
public static $settings = [
|
||||
//[0] - Turn on sound.
|
||||
false,
|
||||
//Audio granularity setting (Sampling of audio every x many machine cycles)
|
||||
public static $audioGranularity = 20;
|
||||
|
||||
//[1] - Force Mono sound.
|
||||
false,
|
||||
//Auto Frame Skip
|
||||
public static $autoFrameskip = true;
|
||||
|
||||
//[2] - Give priority to GameBoy mode
|
||||
true,
|
||||
//Colorize GB mode?
|
||||
public static $colorize = false;
|
||||
|
||||
//[3] - Keyboard button map.
|
||||
//Order: Right, Left, Up, Down, A, B, Select, Start
|
||||
['d', 'a', 'w', 's', ',', '.', 'n', 'm'],
|
||||
//Keyboard button map.
|
||||
//Order: Right, Left, Up, Down, A, B, Select, Start
|
||||
public static $keyboardButtonMap = ['d', 'a', 'w', 's', ',', '.', 'n', 'm'];
|
||||
|
||||
//[4] - Frameskip Amount (Auto frameskip setting allows the script to change this.)
|
||||
0,
|
||||
//Frameskip Amount (Auto frameskip setting allows the script to change this.)
|
||||
public static $frameskipAmout = 0;
|
||||
|
||||
//[5] - Use the data URI BMP method over the canvas tag method?
|
||||
false,
|
||||
//Frameskip base factor
|
||||
public static $frameskipBaseFactor = 10;
|
||||
|
||||
//[6] - How many tiles in each direction when using the BMP method (width * height).
|
||||
[16, 12],
|
||||
//Maximum Frame Skip
|
||||
public static $frameskipMax = 29;
|
||||
|
||||
//[7] - Auto Frame Skip
|
||||
true,
|
||||
//Interval for the emulator loop.
|
||||
public static $loopInterval = 17;
|
||||
|
||||
//[8] - Maximum Frame Skip
|
||||
29,
|
||||
//Target number of machine cycles per loop. (4,194,300 / 1000 * 17)
|
||||
public static $machineCyclesPerLoop = 17826;
|
||||
|
||||
//[9] - Override to allow for MBC1 instead of ROM only (compatibility for broken 3rd-party cartridges).
|
||||
true,
|
||||
//Override MBC RAM disabling and always allow reading and writing to the banks.
|
||||
public static $overrideMBC = true;
|
||||
|
||||
//[10] - Override MBC RAM disabling and always allow reading and writing to the banks.
|
||||
true,
|
||||
//Override to allow for MBC1 instead of ROM only (compatibility for broken 3rd-party cartridges).
|
||||
public static $overrideMBC1 = true;
|
||||
|
||||
//[11] - Audio granularity setting (Sampling of audio every x many machine cycles)
|
||||
20,
|
||||
//Give priority to GameBoy mode
|
||||
public static $priorizeGameBoyMode = true;
|
||||
|
||||
//[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,
|
||||
];
|
||||
//Sample Rate
|
||||
public static $sampleRate = 70000;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user