├── .gitignore ├── src ├── ui │ ├── style │ │ ├── media │ │ │ └── gameboy.png │ │ └── index.scss │ ├── flag.jsx │ ├── register.jsx │ ├── disassemble.jsx │ ├── screen.jsx │ └── index.jsx ├── core │ ├── mappers │ │ ├── flags.js │ │ ├── rom.js │ │ ├── mbc2.js │ │ ├── mbc3.js │ │ ├── mbc5.js │ │ ├── mbc1.js │ │ └── mapper.js │ ├── consts.js │ ├── misc │ │ ├── workram.js │ │ ├── timer.js │ │ ├── joypad.js │ │ └── bios.js │ ├── index.js │ ├── registers.js │ ├── audio │ │ ├── waveform.js │ │ ├── noise.js │ │ ├── square.js │ │ └── audio.js │ ├── video │ │ ├── dma.js │ │ ├── palette.js │ │ ├── lcd.js │ │ └── gpu.js │ ├── core.js │ └── ops │ │ ├── shift.js │ │ └── index.js ├── index.jsx ├── util │ ├── polyfills.js │ ├── memory.js │ └── keyboard.js └── debugger │ └── disassemble.js ├── public └── index.html ├── README.md ├── package.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .sass-cache 3 | .DS_Store 4 | public/* 5 | !public/index.html 6 | -------------------------------------------------------------------------------- /src/ui/style/media/gameboy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asterick/JSBoy/HEAD/src/ui/style/media/gameboy.png -------------------------------------------------------------------------------- /src/core/mappers/flags.js: -------------------------------------------------------------------------------- 1 | export const NONE = 0; 2 | export const RAM = 1; 3 | export const BATTERY = 2; 4 | export const TIMER = 4; 5 | export const RUMBLE = 8; 6 | -------------------------------------------------------------------------------- /src/core/consts.js: -------------------------------------------------------------------------------- 1 | export const IRQ_VBLANK = 1; 2 | export const IRQ_LCD_STAT = 2; 3 | export const IRQ_TIMER = 4; 4 | export const IRQ_SERIAL = 8; 5 | export const IRQ_JOYSTICK = 16; 6 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSBoy 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | import MainView from "./ui"; 5 | 6 | import Polyfills from "./util/polyfills"; 7 | import JSBoy from "./core"; 8 | 9 | 10 | const runtime = new JSBoy(); 11 | 12 | ReactDOM.render(, document.querySelector("#container")); 13 | -------------------------------------------------------------------------------- /src/ui/flag.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | export default class Flag extends Component { 4 | renderValue () { 5 | var v = this.props.chip[this.props.flag]; 6 | 7 | return (v ? '\u2713' : '\u00A0'); 8 | } 9 | 10 | render () { 11 | return ( 12 |
13 | {this.props.name} 14 | {this.renderValue()} 15 |
16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ui/register.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | export default class Register extends Component { 4 | renderValue () { 5 | var v = this.props.chip[this.props.register] || 0; 6 | 7 | return v.toString(16).toUpperCase(); 8 | } 9 | 10 | render () { 11 | return ( 12 |
13 | {this.props.name} 14 | {this.renderValue()} 15 |
16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JSBoy 2 | ===== 3 | 4 | The gameboy color emulator for javascript 5 | 6 | Introduction 7 | ------------ 8 | Self contained and only requires that it be initialized with a 2d context, preferably 160x144px 9 | in size or greater. This is done by passing an array and a name which is used for web storage 10 | 11 | Strong typed rom images are allowed, but you will see no performance increase 12 | due to the use of delegates for virtual memory accesses 13 | 14 | 15 | Technical Features 16 | ------------------ 17 | This emulator features a number of performance optimizations in order to keep 18 | performance at a maximum; this includes interrupt prediction and the use of 19 | memory i/o delegates for fast memory accesses. 20 | 21 | Bugs 22 | ---- 23 | Predicitions are off 24 | Noise playback frequency is wrong (a guess at best) 25 | -------------------------------------------------------------------------------- /src/util/polyfills.js: -------------------------------------------------------------------------------- 1 | // THIS IS CRAP THAT NEEDS TO BE FACTORED OUT 2 | Array.prototype.chunk = function (stride) { 3 | var chunks = []; 4 | 5 | for(var i = 0; i < this.length; i += stride) { 6 | chunks.push(this.slice(i, i + stride)); 7 | } 8 | 9 | return chunks; 10 | }; 11 | 12 | Array.prototype.fill = function (value, pos, length) { 13 | if(pos === undefined) { pos = 0; } 14 | if(length === undefined) { length = this.length; } 15 | 16 | while(length-- > 0 && pos < this.length) { 17 | this[pos++] = value; 18 | } 19 | 20 | return this; 21 | }; 22 | 23 | Array.prototype.copy = function (dest_pos, source, source_pos, length) { 24 | if(source_pos === undefined) { source_pos = 0; } 25 | 26 | if(length === undefined) { length = source.length; } 27 | 28 | while(length-- > 0 && source_pos < source.length) { 29 | this[dest_pos++] = source[source_pos++]; 30 | } 31 | 32 | return this; 33 | }; 34 | -------------------------------------------------------------------------------- /src/ui/disassemble.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Disassembler from "../debugger/disassemble.js"; 3 | 4 | export default class Instruction extends Component { 5 | disassemble() { 6 | var results = [], 7 | pc = this.props.address, 8 | o; 9 | 10 | for (var i = 0; i < 25; i++, pc = o.next) { 11 | var o = Disassembler(this.props.runtime.cpu, pc); 12 | if (!o) { break; } 13 | 14 | results.push( 15 |
16 | {pc.toString(16).toUpperCase()} 17 | {o.hex} 18 | {o.op} 19 |
20 | ) 21 | } 22 | 23 | return results; 24 | } 25 | 26 | render() { 27 | return ( 28 |
{this.disassemble()}
29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/core/mappers/rom.js: -------------------------------------------------------------------------------- 1 | /*** 2 | *** ROM based mapper: Contains no bank switching logic 3 | *** 4 | *** Capabilities supported RAM, BATTERY 5 | *** 6 | ***/ 7 | 8 | import * as memory from "../../util/memory"; 9 | import * as flags from "./flags"; 10 | 11 | export default class mapperROM { 12 | constructor( name, cpu, rom, ramSize, mapperFlags, description ) { 13 | this.rom = rom; 14 | this.ram = memory.ramBlock( ramSize, 0x2000, name ); 15 | this.cpu = cpu; 16 | this.flags = mapperFlags; 17 | 18 | if( this.flags & flags.BATTERY ) { 19 | this.ram.load(); 20 | } 21 | } 22 | 23 | close() 24 | { 25 | if (this.flags & flags.BATTERY) { 26 | this.ram.save(); 27 | } 28 | } 29 | 30 | reset() 31 | { 32 | this.cpu.read.copy (0, this.rom, 0, 0x80); 33 | 34 | var ramMask = this.ramMask; 35 | 36 | if (this.ram) { 37 | this.cpu.readChunks.copy(0xA0, this.ram.read, 0x20); 38 | this.cpu.writeChunks.copy(0xA0, this.ram.write, 0x20); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JSBoy", 3 | "version": "1.0.0", 4 | "description": "JSBoy - The gameboy color emulator for javascript Bryon Vandiver (unicdk@gmail.com) =================================", 5 | "main": "Gruntfile.js", 6 | "dependencies": { 7 | "@babel/core": "^7.4.5", 8 | "@babel/preset-react": "^7.0.0", 9 | "babel-loader": "^8.0.6", 10 | "css-loader": "^3.0.0", 11 | "file-loader": "^4.0.0", 12 | "node-sass": "^4.12.0", 13 | "react": "^16.8.6", 14 | "react-dom": "^16.8.6", 15 | "react-tools": "^0.12.1", 16 | "sass-loader": "^7.1.0", 17 | "style-loader": "^0.23.1", 18 | "url-loader": "^2.0.0", 19 | "webpack": "^4.35.0" 20 | }, 21 | "babel": { 22 | "presets": [ 23 | "@babel/preset-react" 24 | ] 25 | }, 26 | "scripts": { 27 | "start": "webpack-dev-server --open --mode development", 28 | "build": "webpack --mode production" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/asterick/JSBoy.git" 33 | }, 34 | "author": "Robert Vandiver", 35 | "license": "Apache2", 36 | "bugs": { 37 | "url": "https://github.com/asterick/JSBoy/issues" 38 | }, 39 | "homepage": "https://github.com/asterick/JSBoy", 40 | "devDependencies": { 41 | "webpack-cli": "^3.3.5", 42 | "webpack-dev-server": "^3.7.2" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/core/misc/workram.js: -------------------------------------------------------------------------------- 1 | import * as memory from "../../util/memory"; 2 | import * as registers from "../registers"; 3 | 4 | export default class WorkRam { 5 | constructor (cpu) { 6 | this.cpu = cpu; 7 | this.memory = memory.ramBlock(0x8000); 8 | this.zeroPage = memory.ramBlock(0x7F); 9 | this.bank = 0; 10 | } 11 | 12 | write_SVBK (data) { 13 | this.bank = data & 0x7; 14 | var ea = (this.bank || 1) * 0x10; 15 | 16 | // Bankable memory 17 | this.cpu.read.copy(0xD0, this.memory.readChunks, ea, 0x10); 18 | this.cpu.write.copy(0xD0, this.memory.writeChunks, ea, 0x10); 19 | 20 | // Shadow memory 21 | this.cpu.read.copy(0xF0, this.cpu.read, 0xD0, 0x0E); 22 | this.cpu.write.copy(0xF0, this.cpu.write, 0xD0, 0x0E); 23 | } 24 | 25 | read_SVBK () { 26 | return this.bank; 27 | } 28 | 29 | reset () { 30 | // --- Zero page memory (fast) 31 | this.cpu.registers.read.copy(0x80, this.zeroPage.read); 32 | this.cpu.registers.write.copy(0x80, this.zeroPage.write); 33 | 34 | // --- Map the default 8k memory 35 | this.cpu.read.copy(0xC0, this.memory.readChunks, 0, 0x20); 36 | this.cpu.write.copy(0xC0, this.memory.writeChunks, 0, 0x20); 37 | 38 | // --- Shadow memory 39 | this.cpu.read.copy(0xE0, this.cpu.read, 0xC0, 0x1E); 40 | this.cpu.write.copy(0xE0, this.cpu.write, 0xC0, 0x1E); 41 | this.bank = 0; 42 | 43 | this.cpu.registers.read[registers.SVBK] = this.read_SVBK.bind(this); 44 | this.cpu.registers.write[registers.SVBK] = this.write_SVBK.bind(this); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | 4 | const mode = process.env.NODE_ENV || "development"; 5 | 6 | module.exports = { 7 | mode: mode, 8 | entry: './src/index.jsx', 9 | output: { 10 | path: path.join(__dirname, 'public'), 11 | filename: 'app.js', 12 | }, 13 | devServer: { 14 | contentBase: path.join(__dirname, 'public'), 15 | compress: true, 16 | port: 9000 17 | }, 18 | plugins: [ 19 | new webpack.IgnorePlugin(/^text-encoding$/), 20 | new webpack.IgnorePlugin(/^fs$/) 21 | ], 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.(png|woff|woff2|eot|ttf|svg)$/, 26 | use: [ 27 | { 28 | loader: 'url-loader', 29 | options: { limit: 8196 } 30 | } 31 | ] 32 | }, 33 | { 34 | test: /\.s?css$/, 35 | use: [ 36 | "style-loader", // creates style nodes from JS strings 37 | "css-loader", // translates CSS into CommonJS 38 | "sass-loader" // compiles Sass to CSS, using Node Sass by default 39 | ], 40 | }, 41 | { 42 | test: /\.jsx?$/, 43 | exclude: /node_modules/, 44 | use: [ { loader: 'babel-loader' } ], 45 | } 46 | ] 47 | }, 48 | resolve: { 49 | extensions: ['.js', '.jsx', '.scss'] 50 | } 51 | }; 52 | 53 | if (mode !== 'production') { 54 | module.exports.resolve.alias = { 55 | inferno: __dirname + "/node_modules/inferno/dist/index.dev.esm.js" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/core/index.js: -------------------------------------------------------------------------------- 1 | import Core from "./core"; 2 | import mapper from "./mappers/mapper"; 3 | 4 | export default class JSBoy { 5 | constructor() { 6 | // Bios will auto reset when the system initializes 7 | this.cpu = new Core(); 8 | this.running = false; 9 | } 10 | 11 | setContext (ctx) { 12 | this.cpu.setContext(ctx); 13 | } 14 | 15 | reset ( name, data ) { 16 | if (data) { 17 | this.cpu.close(); 18 | this.cpu.insert(mapper(name, this.cpu, data)); 19 | } else { 20 | this.cpu.reset(); 21 | } 22 | } 23 | 24 | close () { 25 | this.cpu.close(); 26 | } 27 | 28 | singleStep () { 29 | this.cpu.singleStep(); 30 | } 31 | 32 | get running () { 33 | return this._running; 34 | } 35 | 36 | set running (state) { 37 | if (this._running === state) { return ; } 38 | 39 | var lastTime = Date.now(), 40 | fraction = 0, 41 | self = this; 42 | 43 | this._running = state; 44 | if (this._running) { 45 | var nextFrame = function () { 46 | if (!self._running) { return ; } 47 | 48 | var nextTime = Date.now(), 49 | ticks = nextTime - lastTime, 50 | advance = Math.min(300000, ticks * 8388.608 + fraction), 51 | cycles = Math.floor(advance); 52 | 53 | fraction = advance - cycles; 54 | lastTime = nextTime; 55 | 56 | window.requestAnimationFrame(nextFrame); 57 | self.cpu.step(cycles); 58 | }; 59 | 60 | window.requestAnimationFrame(nextFrame); 61 | this.cpu.audio.play(); 62 | } else { 63 | this.cpu.audio.mute(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/core/mappers/mbc2.js: -------------------------------------------------------------------------------- 1 | /*** 2 | *** MBC2 based mapper: Contains no bank switching logic 3 | *** 4 | *** Capabilities supported RAM, BATTERY 5 | *** 6 | ***/ 7 | 8 | import * as memory from "../../util/memory"; 9 | import * as flags from "./flags"; 10 | 11 | export default class mapperMBC2 { 12 | constructor( name, cpu, rom, ramSize, mapperFlags, description ) { 13 | this.banks = rom.chunk(0x40); 14 | this.ram = memory.ramBlock(0x200,0x2000,name,0xF); 15 | this.flags = mapperFlags; 16 | this.cpu = cpu; 17 | this.rom = rom; 18 | 19 | if( this.flags & flags.BATTERY ) 20 | this.ram.load(); 21 | } 22 | 23 | close() 24 | { 25 | if( this.flags & flags.BATTERY ) 26 | this.ram.save(); 27 | } 28 | 29 | reset() 30 | { 31 | var re = this.ramEnable.bind(this), 32 | rb = this.romBank.bind(this), 33 | writeMap = new Array(0x100); 34 | 35 | // --- Static mapping 36 | for( var i = 0; i < 0x100; i++ ) 37 | writeMap[i] = (i & 1) ? re : rb; 38 | 39 | this.cpu.write.fill(writeMap, 0, 0x80); 40 | this.cpu.read.copy( 0, this.rom, 0, 0x40 ); 41 | 42 | this.ramEnable(0); 43 | this.romBank(1); 44 | } 45 | 46 | ramEnable( data ) 47 | { 48 | // WARNING: THIS MAY BE INVALID 49 | if( (data & 0xF) == 0xA ) 50 | { 51 | this.cpu.read.copy( 0xA0, this.ram.readChunks, 0, 0x20 ); 52 | this.cpu.write.copy( 0xA0, this.ram.writeChunks, 0, 0x20 ); 53 | } 54 | else 55 | { 56 | this.cpu.read.fill(this.cpu.nullBlock, 0xA0, 0x20); 57 | this.cpu.write.fill(this.cpu.nullBlock, 0xA0, 0x20); 58 | } 59 | } 60 | 61 | romBank( data ) 62 | { 63 | data = (data & 0xF) || 1; 64 | this.cpu.read.copy(0x40,this.banks[data]); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/ui/screen.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | export default class Screen extends Component { 4 | constructor (props) { 5 | super(props); 6 | 7 | this._ref = React.createRef(); 8 | 9 | this.state = { 10 | doubleSize: false, 11 | dragging: false 12 | }; 13 | } 14 | 15 | componentDidMount() { 16 | this.props.runtime.setContext(this._ref.current.getContext('2d')); 17 | } 18 | 19 | screenClicked () { 20 | this.setState({ 21 | doubleSize: !this.state.doubleSize 22 | }); 23 | } 24 | 25 | onDragOver (e) { 26 | e.preventDefault(); 27 | e.dataTransfer.dropEffect = "copy"; 28 | this.setState({ dragging: true }); 29 | } 30 | 31 | onDragLeave () { 32 | this.setState({ dragging: false }); 33 | } 34 | 35 | onDrop (e) { 36 | e.preventDefault(); 37 | 38 | var file = e.dataTransfer.files[0], 39 | reader = new FileReader(); 40 | 41 | reader.onload = (e) => { 42 | var name = this.state.rom_name, 43 | name = name.split(".")[0]; 44 | 45 | this.props.runtime.reset(name, e.target.result); 46 | this.props.runtime.running = true; 47 | }; 48 | 49 | this.setState({ dragging: false, rom_name: file.name }, function () { 50 | reader.readAsArrayBuffer(file); 51 | }); 52 | } 53 | 54 | render () { 55 | var canvasClass = [ 56 | 'display', 57 | this.state.doubleSize && 'double', 58 | this.state.dragging && 'dragging' 59 | ].filter((v) => v).join(" "); 60 | 61 | return ( 62 | this.screenClicked(e)} 64 | onDragOver={(e) => this.onDragOver(e)} 65 | onDragLeave={(e) => this.onDragLeave(e)} 66 | onDrop={(e) => this.onDrop(e)} 67 | className={canvasClass} 68 | /> 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/util/memory.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * JSBoy Memory block helper functions 3 | */ 4 | const delegates = new Array(0x100); 5 | 6 | for(var i = 0; i < 0x100; i++) { 7 | delegates[i] = new Function("return " + i); 8 | } 9 | 10 | export function romBlock(data, length) { 11 | var newData = new Array(length || data.length); 12 | 13 | newData.copy(0, data); 14 | newData.fill(0xFF, data.length); 15 | 16 | return newData.map((d) => delegates[d]).chunk(0x100); 17 | } 18 | 19 | export function ramBlock(size, extend, name, mask) { 20 | if(!size) return null; 21 | 22 | var read = new Array(extend); 23 | var write = new Array(extend); 24 | var data = new Uint8Array(size); 25 | var delegate; 26 | 27 | if(mask && 0xFF & ~mask) { 28 | delegate = function (index) { 29 | data[index] = 0; 30 | read[index] = function () { 31 | return data[index]; 32 | }; 33 | write[index] = function (value) { 34 | data[index] = value & mask; 35 | }; 36 | }; 37 | } else { 38 | delegate = function (index) { 39 | data[index] = 0; 40 | read[index] = function () { 41 | return data[index]; 42 | }; 43 | write[index] = function (value) { 44 | data[index] = value; 45 | }; 46 | }; 47 | } 48 | 49 | for(var i = 0; i < size; i++) { 50 | delegate(i); 51 | } 52 | 53 | for(i = size; i < extend; i++) { 54 | read[i] = read[i % size]; 55 | write[i] = write[i % size]; 56 | } 57 | 58 | var save = function () { 59 | var encoded = ""; 60 | 61 | for(var i = 0; i < data.length; i++) { 62 | encoded += String.fromCharCode(data[i]); 63 | } 64 | 65 | window.localStorage.setItem(name, encoded); 66 | }; 67 | 68 | var load = function () { 69 | var encoded = window.localStorage.getItem(name); 70 | 71 | if(!encoded) { 72 | return; 73 | } 74 | 75 | encoded.split('').map(function (c, idx) { 76 | data[idx] = c.charCodeAt(0); 77 | }); 78 | }; 79 | 80 | return { 81 | readChunks: read.chunk(0x100), 82 | writeChunks: write.chunk(0x100), 83 | read: read, 84 | write: write, 85 | data: data, 86 | save: save, 87 | load: load 88 | }; 89 | } 90 | -------------------------------------------------------------------------------- /src/core/mappers/mbc3.js: -------------------------------------------------------------------------------- 1 | /*** 2 | *** MBC3 based mapper: Contains no bank switching logic 3 | *** 4 | *** Capabilities supported RAM, BATTERY, TIMER 5 | *** 6 | ***/ 7 | 8 | import * as memory from "../../util/memory"; 9 | import * as flags from "./flags"; 10 | 11 | export default class mapperMBC3 { 12 | constructor( name, cpu, rom, ramSize, mapperFlags, description ) { 13 | this.ram = memory.ramBlock( ramSize, 0x2000, name ); 14 | this.banks = rom.chunk(0x40); 15 | 16 | this.cpu = cpu; 17 | this.flags = mapperFlags; 18 | 19 | if( this.flags & flags.BATTERY ) 20 | this.ram.load(); 21 | } 22 | 23 | close() 24 | { 25 | if( this.flags & flags.BATTERY ) 26 | this.ram.save(); 27 | }; 28 | 29 | reset() 30 | { 31 | this.ramEnabled = false; 32 | this.romBank = 1; 33 | this.ramBank = 0; 34 | 35 | // --- Static mapping 36 | this.cpu.read.copy( 0, this.banks[0] ); 37 | 38 | var ramEnableReg = (new Array(0x100)).fill(this.ramEnableReg.bind(this)), 39 | romBankSelectReg = (new Array(0x100)).fill(this.romBankSelectReg.bind(this)), 40 | ramBankSelectReg = (new Array(0x100)).fill(this.ramBankSelectReg.bind(this)), 41 | clockLatchReg = (new Array(0x100)).fill(this.clockLatchReg.bind(this)); 42 | 43 | this.cpu.write.fill(ramEnableReg, 0x00, 0x20); 44 | this.cpu.write.fill(romBankSelectReg, 0x20, 0x20); 45 | this.cpu.write.fill(ramBankSelectReg, 0x40, 0x20); 46 | this.cpu.write.fill(clockLatchReg, 0x60, 0x20); 47 | 48 | this.updateMemoryMap(); 49 | }; 50 | 51 | updateMemoryMap() 52 | { 53 | this.cpu.read.copy( 0x40, this.banks[this.romBank] ); 54 | 55 | if( this.ram && this.ramEnabled && this.ramBank <= 3 ) 56 | { 57 | var ramBankAddr = this.ramBank * 0x20; 58 | this.cpu.read.copy( 0xA0, this.ram.readChunks, ramBankAddr, 0x20 ); 59 | this.cpu.write.copy( 0xA0, this.ram.writeChunks, ramBankAddr, 0x20 ); 60 | } 61 | // TODO: TIMER 62 | else 63 | { 64 | this.cpu.read.fill(this.cpu.nullBlock, 0xA0, 0x20); 65 | this.cpu.write.fill(this.cpu.nullBlock, 0xA0, 0x20); 66 | } 67 | }; 68 | 69 | ramEnableReg( data ) 70 | { 71 | if( !this.ram ) 72 | return ; 73 | 74 | this.ramEnabled = (data & 0xF == 0xA); 75 | this.updateMemoryMap(); 76 | }; 77 | 78 | romBankSelectReg( data ) 79 | { 80 | this.romBank = ((data & 0x7F) % this.banks.length) || 1; 81 | this.updateMemoryMap(); 82 | }; 83 | 84 | ramBankSelectReg( data ) 85 | { 86 | this.ramBank = (data & 0xF); 87 | this.updateMemoryMap(); 88 | }; 89 | 90 | clockLatchReg( data ) 91 | { 92 | // TODO: DO TIMER 93 | }; 94 | } 95 | -------------------------------------------------------------------------------- /src/util/keyboard.js: -------------------------------------------------------------------------------- 1 | export const BACKSPACE = 8; 2 | export const TAB = 9; 3 | export const ENTER = 13; 4 | export const SHIFT = 16; 5 | export const CTRL = 17; 6 | export const ALT = 18; 7 | export const PAUSE = 19; 8 | export const CAPS_LOCK = 20; 9 | export const ESCAPE = 27; 10 | export const PAGE_UP = 33; 11 | export const PAGE_DOWN = 34; 12 | export const END = 35; 13 | export const HOME = 36; 14 | export const LEFT_ARROW = 37; 15 | export const UP_ARROW = 38; 16 | export const RIGHT_ARROW = 39; 17 | export const DOWN_ARROW = 40; 18 | export const INSERT = 45; 19 | export const DELETE = 46; 20 | export const MAIN_0 = 48; 21 | export const MAIN_1 = 49; 22 | export const MAIN_2 = 50; 23 | export const MAIN_3 = 51; 24 | export const MAIN_4 = 52; 25 | export const MAIN_5 = 53; 26 | export const MAIN_6 = 54; 27 | export const MAIN_7 = 55; 28 | export const MAIN_8 = 56; 29 | export const MAIN_9 = 57; 30 | export const A = 65; 31 | export const B = 66; 32 | export const C = 67; 33 | export const D = 68; 34 | export const E = 69; 35 | export const F = 70; 36 | export const G = 71; 37 | export const H = 72; 38 | export const I = 73; 39 | export const J = 74; 40 | export const K = 75; 41 | export const L = 76; 42 | export const M = 77; 43 | export const N = 78; 44 | export const O = 79; 45 | export const P = 80; 46 | export const Q = 81; 47 | export const R = 82; 48 | export const S = 83; 49 | export const T = 84; 50 | export const U = 85; 51 | export const V = 86; 52 | export const W = 87; 53 | export const X = 88; 54 | export const Y = 89; 55 | export const Z = 90; 56 | export const LEFT_WINDOW_KEY = 91; 57 | export const RIGHT_WINDOW_KEY = 92; 58 | export const SELECT_KEY = 93; 59 | export const NUMPAD_0 = 96; 60 | export const NUMPAD_1 = 97; 61 | export const NUMPAD_2 = 98; 62 | export const NUMPAD_3 = 99; 63 | export const NUMPAD_4 = 100; 64 | export const NUMPAD_5 = 101; 65 | export const NUMPAD_6 = 102; 66 | export const NUMPAD_7 = 103; 67 | export const NUMPAD_8 = 104; 68 | export const NUMPAD_9 = 105; 69 | export const MULTIPLY = 106; 70 | export const ADD = 107; 71 | export const SUBTRACT = 109; 72 | export const DECIMAL_POINT = 110; 73 | export const DIVIDE = 111; 74 | export const F1 = 112; 75 | export const F2 = 113; 76 | export const F3 = 114; 77 | export const F4 = 115; 78 | export const F5 = 116; 79 | export const F6 = 117; 80 | export const F7 = 118; 81 | export const F8 = 119; 82 | export const F9 = 120; 83 | export const F10 = 121; 84 | export const F11 = 122; 85 | export const F12 = 123; 86 | export const NUM_LOCK = 144; 87 | export const SCROLL_LOCK = 145; 88 | export const SEMICOLON = 186; 89 | export const EQUAL_SIGN = 187; 90 | export const COMMA = 188; 91 | export const DASH = 189; 92 | export const PERIOD = 190; 93 | export const FORWARD_SLASH = 191; 94 | export const GRAVE_ACCENT = 192; 95 | export const OPEN_BRACKET = 219; 96 | export const BACK_SLASH = 220; 97 | export const CLOSE_BRAKET = 221; 98 | export const INGLE_QUOTE = 222; 99 | -------------------------------------------------------------------------------- /src/core/mappers/mbc5.js: -------------------------------------------------------------------------------- 1 | /*** 2 | *** MBC3 based mapper: Contains no bank switching logic 3 | *** 4 | *** Capabilities supported RAM, BATTERY, RUMBLE 5 | *** 6 | ***/ 7 | 8 | import * as memory from "../../util/memory"; 9 | import * as flags from "./flags"; 10 | 11 | export default class mapperMBC5 { 12 | constructor( name, cpu, rom, ramSize, mapperFlags, description ) { 13 | this.ram = memory.ramBlock( ramSize, 0x2000, name ); 14 | this.banks = rom.chunk(0x40); 15 | 16 | this.cpu = cpu; 17 | this.flags = mapperFlags; 18 | 19 | if( this.flags & flags.BATTERY ) 20 | this.ram.load(); 21 | } 22 | 23 | close() 24 | { 25 | if( this.flags & flags.BATTERY ) 26 | this.ram.save(); 27 | }; 28 | 29 | reset() 30 | { 31 | this.ramEnabled = false; 32 | this.romBank = 0; 33 | this.ramBank = 0; 34 | 35 | // --- Static mapping 36 | this.cpu.read.copy(0, this.banks[0]); 37 | 38 | var ramEnableReg = (new Array(0x100)).fill(this.ramEnableReg.bind(this)), 39 | lowerRomBankSelect = (new Array(0x100)).fill(this.lowerRomBankSelect.bind(this)), 40 | upperRomBankSelect = (new Array(0x100)).fill(this.upperRomBankSelect.bind(this)), 41 | ramBankSelectReg = (new Array(0x100)).fill(this.ramBankSelectReg.bind(this)); 42 | 43 | this.cpu.write.fill(ramEnableReg, 0x00, 0x20); 44 | this.cpu.write.fill(lowerRomBankSelect, 0x20, 0x20); 45 | this.cpu.write.fill(upperRomBankSelect, 0x40, 0x20); 46 | this.cpu.write.fill(ramBankSelectReg, 0x60, 0x20); 47 | 48 | this.updateMemoryMap(); 49 | }; 50 | 51 | updateMemoryMap() 52 | { 53 | this.cpu.read.copy( 0x40, this.banks[(this.romBank || 1) % this.banks.length] ); 54 | 55 | if (this.ram && this.ramEnabled) { 56 | var ramBankAddr = (this.ramBank * 0x20) % this.ram.data.length; 57 | this.cpu.read.copy( 0xA0, this.ram.readChunks, ramBankAddr, 0x20 ); 58 | this.cpu.write.copy( 0xA0, this.ram.writeChunks, ramBankAddr, 0x20 ); 59 | } else { 60 | this.cpu.read.fill(this.cpu.nullBlock, 0xA0, 0x20); 61 | this.cpu.write.fill(this.cpu.nullBlock, 0xA0, 0x20); 62 | } 63 | }; 64 | 65 | ramEnableReg( data ) 66 | { 67 | if( !this.ram ) 68 | return ; 69 | 70 | this.ramEnabled = (data == 0xA); 71 | this.updateMemoryMap(); 72 | }; 73 | 74 | lowerRomBankSelect( data ) 75 | { 76 | this.romBank = (this.romBank & 0xFF00) | (data); 77 | this.updateMemoryMap(); 78 | }; 79 | 80 | upperRomBankSelect( data ) 81 | { 82 | this.romBank = (this.romBank & 0x00FF) | (data << 8); 83 | this.updateMemoryMap(); 84 | }; 85 | 86 | ramBankSelectReg( data ) 87 | { 88 | this.ramBank = data; 89 | this.updateMemoryMap(); 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /src/core/registers.js: -------------------------------------------------------------------------------- 1 | // --- Communication port registers 2 | export const SB = 0x01; 3 | export const SC = 0x02; 4 | export const RP = 0x56; // (CGB) 5 | 6 | // --- Video control registers 7 | export const LCDC = 0x40; 8 | export const STAT = 0x41; 9 | export const SCY = 0x42; 10 | export const SCX = 0x43; 11 | export const LY = 0x44; 12 | export const LYC = 0x45; 13 | export const WY = 0x4A; 14 | export const WX = 0x4B; 15 | 16 | // --- Block memory registers 17 | export const DMA = 0x46; 18 | export const HDMA1 = 0x51; // (CGB) 19 | export const HDMA2 = 0x52; // (CGB) 20 | export const HDMA3 = 0x53; // (CGB) 21 | export const HDMA4 = 0x54; // (CGB) 22 | export const HDMA5 = 0x55; // (CGB) 23 | 24 | // --- Palette registers 25 | export const BGP = 0x47; 26 | export const OBP0 = 0x48; 27 | export const OBP1 = 0x49; 28 | export const BCPS = 0x68; // (CGB) 29 | export const BCPD = 0x69; // (CGB) 30 | export const OCPS = 0x6A; // (CGB) 31 | export const OCPD = 0x6B; // (CGB) 32 | 33 | // --- Timer control registers 34 | export const DIV = 0x04; 35 | export const TIMA = 0x05; 36 | export const TMA = 0x06; 37 | export const TAC = 0x07; 38 | 39 | // --- Memory bank registers 40 | export const VBK = 0x4F; // (CGB) 41 | export const SVBK = 0x70; // (CGB) 42 | 43 | // --- CPU and IRQ Registers 44 | export const KEY1 = 0x4D; // (CGB) 45 | export const IF = 0x0F; 46 | export const IE = 0xFF; 47 | 48 | // --- Joypad registers 49 | export const JOYP = 0x00; 50 | 51 | // --- Undocumented registers 52 | export const LCD_MODE = 0x4C; // (UNDOCUMENTED) 53 | export const BLCK = 0x50; // (UNDOCUMENTED) 54 | export const LOCK = 0x6C; // (UNDOCUMENTED) 55 | 56 | // --- Sound registers 57 | export const NR10 = 0x10; 58 | export const NR11 = 0x11; 59 | export const NR12 = 0x12; 60 | export const NR13 = 0x13; 61 | export const NR14 = 0x14; 62 | export const NR21 = 0x16; 63 | export const NR22 = 0x17; 64 | export const NR23 = 0x18; 65 | export const NR24 = 0x19; 66 | export const NR30 = 0x1A; 67 | export const NR31 = 0x1B; 68 | export const NR32 = 0x1C; 69 | export const NR33 = 0x1D; 70 | export const NR34 = 0x1E; 71 | export const NR41 = 0x20; 72 | export const NR42 = 0x21; 73 | export const NR43 = 0x22; 74 | export const NR44 = 0x23; 75 | export const NR50 = 0x24; 76 | export const NR51 = 0x25; 77 | export const NR52 = 0x26; 78 | 79 | // --- Wave table memory 80 | export const AUD3WAVERAM0 = 0x30; 81 | export const AUD3WAVERAM1 = 0x31; 82 | export const AUD3WAVERAM2 = 0x32; 83 | export const AUD3WAVERAM3 = 0x33; 84 | export const AUD3WAVERAM4 = 0x34; 85 | export const AUD3WAVERAM5 = 0x35; 86 | export const AUD3WAVERAM6 = 0x36; 87 | export const AUD3WAVERAM7 = 0x37; 88 | export const AUD3WAVERAM8 = 0x38; 89 | export const AUD3WAVERAM9 = 0x39; 90 | export const AUD3WAVERAMA = 0x3A; 91 | export const AUD3WAVERAMB = 0x3B; 92 | export const AUD3WAVERAMC = 0x3C; 93 | export const AUD3WAVERAMD = 0x3D; 94 | export const AUD3WAVERAME = 0x3E; 95 | export const AUD3WAVERAMF = 0x3F; 96 | -------------------------------------------------------------------------------- /src/core/mappers/mbc1.js: -------------------------------------------------------------------------------- 1 | /*** 2 | *** MBC1 based mapper: Contains no bank switching logic 3 | *** 4 | *** Capabilities supported RAM, BATTERY 5 | *** 6 | ***/ 7 | 8 | import * as memory from "../../util/memory"; 9 | import * as flags from "./flags"; 10 | 11 | export default class mapperMBC1 { 12 | constructor ( name, cpu, rom, ramSize, mapperFlags, description ) { 13 | this.ram = memory.ramBlock( ramSize, 0x2000, name ); 14 | this.banks = rom.chunk(0x40); 15 | this.cpu = cpu; 16 | this.flags = mapperFlags; 17 | 18 | if( this.flags & flags.BATTERY ) 19 | this.ram.load(); 20 | } 21 | 22 | close () 23 | { 24 | if( this.flags & flags.BATTERY ) 25 | this.ram.save(); 26 | } 27 | 28 | reset () 29 | { 30 | this.ramEnabled = false; 31 | this.ramSelect = false; 32 | this.romBank = 1; 33 | this.upperBank = 0; 34 | 35 | // --- Static mapping 36 | this.cpu.read.copy( 0, this.banks[0] ); 37 | 38 | var ramEnableReg = (new Array(0x100)).fill(this.ramEnableReg.bind(this)), 39 | romBankSelectReg = (new Array(0x100)).fill(this.romBankSelectReg.bind(this)), 40 | upperBankSelectReg = (new Array(0x100)).fill(this.upperBankSelectReg.bind(this)), 41 | ramModeSelectReg = (new Array(0x100)).fill(this.ramModeSelectReg.bind(this)); 42 | 43 | this.cpu.write.fill(ramEnableReg, 0x00, 0x20); 44 | this.cpu.write.fill(romBankSelectReg, 0x20, 0x20); 45 | this.cpu.write.fill(upperBankSelectReg, 0x40, 0x20); 46 | this.cpu.write.fill(ramModeSelectReg, 0x60, 0x20); 47 | 48 | this.updateMemoryMap(); 49 | } 50 | 51 | updateMemoryMap () 52 | { 53 | var ramBankAddr = (this.ramSelect ? this.upperBank : 0) * 0x20; 54 | var romBankAddr = this.romBank | (this.ramSelect ? 0 : (this.upperBank << 5)); 55 | 56 | this.cpu.read.copy( 0x40, this.banks[romBankAddr % this.banks.length] ); 57 | 58 | // --- Ram enable! 59 | if( this.ram ) 60 | { 61 | if( this.ramEnabled ) 62 | { 63 | this.cpu.read.copy( 0xA0, this.ram.read, ramBankAddr, 0x20 ); 64 | this.cpu.write.copy( 0xA0, this.ram.write, ramBankAddr, 0x20 ); 65 | } 66 | else 67 | { 68 | this.cpu.read.fill(this.cpu.nullBlock, 0xA0, 0x20); 69 | this.cpu.write.fill(this.cpu.nullBlock, 0xA0, 0x20); 70 | } 71 | } 72 | } 73 | 74 | ramEnableReg ( data ) 75 | { 76 | if( !this.ram ) 77 | return ; 78 | 79 | this.ramEnabled = (data & 0xF == 0xA); 80 | this.updateMemoryMap(); 81 | } 82 | 83 | ramModeSelectReg ( data ) 84 | { 85 | if( !this.ram ) 86 | return ; 87 | 88 | this.ramSelect = data & 1; 89 | this.updateMemoryMap(); 90 | } 91 | 92 | romBankSelectReg ( data ) 93 | { 94 | this.romBank = (data & 0x1F) || 1; 95 | this.updateMemoryMap(); 96 | } 97 | 98 | upperBankSelectReg ( data ) 99 | { 100 | this.upperBank = data & 3; 101 | this.updateMemoryMap(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/core/audio/waveform.js: -------------------------------------------------------------------------------- 1 | import * as memory from "../../util/memory"; 2 | 3 | export default class WaveformChannel { 4 | constructor(cpu) { 5 | this.cpu = cpu; 6 | this.wavetable = memory.ramBlock(0x16); 7 | this.waveform = this.wavetable.data; 8 | } 9 | 10 | reset() { 11 | this.enabled = false; 12 | this.channelEnable = 0; 13 | 14 | this.frequency = 0; 15 | this.overflow = 0; 16 | 17 | this.lengthEnable = 0; 18 | this.lengthCounter = 0; 19 | this.length = 0; 20 | 21 | this.frequencyCounter = 0; 22 | this.sample = 0; 23 | 24 | this.outputRegister = 0; 25 | this.volume = 0; 26 | } 27 | 28 | clock(ticks) { 29 | if (!this.enabled || !this.channelEnable) { return ; } 30 | 31 | this.frequencyCounter += ticks; 32 | 33 | // Length counter 34 | if (this.lengthEnable) { 35 | this.lengthCounter += ticks; 36 | if (this.lengthCounter >= 32768) { 37 | this.length = (this.length + 1) & 0xFF; 38 | this.enabled = this.length !== 0; 39 | this.lengthCounter &= 32767; 40 | } 41 | } 42 | } 43 | 44 | level() { 45 | if (!this.enabled || !this.channelEnable) { return 0; } 46 | 47 | this.sample = (this.sample + (this.frequencyCounter / this.overflow)) & 31; 48 | this.frequencyCounter %= this.overflow; 49 | 50 | // Determine our current sample 51 | var i = this.sample, 52 | shift = (i & 1) ? 0 : 4, 53 | sample = (this.waveform[i>>1] >> shift) & 0xF; 54 | 55 | return sample * this.volume; 56 | } 57 | 58 | active() { 59 | return this.enabled && this.channelEnable; 60 | } 61 | 62 | // --- Registers 63 | write_enable(d) { 64 | this.channelEnable = d & 0x80; 65 | } 66 | 67 | write_length(d) { 68 | this.length = d; 69 | } 70 | 71 | write_level(d) { 72 | this.outputRegister = d; 73 | 74 | switch (d & 0x60) { 75 | case 0x00: 76 | this.volume = 0; 77 | break ; 78 | case 0x20: 79 | this.volume = 1.00 / 15; 80 | break ; 81 | case 0x40: 82 | this.volume = 0.50 / 15; 83 | break ; 84 | case 0x60: 85 | this.volume = 0.25 / 15; 86 | break ; 87 | } 88 | } 89 | 90 | write_freq_lo(d) { 91 | this.frequency = (this.frequency & 0xFF00) | (d); 92 | } 93 | 94 | write_freq_hi(d) { 95 | this.frequency = (this.frequency & 0x00FF) | ((d & 0x07) << 8); 96 | this.lengthEnable = d & 0x40; 97 | 98 | // Sound frequency 99 | if (d & 0x80) { 100 | this.cpu.catchUp(); 101 | 102 | this.enabled = true; 103 | this.frequencyCounter = 0; 104 | this.sample = 0; 105 | 106 | this.lengthCounter = 0; 107 | this.overflow = (2048 - this.frequency) * 4; 108 | } 109 | } 110 | 111 | read_enable() { 112 | return 0x7F | this.channelEnable; 113 | } 114 | 115 | read_level() { 116 | return 0x9F | this.outputRegister; 117 | } 118 | 119 | read_freq_hi() { 120 | return 0xBF | this.lengthEnable; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/core/misc/timer.js: -------------------------------------------------------------------------------- 1 | import * as registers from "../registers"; 2 | import * as consts from "../consts"; 3 | 4 | const PRESCALARS = [ 5 | (8 * 1024 * 1024) / 4096, 6 | (8 * 1024 * 1024) / 262144, 7 | (8 * 1024 * 1024) / 65536, 8 | (8 * 1024 * 1024) / 16384 9 | ]; 10 | 11 | export default class Timer { 12 | constructor (cpu) { 13 | this.cpu = cpu; 14 | 15 | // DIV register (machine time clocked) 16 | this.div = 0; 17 | 18 | this.scalar = 0; 19 | this.divider = 0; 20 | 21 | this.timer = 0; 22 | this.modulo = 0; 23 | this.enabled = false; 24 | } 25 | 26 | tick (cycles) { 27 | this.div = (this.div + cycles) & 0xFFFF; 28 | } 29 | 30 | clock (cycles) { 31 | if( !this.enabled ) 32 | return ; 33 | 34 | var wrap = PRESCALARS[this.scalar]; 35 | 36 | this.divider += cycles; 37 | this.timer += (this.divider / wrap); 38 | this.divider %= wrap; 39 | 40 | // Timer overflow 41 | if( this.timer >= 0x100 ) 42 | { 43 | this.timer = ((this.timer - this.modulo) % (0x100 - this.modulo)) + this.modulo; 44 | this.cpu.trigger( consts.IRQ_TIMER ); 45 | } 46 | } 47 | 48 | read_TIMA () { 49 | this.cpu.catchUp(); 50 | 51 | return this.timer; 52 | } 53 | 54 | write_TIMA (data) { 55 | this.cpu.catchUp(); 56 | 57 | this.timer = data; 58 | } 59 | 60 | read_TAC () 61 | { 62 | return this.scalar | (this.enabled ? 4 : 0); 63 | } 64 | 65 | write_TAC (data) 66 | { 67 | this.cpu.catchUp(); 68 | 69 | this.scalar = data & 3; 70 | this.enabled = data & 4; 71 | this.divider = 0; 72 | } 73 | 74 | read_TMA () 75 | { 76 | return this.modulo; 77 | } 78 | 79 | write_TMA (data) 80 | { 81 | this.cpu.catchUp(); 82 | this.modulo = data; 83 | } 84 | 85 | read_DIV () { 86 | this.cpu.catchUp(); 87 | return this.div >> 8; 88 | } 89 | 90 | write_DIV () { 91 | this.cpu.catchUp(); 92 | this.div = 0; 93 | } 94 | 95 | reset () { 96 | // Map TIMER 97 | this.timer = 0; 98 | this.modulo = 0; 99 | this.divider = 0; 100 | this.scalar = 0; 101 | this.enabled = false; 102 | 103 | this.cpu.registers.read[registers.TIMA] = this.read_TIMA.bind(this); 104 | this.cpu.registers.write[registers.TIMA] = this.write_TIMA.bind(this); 105 | this.cpu.registers.read[registers.TMA] = this.read_TMA.bind(this); 106 | this.cpu.registers.write[registers.TMA] = this.write_TMA.bind(this); 107 | this.cpu.registers.read[registers.TAC] = this.read_TAC.bind(this); 108 | this.cpu.registers.write[registers.TAC] = this.write_TAC.bind(this); 109 | 110 | // Map DIV register 111 | this.div = 0; 112 | 113 | this.cpu.registers.read[registers.DIV] = this.read_DIV.bind(this); 114 | this.cpu.registers.write[registers.DIV] = this.write_DIV.bind(this); 115 | } 116 | 117 | // Determine how many cycles until the next interrupt 118 | predict () { 119 | if( !this.enabled ) 120 | return ; 121 | 122 | return PRESCALARS[this.scalar] * (0x100 - this.timer) - this.divider; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/core/video/dma.js: -------------------------------------------------------------------------------- 1 | import * as registers from "../registers"; 2 | 3 | export default class DMA { 4 | constructor (gpu, cpu) { 5 | this.gpu = gpu; 6 | this.cpu = cpu; 7 | } 8 | 9 | reset() 10 | { 11 | this.active = false; 12 | this.blocksLeft = 0; 13 | 14 | this.sourceAddress = 0; 15 | this.destinationAddress = 0; 16 | 17 | this.cpu.registers.read[registers.HDMA1] = this.read_HDMA1.bind(this); 18 | this.cpu.registers.write[registers.HDMA1] = this.write_HDMA1.bind(this); 19 | this.cpu.registers.read[registers.HDMA2] = this.read_HDMA2.bind(this); 20 | this.cpu.registers.write[registers.HDMA2] = this.write_HDMA2.bind(this); 21 | this.cpu.registers.read[registers.HDMA3] = this.read_HDMA3.bind(this); 22 | this.cpu.registers.write[registers.HDMA3] = this.write_HDMA3.bind(this); 23 | this.cpu.registers.read[registers.HDMA4] = this.read_HDMA4.bind(this); 24 | this.cpu.registers.write[registers.HDMA4] = this.write_HDMA4.bind(this); 25 | this.cpu.registers.read[registers.HDMA5] = this.read_HDMA5.bind(this); 26 | this.cpu.registers.write[registers.HDMA5] = this.write_HDMA5.bind(this); 27 | }; 28 | 29 | read_HDMA1 () { 30 | return (this.sourceAddress >> 8); 31 | }; 32 | 33 | write_HDMA1 (data) { 34 | this.sourceAddress = (this.sourceAddress & 0x00FF) | (data << 8); 35 | }; 36 | 37 | read_HDMA2 () { 38 | return this.sourceAddress & 0xFF; 39 | }; 40 | 41 | write_HDMA2 (data) { 42 | this.sourceAddress = (this.sourceAddress & 0xFF00) | (data); 43 | }; 44 | 45 | read_HDMA3 () { 46 | return (this.destinationAddress >> 8); 47 | }; 48 | 49 | write_HDMA3 (data) { 50 | this.destinationAddress = (this.destinationAddress & 0x00FF) | (data << 8); 51 | }; 52 | 53 | read_HDMA4 () { 54 | return this.destinationAddress & 0xFF; 55 | }; 56 | 57 | write_HDMA4 (data) { 58 | this.destinationAddress = (this.destinationAddress & 0xFF00) | (data); 59 | }; 60 | 61 | read_HDMA5 () { 62 | return (this.active ? 0 : 0x80) | (this.blocksLeft); 63 | }; 64 | 65 | write_HDMA5 (data) { 66 | this.blocksLeft = data & 0x7F; 67 | this.active = true; 68 | 69 | var masked = this.sourceAddress & 0xE000; 70 | if( masked == 0x8000 || masked == 0xE000 ) 71 | console.log('Source address is outside valid memory'); 72 | 73 | // --- 74 | if( !(data & 0x80) ) 75 | { 76 | while( this.active ) 77 | this.moveBlock(); 78 | } 79 | else 80 | { 81 | console.log('Warning, HBLANK HDMA activated (unsupported)'); 82 | } 83 | }; 84 | 85 | moveBlock () { 86 | // DMA is no longer active 87 | if( !this.active ) 88 | return ; 89 | 90 | if( this.blocksLeft-- === 0 ) 91 | { 92 | this.blocksLeft = 0xFF; 93 | this.active = false; 94 | } 95 | 96 | this.cpu.catchUp(); 97 | 98 | let src_h = this.sourceAddress >> 8; 99 | let src_l = this.sourceAddress & 0xF0; 100 | let dst = this.destinationAddress & 0x1FF0; 101 | let size = 0x10; 102 | 103 | while (size--) { 104 | this.gpu.vbk_cell[dst++] = this.cpu.read[src_h][src_l++](); 105 | } 106 | 107 | this.sourceAddress = (this.sourceAddress + 0x0010) & 0xFFFF; 108 | this.destinationAddress = (this.destinationAddress + 0x0010) & 0xFFFF; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/core/misc/joypad.js: -------------------------------------------------------------------------------- 1 | import * as registers from "../registers"; 2 | import * as consts from "../consts"; 3 | import * as Keyboard from "../../util/keyboard"; 4 | 5 | const DEFAULT_MAPPING = { 6 | A: Keyboard.X, 7 | B: Keyboard.Z, 8 | Select: Keyboard.SHIFT, 9 | Start: Keyboard.ENTER, 10 | Up: Keyboard.UP_ARROW, 11 | Down: Keyboard.DOWN_ARROW, 12 | Left: Keyboard.LEFT_ARROW, 13 | Right: Keyboard.RIGHT_ARROW 14 | }; 15 | 16 | export default class Joypad { 17 | constructor(cpu) { 18 | // --- Internal data storage 19 | this.selectDir = 0; 20 | this.selectButton = 0; 21 | 22 | this.dataDir = 0xF; 23 | this.dataButton = 0xF; 24 | 25 | this.cpu = cpu; 26 | this.keyboard = []; 27 | 28 | this.mapping = Object.create(DEFAULT_MAPPING); 29 | 30 | window.addEventListener( 'keydown', this.keydown.bind(this), false); 31 | window.addEventListener( 'keyup', this.keyup.bind(this), false); 32 | } 33 | 34 | reset () { 35 | this.selectDir = 0; 36 | this.selectButton = 0; 37 | this.dataDir = 0xF; 38 | this.dataButton = 0xF; 39 | 40 | this.cpu.registers.read[registers.JOYP] = this.read_JOYP.bind(this); 41 | this.cpu.registers.write[registers.JOYP] = this.write_JOYP.bind(this); 42 | } 43 | 44 | disableActions (keyEventArgs) 45 | { 46 | var prevent = false; 47 | 48 | Object.keys(this.mapping).forEach(function (key) { 49 | if (this.mapping[key] === keyEventArgs.keyCode) { 50 | prevent = true; 51 | } 52 | }, this); 53 | 54 | if (prevent) { 55 | keyEventArgs.preventDefault(); 56 | return false; 57 | } 58 | } 59 | 60 | keydown (keyEventArgs) 61 | { 62 | this.keyboard[keyEventArgs.keyCode] = true; 63 | this.update(); 64 | 65 | return this.disableActions(keyEventArgs); 66 | } 67 | 68 | keyup (keyEventArgs) 69 | { 70 | this.keyboard[keyEventArgs.keyCode] = false; 71 | this.update(); 72 | 73 | return this.disableActions(keyEventArgs); 74 | } 75 | 76 | update () 77 | { 78 | var oD = this.dataDir, oB = this.dataButton; 79 | 80 | this.dataDir = 0xF; 81 | this.dataButton = 0xF; 82 | 83 | if( this.keyboard[this.mapping.A] ) 84 | this.dataButton &= ~1; 85 | if( this.keyboard[this.mapping.B] ) 86 | this.dataButton &= ~2; 87 | if( this.keyboard[this.mapping.Select]) 88 | this.dataButton &= ~4; 89 | if( this.keyboard[this.mapping.Start] ) 90 | this.dataButton &= ~8; 91 | 92 | // --- NOTE: Exclusively encoded 93 | if( this.keyboard[this.mapping.Right] ) 94 | this.dataDir &= ~1; 95 | else if( this.keyboard[this.mapping.Left] ) 96 | this.dataDir &= ~2; 97 | if( this.keyboard[this.mapping.Up] ) 98 | this.dataDir &= ~4; 99 | else if( this.keyboard[this.mapping.Down] ) 100 | this.dataDir &= ~8; 101 | 102 | if( (oD ^ this.dataDir) || (oB ^ this.dataButton) ) 103 | this.cpu.trigger(consts.IRQ_JOYSTICK); 104 | } 105 | 106 | read_JOYP () { 107 | var data = 0xF; 108 | 109 | if( !this.selectDir ) 110 | data &= this.dataDir; 111 | if( !this.selectButton ) 112 | data &= this.dataButton; 113 | 114 | return data | 115 | (this.selectDir ? 0x10 : 0) | 116 | (this.selectButton ? 0x20 : 0); 117 | } 118 | 119 | write_JOYP (data) { 120 | this.selectDir = data & 0x10; 121 | this.selectButton = data & 0x20; 122 | } 123 | } 124 | 125 | -------------------------------------------------------------------------------- /src/core/mappers/mapper.js: -------------------------------------------------------------------------------- 1 | import * as memory from "../../util/memory"; 2 | import * as flags from "./flags"; 3 | 4 | import mapperROM from "./rom"; 5 | import mapperMBC1 from "./mbc1"; 6 | import mapperMBC2 from "./mbc2"; 7 | import mapperMBC3 from "./mbc3"; 8 | import mapperMBC5 from "./mbc5"; 9 | 10 | function byteAlignment(size, base) 11 | { 12 | if( base === undefined ) 13 | base = 1; 14 | 15 | while( base < size ) 16 | base <<= 1; 17 | 18 | return base; 19 | } 20 | 21 | export default function mapper( name, cpu, rom ) 22 | { 23 | rom = new Uint8Array(rom); 24 | 25 | var description = { 26 | mapper: rom[0x0147], 27 | romSize: rom.length, //rom[0x0148], 28 | ramSize: [0,2048,8192,32768][rom[0x0149]] 29 | }; 30 | 31 | var setups = { 32 | '1': { call: mapperMBC1, flags: flags.NONE }, 33 | '2': { call: mapperMBC1, flags: flags.RAM }, 34 | '3': { call: mapperMBC1, flags: flags.RAM|flags.BATTERY }, 35 | '5': { call: mapperMBC2, flags: flags.NONE }, 36 | '6': { call: mapperMBC2, flags: flags.BATTERY }, 37 | '8': { call: mapperROM, flags: flags.RAM }, 38 | '9': { call: mapperROM, flags: flags.RAM|flags.BATTERY }, 39 | /* 40 | '11': { call: mapperMMM01, flags: flags.NONE }, 41 | '12': { call: mapperMMM01, flags: flags.RAM }, 42 | '13': { call: mapperMMM01, flags: flags.RAM|flags.BATTERY }, 43 | '15': { call: mapperMBC3, flags: flags.TIMER|flags.BATTERY }, 44 | '16': { call: mapperMBC3, flags: flags.TIMER|flags.RAM|flags.BATTERY }, 45 | */ 46 | '17': { call: mapperMBC3, flags: flags.NONE }, 47 | '18': { call: mapperMBC3, flags: flags.RAM }, 48 | '19': { call: mapperMBC3, flags: flags.RAM|flags.BATTERY }, 49 | /* 50 | '21': { call: mapperMBC4, flags: flags.NONE }, 51 | '22': { call: mapperMBC4, flags: flags.RAM }, 52 | '23': { call: mapperMBC4, flags: flags.RAM|flags.BATTERY }, 53 | */ 54 | '25': { call: mapperMBC5, flags: flags.NONE }, 55 | '26': { call: mapperMBC5, flags: flags.RAM }, 56 | '27': { call: mapperMBC5, flags: flags.RAM|flags.BATTERY }, 57 | /* 58 | '28': { call: mapperMBC5, flags: flags.RUMBLE }, 59 | '29': { call: mapperMBC5, flags: flags.RUMBLE|flags.RAM }, 60 | '30': { call: mapperMBC5, flags: flags.RUMBLE|flags.RAM|flags.BATTERY }, 61 | '252': { call: mapperCAMERA, flags: flags.NONE }, 62 | '253': { call: mapperTAMA5, flags: flags.NONE }, 63 | '254': { call: mapperHuC3, flags: flags.NONE }, 64 | '255': { call: mapperHuC1, flags: flags.RAM|flags.BATTERY }, 65 | */ 66 | '0': { call: mapperROM, flags: flags.NONE } 67 | }; 68 | 69 | // Convert rom into data delegates (padded out to 32k, or next highest power of 2) 70 | rom = memory.romBlock(rom, byteAlignment(rom.length, 0x4000) ); 71 | 72 | var code = description.mapper; 73 | var setup = setups[code]; 74 | 75 | if( setups[code] === undefined ) 76 | { 77 | console.log( "Mapper type", code, "is unsupported." ); 78 | return null; 79 | } 80 | 81 | return new (setup.call)(name, cpu, rom, description.ramSize, setups[code].flags, description ); 82 | } 83 | -------------------------------------------------------------------------------- /src/core/video/palette.js: -------------------------------------------------------------------------------- 1 | import * as registers from "../registers"; 2 | 3 | export default class Palette { 4 | constructor (cpu) { 5 | this.cpu = cpu; 6 | 7 | // --- Create a blank palette 8 | var palMemory = new ArrayBuffer(0x100); 9 | this.paletteMemory = new Uint16Array(palMemory); 10 | this.tilePalette = new Uint8Array(palMemory, 0, 0x40); 11 | this.spritePalette = new Uint8Array(palMemory, 0x40, 0x40); 12 | 13 | // DMG Palette registers 14 | this.reg_BGP = 0; 15 | this.reg_OBP0 = 0; 16 | this.reg_OBP1 = 0; 17 | 18 | // CGB Palette registers (upper half always white) 19 | this.reg_BCPS = 0; 20 | this.reg_BCPS_increment = false; 21 | this.reg_OCPS = 0; 22 | this.reg_OCPS_increment = false; 23 | } 24 | 25 | reset (){ 26 | // DMG Palette registers 27 | this.cpu.registers.read[registers.BGP] = this.read_BGP.bind(this); 28 | this.cpu.registers.write[registers.BGP] = this.write_BGP.bind(this); 29 | this.cpu.registers.read[registers.OBP0] = this.read_OBP0.bind(this); 30 | this.cpu.registers.write[registers.OBP0] = this.write_OBP0.bind(this); 31 | this.cpu.registers.read[registers.OBP1] = this.read_OBP1.bind(this); 32 | this.cpu.registers.write[registers.OBP1] = this.write_OBP1.bind(this); 33 | 34 | // CGB Palette registers 35 | this.cpu.registers.read[registers.BCPS] = this.read_BCPS.bind(this); 36 | this.cpu.registers.write[registers.BCPS] = this.write_BCPS.bind(this); 37 | this.cpu.registers.read[registers.BCPD] = this.read_BCPD.bind(this); 38 | this.cpu.registers.write[registers.BCPD] = this.write_BCPD.bind(this); 39 | this.cpu.registers.read[registers.OCPS] = this.read_OCPS.bind(this); 40 | this.cpu.registers.write[registers.OCPS] = this.write_OCPS.bind(this); 41 | this.cpu.registers.read[registers.OCPD] = this.read_OCPD.bind(this); 42 | this.cpu.registers.write[registers.OCPD] = this.write_OCPD.bind(this); 43 | } 44 | 45 | // --- DMG Palette paletteMemory registers 46 | read_BGP () { 47 | return this.reg_BGP; 48 | }; 49 | 50 | write_BGP (data) { 51 | this.cpu.catchUp(); 52 | this.reg_BGP = data; 53 | } 54 | 55 | read_OBP0 () { 56 | return this.reg_OBP0; 57 | } 58 | 59 | write_OBP0 (data) { 60 | this.cpu.catchUp(); 61 | this.reg_OBP0 = data; 62 | } 63 | 64 | read_OBP1 () { 65 | return this.reg_OBP1; 66 | } 67 | 68 | write_OBP1 (data) { 69 | this.cpu.catchUp(); 70 | this.reg_OBP1 = data; 71 | } 72 | 73 | // --- CGB Palette paletteMemory registers 74 | read_BCPS () { 75 | return this.reg_BCPS | (this.reg_BCPS_increment ? 0x80 : 0); 76 | } 77 | 78 | write_BCPS (data) { 79 | this.reg_BCPS_increment = (data & 0x80); 80 | this.reg_BCPS = data & 0x3F; 81 | } 82 | 83 | read_OCPS () { 84 | return this.reg_OCPS | (this.reg_OCPS_increment ? 0x80 : 0); 85 | } 86 | 87 | write_OCPS (data) { 88 | this.reg_OCPS_increment = (data & 0x80); 89 | this.reg_OCPS = data & 0x3F; 90 | } 91 | 92 | read_BCPD () { 93 | return this.tilePalette[this.reg_BCPS]; 94 | } 95 | 96 | write_BCPD (data) { 97 | this.cpu.catchUp(); 98 | 99 | this.tilePalette[this.reg_BCPS] = data; 100 | 101 | if (this.reg_BCPS_increment) { 102 | this.reg_BCPS = (this.reg_BCPS+1) & 0x3F; 103 | } 104 | } 105 | 106 | read_OCPD () { 107 | return this.spritePalette[this.reg_OCPS]; 108 | } 109 | 110 | write_OCPD (data) { 111 | this.cpu.catchUp(); 112 | 113 | this.spritePalette[this.reg_OCPS] = data; 114 | 115 | if (this.reg_OCPS_increment) { 116 | this.reg_OCPS = (this.reg_OCPS+1) & 0x3F; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/core/audio/noise.js: -------------------------------------------------------------------------------- 1 | export default class NoiseChannel { 2 | constructor (cpu) { 3 | this.cpu = cpu; 4 | } 5 | 6 | reset() { 7 | this.length = 0; 8 | this.enabled = false; 9 | 10 | this.envelopeRegister = 0; 11 | this.envelopeCounter = 0; 12 | this.envelopeTick = 0; 13 | this.envelopeDirection = -1; 14 | this.envelopePeriod = 0; 15 | this.initalVolume = 0; 16 | 17 | this.overflow = 128; 18 | this.polyform = 0; 19 | } 20 | 21 | clock(ticks) { 22 | if (!this.enabled) { return ; } 23 | 24 | var shifts, shifted, bit; 25 | 26 | // Determine our current sample 27 | this.frequencyCounter += ticks; 28 | shifts = this.frequencyCounter / this.overflow; 29 | this.frequencyCounter %= this.overflow; 30 | 31 | if (shifts) { 32 | if (this.polyform) { 33 | while (shifts-- > 0) { 34 | shifted = this.lsfr >>> 1; 35 | bit = (this.lsfr ^ shifted) & 1; 36 | this.lsfr = (shifted & 0x3FBF) | (bit << 6); 37 | } 38 | } else { 39 | while (shifts-- > 0) { 40 | shifted = this.lsfr >>> 1; 41 | bit = (this.lsfr ^ shifted) & 1; 42 | this.lsfr = shifted | (bit << 14); 43 | } 44 | } 45 | } 46 | 47 | // Length counter 48 | if (this.lengthEnable) { 49 | this.lengthCounter += ticks; 50 | if (this.lengthCounter >= 32768) { 51 | this.length = (this.length + 1) & 0x3F; 52 | this.enabled = this.length !== 0; 53 | this.lengthCounter &= 32767; 54 | } 55 | } 56 | 57 | // Envelope system 58 | if (this.envelopePeriod) { 59 | this.envelopeCounter += ticks; 60 | if (this.envelopeCounter >= 131072) { 61 | if (++this.envelopeTick == this.envelopePeriod) { 62 | this.volume += this.envelopeDirection; 63 | if (this.volume < 0) { 64 | this.volume = 0; 65 | this.envelopePeriod = 0; 66 | } else if (this.volume > 1) { 67 | this.volume = 1; 68 | this.envelopePeriod = 0; 69 | } 70 | 71 | this.envelopeTick = 0; 72 | } 73 | this.envelopeCounter &= 131071; 74 | } 75 | } 76 | } 77 | 78 | level() { 79 | if (!this.enabled) { return 0; } 80 | 81 | return (~this.lsfr & 1) * this.volume; 82 | } 83 | 84 | active() { 85 | return this.enabled; 86 | } 87 | 88 | // --- Registers 89 | write_length(d) { 90 | this.length = d & 0x3F; 91 | } 92 | 93 | write_volume(d) { 94 | this.volumeRegister = d; 95 | 96 | this.initalVolume = (d >> 4) / 15.0; 97 | this.envelopeDirection = (d & 8) ? (1.0/15) : (-1.0/15); 98 | this.envelopePeriod = d & 7; 99 | } 100 | 101 | write_poly(d) { 102 | this.cpu.catchUp(); 103 | 104 | this.polyRegister = d; 105 | 106 | var r = d >> 4, 107 | s = d & 7; 108 | 109 | this.polyform = d & 8; 110 | this.overflow = (r+1)*(16<> 7; 39 | let l = i & 0xFF; 40 | 41 | for (let b = 0; b < 8; b++) { 42 | tileDecodeForward[i][7-b] = 43 | tileDecodeReverse[i][b] = ((h >> b) & 2) | ((l >> b) & 1); 44 | } 45 | } 46 | 47 | export default class LCD { 48 | constructor (palette) { 49 | // --- Setup display 50 | this.scanline = new Uint8Array(172); 51 | this.paletteMemory = palette.paletteMemory; 52 | } 53 | 54 | setContext(ctx) { 55 | this.context = ctx; 56 | 57 | this.buffer = this.context.getImageData(0,0,160,144); 58 | this.bufferData = new Uint32Array(this.buffer.data.buffer); 59 | }; 60 | 61 | update () { 62 | this.context.putImageData(this.buffer, 0, 0); 63 | }; 64 | 65 | clear () { 66 | for (var i = 0; i < 172; i++) { 67 | this.scanline[i] = WHITEOUT; 68 | } 69 | }; 70 | 71 | copyTileBG (x, l, h, pal, flip, pri) { 72 | var px = (flip ? tileDecodeForward : tileDecodeReverse)[l|(h<<8)], 73 | over = (pal << PALETTE_SHIFT) | (pri ? PRIORITY : 0), 74 | scanline = this.scanline, 75 | b = 8; 76 | 77 | while (b) { 78 | scanline[x++] = px[--b] | over; 79 | } 80 | 81 | return x; 82 | }; 83 | 84 | copyTileOBJ (x, l, h, pal, flip, pri) { 85 | var px = (flip ? tileDecodeForward : tileDecodeReverse)[l|(h<<8)], 86 | over = (pal << PALETTE_SHIFT) | SPRITE_FLAG, 87 | scanline = this.scanline; 88 | 89 | // Draw OAM when tile has priority 90 | for (var b = 8; b; x++) 91 | { 92 | var npx = px[--b], opx = scanline[x]; 93 | 94 | // Sprite pixel is invisible ... 95 | // ... or higher priority sprite already exists 96 | if(!(npx & PIXELS) || (opx & SPRITE_FLAG)) { 97 | continue; 98 | } 99 | 100 | // Background tile does not have priority 101 | if(!((pri || (opx & PRIORITY)) && (opx & PIXELS))) { 102 | scanline[x] = npx | over; 103 | } 104 | } 105 | }; 106 | 107 | copyScanline (y) { 108 | if (!this.bufferData) { return ; } 109 | 110 | var palette = this.paletteMemory, 111 | scanline = this.scanline, 112 | data = this.bufferData, 113 | o = y * 160; 114 | 115 | for (var b = 8; b < 168; b++) { 116 | data[o++] = colorTable[palette[scanline[b] & COLOR]]; 117 | } 118 | }; 119 | 120 | copyScanlineLegacy (y, bp, op0, op1) { 121 | if (!this.bufferData) { return ; } 122 | 123 | var palette = this.paletteMemory, 124 | scanline = this.scanline, 125 | data = this.bufferData, 126 | o = y * 160; 127 | 128 | // Similar to copy scanline, but this uses the old-style palette registers 129 | for (var b = 8; b < 168; b++) { 130 | var px = scanline[b]; 131 | var c = ((px & PIXELS) << 1); 132 | 133 | if( px & SPRITE_FLAG ) { 134 | if (px & PALETTE) { 135 | c = ((op1 >> c) & 3) | SPRITE_FLAG; 136 | } else { 137 | c = ((op0 >> c) & 3) | SPRITE_FLAG; 138 | } 139 | } else { 140 | c = (bp >> c) & 3; 141 | } 142 | 143 | data[o++] = colorTable[palette[c]]; 144 | } 145 | } 146 | } -------------------------------------------------------------------------------- /src/ui/style/index.scss: -------------------------------------------------------------------------------- 1 | * { 2 | color: inherit; 3 | text-decoration: inherit; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | body { 9 | font-family: "Lato", "Helvetica Neue", Helvetica, sans-serif; 10 | font-size: 16px; 11 | line-height: 20px; 12 | } 13 | 14 | .dragging { 15 | background: #EEE; 16 | } 17 | 18 | /* ------ Title ------ */ 19 | 20 | .title-bar { 21 | display: block; 22 | 23 | background: -moz-linear-gradient(top, rgba(142,198,255,1) 0%, rgba(96,171,248,1) 26%, rgba(64,150,238,1) 100%); /* FF3.6+ */ 24 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(142,198,255,1)), color-stop(26%,rgba(96,171,248,1)), color-stop(100%,rgba(64,150,238,1))); /* Chrome,Safari4+ */ 25 | background: -webkit-linear-gradient(top, rgba(142,198,255,1) 0%,rgba(96,171,248,1) 26%,rgba(64,150,238,1) 100%); /* Chrome10+,Safari5.1+ */ 26 | background: -o-linear-gradient(top, rgba(142,198,255,1) 0%,rgba(96,171,248,1) 26%,rgba(64,150,238,1) 100%); /* Opera 11.10+ */ 27 | background: -ms-linear-gradient(top, rgba(142,198,255,1) 0%,rgba(96,171,248,1) 26%,rgba(64,150,238,1) 100%); /* IE10+ */ 28 | background: linear-gradient(to bottom, rgba(142,198,255,1) 0%,rgba(96,171,248,1) 26%,rgba(64,150,238,1) 100%); /* W3C */ 29 | 30 | h1 { 31 | width: 940px; 32 | margin: 0 auto; 33 | display: block; 34 | font-size: 24px; 35 | line-height: 32px; 36 | color: white; 37 | } 38 | } 39 | 40 | .emulator { 41 | display: block; 42 | width: 940px; 43 | margin: 0 auto; 44 | padding: 10px 0; 45 | } 46 | 47 | /* ------ Gameboy display objects ------- */ 48 | .display { 49 | $gameboy-width: 320px; 50 | $gameboy-height: 537px; 51 | $screen-width: 160px; 52 | $screen-height: 144px; 53 | $screen-y: 57px; 54 | 55 | position: absolute; 56 | padding: ($screen-y) floor(($gameboy-width - $screen-width) / 2) ($gameboy-height - $screen-height - $screen-y); 57 | 58 | display: inline-block; 59 | background: url('media/gameboy.png'); 60 | 61 | width: $screen-width; 62 | height: $screen-height; 63 | 64 | &.double { 65 | width: $screen-width * 2; 66 | height: $screen-height * 2; 67 | 68 | padding: 0 floor($gameboy-width / 2 - $screen-width) ($gameboy-height - $screen-height * 2); 69 | } 70 | } 71 | 72 | .button-group { 73 | padding: 0 0 10px; 74 | margin: 0 0 0 340px; 75 | 76 | font-size: 0; 77 | 78 | li { 79 | display: inline-block; 80 | border-radius: 5px; 81 | background: #49E; 82 | border-bottom: 2px solid darken(#49E, 20%); 83 | color: #fff; 84 | padding: 0 10px; 85 | margin: 0 5px 0 0; 86 | line-height: 20px; 87 | font-size: 12px; 88 | cursor: pointer; 89 | cursor: hand; 90 | vertical-align: middle; 91 | } 92 | } 93 | 94 | .debugger { 95 | $disasm-lines: 25; 96 | 97 | display: block; 98 | 99 | border: 1px solid #ddd; 100 | box-shadow: 0 1px 3px 0 rgba(#000,0.15); 101 | padding: 0; 102 | margin: 0 0 0 340px; 103 | border-radius: 5px; 104 | overflow: hidden; 105 | 106 | font-family: Courier, monospace; 107 | 108 | .registers { 109 | display: inline-block; 110 | vertical-align: top; 111 | 112 | padding: 0; 113 | 114 | min-height: $disasm-lines * 20px; 115 | background: #eee; 116 | 117 | .header { 118 | font-family: "Lato", "Helvetica Neue", Helvetica, sans-serif; 119 | font-size: 14px; 120 | display: block; 121 | vertical-align: middle; 122 | line-height: 24px; 123 | text-align: center; 124 | background: #D553D5; 125 | color: #fff; 126 | } 127 | 128 | .column { 129 | display: inline-block; 130 | padding: 0 4px; 131 | text-align: right; 132 | } 133 | 134 | .name { 135 | display: inline-block; 136 | width: 2em; 137 | } 138 | 139 | .value, .flag { 140 | display: inline-block; 141 | background: #DDD; 142 | } 143 | 144 | .value { 145 | width: 3em; 146 | } 147 | 148 | .flag { 149 | width: 1em; 150 | color: rgb(24,137,47); 151 | text-align: center; 152 | } 153 | } 154 | 155 | .disassembly { 156 | display: inline-block; 157 | vertical-align: top; 158 | } 159 | 160 | .row { 161 | width: 100%; 162 | } 163 | 164 | .addr { 165 | box-sizing: border-box; 166 | padding: 0 6px; 167 | display: inline-block; 168 | width: 7ex; 169 | text-align: right; 170 | cursor: pointer; 171 | cursor: hand; 172 | } 173 | 174 | .hex { 175 | display: inline-block; 176 | width: 12ex; 177 | background: #DDD; 178 | } 179 | 180 | .instruction { 181 | padding-left: 1ex; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/core/audio/square.js: -------------------------------------------------------------------------------- 1 | export default class SquareChannel { 2 | constructor(cpu) { 3 | this.cpu = cpu; 4 | } 5 | 6 | reset() { 7 | this.enabled = false; 8 | 9 | // Envelope system 10 | this.envelopeRegister = 0; 11 | this.envelopeCounter = 0; 12 | this.envelopeTick = 0; 13 | this.envelopeDirection = -1; 14 | this.envelopePeriod = 0; 15 | 16 | // Sweep system 17 | this.sweepRegister = 0; 18 | this.sweepCounter = 0; 19 | this.sweepTick = 0; 20 | this.sweepPeriod = 0; 21 | 22 | this.lengthEnable = 0; 23 | this.lengthCounter = 0; 24 | this.frequency = 0; 25 | 26 | this.sample = 0; 27 | this.frequencyCounter = 0; 28 | 29 | this.initalVolume = 0; 30 | this.duty = 0; 31 | this.length = 0; 32 | this.waveform = 0x01; 33 | } 34 | 35 | clock(ticks) { 36 | if (!this.enabled) { return ; } 37 | 38 | this.frequencyCounter += ticks; 39 | 40 | // Length counter 41 | if (this.lengthEnable) { 42 | this.lengthCounter += ticks; 43 | if (this.lengthCounter >= 32768) { 44 | this.length = (this.length + 1) & 0x3F; 45 | this.enabled = this.length !== 0; 46 | this.lengthCounter &= 32767; 47 | } 48 | } 49 | 50 | // Envelope system 51 | if (this.envelopePeriod) { 52 | this.envelopeCounter += ticks; 53 | if (this.envelopeCounter >= 131072) { 54 | if (++this.envelopeTick == this.envelopePeriod) { 55 | this.volume += this.envelopeDirection; 56 | if (this.volume < 0) { 57 | this.volume = 0; 58 | this.envelopePeriod = 0; 59 | } else if (this.volume > 1) { 60 | this.volume = 1; 61 | this.envelopePeriod = 0; 62 | } 63 | 64 | this.envelopeTick = 0; 65 | } 66 | this.envelopeCounter &= 131071; 67 | } 68 | } 69 | 70 | // Sweep system 71 | if (this.sweepPeriod) { 72 | this.sweepCounter += ticks; 73 | if (this.sweepCounter >= 65535) { 74 | if (++this.sweepTick == this.sweepPeriod) { 75 | this.activeFrequency += (this.activeFrequency >> this.sweepShift) * this.sweepDirection; 76 | 77 | if (this.activeFrequency >= 2048) { this.enabled = false; } 78 | 79 | this.overflow = (2048 - this.activeFrequency) * 8; 80 | this.sweepTick = 0; 81 | } 82 | 83 | this.sweepCounter &= 65535; 84 | } 85 | } 86 | } 87 | 88 | level() { 89 | if (!this.enabled) { return 0; } 90 | 91 | // Determine our current sample 92 | this.sample = (this.sample + (this.frequencyCounter / this.overflow)) & 7; 93 | this.frequencyCounter %= this.overflow; 94 | 95 | return ((this.waveform >> this.sample) & 1) * this.volume; 96 | } 97 | 98 | active() { 99 | return this.enabled; 100 | } 101 | 102 | // --- Registers 103 | write_sweep(d) { 104 | this.sweepRegister = d; 105 | 106 | this.sweepPeriod = (d >> 4) & 7; 107 | this.sweepDirection = (d & 8) ? -1 : 1; 108 | this.sweepShift = d & 7; 109 | } 110 | 111 | write_length(d) { 112 | this.duty = d & 0xC0; 113 | this.length = d & 0x3F; 114 | 115 | switch(this.duty) { 116 | case 0x00: 117 | this.waveform = 0x01; 118 | break ; 119 | case 0x40: 120 | this.waveform = 0x81; 121 | break ; 122 | case 0x80: 123 | this.waveform = 0x87; 124 | break ; 125 | case 0xC0: 126 | this.waveform = 0x7E; 127 | break ; 128 | } 129 | } 130 | 131 | write_volume(d) { 132 | this.envelopeRegister = d; 133 | 134 | this.initalVolume = (d >> 4) / 15.0; 135 | this.envelopeDirection = (d & 8) ? (1.0/15) : (-1.0/15); 136 | this.envelopePeriod = d & 7; 137 | } 138 | 139 | write_freq_lo(d) { 140 | this.frequency = (this.frequency & 0xFF00) | (d); 141 | } 142 | 143 | write_freq_hi(d) { 144 | this.frequency = (this.frequency & 0x00FF) | ((d & 0x07) << 8); 145 | this.lengthEnable = d & 0x40; 146 | 147 | // Sound frequency 148 | if (d & 0x80) { 149 | this.cpu.catchUp(); 150 | 151 | this.enabled = true; 152 | this.activeFrequency = this.frequency; 153 | this.overflow = (2048 - this.activeFrequency) * 8; 154 | 155 | this.frequencyCounter = 0; 156 | this.lengthCounter = 0; 157 | 158 | this.envelopeCounter = 0; 159 | this.envelopeTick = 0; 160 | 161 | this.sweepCounter = 0; 162 | this.sweepTick = 0; 163 | 164 | this.volume = this.initalVolume; 165 | } 166 | } 167 | 168 | read_sweep() { 169 | return 0x80 | this.sweepRegister; 170 | } 171 | 172 | read_length() { 173 | return 0x3F | this.duty; 174 | } 175 | 176 | read_volume() { 177 | return this.envelopeRegister; 178 | } 179 | 180 | read_freq_hi() { 181 | return 0xBF | this.lengthEnable; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/ui/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | import Register from "./register"; 4 | import Flag from "./flag"; 5 | import Disassembler from "./disassemble"; 6 | import Screen from "./screen"; 7 | 8 | import style from './style'; 9 | 10 | export default class MainView extends Component { 11 | // --- Life-cycle operations --- 12 | componentDidMount () { 13 | window.onclose = this.close; 14 | } 15 | 16 | componentWillUnmount () { 17 | window.onclose = null; 18 | } 19 | 20 | close () { 21 | this.props.runtime.close(); 22 | } 23 | 24 | // --- UI operations --- 25 | toggle () { 26 | this.props.runtime.running = !this.props.runtime.running; 27 | this.forceUpdate(); 28 | } 29 | 30 | reset () { 31 | this.props.runtime.reset(); 32 | this.forceUpdate(); 33 | } 34 | 35 | step () { 36 | this.props.runtime.singleStep(); 37 | this.forceUpdate(); 38 | } 39 | 40 | stop_predictions () { 41 | this.props.runtime.cpu.predictEvent = function () { return 0; }; 42 | } 43 | 44 | runTo (addr) { 45 | var that = this; 46 | return function () { 47 | for(var i = 0; i < 125000 && that.props.runtime.cpu.pc !== addr; i++) { 48 | that.props.runtime.singleStep(); 49 | } 50 | that.forceUpdate(); 51 | } 52 | } 53 | 54 | // --- Rendering --- 55 | render () { 56 | return ( 57 |
58 |
59 |

JSBoy

60 |
61 | 62 |
63 | 64 | 65 |
    66 |
  • this.toggle()}>{ this.props.runtime.running ? 'Stop' : 'Run' }
  • 67 |
  • this.reset()}>Reset
  • 68 |
  • this.step()}>Step
  • 69 |
  • this.stop_predictions()}>Disable predictions
  • 70 |
71 | 72 |
73 |
74 |
75 |
CPU Flags
76 |
77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
86 |
87 | 88 | 89 | 90 | 91 | 92 | 93 |
94 |
GPU Flags
95 |
96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
104 |
105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
114 |
115 | 116 | 117 | 118 | 119 |
120 |
121 | 122 | 123 |
124 |
125 |
126 |
127 | ); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/core/audio/audio.js: -------------------------------------------------------------------------------- 1 | import * as registers from "../registers"; 2 | 3 | import SquareChannel from "./square"; 4 | import WaveformChannel from "./waveform"; 5 | import NoiseChannel from "./noise"; 6 | 7 | var BUFFER_LENGTH = 2048, // ~91ms buffer 8 | LONG_BUFFER = BUFFER_LENGTH * 2, // Render to a much larger buffer 9 | CLOCK_RATE = 8388608; 10 | 11 | export default class Sound { 12 | constructor(cpu) { 13 | this.cpu = cpu; 14 | this.square1 = new SquareChannel(cpu); 15 | this.square2 = new SquareChannel(cpu); 16 | this.waveform = new WaveformChannel(cpu); 17 | this.noise = new NoiseChannel(cpu); 18 | 19 | this.context = new AudioContext(); 20 | 21 | if (this.context) { 22 | this.node = this.context.createScriptProcessor(BUFFER_LENGTH, 2, 2); 23 | this.node.onaudioprocess = this.process.bind(this); 24 | this.sampleRate = this.context.sampleRate; 25 | } else { 26 | this.sampleRate = 0; 27 | } 28 | 29 | this.leftBuffer = new Float32Array(LONG_BUFFER); 30 | this.rightBuffer = new Float32Array(LONG_BUFFER); 31 | 32 | // Playback buffering 33 | this.activeSample = 0; // Next sample written 34 | this.sampleTime = 0; // Bresenham sample counter 35 | } 36 | 37 | clock(ticks) { 38 | var s; 39 | 40 | this.sampleTime += ticks * this.sampleRate; 41 | 42 | if (!this.masterEnable) { 43 | while (this.sampleTime >= CLOCK_RATE) { 44 | s = this.activeSample; 45 | 46 | this.sampleTime -= CLOCK_RATE; 47 | this.rightBuffer[s] = 0; 48 | this.leftBuffer[s] = 0; 49 | 50 | if (++this.activeSample >= LONG_BUFFER) { 51 | this.activeSample = 0; 52 | } 53 | } 54 | return ; 55 | } 56 | 57 | this.square1.clock(ticks); 58 | this.square2.clock(ticks); 59 | this.waveform.clock(ticks); 60 | this.noise.clock(ticks); 61 | 62 | while (this.sampleTime >= CLOCK_RATE) { 63 | var ch0 = this.square1.level(), 64 | ch1 = this.square2.level(), 65 | ch2 = this.waveform.level(), 66 | ch3 = this.noise.level(); 67 | 68 | s = this.activeSample; 69 | this.sampleTime -= CLOCK_RATE; 70 | 71 | this.rightBuffer[s] = ( 72 | ch0*this.ch0right + 73 | ch1*this.ch1right + 74 | ch2*this.ch2right + 75 | ch3*this.ch3right) * this.rightVolume * 0.25; 76 | 77 | this.leftBuffer[s] = ( 78 | ch0*this.ch0left + 79 | ch1*this.ch1left + 80 | ch2*this.ch2left + 81 | ch3*this.ch3left) * this.leftVolume * 0.25; 82 | 83 | if (++this.activeSample >= LONG_BUFFER) { 84 | this.activeSample = 0; 85 | } 86 | } 87 | } 88 | 89 | reset() { 90 | var self = this; 91 | 92 | this.square1.reset(); 93 | this.square2.reset(); 94 | this.waveform.reset(); 95 | this.noise.reset(); 96 | 97 | this.NR50 = 0; 98 | this.NR51 = 0; 99 | 100 | this.leftVolume = 0; 101 | this.rightVolume = 0; 102 | this.masterEnable = 0; 103 | 104 | this.ch0right = 0; 105 | this.ch1right = 0; 106 | this.ch2right = 0; 107 | this.ch3right = 0; 108 | this.ch0left = 0; 109 | this.ch1left = 0; 110 | this.ch2left = 0; 111 | this.ch3left = 0; 112 | 113 | this.cpu.registers.read.copy(registers.AUD3WAVERAM0, this.waveform.wavetable.read); 114 | this.cpu.registers.write.copy(registers.AUD3WAVERAM0, this.waveform.wavetable.write); 115 | 116 | // --- Square register channels 117 | this.cpu.registers.write[registers.NR10] = this.square1.write_sweep.bind(this.square1); 118 | this.cpu.registers.write[registers.NR11] = this.square1.write_length.bind(this.square1); 119 | this.cpu.registers.write[registers.NR12] = this.square1.write_volume.bind(this.square1); 120 | this.cpu.registers.write[registers.NR13] = this.square1.write_freq_lo.bind(this.square1); 121 | this.cpu.registers.write[registers.NR14] = this.square1.write_freq_hi.bind(this.square1); 122 | 123 | this.cpu.registers.read[registers.NR10] = this.square1.read_sweep.bind(this.square1); 124 | this.cpu.registers.read[registers.NR11] = this.square1.read_length.bind(this.square1); 125 | this.cpu.registers.read[registers.NR12] = this.square1.read_volume.bind(this.square1); 126 | this.cpu.registers.read[registers.NR14] = this.square1.read_freq_hi.bind(this.square1); 127 | 128 | this.cpu.registers.write[registers.NR21] = this.square2.write_length.bind(this.square2); 129 | this.cpu.registers.write[registers.NR22] = this.square2.write_volume.bind(this.square2); 130 | this.cpu.registers.write[registers.NR23] = this.square2.write_freq_lo.bind(this.square2); 131 | this.cpu.registers.write[registers.NR24] = this.square2.write_freq_hi.bind(this.square2); 132 | 133 | this.cpu.registers.read[registers.NR21] = this.square2.read_length.bind(this.square2); 134 | this.cpu.registers.read[registers.NR22] = this.square2.read_volume.bind(this.square2); 135 | this.cpu.registers.read[registers.NR24] = this.square2.read_freq_hi.bind(this.square2); 136 | 137 | // --- Waveform Channel 138 | this.cpu.registers.write[registers.NR30] = this.waveform.write_enable.bind(this.waveform); 139 | this.cpu.registers.write[registers.NR31] = this.waveform.write_length.bind(this.waveform); 140 | this.cpu.registers.write[registers.NR32] = this.waveform.write_level.bind(this.waveform); 141 | this.cpu.registers.write[registers.NR33] = this.waveform.write_freq_lo.bind(this.waveform); 142 | this.cpu.registers.write[registers.NR34] = this.waveform.write_freq_hi.bind(this.waveform); 143 | 144 | this.cpu.registers.read[registers.NR30] = this.waveform.read_enable.bind(this.waveform); 145 | this.cpu.registers.read[registers.NR32] = this.waveform.read_level.bind(this.waveform); 146 | this.cpu.registers.read[registers.NR34] = this.waveform.read_freq_hi.bind(this.waveform); 147 | 148 | // --- Noise Channel 149 | this.cpu.registers.write[registers.NR41] = this.noise.write_length.bind(this.noise); 150 | this.cpu.registers.write[registers.NR42] = this.noise.write_volume.bind(this.noise); 151 | this.cpu.registers.write[registers.NR43] = this.noise.write_poly.bind(this.noise); 152 | this.cpu.registers.write[registers.NR44] = this.noise.write_control.bind(this.noise); 153 | 154 | this.cpu.registers.read[registers.NR42] = this.noise.read_volume.bind(this.noise); 155 | this.cpu.registers.read[registers.NR43] = this.noise.read_poly.bind(this.noise); 156 | this.cpu.registers.read[registers.NR44] = this.noise.read_control.bind(this.noise); 157 | 158 | // --- Control registers 159 | this.cpu.registers.write[registers.NR50] = this.write_NR50.bind(this); 160 | this.cpu.registers.write[registers.NR51] = this.write_NR51.bind(this); 161 | this.cpu.registers.write[registers.NR52] = this.write_NR52.bind(this); 162 | 163 | this.cpu.registers.read[registers.NR50] = this.read_NR50.bind(this); 164 | this.cpu.registers.read[registers.NR51] = this.read_NR51.bind(this); 165 | this.cpu.registers.read[registers.NR52] = this.read_NR52.bind(this); 166 | } 167 | 168 | mute() { 169 | if (!this.node) { return ; } 170 | 171 | this.node.disconnect(); 172 | } 173 | 174 | play() { 175 | if (!this.node) { return ; } 176 | 177 | this.node.connect(this.context.destination); 178 | } 179 | 180 | process(e) { 181 | var left = e.outputBuffer.getChannelData(0), 182 | right = e.outputBuffer.getChannelData(1), 183 | length = left.length, 184 | s = (this.activeSample & BUFFER_LENGTH) ^ BUFFER_LENGTH, 185 | i = 0; 186 | 187 | for(; i < length; i++, s++) { 188 | left[i] = this.leftBuffer[s]; 189 | right[i] = this.rightBuffer[s]; 190 | } 191 | } 192 | 193 | // --- Control registers 194 | write_NR50(d) { 195 | this.NR50 = d; 196 | 197 | // Nothing uses VIN, ignored for now 198 | this.leftVolume = ((d & 0x70) >> 4) / 7.0; 199 | this.rightVolume = (d & 0x07) / 7.0; 200 | } 201 | 202 | write_NR51(d) { 203 | this.NR51 = d; 204 | 205 | this.ch0right = (d >> 0) & 1; 206 | this.ch1right = (d >> 1) & 1; 207 | this.ch2right = (d >> 2) & 1; 208 | this.ch3right = (d >> 3) & 1; 209 | this.ch0left = (d >> 4) & 1; 210 | this.ch1left = (d >> 5) & 1; 211 | this.ch2left = (d >> 6) & 1; 212 | this.ch3left = (d >> 7) & 1; 213 | } 214 | 215 | write_NR52(d) { 216 | this.masterEnable = d & 0x80; 217 | } 218 | 219 | read_NR50() { 220 | return this.NR50; 221 | } 222 | 223 | read_NR51() { 224 | return this.NR51; 225 | } 226 | 227 | read_NR52() { 228 | if (!this.masterEnable) { return 0; } 229 | 230 | this.cpu.catchUp(); 231 | return this.masterEnable | 232 | (this.square1.active() ? 1 : 0) | 233 | (this.square2.active() ? 2 : 0) | 234 | (this.waveform.active() ? 4 : 0) | 235 | (this.noise.active() ? 8 : 0); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/core/core.js: -------------------------------------------------------------------------------- 1 | import Timer from "./misc/timer"; 2 | import WorkRam from "./misc/workram"; 3 | import JoyPad from "./misc/joypad"; 4 | import BIOS from "./misc/bios"; 5 | import GPU from "./video/gpu"; 6 | import Audio from "./audio/audio"; 7 | 8 | import OPs from "./ops"; 9 | 10 | import * as registers from "./registers"; 11 | 12 | 13 | export default class Core extends OPs { 14 | constructor () { 15 | super(); 16 | 17 | // External hardware 18 | this.audio = new Audio(this); 19 | this.gpu = new GPU(this); 20 | this.joypad = new JoyPad(this); 21 | this.wram = new WorkRam(this); 22 | this.timer = new Timer(this); 23 | this.bios = new BIOS(this); 24 | this.rom = null; 25 | 26 | this.reset(); 27 | } 28 | 29 | get bc () { 30 | return this.regBC(); 31 | } 32 | 33 | get de () { 34 | return this.regDE(); 35 | } 36 | 37 | get hl () { 38 | return this.regHL(); 39 | } 40 | 41 | get f () { 42 | return this.getF(); 43 | } 44 | 45 | set f (v) { 46 | this.setF(v); 47 | } 48 | 49 | setContext (ctx) { 50 | this.gpu.setContext(ctx); 51 | } 52 | 53 | close () { 54 | if (this.rom) { 55 | this.rom.close(); 56 | this.rom = null; 57 | } 58 | } 59 | 60 | insert (cartridge) { 61 | this.close(); 62 | 63 | if (cartridge !== undefined) { 64 | this.rom = cartridge; 65 | } 66 | 67 | this.reset(); 68 | } 69 | 70 | reset () { 71 | // IF, IE registers 72 | this.irq_enable = 0; 73 | this.irq_request = 0; 74 | this.irq_master = false; 75 | 76 | // KEY1 register 77 | this.setCPUSpeed(false); 78 | this.invalidate(); 79 | 80 | // CPU registers 81 | this.a = 0; 82 | this.b = 0; 83 | this.c = 0; 84 | this.d = 0; 85 | this.e = 0; 86 | this.h = 0; 87 | this.l = 0; 88 | 89 | this.pc = 0; 90 | this.sp = 0; 91 | 92 | this.cf = false; 93 | this.hf = false; 94 | this.zf = false; 95 | this.nf = false; 96 | 97 | this.halted = false; 98 | 99 | // CPU timing / runtime variables 100 | this.cycles = 0; 101 | 102 | function nullBody() { return 0xFF; } 103 | 104 | // Reset memory map 105 | this.read = (new Array(0x10000)).fill(nullBody).chunk(0x100); 106 | this.write = (new Array(0x10000)).fill(nullBody).chunk(0x100); 107 | this.nullBlock = (new Array(0x100)).fill(nullBody); 108 | 109 | this.registers = { 110 | read: this.read[0xFF], 111 | write: this.write[0xFF] 112 | }; 113 | 114 | // For debugging purposes, alert me when the system accesses a register it does not recognize 115 | for (var i = 0xFF00; i < 0xFF80; i++) { 116 | this.alertIllegal(i); 117 | } 118 | 119 | // Map external hardware 120 | if (this.rom) { 121 | this.rom.reset(); 122 | } 123 | 124 | this.audio.reset(); 125 | this.gpu.reset(); 126 | this.joypad.reset(); 127 | this.wram.reset(); 128 | this.timer.reset(); 129 | this.bios.reset(); 130 | 131 | // Map IRQ specific registers into CPU 132 | this.registers.read[registers.IE] = this.read_IE.bind(this); 133 | this.registers.write[registers.IE] = this.write_IE.bind(this); 134 | this.registers.read[registers.IF] = this.read_IF.bind(this); 135 | this.registers.write[registers.IF] = this.write_IF.bind(this); 136 | 137 | // Hardware lockout register 138 | this.registers.write[registers.LOCK] = this.write_LOCK.bind(this); 139 | 140 | // Map speed control register into CPU 141 | this.registers.read[registers.KEY1] = this.read_KEY1.bind(this); 142 | this.registers.write[registers.KEY1] = this.write_KEY1.bind(this); 143 | } 144 | 145 | setCPUSpeed (fast) { 146 | this.doubleSpeed = fast; 147 | this.prepareSpeed = false; 148 | } 149 | 150 | alertIllegal (addr) { 151 | var addrName = addr.toString(16).toUpperCase(), 152 | hi = addr>>8, 153 | lo = addr & 0xFF; 154 | 155 | this.read[hi][lo] = function () { 156 | console.log("ILLEGAL READ: ", addrName); 157 | return 0xFF; 158 | }; 159 | this.write[hi][lo] = function (data) { 160 | console.log("ILLEGAL WRITE: ", addrName, data.toString(16).toUpperCase()); 161 | }; 162 | } 163 | 164 | // --- This occurs when the an event causes the IRQ prediction to be invalid 165 | invalidate () { 166 | this.predictDivided = 0; 167 | } 168 | 169 | predictEvent () { 170 | // Never clock more than the rest of the frame! 171 | var b = this.gpu.predict(); 172 | var c = this.timer.predict(); 173 | //TODO: SERIAL? 174 | 175 | return (b < c) ? b : c; 176 | } 177 | 178 | // --- Send CPU accumulation clock to the external components 179 | catchUp () { 180 | this.audio.clock(this.cycles); 181 | this.timer.clock(this.cycles); 182 | this.gpu.clock(this.cycles); 183 | 184 | // Increment DIV based on the CPU ticks, not the system clock 185 | var cpuTicks = this.doubleSpeed ? this.cycles : (this.cycles >> 1); 186 | this.timer.tick(cpuTicks); 187 | 188 | // Flush the cycle buffer 189 | this.invalidate(); 190 | this.cycles = 0; 191 | } 192 | 193 | // --- Interrupt logic 194 | interrupt () { 195 | // Absolutely no IRQs are pending, so we simply give up 196 | if (!this.irq_request) { 197 | return ; 198 | } 199 | 200 | this.halted = false; 201 | 202 | // --- Master IRQ enabled? 203 | if (!this.irq_master) { 204 | return ; 205 | } 206 | 207 | // --- Any servicable interrupts available 208 | var masked = this.irq_enable & this.irq_request; 209 | if (!masked) { 210 | return ; 211 | } 212 | 213 | // Locate vector 214 | var vector = 0x40; 215 | var select = 0x01; 216 | 217 | while(!(masked & select) && 0x20 > select) { 218 | select <<= 1; 219 | vector += 8; 220 | } 221 | 222 | // Service the IRQ 223 | this.call(vector); 224 | this.irq_request &= ~select; 225 | this.irq_master = false; 226 | this.cycles += this.doubleSpeed ? 4 : 8; 227 | } 228 | 229 | // --- Trigger IRQ (auto invalidate prediction) 230 | trigger (irq) { 231 | // There is no prediction, the game did, in fact, have an event 232 | this.irq_request |= irq; 233 | this.invalidate(); 234 | } 235 | 236 | // --- Step the CPU to the next event point 237 | step (frameCycles) { 238 | while( frameCycles > 0 ) 239 | { 240 | var clockRate = this.doubleSpeed ? 4 : 8; 241 | var predict = this.predictEvent() || clockRate; 242 | 243 | if( predict > frameCycles ) 244 | predict = frameCycles; 245 | 246 | // CPU is stopped, or halted, simply clock the machine up to 247 | // the predicted value 248 | if( this.halted ) 249 | { 250 | this.cycles += predict; 251 | } 252 | else 253 | { 254 | this.predictDivided = predict / clockRate; 255 | var cycles = 0; 256 | 257 | // CPU is running, run up until the IRQ prediction mark 258 | do { 259 | cycles += this.stepBase(); 260 | } while( this.predictDivided > cycles ); 261 | 262 | this.cycles += cycles * clockRate; 263 | } 264 | 265 | frameCycles -= this.cycles; 266 | 267 | this.catchUp(); 268 | this.interrupt(); 269 | } 270 | } 271 | 272 | singleStep () { 273 | var clockSpeed = (this.doubleSpeed ? 4 : 8); 274 | 275 | if( this.halted ) 276 | this.cycles += clockSpeed; 277 | else 278 | this.cycles += this.stepBase() * clockSpeed; 279 | 280 | this.catchUp(); 281 | this.interrupt(); 282 | } 283 | 284 | // --- Start emulation helpers 285 | regBC () { 286 | return (this.b<<8) | this.c; 287 | } 288 | 289 | regDE () { 290 | return (this.d<<8) | this.e; 291 | } 292 | 293 | regHL () { 294 | return (this.h<<8) | this.l; 295 | } 296 | 297 | setF (data) { 298 | this.cf = data & 0x10; 299 | this.hf = data & 0x20; 300 | this.nf = data & 0x40; 301 | this.zf = data & 0x80; 302 | } 303 | 304 | getF () { 305 | return (this.zf ? 0x80 : 0) | 306 | (this.nf ? 0x40 : 0) | 307 | (this.hf ? 0x20 : 0) | 308 | (this.cf ? 0x10 : 0); 309 | } 310 | 311 | push (data) { 312 | this.sp = (this.sp - 1) & 0xFFFF; 313 | var h = this.sp >> 8, 314 | l = this.sp & 0xFF; 315 | 316 | this.write[h][l](data); 317 | } 318 | 319 | pop () { 320 | var h = this.sp >> 8, 321 | l = this.sp & 0xFF, 322 | data = this.read[h][l](); 323 | this.sp = (this.sp + 1) & 0xFFFF; 324 | return data; 325 | } 326 | 327 | call (addr) { 328 | this.push(this.pc>>8); 329 | this.push(this.pc&0xFF); 330 | this.pc = addr; 331 | } 332 | 333 | ret () { 334 | this.pc = this.pop(); 335 | this.pc |= this.pop() << 8; 336 | } 337 | 338 | nextByte () { 339 | var h = this.pc >> 8, 340 | l = this.pc & 0xFF; 341 | var op = this.read[h][l](); 342 | this.pc = (this.pc + 1) & 0xFFFF; 343 | return op; 344 | } 345 | 346 | nextSignedByte () { 347 | // Sign extend byte 348 | var b = this.nextByte(); 349 | return (b & 0x7F) - (b & 0x80); 350 | } 351 | 352 | nextRelative () { 353 | var b = this.nextSignedByte(); 354 | return (this.pc + (b & 0x7F) - (b & 0x80)) & 0xFFFF; 355 | } 356 | 357 | nextWord () { 358 | var l = this.nextByte(); 359 | var h = this.nextByte(); 360 | return (h<<8) | l; 361 | } 362 | 363 | delayByte () { 364 | // This is a cute way of preventing the CPU from incrementing PC for a 365 | // clock happens on halts and stops with IME disabled 366 | this.nextByte = function() { 367 | var h = this.pc >> 8, 368 | l = this.pc & 0xFF; 369 | delete this.nextByte; 370 | return this.read[h][l](); 371 | }; 372 | } 373 | 374 | // --- CPU Level hardware registers (IEQ, Speed and CPU dependant timer) 375 | read_IE () 376 | { 377 | return this.irq_enable; 378 | } 379 | 380 | write_IE ( data ) 381 | { 382 | this.catchUp(); 383 | this.irq_enable = data & 0x1F; 384 | } 385 | 386 | read_IF () 387 | { 388 | this.catchUp(); 389 | return this.irq_request; 390 | } 391 | 392 | write_IF ( data ) 393 | { 394 | this.catchUp(); 395 | this.irq_request = data & 0x1F; 396 | } 397 | 398 | read_KEY1 () 399 | { 400 | return (this.doubleSpeed ? 0x80 : 0) | 401 | (this.prepareSpeed ? 0x01 : 0); 402 | } 403 | 404 | write_KEY1 (data) 405 | { 406 | this.prepareSpeed = data & 1; 407 | } 408 | 409 | write_LOCK (data) 410 | { 411 | if( data != 1 ) 412 | return ; 413 | 414 | // This perminantly locks down all GBC specfic hardware, preventing it from 415 | // being inadvertantly accessed by mono gameboy titles 416 | // BIOS appears to have priviledged access to the hardware, so until 417 | // it locks, don't disable the advanced registers 418 | 419 | var self = this; 420 | var lock = this.registers.write[registers.BLCK]; 421 | 422 | this.registers.write[registers.BLCK] = function(data) 423 | { 424 | if( data != 0x11 ) 425 | return ; 426 | 427 | // --- IR Communication 428 | self.alertIllegal(registers.RP); 429 | 430 | // --- Video DMA 431 | self.alertIllegal(registers.HDMA1); 432 | self.alertIllegal(registers.HDMA2); 433 | self.alertIllegal(registers.HDMA3); 434 | self.alertIllegal(registers.HDMA4); 435 | self.alertIllegal(registers.HDMA5); 436 | 437 | // --- Palette access 438 | self.alertIllegal(registers.BCPS); 439 | self.alertIllegal(registers.BCPD); 440 | self.alertIllegal(registers.OCPS); 441 | self.alertIllegal(registers.OCPD); 442 | 443 | // --- Memory banking 444 | self.alertIllegal(registers.VBK); 445 | self.alertIllegal(registers.SVBK); 446 | 447 | // --- Speed control 448 | self.alertIllegal(registers.KEY1); 449 | 450 | // --- Lockout controls 451 | self.alertIllegal(registers.LCD_MODE); 452 | self.alertIllegal(registers.LOCK); 453 | 454 | lock(0x11); 455 | }; 456 | } 457 | } 458 | -------------------------------------------------------------------------------- /src/core/misc/bios.js: -------------------------------------------------------------------------------- 1 | import * as registers from "../registers"; 2 | import * as memory from "../../util/memory"; 3 | 4 | // Special thanks to costis for this 5 | const COLOR_BIOS = memory.romBlock([ 6 | 0x31, 0xFE, 0xFF, 0x3E, 0x02, 0xC3, 0x7C, 0x00, 0xD3, 0x00, 0x98, 0xA0, 0x12, 0xD3, 0x00, 0x80, 7 | 0x00, 0x40, 0x1E, 0x53, 0xD0, 0x00, 0x1F, 0x42, 0x1C, 0x00, 0x14, 0x2A, 0x4D, 0x19, 0x8C, 0x7E, 8 | 0x00, 0x7C, 0x31, 0x6E, 0x4A, 0x45, 0x52, 0x4A, 0x00, 0x00, 0xFF, 0x53, 0x1F, 0x7C, 0xFF, 0x03, 9 | 0x1F, 0x00, 0xFF, 0x1F, 0xA7, 0x00, 0xEF, 0x1B, 0x1F, 0x00, 0xEF, 0x1B, 0x00, 0x7C, 0x00, 0x00, 10 | 0xFF, 0x03, 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 11 | 0x00, 0x0D, 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 12 | 0xD9, 0x99, 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 13 | 0x33, 0x3E, 0x3C, 0x42, 0xB9, 0xA5, 0xB9, 0xA5, 0x42, 0x3C, 0x58, 0x43, 0xE0, 0x70, 0x3E, 0xFC, 14 | 0xE0, 0x47, 0xCD, 0x75, 0x02, 0xCD, 0x00, 0x02, 0x26, 0xD0, 0xCD, 0x03, 0x02, 0x21, 0x00, 0xFE, 15 | 0x0E, 0xA0, 0xAF, 0x22, 0x0D, 0x20, 0xFC, 0x11, 0x04, 0x01, 0x21, 0x10, 0x80, 0x4C, 0x1A, 0xE2, 16 | 0x0C, 0xCD, 0xC6, 0x03, 0xCD, 0xC7, 0x03, 0x13, 0x7B, 0xFE, 0x34, 0x20, 0xF1, 0x11, 0x72, 0x00, 17 | 0x06, 0x08, 0x1A, 0x13, 0x22, 0x23, 0x05, 0x20, 0xF9, 0xCD, 0xF0, 0x03, 0x3E, 0x01, 0xE0, 0x4F, 18 | 0x3E, 0x91, 0xE0, 0x40, 0x21, 0xB2, 0x98, 0x06, 0x4E, 0x0E, 0x44, 0xCD, 0x91, 0x02, 0xAF, 0xE0, 19 | 0x4F, 0x0E, 0x80, 0x21, 0x42, 0x00, 0x06, 0x18, 0xF2, 0x0C, 0xBE, 0x20, 0xFE, 0x23, 0x05, 0x20, 20 | 0xF7, 0x21, 0x34, 0x01, 0x06, 0x19, 0x78, 0x86, 0x2C, 0x05, 0x20, 0xFB, 0x86, 0x20, 0xFE, 0xCD, 21 | 0x1C, 0x03, 0x18, 0x02, 0x00, 0x00, 0xCD, 0xD0, 0x05, 0xAF, 0xE0, 0x70, 0x3E, 0x11, 0xE0, 0x50, 22 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 23 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 24 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 25 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 26 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 27 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 28 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 29 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 30 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 31 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 32 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 33 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 34 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 35 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 36 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 37 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 38 | 0x21, 0x00, 0x80, 0xAF, 0x22, 0xCB, 0x6C, 0x28, 0xFB, 0xC9, 0x2A, 0x12, 0x13, 0x0D, 0x20, 0xFA, 39 | 0xC9, 0xE5, 0x21, 0x0F, 0xFF, 0xCB, 0x86, 0xCB, 0x46, 0x28, 0xFC, 0xE1, 0xC9, 0x11, 0x00, 0xFF, 40 | 0x21, 0x03, 0xD0, 0x0E, 0x0F, 0x3E, 0x30, 0x12, 0x3E, 0x20, 0x12, 0x1A, 0x2F, 0xA1, 0xCB, 0x37, 41 | 0x47, 0x3E, 0x10, 0x12, 0x1A, 0x2F, 0xA1, 0xB0, 0x4F, 0x7E, 0xA9, 0xE6, 0xF0, 0x47, 0x2A, 0xA9, 42 | 0xA1, 0xB0, 0x32, 0x47, 0x79, 0x77, 0x3E, 0x30, 0x12, 0xC9, 0x3E, 0x80, 0xE0, 0x68, 0xE0, 0x6A, 43 | 0x0E, 0x6B, 0x2A, 0xE2, 0x05, 0x20, 0xFB, 0x4A, 0x09, 0x43, 0x0E, 0x69, 0x2A, 0xE2, 0x05, 0x20, 44 | 0xFB, 0xC9, 0xC5, 0xD5, 0xE5, 0x21, 0x00, 0xD8, 0x06, 0x01, 0x16, 0x3F, 0x1E, 0x40, 0xCD, 0x4A, 45 | 0x02, 0xE1, 0xD1, 0xC1, 0xC9, 0x3E, 0x80, 0xE0, 0x26, 0xE0, 0x11, 0x3E, 0xF3, 0xE0, 0x12, 0xE0, 46 | 0x25, 0x3E, 0x77, 0xE0, 0x24, 0x21, 0x30, 0xFF, 0xAF, 0x0E, 0x10, 0x22, 0x2F, 0x0D, 0x20, 0xFB, 47 | 0xC9, 0xCD, 0x11, 0x02, 0xCD, 0x62, 0x02, 0x79, 0xFE, 0x38, 0x20, 0x14, 0xE5, 0xAF, 0xE0, 0x4F, 48 | 0x21, 0xA7, 0x99, 0x3E, 0x38, 0x22, 0x3C, 0xFE, 0x3F, 0x20, 0xFA, 0x3E, 0x01, 0xE0, 0x4F, 0xE1, 49 | 0xC5, 0xE5, 0x21, 0x43, 0x01, 0xCB, 0x7E, 0xCC, 0x89, 0x05, 0xE1, 0xC1, 0xCD, 0x11, 0x02, 0x79, 50 | 0xD6, 0x30, 0xD2, 0x06, 0x03, 0x79, 0xFE, 0x01, 0xCA, 0x06, 0x03, 0x7D, 0xFE, 0xD1, 0x28, 0x21, 51 | 0xC5, 0x06, 0x03, 0x0E, 0x01, 0x16, 0x03, 0x7E, 0xE6, 0xF8, 0xB1, 0x22, 0x15, 0x20, 0xF8, 0x0C, 52 | 0x79, 0xFE, 0x06, 0x20, 0xF0, 0x11, 0x11, 0x00, 0x19, 0x05, 0x20, 0xE7, 0x11, 0xA1, 0xFF, 0x19, 53 | 0xC1, 0x04, 0x78, 0x1E, 0x83, 0xFE, 0x62, 0x28, 0x06, 0x1E, 0xC1, 0xFE, 0x64, 0x20, 0x07, 0x7B, 54 | 0xE0, 0x13, 0x3E, 0x87, 0xE0, 0x14, 0xFA, 0x02, 0xD0, 0xFE, 0x00, 0x28, 0x0A, 0x3D, 0xEA, 0x02, 55 | 0xD0, 0x79, 0xFE, 0x01, 0xCA, 0x91, 0x02, 0x0D, 0xC2, 0x91, 0x02, 0xC9, 0x0E, 0x26, 0xCD, 0x4A, 56 | 0x03, 0xCD, 0x11, 0x02, 0xCD, 0x62, 0x02, 0x0D, 0x20, 0xF4, 0xCD, 0x11, 0x02, 0x3E, 0x01, 0xE0, 57 | 0x4F, 0xCD, 0x3E, 0x03, 0xCD, 0x41, 0x03, 0xAF, 0xE0, 0x4F, 0xCD, 0x3E, 0x03, 0xC9, 0x21, 0x08, 58 | 0x00, 0x11, 0x51, 0xFF, 0x0E, 0x05, 0xCD, 0x0A, 0x02, 0xC9, 0xC5, 0xD5, 0xE5, 0x21, 0x40, 0xD8, 59 | 0x0E, 0x20, 0x7E, 0xE6, 0x1F, 0xFE, 0x1F, 0x28, 0x01, 0x3C, 0x57, 0x2A, 0x07, 0x07, 0x07, 0xE6, 60 | 0x07, 0x47, 0x3A, 0x07, 0x07, 0x07, 0xE6, 0x18, 0xB0, 0xFE, 0x1F, 0x28, 0x01, 0x3C, 0x0F, 0x0F, 61 | 0x0F, 0x47, 0xE6, 0xE0, 0xB2, 0x22, 0x78, 0xE6, 0x03, 0x5F, 0x7E, 0x0F, 0x0F, 0xE6, 0x1F, 0xFE, 62 | 0x1F, 0x28, 0x01, 0x3C, 0x07, 0x07, 0xB3, 0x22, 0x0D, 0x20, 0xC7, 0xE1, 0xD1, 0xC1, 0xC9, 0x0E, 63 | 0x00, 0x1A, 0xE6, 0xF0, 0xCB, 0x49, 0x28, 0x02, 0xCB, 0x37, 0x47, 0x23, 0x7E, 0xB0, 0x22, 0x1A, 64 | 0xE6, 0x0F, 0xCB, 0x49, 0x20, 0x02, 0xCB, 0x37, 0x47, 0x23, 0x7E, 0xB0, 0x22, 0x13, 0xCB, 0x41, 65 | 0x28, 0x0D, 0xD5, 0x11, 0xF8, 0xFF, 0xCB, 0x49, 0x28, 0x03, 0x11, 0x08, 0x00, 0x19, 0xD1, 0x0C, 66 | 0x79, 0xFE, 0x18, 0x20, 0xCC, 0xC9, 0x47, 0xD5, 0x16, 0x04, 0x58, 0xCB, 0x10, 0x17, 0xCB, 0x13, 67 | 0x17, 0x15, 0x20, 0xF6, 0xD1, 0x22, 0x23, 0x22, 0x23, 0xC9, 0x3E, 0x19, 0xEA, 0x10, 0x99, 0x21, 68 | 0x2F, 0x99, 0x0E, 0x0C, 0x3D, 0x28, 0x08, 0x32, 0x0D, 0x20, 0xF9, 0x2E, 0x0F, 0x18, 0xF3, 0xC9, 69 | 0x3E, 0x01, 0xE0, 0x4F, 0xCD, 0x00, 0x02, 0x11, 0x07, 0x06, 0x21, 0x80, 0x80, 0x0E, 0xC0, 0x1A, 70 | 0x22, 0x23, 0x22, 0x23, 0x13, 0x0D, 0x20, 0xF7, 0x11, 0x04, 0x01, 0xCD, 0x8F, 0x03, 0x01, 0xA8, 71 | 0xFF, 0x09, 0xCD, 0x8F, 0x03, 0x01, 0xF8, 0xFF, 0x09, 0x11, 0x72, 0x00, 0x0E, 0x08, 0x23, 0x1A, 72 | 0x22, 0x13, 0x0D, 0x20, 0xF9, 0x21, 0xC2, 0x98, 0x06, 0x08, 0x3E, 0x08, 0x0E, 0x10, 0x22, 0x0D, 73 | 0x20, 0xFC, 0x11, 0x10, 0x00, 0x19, 0x05, 0x20, 0xF3, 0xAF, 0xE0, 0x4F, 0x21, 0xC2, 0x98, 0x3E, 74 | 0x08, 0x22, 0x3C, 0xFE, 0x18, 0x20, 0x02, 0x2E, 0xE2, 0xFE, 0x28, 0x20, 0x03, 0x21, 0x02, 0x99, 75 | 0xFE, 0x38, 0x20, 0xED, 0x21, 0xD8, 0x08, 0x11, 0x40, 0xD8, 0x06, 0x08, 0x3E, 0xFF, 0x12, 0x13, 76 | 0x12, 0x13, 0x0E, 0x02, 0xCD, 0x0A, 0x02, 0x3E, 0x00, 0x12, 0x13, 0x12, 0x13, 0x13, 0x13, 0x05, 77 | 0x20, 0xEA, 0xCD, 0x62, 0x02, 0x21, 0x4B, 0x01, 0x7E, 0xFE, 0x33, 0x20, 0x0B, 0x2E, 0x44, 0x1E, 78 | 0x30, 0x2A, 0xBB, 0x20, 0x49, 0x1C, 0x18, 0x04, 0x2E, 0x4B, 0x1E, 0x01, 0x2A, 0xBB, 0x20, 0x3E, 79 | 0x2E, 0x34, 0x01, 0x10, 0x00, 0x2A, 0x80, 0x47, 0x0D, 0x20, 0xFA, 0xEA, 0x00, 0xD0, 0x21, 0xC7, 80 | 0x06, 0x0E, 0x00, 0x2A, 0xB8, 0x28, 0x08, 0x0C, 0x79, 0xFE, 0x4F, 0x20, 0xF6, 0x18, 0x1F, 0x79, 81 | 0xD6, 0x41, 0x38, 0x1C, 0x21, 0x16, 0x07, 0x16, 0x00, 0x5F, 0x19, 0xFA, 0x37, 0x01, 0x57, 0x7E, 82 | 0xBA, 0x28, 0x0D, 0x11, 0x0E, 0x00, 0x19, 0x79, 0x83, 0x4F, 0xD6, 0x5E, 0x38, 0xED, 0x0E, 0x00, 83 | 0x21, 0x33, 0x07, 0x06, 0x00, 0x09, 0x7E, 0xE6, 0x1F, 0xEA, 0x08, 0xD0, 0x7E, 0xE6, 0xE0, 0x07, 84 | 0x07, 0x07, 0xEA, 0x0B, 0xD0, 0xCD, 0xE9, 0x04, 0xC9, 0x11, 0x91, 0x07, 0x21, 0x00, 0xD9, 0xFA, 85 | 0x0B, 0xD0, 0x47, 0x0E, 0x1E, 0xCB, 0x40, 0x20, 0x02, 0x13, 0x13, 0x1A, 0x22, 0x20, 0x02, 0x1B, 86 | 0x1B, 0xCB, 0x48, 0x20, 0x02, 0x13, 0x13, 0x1A, 0x22, 0x13, 0x13, 0x20, 0x02, 0x1B, 0x1B, 0xCB, 87 | 0x50, 0x28, 0x05, 0x1B, 0x2B, 0x1A, 0x22, 0x13, 0x1A, 0x22, 0x13, 0x0D, 0x20, 0xD7, 0x21, 0x00, 88 | 0xD9, 0x11, 0x00, 0xDA, 0xCD, 0x64, 0x05, 0xC9, 0x21, 0x12, 0x00, 0xFA, 0x05, 0xD0, 0x07, 0x07, 89 | 0x06, 0x00, 0x4F, 0x09, 0x11, 0x40, 0xD8, 0x06, 0x08, 0xE5, 0x0E, 0x02, 0xCD, 0x0A, 0x02, 0x13, 90 | 0x13, 0x13, 0x13, 0x13, 0x13, 0xE1, 0x05, 0x20, 0xF0, 0x11, 0x42, 0xD8, 0x0E, 0x02, 0xCD, 0x0A, 91 | 0x02, 0x11, 0x4A, 0xD8, 0x0E, 0x02, 0xCD, 0x0A, 0x02, 0x2B, 0x2B, 0x11, 0x44, 0xD8, 0x0E, 0x02, 92 | 0xCD, 0x0A, 0x02, 0xC9, 0x0E, 0x60, 0x2A, 0xE5, 0xC5, 0x21, 0xE8, 0x07, 0x06, 0x00, 0x4F, 0x09, 93 | 0x0E, 0x08, 0xCD, 0x0A, 0x02, 0xC1, 0xE1, 0x0D, 0x20, 0xEC, 0xC9, 0xFA, 0x08, 0xD0, 0x11, 0x18, 94 | 0x00, 0x3C, 0x3D, 0x28, 0x03, 0x19, 0x20, 0xFA, 0xC9, 0xCD, 0x1D, 0x02, 0x78, 0xE6, 0xFF, 0x28, 95 | 0x0F, 0x21, 0xE4, 0x08, 0x06, 0x00, 0x2A, 0xB9, 0x28, 0x08, 0x04, 0x78, 0xFE, 0x0C, 0x20, 0xF6, 96 | 0x18, 0x2D, 0x78, 0xEA, 0x05, 0xD0, 0x3E, 0x1E, 0xEA, 0x02, 0xD0, 0x11, 0x0B, 0x00, 0x19, 0x56, 97 | 0x7A, 0xE6, 0x1F, 0x5F, 0x21, 0x08, 0xD0, 0x3A, 0x22, 0x7B, 0x77, 0x7A, 0xE6, 0xE0, 0x07, 0x07, 98 | 0x07, 0x5F, 0x21, 0x0B, 0xD0, 0x3A, 0x22, 0x7B, 0x77, 0xCD, 0xE9, 0x04, 0xCD, 0x28, 0x05, 0xC9, 99 | 0xCD, 0x11, 0x02, 0xFA, 0x43, 0x01, 0xCB, 0x7F, 0x28, 0x04, 0xE0, 0x4C, 0x18, 0x28, 0x3E, 0x04, 100 | 0xE0, 0x4C, 0x3E, 0x01, 0xE0, 0x6C, 0x21, 0x00, 0xDA, 0xCD, 0x7B, 0x05, 0x06, 0x10, 0x16, 0x00, 101 | 0x1E, 0x08, 0xCD, 0x4A, 0x02, 0x21, 0x7A, 0x00, 0xFA, 0x00, 0xD0, 0x47, 0x0E, 0x02, 0x2A, 0xB8, 102 | 0xCC, 0xDA, 0x03, 0x0D, 0x20, 0xF8, 0xC9, 0x01, 0x0F, 0x3F, 0x7E, 0xFF, 0xFF, 0xC0, 0x00, 0xC0, 103 | 0xF0, 0xF1, 0x03, 0x7C, 0xFC, 0xFE, 0xFE, 0x03, 0x07, 0x07, 0x0F, 0xE0, 0xE0, 0xF0, 0xF0, 0x1E, 104 | 0x3E, 0x7E, 0xFE, 0x0F, 0x0F, 0x1F, 0x1F, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x01, 0x01, 0x03, 0xFF, 105 | 0xFF, 0xE1, 0xE0, 0xC0, 0xF0, 0xF9, 0xFB, 0x1F, 0x7F, 0xF8, 0xE0, 0xF3, 0xFD, 0x3E, 0x1E, 0xE0, 106 | 0xF0, 0xF9, 0x7F, 0x3E, 0x7C, 0xF8, 0xE0, 0xF8, 0xF0, 0xF0, 0xF8, 0x00, 0x00, 0x7F, 0x7F, 0x07, 107 | 0x0F, 0x9F, 0xBF, 0x9E, 0x1F, 0xFF, 0xFF, 0x0F, 0x1E, 0x3E, 0x3C, 0xF1, 0xFB, 0x7F, 0x7F, 0xFE, 108 | 0xDE, 0xDF, 0x9F, 0x1F, 0x3F, 0x3E, 0x3C, 0xF8, 0xF8, 0x00, 0x00, 0x03, 0x03, 0x07, 0x07, 0xFF, 109 | 0xFF, 0xC1, 0xC0, 0xF3, 0xE7, 0xF7, 0xF3, 0xC0, 0xC0, 0xC0, 0xC0, 0x1F, 0x1F, 0x1E, 0x3E, 0x3F, 110 | 0x1F, 0x3E, 0x3E, 0x80, 0x00, 0x00, 0x00, 0x7C, 0x1F, 0x07, 0x00, 0x0F, 0xFF, 0xFE, 0x00, 0x7C, 111 | 0xF8, 0xF0, 0x00, 0x1F, 0x0F, 0x0F, 0x00, 0x7C, 0xF8, 0xF8, 0x00, 0x3F, 0x3E, 0x1C, 0x00, 0x0F, 112 | 0x0F, 0x0F, 0x00, 0x7C, 0xFF, 0xFF, 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x07, 0x0F, 0x0F, 0x00, 0x81, 113 | 0xFF, 0xFF, 0x00, 0xF3, 0xE1, 0x80, 0x00, 0xE0, 0xFF, 0x7F, 0x00, 0xFC, 0xF0, 0xC0, 0x00, 0x3E, 114 | 0x7C, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x16, 0x36, 0xD1, 0xDB, 0xF2, 0x3C, 0x8C, 115 | 0x92, 0x3D, 0x5C, 0x58, 0xC9, 0x3E, 0x70, 0x1D, 0x59, 0x69, 0x19, 0x35, 0xA8, 0x14, 0xAA, 0x75, 116 | 0x95, 0x99, 0x34, 0x6F, 0x15, 0xFF, 0x97, 0x4B, 0x90, 0x17, 0x10, 0x39, 0xF7, 0xF6, 0xA2, 0x49, 117 | 0x4E, 0x43, 0x68, 0xE0, 0x8B, 0xF0, 0xCE, 0x0C, 0x29, 0xE8, 0xB7, 0x86, 0x9A, 0x52, 0x01, 0x9D, 118 | 0x71, 0x9C, 0xBD, 0x5D, 0x6D, 0x67, 0x3F, 0x6B, 0xB3, 0x46, 0x28, 0xA5, 0xC6, 0xD3, 0x27, 0x61, 119 | 0x18, 0x66, 0x6A, 0xBF, 0x0D, 0xF4, 0x42, 0x45, 0x46, 0x41, 0x41, 0x52, 0x42, 0x45, 0x4B, 0x45, 120 | 0x4B, 0x20, 0x52, 0x2D, 0x55, 0x52, 0x41, 0x52, 0x20, 0x49, 0x4E, 0x41, 0x49, 0x4C, 0x49, 0x43, 121 | 0x45, 0x20, 0x52, 0x7C, 0x08, 0x12, 0xA3, 0xA2, 0x07, 0x87, 0x4B, 0x20, 0x12, 0x65, 0xA8, 0x16, 122 | 0xA9, 0x86, 0xB1, 0x68, 0xA0, 0x87, 0x66, 0x12, 0xA1, 0x30, 0x3C, 0x12, 0x85, 0x12, 0x64, 0x1B, 123 | 0x07, 0x06, 0x6F, 0x6E, 0x6E, 0xAE, 0xAF, 0x6F, 0xB2, 0xAF, 0xB2, 0xA8, 0xAB, 0x6F, 0xAF, 0x86, 124 | 0xAE, 0xA2, 0xA2, 0x12, 0xAF, 0x13, 0x12, 0xA1, 0x6E, 0xAF, 0xAF, 0xAD, 0x06, 0x4C, 0x6E, 0xAF, 125 | 0xAF, 0x12, 0x7C, 0xAC, 0xA8, 0x6A, 0x6E, 0x13, 0xA0, 0x2D, 0xA8, 0x2B, 0xAC, 0x64, 0xAC, 0x6D, 126 | 0x87, 0xBC, 0x60, 0xB4, 0x13, 0x72, 0x7C, 0xB5, 0xAE, 0xAE, 0x7C, 0x7C, 0x65, 0xA2, 0x6C, 0x64, 127 | 0x85, 0x80, 0xB0, 0x40, 0x88, 0x20, 0x68, 0xDE, 0x00, 0x70, 0xDE, 0x20, 0x78, 0x20, 0x20, 0x38, 128 | 0x20, 0xB0, 0x90, 0x20, 0xB0, 0xA0, 0xE0, 0xB0, 0xC0, 0x98, 0xB6, 0x48, 0x80, 0xE0, 0x50, 0x1E, 129 | 0x1E, 0x58, 0x20, 0xB8, 0xE0, 0x88, 0xB0, 0x10, 0x20, 0x00, 0x10, 0x20, 0xE0, 0x18, 0xE0, 0x18, 130 | 0x00, 0x18, 0xE0, 0x20, 0xA8, 0xE0, 0x20, 0x18, 0xE0, 0x00, 0x20, 0x18, 0xD8, 0xC8, 0x18, 0xE0, 131 | 0x00, 0xE0, 0x40, 0x28, 0x28, 0x28, 0x18, 0xE0, 0x60, 0x20, 0x18, 0xE0, 0x00, 0x00, 0x08, 0xE0, 132 | 0x18, 0x30, 0xD0, 0xD0, 0xD0, 0x20, 0xE0, 0xE8, 0xFF, 0x7F, 0xBF, 0x32, 0xD0, 0x00, 0x00, 0x00, 133 | 0x9F, 0x63, 0x79, 0x42, 0xB0, 0x15, 0xCB, 0x04, 0xFF, 0x7F, 0x31, 0x6E, 0x4A, 0x45, 0x00, 0x00, 134 | 0xFF, 0x7F, 0xEF, 0x1B, 0x00, 0x02, 0x00, 0x00, 0xFF, 0x7F, 0x1F, 0x42, 0xF2, 0x1C, 0x00, 0x00, 135 | 0xFF, 0x7F, 0x94, 0x52, 0x4A, 0x29, 0x00, 0x00, 0xFF, 0x7F, 0xFF, 0x03, 0x2F, 0x01, 0x00, 0x00, 136 | 0xFF, 0x7F, 0xEF, 0x03, 0xD6, 0x01, 0x00, 0x00, 0xFF, 0x7F, 0xB5, 0x42, 0xC8, 0x3D, 0x00, 0x00, 137 | 0x74, 0x7E, 0xFF, 0x03, 0x80, 0x01, 0x00, 0x00, 0xFF, 0x67, 0xAC, 0x77, 0x13, 0x1A, 0x6B, 0x2D, 138 | 0xD6, 0x7E, 0xFF, 0x4B, 0x75, 0x21, 0x00, 0x00, 0xFF, 0x53, 0x5F, 0x4A, 0x52, 0x7E, 0x00, 0x00, 139 | 0xFF, 0x4F, 0xD2, 0x7E, 0x4C, 0x3A, 0xE0, 0x1C, 0xED, 0x03, 0xFF, 0x7F, 0x5F, 0x25, 0x00, 0x00, 140 | 0x6A, 0x03, 0x1F, 0x02, 0xFF, 0x03, 0xFF, 0x7F, 0xFF, 0x7F, 0xDF, 0x01, 0x12, 0x01, 0x00, 0x00, 141 | 0x1F, 0x23, 0x5F, 0x03, 0xF2, 0x00, 0x09, 0x00, 0xFF, 0x7F, 0xEA, 0x03, 0x1F, 0x01, 0x00, 0x00, 142 | 0x9F, 0x29, 0x1A, 0x00, 0x0C, 0x00, 0x00, 0x00, 0xFF, 0x7F, 0x7F, 0x02, 0x1F, 0x00, 0x00, 0x00, 143 | 0xFF, 0x7F, 0xE0, 0x03, 0x06, 0x02, 0x20, 0x01, 0xFF, 0x7F, 0xEB, 0x7E, 0x1F, 0x00, 0x00, 0x7C, 144 | 0xFF, 0x7F, 0xFF, 0x3F, 0x00, 0x7E, 0x1F, 0x00, 0xFF, 0x7F, 0xFF, 0x03, 0x1F, 0x00, 0x00, 0x00, 145 | 0xFF, 0x03, 0x1F, 0x00, 0x0C, 0x00, 0x00, 0x00, 0xFF, 0x7F, 0x3F, 0x03, 0x93, 0x01, 0x00, 0x00, 146 | 0x00, 0x00, 0x00, 0x42, 0x7F, 0x03, 0xFF, 0x7F, 0xFF, 0x7F, 0x8C, 0x7E, 0x00, 0x7C, 0x00, 0x00, 147 | 0xFF, 0x7F, 0xEF, 0x1B, 0x80, 0x61, 0x00, 0x00, 0xFF, 0x7F, 0x00, 0x7C, 0xE0, 0x03, 0x1F, 0x7C, 148 | 0x1F, 0x00, 0xFF, 0x03, 0x40, 0x41, 0x42, 0x20, 0x21, 0x22, 0x80, 0x81, 0x82, 0x10, 0x11, 0x12, 149 | 0x12, 0xB0, 0x79, 0xB8, 0xAD, 0x16, 0x17, 0x07, 0xBA, 0x05, 0x7C, 0x13, 0x00, 0x00, 0x00, 0x00 150 | ]); 151 | 152 | export default class BIOS { 153 | constructor (cpu) { 154 | this.cpu = cpu; 155 | } 156 | 157 | reset () { 158 | // Preserve non-overlay memory 159 | this.cart = this.cpu.read.slice(0, COLOR_BIOS.length); 160 | 161 | // This overlays bios on top of the cart (~2.5k), preserving old cart 162 | // This also assumes cart will never change bank 0 during startup 163 | this.cpu.read.copy( 0, COLOR_BIOS ); 164 | this.cpu.read[0x01] = this.cart[0x01]; 165 | 166 | this.cpu.registers.write[registers.BLCK] = this.write_BLCK.bind(this); 167 | } 168 | 169 | write_BLCK (data) { 170 | if (data != 0x11) { 171 | return ; 172 | } 173 | 174 | // De-overlay the bios 175 | this.cpu.read.copy( 0, this.cart ); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/core/video/gpu.js: -------------------------------------------------------------------------------- 1 | import LCD from "./lcd"; 2 | import DMA from "./dma"; 3 | import Palette from "./palette"; 4 | 5 | import * as memory from "../../util/memory"; 6 | import * as registers from "../registers"; 7 | import * as consts from "../consts"; 8 | 9 | // These clocks are in GBC machine instruction cycles (Double speed) 10 | // IE: 4MHZ / 4 * 2 11 | const SCANLINES = 154; 12 | const DRAWLINES = 144; 13 | const MODE_0_TIME = 408; 14 | const MODE_2_TIME = 160; 15 | const MODE_3_TIME = 344; 16 | const TICKS_PER_LINE = MODE_0_TIME + MODE_2_TIME + MODE_3_TIME; 17 | const DRAW_PHASE = DRAWLINES * TICKS_PER_LINE; 18 | const TICKS_PER_FRAME = SCANLINES * TICKS_PER_LINE; 19 | 20 | export default class GPU { 21 | constructor (cpu) { 22 | // --- System registers 23 | this.videoMemory = memory.ramBlock(0x4000); 24 | this.oamMemory = memory.ramBlock(0xA0); 25 | this.palette = new Palette(cpu); 26 | this.lcd = new LCD(this.palette); 27 | this.dma = new DMA(this, cpu); 28 | this.cpu = cpu; 29 | this.videoBanks = [this.videoMemory.data, this.videoMemory.data.subarray(0x2000)]; 30 | 31 | // Video registers 32 | this.vbk = 0; 33 | this.lyc = 0; 34 | this.pixelClock = 0; 35 | 36 | this.scx = 0; 37 | this.scy = 0; 38 | this.wx = 0; 39 | this.wy = 0; 40 | 41 | this.lcd_enable = false; 42 | this.window_map = false; 43 | this.background_map = false; 44 | this.window_enable = false; 45 | this.map_tile_data = false; 46 | this.obj_size = false; 47 | this.obj_enable = false; 48 | this.bg_display = false; 49 | 50 | this.lycIRQ = false; 51 | this.mode2IRQ = false; 52 | this.mode1IRQ = false; 53 | this.mode0IRQ = false; 54 | 55 | // Keep a preallocated array of sprite indexes, will be sorted in legacy 56 | // scanlines 57 | this.legacySpriteOrder = new Array(40); 58 | for( var i = 0; i < 40; i++ ) 59 | this.legacySpriteOrder[i] = (i << 2); 60 | 61 | // We don't want to create this closure more than once. 62 | var oam = this.oamMemory.data; 63 | this.legacySorter = function(a,b) { 64 | var ax = oam[a+1]; 65 | var bx = oam[b+1]; 66 | return ax - bx; 67 | }; 68 | } 69 | 70 | setContext (ctx) { 71 | this.lcd.setContext(ctx); 72 | } 73 | 74 | reset () 75 | { 76 | // Clear the scanline override 77 | delete this.drawScanline; 78 | 79 | this.dma.reset(); 80 | this.palette.reset(); 81 | 82 | this.lyc = 0; 83 | this.pixelClock = 0; 84 | 85 | this.scx = 0; 86 | this.scy = 0; 87 | this.wx = 0; 88 | this.wy = 0; 89 | 90 | this.lcd_enable = false; 91 | this.window_map = false; 92 | this.background_map = false; 93 | this.window_enable = false; 94 | this.map_tile_data = false; 95 | this.obj_size = false; 96 | this.obj_enable = false; 97 | this.bg_display = false; 98 | 99 | this.lycIRQ = false; 100 | this.mode2IRQ = false; 101 | this.mode1IRQ = false; 102 | this.mode0IRQ = false; 103 | 104 | this.write_VBK(0); 105 | 106 | this.cpu.read[0xFE].copy(0, this.oamMemory.read); 107 | this.cpu.write[0xFE].copy(0, this.oamMemory.write); 108 | 109 | this.cpu.registers.write[registers.DMA] = this.write_DMA.bind(this); 110 | 111 | this.cpu.registers.read[registers.LCDC] = this.read_LCDC.bind(this); 112 | this.cpu.registers.write[registers.LCDC] = this.write_LCDC.bind(this); 113 | this.cpu.registers.read[registers.STAT] = this.read_STAT.bind(this); 114 | this.cpu.registers.write[registers.STAT] = this.write_STAT.bind(this); 115 | 116 | this.cpu.registers.read[registers.SCX] = this.read_SCX.bind(this); 117 | this.cpu.registers.write[registers.SCX] = this.write_SCX.bind(this); 118 | this.cpu.registers.read[registers.SCY] = this.read_SCY.bind(this); 119 | this.cpu.registers.write[registers.SCY] = this.write_SCY.bind(this); 120 | this.cpu.registers.read[registers.WX] = this.read_WX.bind(this); 121 | this.cpu.registers.write[registers.WX] = this.write_WX.bind(this); 122 | this.cpu.registers.read[registers.WY] = this.read_WY.bind(this); 123 | this.cpu.registers.write[registers.WY] = this.write_WY.bind(this); 124 | 125 | this.cpu.registers.read[registers.LY] = this.read_LY.bind(this); 126 | this.cpu.registers.read[registers.LYC] = this.read_LYC.bind(this); 127 | this.cpu.registers.write[registers.LYC] = this.write_LYC.bind(this); 128 | 129 | this.cpu.registers.read[registers.VBK] = this.read_VBK.bind(this); 130 | this.cpu.registers.write[registers.VBK] = this.write_VBK.bind(this); 131 | 132 | this.cpu.registers.write[registers.LCD_MODE] = this.write_LCD_MODE.bind(this); 133 | } 134 | 135 | drawMapTile (mapAddr, tpx, tpy) 136 | { 137 | var attr = this.videoMemory.data[mapAddr | 0x2000]; 138 | var tile = this.videoMemory.data[mapAddr] << 4; 139 | 140 | var pal = attr & 7; 141 | var bank = (attr & 8) ? 0x2000 : 0x0000; 142 | var hflip = attr & 32; 143 | var vflip = attr & 64; 144 | var priority = this.bg_display && (attr & 128); 145 | 146 | var tileAddr = bank | (((vflip ? ~tpy : tpy) & 7) << 1); 147 | 148 | if( !this.map_tile_data && !(tile & 0x800) ) 149 | tileAddr += 0x1000 + tile; 150 | else 151 | tileAddr += tile; 152 | 153 | this.lcd.copyTileBG( tpx, this.videoMemory.data[tileAddr], this.videoMemory.data[tileAddr+1], pal, hflip, priority ); 154 | } 155 | 156 | drawLegacyMapTile (mapAddr, tpx, tpy) 157 | { 158 | var tile = this.videoMemory.data[mapAddr] << 4; 159 | 160 | var tileAddr = (tpy & 7) << 1; 161 | 162 | if( !this.map_tile_data && !(tile & 0x800) ) 163 | tileAddr += 0x1000 + tile; 164 | else 165 | tileAddr += tile; 166 | 167 | this.lcd.copyTileBG( tpx, this.videoMemory.data[tileAddr], this.videoMemory.data[tileAddr+1], 0, false, false ); 168 | } 169 | 170 | drawScanline (line) 171 | { 172 | if( !this.lcd_enable ) 173 | return ; 174 | 175 | // Locate the base line for the tile map 176 | var mapLine = this.scy + line; 177 | var mapAddr = (this.background_map ? 0x1C00 : 0x1800) | (((mapLine & 0xFF) >> 3) << 5); 178 | var tpx = 8-(this.scx & 7); 179 | var tx = this.scx >> 3; 180 | 181 | for( ; tpx < 168; tpx += 8, tx++ ) 182 | this.drawMapTile( mapAddr | (tx & 0x1F), tpx, mapLine ); 183 | 184 | // Draw the window when it's enabled 185 | mapLine = line - this.wy; 186 | if( this.window_enable && mapLine >= 0 ) 187 | { 188 | mapAddr = (this.window_map ? 0x1C00 : 0x1800) | (((mapLine & 0xFF) >> 3) << 5); 189 | tpx = this.wx + 1; 190 | tx = 0; 191 | 192 | for( ; tpx < 168; tpx += 8, tx++ ) 193 | this.drawMapTile( mapAddr | (tx & 0x1F), tpx, mapLine ); 194 | } 195 | 196 | if( this.obj_enable ) 197 | { 198 | var sprites = 0; 199 | var spriteBound = this.obj_size ? 15 : 7; 200 | 201 | var oam = this.oamMemory.data; 202 | for( var i = 0; i < 0xA0 && sprites < 10; i += 4 ) 203 | { 204 | var y = line + 16 - oam[i]; 205 | 206 | if( y < 0 || y > spriteBound ) 207 | continue; 208 | 209 | sprites++; 210 | 211 | var x = oam[i+1]; 212 | var tile = oam[i+2]; 213 | var attr = oam[i+3]; 214 | 215 | var priority = this.bg_display && (attr & 128); 216 | var yflip = attr & 64; 217 | var hflip = attr & 32; 218 | var bank = (attr & 8) ? 0x2000 : 0x0000; 219 | var pal = attr & 7; 220 | 221 | var tpy = yflip ? (y ^ spriteBound) : (y); 222 | 223 | var tileAddr = bank | (tpy << 1) | (tile << 4); 224 | 225 | this.lcd.copyTileOBJ( x, this.videoMemory.data[tileAddr], this.videoMemory.data[tileAddr+1], pal, hflip, priority ); 226 | } 227 | } 228 | 229 | this.lcd.copyScanline(line); 230 | } 231 | 232 | drawLegacyScanline (line) 233 | { 234 | var mapLine, mapAddr, tpx, tx; 235 | 236 | // White out the line 237 | if( !this.lcd_enable ) 238 | { 239 | this.lcd.clear(); 240 | this.lcd.copyScanline(line); 241 | return ; 242 | } 243 | 244 | // Locate the base line for the tile map 245 | if( this.bg_display ) 246 | { 247 | mapLine = this.scy + line; 248 | mapAddr = (this.background_map ? 0x1C00 : 0x1800) | (((mapLine & 0xFF) >> 3) << 5); 249 | tpx = 8-(this.scx & 7); 250 | tx = this.scx >> 3; 251 | 252 | for( ; tpx < 168; tpx += 8, tx++ ) 253 | this.drawLegacyMapTile( mapAddr | (tx & 0x1F), tpx, mapLine ); 254 | } 255 | else 256 | { 257 | this.lcd.clear(); 258 | } 259 | 260 | // Draw the window when it's enabled 261 | mapLine = line - this.wy; 262 | if( this.window_enable && mapLine >= 0 ) 263 | { 264 | mapAddr = (this.window_map ? 0x1C00 : 0x1800) | (((mapLine & 0xFF) >> 3) << 5); 265 | tpx = this.wx + 1; 266 | tx = 0; 267 | 268 | for( ; tpx < 168; tpx += 8, tx++ ) 269 | this.drawLegacyMapTile( mapAddr | (tx & 0x1F), tpx, mapLine ); 270 | } 271 | 272 | if( this.obj_enable ) 273 | { 274 | // Sort sprite index list based on their X coordinate 275 | var oam = this.oamMemory.data; 276 | var order = this.legacySpriteOrder; 277 | order.sort( this.legacySorter ); 278 | 279 | var sprites = 0; 280 | var spriteBound = this.obj_size ? 15 : 7; 281 | 282 | for( var s = 0; s < 40 && sprites < 10; s ++ ) 283 | { 284 | // Use the X sorted sprite array 285 | var i = order[s]; 286 | 287 | var y = line + 16 - oam[i]; 288 | 289 | if( y < 0 || y > spriteBound ) 290 | continue; 291 | 292 | sprites++; 293 | 294 | var x = oam[i+1]; 295 | var tile = oam[i+2]; 296 | var attr = oam[i+3]; 297 | 298 | var priority = attr & 128; 299 | var yflip = attr & 64; 300 | var hflip = attr & 32; 301 | var pal = attr & 16; 302 | 303 | tpy = yflip ? (y ^ spriteBound) : (y); 304 | 305 | var tileAddr = (tpy << 1) | (tile << 4); 306 | 307 | this.lcd.copyTileOBJ( x, this.videoMemory.data[tileAddr], this.videoMemory.data[tileAddr+1], pal, hflip, priority ); 308 | } 309 | } 310 | 311 | this.lcd.copyScanlineLegacy(line, 312 | this.palette.reg_BGP, 313 | this.palette.reg_OBP0, 314 | this.palette.reg_OBP1); 315 | } 316 | 317 | predictEndOfFrame () 318 | { 319 | return TICKS_PER_FRAME - this.pixelClock; 320 | } 321 | 322 | predict () 323 | { 324 | var phase = this.pixelClock % TICKS_PER_LINE, 325 | a, b, c; 326 | 327 | // LCD-Stat registers 328 | if( this.mode2IRQ ) 329 | a = this.timeUntilDrawClock(0, phase); 330 | if( this.mode0IRQ ) 331 | b = this.timeUntilDrawClock(MODE_2_TIME + MODE_3_TIME, phase); 332 | if( this.lycIRQ ) 333 | c = this.timeUntilLine(this.lyc); 334 | 335 | // Note: This relies a lot on 'undefined' comparison behavior 336 | if( a < b && a < c ) 337 | return a; 338 | else if( b < c ) 339 | return b; 340 | else 341 | return c; 342 | } 343 | 344 | timeUntilVBlank () 345 | { 346 | if( this.pixelClock < DRAW_PHASE ) 347 | return DRAW_PHASE - this.pixelClock; 348 | else 349 | return DRAW_PHASE + TICKS_PER_FRAME - this.pixelClock; 350 | } 351 | 352 | timeUntilDrawClock (phase, period) 353 | { 354 | // The next one is on a banking line, so we wait until line 0's period 355 | if( this.pixelClock >= DRAW_PHASE - TICKS_PER_LINE + period ) 356 | return DRAW_PHASE - this.pixelClock + period; 357 | 358 | // Calculate time until phase crossing 359 | else if( phase < period ) 360 | return period - phase; 361 | else 362 | return period - phase + TICKS_PER_LINE; 363 | } 364 | 365 | timeUntilLine (line) 366 | { 367 | if( line >= SCANLINES ) 368 | return ; 369 | 370 | var bias = TICKS_PER_LINE * line; 371 | 372 | if( this.pixelClock < bias ) 373 | return bias - this.pixelClock; 374 | else 375 | return bias - this.pixelClock + TICKS_PER_FRAME; 376 | } 377 | 378 | clock (cycles) 379 | { 380 | var phase = this.pixelClock % TICKS_PER_LINE; 381 | 382 | if( this.timeUntilVBlank() <= cycles ) 383 | { 384 | if( this.mode1IRQ ) 385 | this.cpu.trigger( consts.IRQ_LCD_STAT ); 386 | this.cpu.trigger( consts.IRQ_VBLANK ); 387 | } 388 | 389 | // LCD-Stat registers 390 | if( this.lycIRQ ) 391 | { 392 | var ttl = this.timeUntilLine(this.lyc); 393 | if( ttl !== null && ttl <= cycles ) 394 | this.cpu.trigger( consts.IRQ_LCD_STAT ); 395 | } 396 | else if( this.mode2IRQ && this.timeUntilDrawClock(phase, 0) <= cycles ) 397 | { 398 | this.cpu.trigger( consts.IRQ_LCD_STAT ); 399 | } 400 | else if( this.mode0IRQ && this.timeUntilDrawClock(phase, MODE_2_TIME + MODE_3_TIME) <= cycles ) 401 | { 402 | this.cpu.trigger( consts.IRQ_LCD_STAT ); 403 | } 404 | 405 | // Ticks until the next line begins (End of mode 2) 406 | var nextDraw = MODE_2_TIME - phase; 407 | if( nextDraw < 0 ) 408 | nextDraw += TICKS_PER_LINE; 409 | 410 | if( cycles >= nextDraw ) 411 | { 412 | // Attempt to discover which line we are drawing 413 | var currentLine = this.activeLine(); 414 | if( phase >= MODE_2_TIME ) 415 | currentLine = (currentLine + 1) % SCANLINES; 416 | 417 | var lines = Math.floor( (cycles - nextDraw) / TICKS_PER_LINE ) + 1; 418 | 419 | while( lines ) 420 | { 421 | // Skip vblank (as it is wasteful) 422 | if( currentLine >= DRAWLINES ) 423 | { 424 | var blankLines = (SCANLINES-currentLine); 425 | if( lines < blankLines ) 426 | break ; 427 | 428 | lines -= blankLines; 429 | currentLine = 0; 430 | this.lcd.update(); 431 | } 432 | // Draw a regular raster 433 | else 434 | { 435 | this.drawScanline(currentLine++); 436 | this.dma.moveBlock(); // Perform H-Blank DMA 437 | lines--; 438 | } 439 | } 440 | } 441 | 442 | this.pixelClock = (this.pixelClock + cycles) % TICKS_PER_FRAME; 443 | } 444 | 445 | // --- Video control register 446 | read_LCDC () 447 | { 448 | return (this.lcd_enable ? 0x80 : 0) | 449 | (this.window_map ? 0x40 : 0) | 450 | (this.window_enable ? 0x20 : 0) | 451 | (this.map_tile_data ? 0x10 : 0) | 452 | (this.background_map ? 0x08 : 0) | 453 | (this.obj_size ? 0x04 : 0) | 454 | (this.obj_enable ? 0x02 : 0) | 455 | (this.bg_display ? 0x01 : 0); 456 | } 457 | 458 | write_LCDC (data) 459 | { 460 | this.cpu.catchUp(); 461 | 462 | this.lcd_enable = data & 0x80; 463 | this.window_map = data & 0x40; 464 | this.window_enable = data & 0x20; 465 | this.map_tile_data = data & 0x10; 466 | this.background_map = data & 0x08; 467 | this.obj_size = data & 0x04; 468 | this.obj_enable = data & 0x02; 469 | this.bg_display = data & 0x01; 470 | } 471 | 472 | // --- Video stat register 473 | read_STAT () 474 | { 475 | this.cpu.catchUp(); 476 | 477 | var data = ((this.lyc==this.activeLine()) ? 4 : 0) | 478 | (this.lycIRQ ? 0x40 : 0) | 479 | (this.mode2IRQ ? 0x20 : 0) | 480 | (this.mode1IRQ ? 0x10 : 0) | 481 | (this.mode0IRQ ? 0x08 : 0); 482 | 483 | // Drawing period (3 state phase) 484 | if( this.pixelClock < DRAW_PHASE ) 485 | { 486 | var phase = this.pixelClock % TICKS_PER_LINE; 487 | 488 | if( phase < MODE_2_TIME ) 489 | return data | 2; 490 | else if( phase < MODE_2_TIME + MODE_3_TIME ) 491 | return data | 3; 492 | else 493 | return data; 494 | } 495 | // Vertical blank period 496 | return data | 1; 497 | } 498 | 499 | write_STAT (data) 500 | { 501 | this.cpu.catchUp(); 502 | 503 | this.lycIRQ = data & 0x40; 504 | this.mode2IRQ = data & 0x20; 505 | this.mode1IRQ = data & 0x10; 506 | this.mode0IRQ = data & 0x08; 507 | } 508 | 509 | // --- Video position / clock values 510 | read_WX () 511 | { 512 | return this.wx; 513 | } 514 | 515 | write_WX (data) 516 | { 517 | this.cpu.catchUp(); 518 | this.wx = data; 519 | } 520 | 521 | read_WY () 522 | { 523 | return this.wy; 524 | } 525 | 526 | write_WY (data) 527 | { 528 | this.cpu.catchUp(); 529 | this.wy = data; 530 | } 531 | 532 | read_SCX () 533 | { 534 | return this.scx; 535 | } 536 | 537 | write_SCX (data) 538 | { 539 | this.cpu.catchUp(); 540 | this.scx = data; 541 | } 542 | 543 | read_SCY () { 544 | return this.scy; 545 | } 546 | 547 | write_SCY (data) { 548 | this.cpu.catchUp(); 549 | this.scy = data; 550 | } 551 | 552 | activeLine () { 553 | return Math.floor(this.pixelClock/TICKS_PER_LINE); 554 | } 555 | 556 | read_LY () { 557 | this.cpu.catchUp(); 558 | return this.activeLine(); 559 | } 560 | 561 | read_LYC () { 562 | return this.lyc; 563 | } 564 | 565 | write_LYC (data) { 566 | this.cpu.catchUp(); 567 | this.lyc = data; 568 | } 569 | 570 | // --- Bank register 571 | write_VBK (data) { 572 | this.vbk = data & 1; 573 | let bank = this.vbk * 0x20; 574 | 575 | this.vbk_cell = this.videoBanks[this.vbk]; 576 | 577 | this.cpu.read.copy(0x80, this.videoMemory.readChunks, bank, 0x20); 578 | this.cpu.write.copy(0x80, this.videoMemory.writeChunks, bank, 0x20); 579 | } 580 | 581 | read_VBK () { 582 | return this.vbk; 583 | } 584 | 585 | write_DMA (data) { 586 | this.cpu.catchUp(); 587 | 588 | var oam = this.oamMemory.data, 589 | src = this.cpu.read[data]; 590 | 591 | for (var i = 0; i < 0xA0; i++) { 592 | oam[i] = src[i](); 593 | } 594 | } 595 | 596 | write_LCD_MODE (data) { 597 | // I don't know how this is actually supose to work. 598 | if (data != 4) { 599 | return ; 600 | } 601 | 602 | this.drawScanline = this.drawLegacyScanline; 603 | } 604 | } 605 | -------------------------------------------------------------------------------- /src/debugger/disassemble.js: -------------------------------------------------------------------------------- 1 | const IMPLIED = 0; 2 | const WORD = 1; 3 | const BYTE = 2; 4 | const SIGNED = 3; 5 | const RELATIVE_PC = 4; 6 | 7 | const BASE_INSTRUCTION_SET = [ 8 | { format: 'NOP', arg:IMPLIED }, 9 | { format: 'LD BC,$%', arg:WORD }, 10 | { format: 'LD (BC),A', arg:IMPLIED }, 11 | { format: 'INC BC', arg:IMPLIED }, 12 | { format: 'INC B', arg:IMPLIED }, 13 | { format: 'DEC B', arg:IMPLIED }, 14 | { format: 'LD B,$%', arg:BYTE }, 15 | { format: 'RLCA', arg:IMPLIED }, 16 | { format: 'LD ($%),SP', arg:WORD }, 17 | { format: 'ADD HL,BC', arg:IMPLIED }, 18 | { format: 'LD A,(BC)', arg:IMPLIED }, 19 | { format: 'DEC BC', arg:IMPLIED }, 20 | { format: 'INC C', arg:IMPLIED }, 21 | { format: 'DEC C', arg:IMPLIED }, 22 | { format: 'LD C,$%', arg:BYTE }, 23 | { format: 'RRCA', arg:IMPLIED }, 24 | { format: 'STOP', arg:IMPLIED }, 25 | { format: 'LD DE,$%', arg:WORD }, 26 | { format: 'LD (DE),A', arg:IMPLIED }, 27 | { format: 'INC DE', arg:IMPLIED }, 28 | { format: 'INC D', arg:IMPLIED }, 29 | { format: 'DEC D', arg:IMPLIED }, 30 | { format: 'LD D,$%', arg:BYTE }, 31 | { format: 'RLA', arg:IMPLIED }, 32 | { format: 'JR $%', arg:RELATIVE_PC }, 33 | { format: 'ADD HL,DE', arg:IMPLIED }, 34 | { format: 'LD A,(DE)', arg:IMPLIED }, 35 | { format: 'DEC DE', arg:IMPLIED }, 36 | { format: 'INC E', arg:IMPLIED }, 37 | { format: 'DEC E', arg:IMPLIED }, 38 | { format: 'LD E,$%', arg:BYTE }, 39 | { format: 'RRA', arg:IMPLIED }, 40 | { format: 'JR NZ,$%', arg:RELATIVE_PC }, 41 | { format: 'LD HL,$%', arg:WORD }, 42 | { format: 'LDI (HL),A', arg:IMPLIED }, 43 | { format: 'INC HL', arg:IMPLIED }, 44 | { format: 'INC H', arg:IMPLIED }, 45 | { format: 'DEC H', arg:IMPLIED }, 46 | { format: 'LD H,$%', arg:BYTE }, 47 | { format: 'DAA', arg:IMPLIED }, 48 | { format: 'JR Z,$%', arg:RELATIVE_PC }, 49 | { format: 'ADD HL,HL', arg:IMPLIED }, 50 | { format: 'LDI A,(HL)', arg:IMPLIED }, 51 | { format: 'DEC HL', arg:IMPLIED }, 52 | { format: 'INC L', arg:IMPLIED }, 53 | { format: 'DEC L', arg:IMPLIED }, 54 | { format: 'LD L,$%', arg:BYTE }, 55 | { format: 'CPL', arg:IMPLIED }, 56 | { format: 'JR NC,$%', arg:RELATIVE_PC }, 57 | { format: 'LD SP,$%', arg:WORD }, 58 | { format: 'LDD (HL),A', arg:IMPLIED }, 59 | { format: 'INC SP', arg:IMPLIED }, 60 | { format: 'INC (HL)', arg:IMPLIED }, 61 | { format: 'DEC (HL)', arg:IMPLIED }, 62 | { format: 'LD (HL),$%', arg:BYTE }, 63 | { format: 'SCF', arg:IMPLIED }, 64 | { format: 'JR C,$%', arg:RELATIVE_PC }, 65 | { format: 'ADD HL,SP', arg:IMPLIED }, 66 | { format: 'LDD A,(HL)', arg:IMPLIED }, 67 | { format: 'DEC SP', arg:IMPLIED }, 68 | { format: 'INC A', arg:IMPLIED }, 69 | { format: 'DEC A', arg:IMPLIED }, 70 | { format: 'LD A,$%', arg:BYTE }, 71 | { format: 'CCF', arg:IMPLIED }, 72 | { format: 'LD B,B', arg:IMPLIED }, 73 | { format: 'LD B,C', arg:IMPLIED }, 74 | { format: 'LD B,D', arg:IMPLIED }, 75 | { format: 'LD B,E', arg:IMPLIED }, 76 | { format: 'LD B,H', arg:IMPLIED }, 77 | { format: 'LD B,L', arg:IMPLIED }, 78 | { format: 'LD B,(HL)', arg:IMPLIED }, 79 | { format: 'LD B,A', arg:IMPLIED }, 80 | { format: 'LD C,B', arg:IMPLIED }, 81 | { format: 'LD C,C', arg:IMPLIED }, 82 | { format: 'LD C,D', arg:IMPLIED }, 83 | { format: 'LD C,E', arg:IMPLIED }, 84 | { format: 'LD C,H', arg:IMPLIED }, 85 | { format: 'LD C,L', arg:IMPLIED }, 86 | { format: 'LD C,(HL)', arg:IMPLIED }, 87 | { format: 'LD C,A', arg:IMPLIED }, 88 | { format: 'LD D,B', arg:IMPLIED }, 89 | { format: 'LD D,C', arg:IMPLIED }, 90 | { format: 'LD D,D', arg:IMPLIED }, 91 | { format: 'LD D,E', arg:IMPLIED }, 92 | { format: 'LD D,H', arg:IMPLIED }, 93 | { format: 'LD D,L', arg:IMPLIED }, 94 | { format: 'LD D,(HL)', arg:IMPLIED }, 95 | { format: 'LD D,A', arg:IMPLIED }, 96 | { format: 'LD E,B', arg:IMPLIED }, 97 | { format: 'LD E,C', arg:IMPLIED }, 98 | { format: 'LD E,D', arg:IMPLIED }, 99 | { format: 'LD E,E', arg:IMPLIED }, 100 | { format: 'LD E,H', arg:IMPLIED }, 101 | { format: 'LD E,L', arg:IMPLIED }, 102 | { format: 'LD E,(HL)', arg:IMPLIED }, 103 | { format: 'LD E,A', arg:IMPLIED }, 104 | { format: 'LD H,B', arg:IMPLIED }, 105 | { format: 'LD H,C', arg:IMPLIED }, 106 | { format: 'LD H,D', arg:IMPLIED }, 107 | { format: 'LD H,E', arg:IMPLIED }, 108 | { format: 'LD H,H', arg:IMPLIED }, 109 | { format: 'LD H,L', arg:IMPLIED }, 110 | { format: 'LD H,(HL)', arg:IMPLIED }, 111 | { format: 'LD H,A', arg:IMPLIED }, 112 | { format: 'LD L,B', arg:IMPLIED }, 113 | { format: 'LD L,C', arg:IMPLIED }, 114 | { format: 'LD L,D', arg:IMPLIED }, 115 | { format: 'LD L,E', arg:IMPLIED }, 116 | { format: 'LD L,H', arg:IMPLIED }, 117 | { format: 'LD L,L', arg:IMPLIED }, 118 | { format: 'LD L,(HL)', arg:IMPLIED }, 119 | { format: 'LD L,A', arg:IMPLIED }, 120 | { format: 'LD (HL),B', arg:IMPLIED }, 121 | { format: 'LD (HL),C', arg:IMPLIED }, 122 | { format: 'LD (HL),D', arg:IMPLIED }, 123 | { format: 'LD (HL),E', arg:IMPLIED }, 124 | { format: 'LD (HL),H', arg:IMPLIED }, 125 | { format: 'LD (HL),L', arg:IMPLIED }, 126 | { format: 'HALT', arg:IMPLIED }, 127 | { format: 'LD (HL),A', arg:IMPLIED }, 128 | { format: 'LD A,B', arg:IMPLIED }, 129 | { format: 'LD A,C', arg:IMPLIED }, 130 | { format: 'LD A,D', arg:IMPLIED }, 131 | { format: 'LD A,E', arg:IMPLIED }, 132 | { format: 'LD A,H', arg:IMPLIED }, 133 | { format: 'LD A,L', arg:IMPLIED }, 134 | { format: 'LD A,(HL)', arg:IMPLIED }, 135 | { format: 'LD A,A', arg:IMPLIED }, 136 | { format: 'ADD A,B', arg:IMPLIED }, 137 | { format: 'ADD A,C', arg:IMPLIED }, 138 | { format: 'ADD A,D', arg:IMPLIED }, 139 | { format: 'ADD A,E', arg:IMPLIED }, 140 | { format: 'ADD A,H', arg:IMPLIED }, 141 | { format: 'ADD A,L', arg:IMPLIED }, 142 | { format: 'ADD A,(HL)', arg:IMPLIED }, 143 | { format: 'ADD A,A', arg:IMPLIED }, 144 | { format: 'ADC A,B', arg:IMPLIED }, 145 | { format: 'ADC A,C', arg:IMPLIED }, 146 | { format: 'ADC A,D', arg:IMPLIED }, 147 | { format: 'ADC A,E', arg:IMPLIED }, 148 | { format: 'ADC A,H', arg:IMPLIED }, 149 | { format: 'ADC A,L', arg:IMPLIED }, 150 | { format: 'ADC A,(HL)', arg:IMPLIED }, 151 | { format: 'ADC A,A', arg:IMPLIED }, 152 | { format: 'SUB B', arg:IMPLIED }, 153 | { format: 'SUB C', arg:IMPLIED }, 154 | { format: 'SUB D', arg:IMPLIED }, 155 | { format: 'SUB E', arg:IMPLIED }, 156 | { format: 'SUB H', arg:IMPLIED }, 157 | { format: 'SUB L', arg:IMPLIED }, 158 | { format: 'SUB (HL)', arg:IMPLIED }, 159 | { format: 'SUB A', arg:IMPLIED }, 160 | { format: 'SBC A,B', arg:IMPLIED }, 161 | { format: 'SBC A,C', arg:IMPLIED }, 162 | { format: 'SBC A,D', arg:IMPLIED }, 163 | { format: 'SBC A,E', arg:IMPLIED }, 164 | { format: 'SBC A,H', arg:IMPLIED }, 165 | { format: 'SBC A,L', arg:IMPLIED }, 166 | { format: 'SBC A,(HL)', arg:IMPLIED }, 167 | { format: 'SBC A,A', arg:IMPLIED }, 168 | { format: 'AND B', arg:IMPLIED }, 169 | { format: 'AND C', arg:IMPLIED }, 170 | { format: 'AND D', arg:IMPLIED }, 171 | { format: 'AND E', arg:IMPLIED }, 172 | { format: 'AND H', arg:IMPLIED }, 173 | { format: 'AND L', arg:IMPLIED }, 174 | { format: 'AND (HL)', arg:IMPLIED }, 175 | { format: 'AND A', arg:IMPLIED }, 176 | { format: 'XOR B', arg:IMPLIED }, 177 | { format: 'XOR C', arg:IMPLIED }, 178 | { format: 'XOR D', arg:IMPLIED }, 179 | { format: 'XOR E', arg:IMPLIED }, 180 | { format: 'XOR H', arg:IMPLIED }, 181 | { format: 'XOR L', arg:IMPLIED }, 182 | { format: 'XOR (HL)', arg:IMPLIED }, 183 | { format: 'XOR A', arg:IMPLIED }, 184 | { format: 'OR B', arg:IMPLIED }, 185 | { format: 'OR C', arg:IMPLIED }, 186 | { format: 'OR D', arg:IMPLIED }, 187 | { format: 'OR E', arg:IMPLIED }, 188 | { format: 'OR H', arg:IMPLIED }, 189 | { format: 'OR L', arg:IMPLIED }, 190 | { format: 'OR (HL)', arg:IMPLIED }, 191 | { format: 'OR A', arg:IMPLIED }, 192 | { format: 'CP B', arg:IMPLIED }, 193 | { format: 'CP C', arg:IMPLIED }, 194 | { format: 'CP D', arg:IMPLIED }, 195 | { format: 'CP E', arg:IMPLIED }, 196 | { format: 'CP H', arg:IMPLIED }, 197 | { format: 'CP L', arg:IMPLIED }, 198 | { format: 'CP (HL)', arg:IMPLIED }, 199 | { format: 'CP A', arg:IMPLIED }, 200 | { format: 'RET NZ', arg:IMPLIED }, 201 | { format: 'POP BC', arg:IMPLIED }, 202 | { format: 'JP NZ,$%', arg:WORD }, 203 | { format: 'JP $%', arg:WORD }, 204 | { format: 'CALL NZ,$%', arg:WORD }, 205 | { format: 'PUSH BC', arg:IMPLIED }, 206 | { format: 'ADD A,$%', arg:BYTE }, 207 | { format: 'RST $00', arg:IMPLIED }, 208 | { format: 'RET Z', arg:IMPLIED }, 209 | { format: 'RET', arg:IMPLIED }, 210 | { format: 'JP Z,$%', arg:WORD }, 211 | null, 212 | { format: 'CALL Z,$%', arg:WORD }, 213 | { format: 'CALL $%', arg:WORD }, 214 | { format: 'ADC A,$%', arg:BYTE }, 215 | { format: 'RST $08', arg:IMPLIED }, 216 | { format: 'RET NC', arg:IMPLIED }, 217 | { format: 'POP DE', arg:IMPLIED }, 218 | { format: 'JP NC,$%', arg:WORD }, 219 | null, 220 | { format: 'CALL NC,$%', arg:WORD }, 221 | { format: 'PUSH DE', arg:IMPLIED }, 222 | { format: 'SUB $%', arg:BYTE }, 223 | { format: 'RST $10', arg:IMPLIED }, 224 | { format: 'RET C', arg:IMPLIED }, 225 | { format: 'RETI', arg:IMPLIED }, 226 | { format: 'JP C,$%', arg:WORD }, 227 | null, 228 | { format: 'CALL C,$%', arg:WORD }, 229 | null, 230 | { format: 'SBC A,$%', arg:BYTE }, 231 | { format: 'RST $18', arg:IMPLIED }, 232 | { format: 'LDH ($%),A', arg:BYTE }, 233 | { format: 'POP HL', arg:IMPLIED }, 234 | { format: 'LDH (C),A', arg:IMPLIED }, 235 | null, 236 | null, 237 | { format: 'PUSH HL', arg:IMPLIED }, 238 | { format: 'AND $%', arg:BYTE }, 239 | { format: 'RST $20', arg:IMPLIED }, 240 | { format: 'ADD SP,$%', arg:SIGNED }, 241 | { format: 'JP HL', arg:IMPLIED }, 242 | { format: 'LD ($%),A', arg:WORD }, 243 | null, 244 | null, 245 | null, 246 | { format: 'XOR $%', arg:BYTE }, 247 | { format: 'RST $28', arg:IMPLIED }, 248 | { format: 'LDH A,($%)', arg:BYTE }, 249 | { format: 'POP AF', arg:IMPLIED }, 250 | { format: 'LDH A,(C)', arg:IMPLIED }, 251 | { format: 'DI', arg:IMPLIED }, 252 | null, 253 | { format: 'PUSH AF', arg:IMPLIED }, 254 | { format: 'OR $%', arg:BYTE }, 255 | { format: 'RST $30', arg:IMPLIED }, 256 | { format: 'LD HL,SP', arg:IMPLIED }, 257 | { format: 'LD SP,HL', arg:IMPLIED }, 258 | { format: 'LD A,($%)', arg:WORD }, 259 | { format: 'EI', arg:IMPLIED }, 260 | null, 261 | null, 262 | { format: 'CP $%', arg:BYTE }, 263 | { format: 'RST $38', arg:IMPLIED }, 264 | ]; 265 | 266 | var EXTENDED_INSTRUCTION_SET = [ 267 | { format: 'RLC B', arg:IMPLIED }, 268 | { format: 'RLC C', arg:IMPLIED }, 269 | { format: 'RLC D', arg:IMPLIED }, 270 | { format: 'RLC E', arg:IMPLIED }, 271 | { format: 'RLC H', arg:IMPLIED }, 272 | { format: 'RLC L', arg:IMPLIED }, 273 | { format: 'RLC (HL)', arg:IMPLIED }, 274 | { format: 'RLC A', arg:IMPLIED }, 275 | { format: 'RRC B', arg:IMPLIED }, 276 | { format: 'RRC C', arg:IMPLIED }, 277 | { format: 'RRC D', arg:IMPLIED }, 278 | { format: 'RRC E', arg:IMPLIED }, 279 | { format: 'RRC H', arg:IMPLIED }, 280 | { format: 'RRC L', arg:IMPLIED }, 281 | { format: 'RRC (HL)', arg:IMPLIED }, 282 | { format: 'RRC A', arg:IMPLIED }, 283 | { format: 'RL B', arg:IMPLIED }, 284 | { format: 'RL C', arg:IMPLIED }, 285 | { format: 'RL D', arg:IMPLIED }, 286 | { format: 'RL E', arg:IMPLIED }, 287 | { format: 'RL H', arg:IMPLIED }, 288 | { format: 'RL L', arg:IMPLIED }, 289 | { format: 'RL (HL)', arg:IMPLIED }, 290 | { format: 'RL A', arg:IMPLIED }, 291 | { format: 'RR B', arg:IMPLIED }, 292 | { format: 'RR C', arg:IMPLIED }, 293 | { format: 'RR D', arg:IMPLIED }, 294 | { format: 'RR E', arg:IMPLIED }, 295 | { format: 'RR H', arg:IMPLIED }, 296 | { format: 'RR L', arg:IMPLIED }, 297 | { format: 'RR (HL)', arg:IMPLIED }, 298 | { format: 'RR A', arg:IMPLIED }, 299 | { format: 'SLA B', arg:IMPLIED }, 300 | { format: 'SLA C', arg:IMPLIED }, 301 | { format: 'SLA D', arg:IMPLIED }, 302 | { format: 'SLA E', arg:IMPLIED }, 303 | { format: 'SLA H', arg:IMPLIED }, 304 | { format: 'SLA L', arg:IMPLIED }, 305 | { format: 'SLA (HL)', arg:IMPLIED }, 306 | { format: 'SLA A', arg:IMPLIED }, 307 | { format: 'SRA B', arg:IMPLIED }, 308 | { format: 'SRA C', arg:IMPLIED }, 309 | { format: 'SRA D', arg:IMPLIED }, 310 | { format: 'SRA E', arg:IMPLIED }, 311 | { format: 'SRA H', arg:IMPLIED }, 312 | { format: 'SRA L', arg:IMPLIED }, 313 | { format: 'SRA (HL)', arg:IMPLIED }, 314 | { format: 'SRA A', arg:IMPLIED }, 315 | { format: 'SWAP B', arg:IMPLIED }, 316 | { format: 'SWAP C', arg:IMPLIED }, 317 | { format: 'SWAP D', arg:IMPLIED }, 318 | { format: 'SWAP E', arg:IMPLIED }, 319 | { format: 'SWAP H', arg:IMPLIED }, 320 | { format: 'SWAP L', arg:IMPLIED }, 321 | { format: 'SWAP (HL)', arg:IMPLIED }, 322 | { format: 'SWAP A', arg:IMPLIED }, 323 | { format: 'SRL B', arg:IMPLIED }, 324 | { format: 'SRL C', arg:IMPLIED }, 325 | { format: 'SRL D', arg:IMPLIED }, 326 | { format: 'SRL E', arg:IMPLIED }, 327 | { format: 'SRL H', arg:IMPLIED }, 328 | { format: 'SRL L', arg:IMPLIED }, 329 | { format: 'SRL (HL)', arg:IMPLIED }, 330 | { format: 'SRL A', arg:IMPLIED }, 331 | { format: 'BIT 0, B', arg:IMPLIED }, 332 | { format: 'BIT 0, C', arg:IMPLIED }, 333 | { format: 'BIT 0, D', arg:IMPLIED }, 334 | { format: 'BIT 0, E', arg:IMPLIED }, 335 | { format: 'BIT 0, H', arg:IMPLIED }, 336 | { format: 'BIT 0, L', arg:IMPLIED }, 337 | { format: 'BIT 0, (HL)', arg:IMPLIED }, 338 | { format: 'BIT 0, A', arg:IMPLIED }, 339 | { format: 'BIT 1, B', arg:IMPLIED }, 340 | { format: 'BIT 1, C', arg:IMPLIED }, 341 | { format: 'BIT 1, D', arg:IMPLIED }, 342 | { format: 'BIT 1, E', arg:IMPLIED }, 343 | { format: 'BIT 1, H', arg:IMPLIED }, 344 | { format: 'BIT 1, L', arg:IMPLIED }, 345 | { format: 'BIT 1, (HL)', arg:IMPLIED }, 346 | { format: 'BIT 1, A', arg:IMPLIED }, 347 | { format: 'BIT 2, B', arg:IMPLIED }, 348 | { format: 'BIT 2, C', arg:IMPLIED }, 349 | { format: 'BIT 2, D', arg:IMPLIED }, 350 | { format: 'BIT 2, E', arg:IMPLIED }, 351 | { format: 'BIT 2, H', arg:IMPLIED }, 352 | { format: 'BIT 2, L', arg:IMPLIED }, 353 | { format: 'BIT 2, (HL)', arg:IMPLIED }, 354 | { format: 'BIT 2, A', arg:IMPLIED }, 355 | { format: 'BIT 3, B', arg:IMPLIED }, 356 | { format: 'BIT 3, C', arg:IMPLIED }, 357 | { format: 'BIT 3, D', arg:IMPLIED }, 358 | { format: 'BIT 3, E', arg:IMPLIED }, 359 | { format: 'BIT 3, H', arg:IMPLIED }, 360 | { format: 'BIT 3, L', arg:IMPLIED }, 361 | { format: 'BIT 3, (HL)', arg:IMPLIED }, 362 | { format: 'BIT 3, A', arg:IMPLIED }, 363 | { format: 'BIT 4, B', arg:IMPLIED }, 364 | { format: 'BIT 4, C', arg:IMPLIED }, 365 | { format: 'BIT 4, D', arg:IMPLIED }, 366 | { format: 'BIT 4, E', arg:IMPLIED }, 367 | { format: 'BIT 4, H', arg:IMPLIED }, 368 | { format: 'BIT 4, L', arg:IMPLIED }, 369 | { format: 'BIT 4, (HL)', arg:IMPLIED }, 370 | { format: 'BIT 4, A', arg:IMPLIED }, 371 | { format: 'BIT 5, B', arg:IMPLIED }, 372 | { format: 'BIT 5, C', arg:IMPLIED }, 373 | { format: 'BIT 5, D', arg:IMPLIED }, 374 | { format: 'BIT 5, E', arg:IMPLIED }, 375 | { format: 'BIT 5, H', arg:IMPLIED }, 376 | { format: 'BIT 5, L', arg:IMPLIED }, 377 | { format: 'BIT 5, (HL)', arg:IMPLIED }, 378 | { format: 'BIT 5, A', arg:IMPLIED }, 379 | { format: 'BIT 6, B', arg:IMPLIED }, 380 | { format: 'BIT 6, C', arg:IMPLIED }, 381 | { format: 'BIT 6, D', arg:IMPLIED }, 382 | { format: 'BIT 6, E', arg:IMPLIED }, 383 | { format: 'BIT 6, H', arg:IMPLIED }, 384 | { format: 'BIT 6, L', arg:IMPLIED }, 385 | { format: 'BIT 6, (HL)', arg:IMPLIED }, 386 | { format: 'BIT 6, A', arg:IMPLIED }, 387 | { format: 'BIT 7, B', arg:IMPLIED }, 388 | { format: 'BIT 7, C', arg:IMPLIED }, 389 | { format: 'BIT 7, D', arg:IMPLIED }, 390 | { format: 'BIT 7, E', arg:IMPLIED }, 391 | { format: 'BIT 7, H', arg:IMPLIED }, 392 | { format: 'BIT 7, L', arg:IMPLIED }, 393 | { format: 'BIT 7, (HL)', arg:IMPLIED }, 394 | { format: 'BIT 7, A', arg:IMPLIED }, 395 | { format: 'RES 0, B', arg:IMPLIED }, 396 | { format: 'RES 0, C', arg:IMPLIED }, 397 | { format: 'RES 0, D', arg:IMPLIED }, 398 | { format: 'RES 0, E', arg:IMPLIED }, 399 | { format: 'RES 0, H', arg:IMPLIED }, 400 | { format: 'RES 0, L', arg:IMPLIED }, 401 | { format: 'RES 0, (HL)', arg:IMPLIED }, 402 | { format: 'RES 0, A', arg:IMPLIED }, 403 | { format: 'RES 1, B', arg:IMPLIED }, 404 | { format: 'RES 1, C', arg:IMPLIED }, 405 | { format: 'RES 1, D', arg:IMPLIED }, 406 | { format: 'RES 1, E', arg:IMPLIED }, 407 | { format: 'RES 1, H', arg:IMPLIED }, 408 | { format: 'RES 1, L', arg:IMPLIED }, 409 | { format: 'RES 1, (HL)', arg:IMPLIED }, 410 | { format: 'RES 1, A', arg:IMPLIED }, 411 | { format: 'RES 2, B', arg:IMPLIED }, 412 | { format: 'RES 2, C', arg:IMPLIED }, 413 | { format: 'RES 2, D', arg:IMPLIED }, 414 | { format: 'RES 2, E', arg:IMPLIED }, 415 | { format: 'RES 2, H', arg:IMPLIED }, 416 | { format: 'RES 2, L', arg:IMPLIED }, 417 | { format: 'RES 2, (HL)', arg:IMPLIED }, 418 | { format: 'RES 2, A', arg:IMPLIED }, 419 | { format: 'RES 3, B', arg:IMPLIED }, 420 | { format: 'RES 3, C', arg:IMPLIED }, 421 | { format: 'RES 3, D', arg:IMPLIED }, 422 | { format: 'RES 3, E', arg:IMPLIED }, 423 | { format: 'RES 3, H', arg:IMPLIED }, 424 | { format: 'RES 3, L', arg:IMPLIED }, 425 | { format: 'RES 3, (HL)', arg:IMPLIED }, 426 | { format: 'RES 3, A', arg:IMPLIED }, 427 | { format: 'RES 4, B', arg:IMPLIED }, 428 | { format: 'RES 4, C', arg:IMPLIED }, 429 | { format: 'RES 4, D', arg:IMPLIED }, 430 | { format: 'RES 4, E', arg:IMPLIED }, 431 | { format: 'RES 4, H', arg:IMPLIED }, 432 | { format: 'RES 4, L', arg:IMPLIED }, 433 | { format: 'RES 4, (HL)', arg:IMPLIED }, 434 | { format: 'RES 4, A', arg:IMPLIED }, 435 | { format: 'RES 5, B', arg:IMPLIED }, 436 | { format: 'RES 5, C', arg:IMPLIED }, 437 | { format: 'RES 5, D', arg:IMPLIED }, 438 | { format: 'RES 5, E', arg:IMPLIED }, 439 | { format: 'RES 5, H', arg:IMPLIED }, 440 | { format: 'RES 5, L', arg:IMPLIED }, 441 | { format: 'RES 5, (HL)', arg:IMPLIED }, 442 | { format: 'RES 5, A', arg:IMPLIED }, 443 | { format: 'RES 6, B', arg:IMPLIED }, 444 | { format: 'RES 6, C', arg:IMPLIED }, 445 | { format: 'RES 6, D', arg:IMPLIED }, 446 | { format: 'RES 6, E', arg:IMPLIED }, 447 | { format: 'RES 6, H', arg:IMPLIED }, 448 | { format: 'RES 6, L', arg:IMPLIED }, 449 | { format: 'RES 6, (HL)', arg:IMPLIED }, 450 | { format: 'RES 6, A', arg:IMPLIED }, 451 | { format: 'RES 7, B', arg:IMPLIED }, 452 | { format: 'RES 7, C', arg:IMPLIED }, 453 | { format: 'RES 7, D', arg:IMPLIED }, 454 | { format: 'RES 7, E', arg:IMPLIED }, 455 | { format: 'RES 7, H', arg:IMPLIED }, 456 | { format: 'RES 7, L', arg:IMPLIED }, 457 | { format: 'RES 7, (HL)', arg:IMPLIED }, 458 | { format: 'RES 7, A', arg:IMPLIED }, 459 | { format: 'SET 0, B', arg:IMPLIED }, 460 | { format: 'SET 0, C', arg:IMPLIED }, 461 | { format: 'SET 0, D', arg:IMPLIED }, 462 | { format: 'SET 0, E', arg:IMPLIED }, 463 | { format: 'SET 0, H', arg:IMPLIED }, 464 | { format: 'SET 0, L', arg:IMPLIED }, 465 | { format: 'SET 0, (HL)', arg:IMPLIED }, 466 | { format: 'SET 0, A', arg:IMPLIED }, 467 | { format: 'SET 1, B', arg:IMPLIED }, 468 | { format: 'SET 1, C', arg:IMPLIED }, 469 | { format: 'SET 1, D', arg:IMPLIED }, 470 | { format: 'SET 1, E', arg:IMPLIED }, 471 | { format: 'SET 1, H', arg:IMPLIED }, 472 | { format: 'SET 1, L', arg:IMPLIED }, 473 | { format: 'SET 1, (HL)', arg:IMPLIED }, 474 | { format: 'SET 1, A', arg:IMPLIED }, 475 | { format: 'SET 2, B', arg:IMPLIED }, 476 | { format: 'SET 2, C', arg:IMPLIED }, 477 | { format: 'SET 2, D', arg:IMPLIED }, 478 | { format: 'SET 2, E', arg:IMPLIED }, 479 | { format: 'SET 2, H', arg:IMPLIED }, 480 | { format: 'SET 2, L', arg:IMPLIED }, 481 | { format: 'SET 2, (HL)', arg:IMPLIED }, 482 | { format: 'SET 2, A', arg:IMPLIED }, 483 | { format: 'SET 3, B', arg:IMPLIED }, 484 | { format: 'SET 3, C', arg:IMPLIED }, 485 | { format: 'SET 3, D', arg:IMPLIED }, 486 | { format: 'SET 3, E', arg:IMPLIED }, 487 | { format: 'SET 3, H', arg:IMPLIED }, 488 | { format: 'SET 3, L', arg:IMPLIED }, 489 | { format: 'SET 3, (HL)', arg:IMPLIED }, 490 | { format: 'SET 3, A', arg:IMPLIED }, 491 | { format: 'SET 4, B', arg:IMPLIED }, 492 | { format: 'SET 4, C', arg:IMPLIED }, 493 | { format: 'SET 4, D', arg:IMPLIED }, 494 | { format: 'SET 4, E', arg:IMPLIED }, 495 | { format: 'SET 4, H', arg:IMPLIED }, 496 | { format: 'SET 4, L', arg:IMPLIED }, 497 | { format: 'SET 4, (HL)', arg:IMPLIED }, 498 | { format: 'SET 4, A', arg:IMPLIED }, 499 | { format: 'SET 5, B', arg:IMPLIED }, 500 | { format: 'SET 5, C', arg:IMPLIED }, 501 | { format: 'SET 5, D', arg:IMPLIED }, 502 | { format: 'SET 5, E', arg:IMPLIED }, 503 | { format: 'SET 5, H', arg:IMPLIED }, 504 | { format: 'SET 5, L', arg:IMPLIED }, 505 | { format: 'SET 5, (HL)', arg:IMPLIED }, 506 | { format: 'SET 5, A', arg:IMPLIED }, 507 | { format: 'SET 6, B', arg:IMPLIED }, 508 | { format: 'SET 6, C', arg:IMPLIED }, 509 | { format: 'SET 6, D', arg:IMPLIED }, 510 | { format: 'SET 6, E', arg:IMPLIED }, 511 | { format: 'SET 6, H', arg:IMPLIED }, 512 | { format: 'SET 6, L', arg:IMPLIED }, 513 | { format: 'SET 6, (HL)', arg:IMPLIED }, 514 | { format: 'SET 6, A', arg:IMPLIED }, 515 | { format: 'SET 7, B', arg:IMPLIED }, 516 | { format: 'SET 7, C', arg:IMPLIED }, 517 | { format: 'SET 7, D', arg:IMPLIED }, 518 | { format: 'SET 7, E', arg:IMPLIED }, 519 | { format: 'SET 7, H', arg:IMPLIED }, 520 | { format: 'SET 7, L', arg:IMPLIED }, 521 | { format: 'SET 7, (HL)', arg:IMPLIED }, 522 | { format: 'SET 7, A', arg:IMPLIED } 523 | ]; 524 | 525 | export default function disassemble(cpu, pc) 526 | { 527 | function hex(v, p) { 528 | var o = v.toString(16).toUpperCase(); 529 | 530 | while( o.length < p ) 531 | o = "0"+o; 532 | return o; 533 | } 534 | 535 | function readPC() { 536 | var h = pc >> 8, 537 | l = pc & 0xFF; 538 | pc = (pc+1) & 0xFFFF; 539 | return cpu.read[h][l](); 540 | } 541 | 542 | var op = readPC(), 543 | bytes = hex(op,2), 544 | template; 545 | 546 | if (op == 0xCB) { 547 | op = readPC(); 548 | bytes += " " + hex(op,2); 549 | template = EXTENDED_INSTRUCTION_SET[op]; 550 | } else { 551 | template = BASE_INSTRUCTION_SET[op]; 552 | } 553 | 554 | if (!template) { 555 | return null; 556 | } 557 | 558 | var a, b, d; 559 | 560 | switch( template.arg ) 561 | { 562 | case WORD: 563 | b = readPC(); 564 | a = readPC(); 565 | bytes += " " + hex(b,2) + " " + hex(a,2); 566 | 567 | d = (a << 8) | b; 568 | 569 | return { op: template.format.split("%").join(hex(d,4)), hex: bytes, next: pc }; 570 | case BYTE: 571 | a = readPC(); 572 | bytes += " " + hex(a,2); 573 | 574 | return { op: template.format.split("%").join(hex(a,2)), hex: bytes, next: pc }; 575 | case SIGNED: 576 | a = readPC(); 577 | a = (a & 0x7F) - (a & 0x80); 578 | bytes += " " + hex(a,2); 579 | 580 | if(a & 0x80) 581 | a |= 0xFFFFFF00; 582 | 583 | return { op: template.format.split("%").join(a), hex: bytes, next: pc }; 584 | case RELATIVE_PC: 585 | a = readPC(); 586 | bytes += " " + hex(a,2); 587 | a = (a & 0x7F) - (a & 0x80); 588 | 589 | a = (pc + a) & 0xFFFF; 590 | 591 | return { op: template.format.split("%").join(hex(a,4)), hex: bytes, next: pc }; 592 | // Implied 593 | default: 594 | return { op: template.format, hex: bytes, next: pc }; 595 | } 596 | } 597 | -------------------------------------------------------------------------------- /src/core/ops/shift.js: -------------------------------------------------------------------------------- 1 | /*** 2 | *** Shifter ALU Instructions 3 | *** These instruction fall in the 0xCB xx instruction range, 4 | ***/ 5 | 6 | export default class ShiftOPs { 7 | swap( v ) 8 | { 9 | var n = ((v >> 4) | (v << 4)) & 0xFF; 10 | 11 | this.zf = (n === 0); 12 | this.hf = false; 13 | this.nf = false; 14 | this.cf = false; 15 | 16 | return n; 17 | }; 18 | 19 | sla( v ) 20 | { 21 | var n = (v << 1) & 0xFF; 22 | 23 | this.zf = !n; 24 | this.hf = false; 25 | this.nf = false; 26 | this.cf = (v & 0x80); 27 | 28 | return n; 29 | }; 30 | 31 | sra( v ) 32 | { 33 | var n = (v >> 1) | (v & 0x80); 34 | 35 | this.zf = !n; 36 | this.hf = false; 37 | this.nf = false; 38 | this.cf = (v & 1); 39 | 40 | return n; 41 | }; 42 | 43 | srl( v ) 44 | { 45 | var n = (v >> 1); 46 | 47 | this.zf = !n; 48 | this.hf = false; 49 | this.nf = false; 50 | this.cf = (v & 1); 51 | 52 | return n; 53 | }; 54 | 55 | rrc( v ) 56 | { 57 | var n = ((v >> 1) | (v << 7)) & 0xFF; 58 | 59 | this.zf = !n; 60 | this.hf = false; 61 | this.nf = false; 62 | this.cf = (v & 1); 63 | 64 | return n; 65 | }; 66 | 67 | rlc( v ) 68 | { 69 | var n = ((v >> 7) | (v << 1)) & 0xFF; 70 | 71 | this.zf = !n; 72 | this.hf = false; 73 | this.nf = false; 74 | this.cf = (v & 0x80); 75 | 76 | return n; 77 | }; 78 | 79 | rr( v ) 80 | { 81 | var n = (this.cf ? 0x80 : 0) | (v >> 1); 82 | 83 | this.zf = !n; 84 | this.hf = false; 85 | this.nf = false; 86 | this.cf = (v & 1); 87 | 88 | return n; 89 | }; 90 | 91 | rl( v ) 92 | { 93 | var n = ((this.cf ? 1 : 0) | (v << 1)) & 0xFF; 94 | 95 | this.zf = !n; 96 | this.hf = false; 97 | this.nf = false; 98 | this.cf = (v & 0x80); 99 | 100 | return n; 101 | }; 102 | 103 | bit( v, bit ) 104 | { 105 | this.zf = !(v & (1 << bit)); 106 | this.hf = true; 107 | this.nf = false; 108 | }; 109 | 110 | set( v, bit ) 111 | { 112 | return v | (1<> 7) | (this.a << 1)) & 0xFF; 7 | this.cf = (this.a & 0x80); 8 | this.zf = false; 9 | this.nf = false; 10 | this.hf = false; 11 | this.a = n; 12 | }; 13 | 14 | rrca() { 15 | var n = ((this.a >> 1) | (this.a << 7)) & 0xFF; 16 | this.cf = (this.a & 1); 17 | this.zf = false; 18 | this.nf = false; 19 | this.hf = false; 20 | this.a = n; 21 | }; 22 | 23 | rla() { 24 | var n = ((this.cf ? 1 : 0) | (this.a << 1)) & 0xFF; 25 | this.cf = (this.a & 0x80); 26 | this.zf = false; 27 | this.nf = false; 28 | this.hf = false; 29 | this.a = n; 30 | }; 31 | 32 | rra() { 33 | var n = (this.a >> 1) | (this.cf ? 0x80 : 0); 34 | this.cf = (this.a & 1); 35 | this.zf = false; 36 | this.nf = false; 37 | this.hf = false; 38 | this.a = n; 39 | }; 40 | 41 | add16( v ) { 42 | var q = this.hl; 43 | var r = q + v; 44 | 45 | this.nf = false; 46 | this.hf = ((q & 0xFFF) + (v & 0xFFF)) >= 0x1000; 47 | this.cf = r >= 0x10000; 48 | 49 | this.h = (r >> 8) & 0xFF; 50 | this.l = r & 0xFF; 51 | }; 52 | 53 | addSP() { 54 | var q = this.sp; 55 | var v = this.nextSignedByte(); 56 | var r = q + v; 57 | 58 | this.zf = false; 59 | this.nf = false; 60 | this.cf = ((q & 0xFF) + (v & 0xFF)) >= 0x100; 61 | this.hf = ((q & 0xF) + (v & 0xF)) >= 0x10; 62 | 63 | return r & 0xFFFF; 64 | }; 65 | 66 | incHL() { 67 | var v = (this.hl + 1) & 0xFFFF; 68 | this.h = v >> 8; 69 | this.l = v & 0xFF; 70 | }; 71 | 72 | decHL() { 73 | var v = (this.hl - 1) & 0xFFFF; 74 | this.h = v >> 8; 75 | this.l = v & 0xFF; 76 | }; 77 | 78 | incDE() { 79 | var v = (this.de + 1) & 0xFFFF; 80 | this.d = v >> 8; 81 | this.e = v & 0xFF; 82 | }; 83 | 84 | decDE() { 85 | var v = (this.de - 1) & 0xFFFF; 86 | this.d = v >> 8; 87 | this.e = v & 0xFF; 88 | }; 89 | 90 | incBC() { 91 | var v = (this.bc + 1) & 0xFFFF; 92 | this.b = v >> 8; 93 | this.c = v & 0xFF; 94 | }; 95 | 96 | decBC() { 97 | var v = (this.bc - 1) & 0xFFFF; 98 | this.b = v >> 8; 99 | this.c = v & 0xFF; 100 | }; 101 | 102 | inc( v ){ 103 | var o = (v + 1) & 0xFF; 104 | 105 | this.zf = !o; 106 | this.nf = false; 107 | this.hf = (v & 0xF0) !== (o & 0xF0); 108 | 109 | return o; 110 | }; 111 | 112 | dec( v ){ 113 | var o = (v - 1) & 0xFF; 114 | 115 | this.zf = !o; 116 | this.nf = true; 117 | this.hf = (v & 0xF0) !== (o & 0xF0); 118 | 119 | return o; 120 | }; 121 | 122 | and( v ){ 123 | this.a &= v; 124 | this.zf = !this.a; 125 | this.nf = false; 126 | this.hf = true; 127 | this.cf = false; 128 | }; 129 | 130 | or(v) { 131 | this.a |= v; 132 | this.zf = !this.a; 133 | this.nf = false; 134 | this.hf = false; 135 | this.cf = false; 136 | }; 137 | 138 | xor(v) { 139 | this.a ^= v; 140 | this.zf = !this.a; 141 | this.nf = false; 142 | this.hf = false; 143 | this.cf = false; 144 | }; 145 | 146 | add( v ) { 147 | this.hf = ((this.a & 0xF) + (v & 0xF)) >= 0x10; 148 | 149 | this.a = this.a + v; 150 | this.cf = this.a >= 0x100; 151 | this.nf = false; 152 | this.a &= 0xFF; 153 | 154 | this.zf = !this.a; 155 | }; 156 | 157 | adc( v ) { 158 | this.hf = ((this.a & 0xF) + (v & 0xF) + (this.cf ? 1 : 0)) >= 0x10; 159 | 160 | this.a = this.a + v + (this.cf ? 1 : 0); 161 | this.cf = this.a >= 0x100; 162 | this.nf = false; 163 | this.a &= 0xFF; 164 | 165 | this.zf = !this.a; 166 | }; 167 | 168 | sub( v ) { 169 | this.hf = ((this.a & 0xF) - (v & 0xF)) < 0; 170 | 171 | this.a = this.a - v; 172 | this.cf = this.a < 0; 173 | this.nf = true; 174 | this.a &= 0xFF; 175 | 176 | this.zf = !this.a; 177 | }; 178 | 179 | sbc( v ) { 180 | this.hf = ((this.a & 0xF) - (v & 0xF) - (this.cf ? 1 : 0)) < 0; 181 | 182 | this.a = this.a - v - (this.cf ? 1 : 0); 183 | this.cf = this.a < 0; 184 | this.nf = true; 185 | this.a &= 0xFF; 186 | 187 | this.zf = !this.a; 188 | }; 189 | 190 | cp( v ){ 191 | this.hf = ((this.a & 0xF) - (v & 0xF)) < 0; 192 | 193 | var o = this.a - v; 194 | this.cf = o < 0; 195 | this.nf = true; 196 | this.zf = !(o & 0xFF); 197 | }; 198 | 199 | cpl() { 200 | this.a = this.a ^ 0xFF; 201 | this.hf = true; 202 | this.nf = true; 203 | }; 204 | 205 | // --- THIS INSTRUCTION REQUIRES A CONFIDENCE CHECK! 206 | daa() { 207 | var result = this.a; 208 | 209 | if ( this.nf ) 210 | { 211 | if ( this.hf ) { 212 | result -= 6; 213 | if ( !this.cf ) 214 | result &= 0xFF; 215 | } 216 | 217 | if ( this.cf ) 218 | result -= 0x60; 219 | } else { 220 | if ( this.hf || ( result & 0x0F ) > 9 ) 221 | result += 6; 222 | if ( this.cf || result > 0x9F ) 223 | result += 0x60; 224 | } 225 | 226 | this.a = result & 0xFF; 227 | this.zf = (this.a === 0); 228 | this.cf = this.cf || ((result & 0xFF00) !== 0); 229 | this.hf = false; 230 | }; 231 | 232 | stepBase () { 233 | var opCode = this.nextByte(), 234 | h, l, o; 235 | 236 | switch(opCode) 237 | { 238 | case 0x00: 239 | return 1; 240 | 241 | // --- Load instructions 242 | case 0x40: 243 | return 1; 244 | case 0x41: 245 | this.b = this.c; 246 | return 1; 247 | case 0x42: 248 | this.b = this.d; 249 | return 1; 250 | case 0x43: 251 | this.b = this.e; 252 | return 1; 253 | case 0x44: 254 | this.b = this.h; 255 | return 1; 256 | case 0x45: 257 | this.b = this.l; 258 | return 1; 259 | case 0x46: 260 | this.b = this.read[this.h][this.l](); 261 | return 2; 262 | case 0x47: 263 | this.b = this.a; 264 | return 1; 265 | case 0x48: 266 | this.c = this.b; 267 | return 1; 268 | case 0x49: 269 | return 1; 270 | case 0x4A: 271 | this.c = this.d; 272 | return 1; 273 | case 0x4B: 274 | this.c = this.e; 275 | return 1; 276 | case 0x4C: 277 | this.c = this.h; 278 | return 1; 279 | case 0x4D: 280 | this.c = this.l; 281 | return 1; 282 | case 0x4E: 283 | this.c = this.read[this.h][this.l](); 284 | return 2; 285 | case 0x4F: 286 | this.c = this.a; 287 | return 1; 288 | case 0x50: 289 | this.d = this.b; 290 | return 1; 291 | case 0x51: 292 | this.d = this.c; 293 | return 1; 294 | case 0x52: 295 | return 1; 296 | case 0x53: 297 | this.d = this.e; 298 | return 1; 299 | case 0x54: 300 | this.d = this.h; 301 | return 1; 302 | case 0x55: 303 | this.d = this.l; 304 | return 1; 305 | case 0x56: 306 | this.d = this.read[this.h][this.l](); 307 | return 2; 308 | case 0x57: 309 | this.d = this.a; 310 | return 1; 311 | case 0x58: 312 | this.e = this.b; 313 | return 1; 314 | case 0x59: 315 | this.e = this.c; 316 | return 1; 317 | case 0x5A: 318 | this.e = this.d; 319 | return 1; 320 | case 0x5B: 321 | return 1; 322 | case 0x5C: 323 | this.e = this.h; 324 | return 1; 325 | case 0x5D: 326 | this.e = this.l; 327 | return 1; 328 | case 0x5E: 329 | this.e = this.read[this.h][this.l](); 330 | return 2; 331 | case 0x5F: 332 | this.e = this.a; 333 | return 1; 334 | case 0x60: 335 | this.h = this.b; 336 | return 1; 337 | case 0x61: 338 | this.h = this.c; 339 | return 1; 340 | case 0x62: 341 | this.h = this.d; 342 | return 1; 343 | case 0x63: 344 | this.h = this.e; 345 | return 1; 346 | case 0x64: 347 | return 1; 348 | case 0x65: 349 | this.h = this.l; 350 | return 1; 351 | case 0x66: 352 | this.h = this.read[this.h][this.l](); 353 | return 2; 354 | case 0x67: 355 | this.h = this.a; 356 | return 1; 357 | case 0x68: 358 | this.l = this.b; 359 | return 1; 360 | case 0x69: 361 | this.l = this.c; 362 | return 1; 363 | case 0x6A: 364 | this.l = this.d; 365 | return 1; 366 | case 0x6B: 367 | this.l = this.e; 368 | return 1; 369 | case 0x6C: 370 | this.l = this.h; 371 | return 1; 372 | case 0x6D: 373 | return 1; 374 | case 0x6E: 375 | this.l = this.read[this.h][this.l](); 376 | return 2; 377 | case 0x6F: 378 | this.l = this.a; 379 | return 1; 380 | case 0x70: 381 | this.write[this.h][this.l](this.b); 382 | return 2; 383 | case 0x71: 384 | this.write[this.h][this.l](this.c); 385 | return 2; 386 | case 0x72: 387 | this.write[this.h][this.l](this.d); 388 | return 2; 389 | case 0x73: 390 | this.write[this.h][this.l](this.e); 391 | return 2; 392 | case 0x74: 393 | this.write[this.h][this.l](this.h); 394 | return 2; 395 | case 0x75: 396 | this.write[this.h][this.l](this.l); 397 | return 2; 398 | case 0x77: 399 | this.write[this.h][this.l](this.a); 400 | return 2; 401 | case 0x78: 402 | this.a = this.b; 403 | return 1; 404 | case 0x79: 405 | this.a = this.c; 406 | return 1; 407 | case 0x7A: 408 | this.a = this.d; 409 | return 1; 410 | case 0x7B: 411 | this.a = this.e; 412 | return 1; 413 | case 0x7C: 414 | this.a = this.h; 415 | return 1; 416 | case 0x7D: 417 | this.a = this.l; 418 | return 1; 419 | case 0x7E: 420 | this.a = this.read[this.h][this.l](); 421 | return 2; 422 | case 0x7F: 423 | return 1; 424 | 425 | case 0x06: 426 | this.b = this.nextByte(); 427 | return 2; 428 | case 0x0E: 429 | this.c = this.nextByte(); 430 | return 2; 431 | case 0x16: 432 | this.d = this.nextByte(); 433 | return 2; 434 | case 0x1E: 435 | this.e = this.nextByte(); 436 | return 2; 437 | case 0x26: 438 | this.h = this.nextByte(); 439 | return 2; 440 | case 0x2E: 441 | this.l = this.nextByte(); 442 | return 2; 443 | case 0x36: 444 | this.write[this.h][this.l](this.nextByte()); 445 | return 3; 446 | case 0x3E: 447 | this.a = this.nextByte(); 448 | return 2; 449 | 450 | case 0x01: 451 | this.c = this.nextByte(); this.b = this.nextByte(); 452 | return 3; 453 | case 0x11: 454 | this.e = this.nextByte(); this.d = this.nextByte(); 455 | return 3; 456 | case 0x21: 457 | this.l = this.nextByte(); this.h = this.nextByte(); 458 | return 3; 459 | case 0x31: 460 | this.sp = this.nextWord(); 461 | return 3; 462 | case 0xF9: 463 | this.sp = this.hl; 464 | return 2; 465 | 466 | case 0x02: 467 | this.write[this.b][this.c](this.a); 468 | return 2; 469 | case 0x12: 470 | this.write[this.d][this.e](this.a); 471 | return 2; 472 | case 0x0A: 473 | this.a = this.read[this.b][this.c](); 474 | return 2; 475 | case 0x1A: 476 | this.a = this.read[this.d][this.e](); 477 | return 2; 478 | case 0xEA: 479 | l = this.nextByte(); 480 | h = this.nextByte(); 481 | this.write[h][l](this.a); 482 | return 4; 483 | case 0xFA: 484 | l = this.nextByte(); 485 | h = this.nextByte(); 486 | this.a = this.read[h][l](); 487 | return 4; 488 | 489 | case 0xE0: 490 | this.write[0xFF][this.nextByte()](this.a); 491 | return 3; 492 | case 0xE2: 493 | this.write[0xFF][this.c](this.a); 494 | return 2; 495 | case 0xF0: 496 | this.a = this.read[0xFF][this.nextByte()](); 497 | return 3; 498 | case 0xF2: 499 | this.a = this.read[0xFF][this.c](); 500 | return 3; 501 | 502 | case 0x08: 503 | l = this.nextByte(); 504 | h = this.nextByte(); 505 | this.write[h][l](this.sp&0xFF); 506 | h += ((++l) >> 8); 507 | this.write[h&0xFF][l&0xFF](this.sp>>8); 508 | return 5; 509 | 510 | // --- LDI and LDD instructions 511 | case 0x22: 512 | this.write[this.h][this.l](this.a); 513 | this.incHL(); 514 | return 2; 515 | case 0x2A: 516 | this.a = this.read[this.h][this.l](); 517 | this.incHL(); 518 | return 2; 519 | case 0x32: 520 | this.write[this.h][this.l](this.a); 521 | this.decHL(); 522 | return 2; 523 | case 0x3A: 524 | this.a = this.read[this.h][this.l](); 525 | this.decHL(); 526 | return 2; 527 | 528 | // --- ALU operations 529 | case 0x80: 530 | this.add(this.b); 531 | return 1; 532 | case 0x81: 533 | this.add(this.c); 534 | return 1; 535 | case 0x82: 536 | this.add(this.d); 537 | return 1; 538 | case 0x83: 539 | this.add(this.e); 540 | return 1; 541 | case 0x84: 542 | this.add(this.h); 543 | return 1; 544 | case 0x85: 545 | this.add(this.l); 546 | return 1; 547 | case 0x86: 548 | this.add(this.read[this.h][this.l]()); 549 | return 2; 550 | case 0x87: 551 | this.add(this.a); 552 | return 1; 553 | case 0x88: 554 | this.adc(this.b); 555 | return 1; 556 | case 0x89: 557 | this.adc(this.c); 558 | return 1; 559 | case 0x8A: 560 | this.adc(this.d); 561 | return 1; 562 | case 0x8B: 563 | this.adc(this.e); 564 | return 1; 565 | case 0x8C: 566 | this.adc(this.h); 567 | return 1; 568 | case 0x8D: 569 | this.adc(this.l); 570 | return 1; 571 | case 0x8E: 572 | this.adc(this.read[this.h][this.l]()); 573 | return 2; 574 | case 0x8F: 575 | this.adc(this.a); 576 | return 1; 577 | case 0x90: 578 | this.sub(this.b); 579 | return 1; 580 | case 0x91: 581 | this.sub(this.c); 582 | return 1; 583 | case 0x92: 584 | this.sub(this.d); 585 | return 1; 586 | case 0x93: 587 | this.sub(this.e); 588 | return 1; 589 | case 0x94: 590 | this.sub(this.h); 591 | return 1; 592 | case 0x95: 593 | this.sub(this.l); 594 | return 1; 595 | case 0x96: 596 | this.sub(this.read[this.h][this.l]()); 597 | return 2; 598 | case 0x97: 599 | this.sub(this.a); 600 | return 1; 601 | case 0x98: 602 | this.sbc(this.b); 603 | return 1; 604 | case 0x99: 605 | this.sbc(this.c); 606 | return 1; 607 | case 0x9A: 608 | this.sbc(this.d); 609 | return 1; 610 | case 0x9B: 611 | this.sbc(this.e); 612 | return 1; 613 | case 0x9C: 614 | this.sbc(this.h); 615 | return 1; 616 | case 0x9D: 617 | this.sbc(this.l); 618 | return 1; 619 | case 0x9E: 620 | this.sbc(this.read[this.h][this.l]()); 621 | return 2; 622 | case 0x9F: 623 | this.sbc(this.a); 624 | return 1; 625 | case 0xA0: 626 | this.and(this.b); 627 | return 1; 628 | case 0xA1: 629 | this.and(this.c); 630 | return 1; 631 | case 0xA2: 632 | this.and(this.d); 633 | return 1; 634 | case 0xA3: 635 | this.and(this.e); 636 | return 1; 637 | case 0xA4: 638 | this.and(this.h); 639 | return 1; 640 | case 0xA5: 641 | this.and(this.l); 642 | return 1; 643 | case 0xA6: 644 | this.and(this.read[this.h][this.l]()); 645 | return 2; 646 | case 0xA7: 647 | this.and(this.a); 648 | return 1; 649 | case 0xA8: 650 | this.xor(this.b); 651 | return 1; 652 | case 0xA9: 653 | this.xor(this.c); 654 | return 1; 655 | case 0xAA: 656 | this.xor(this.d); 657 | return 1; 658 | case 0xAB: 659 | this.xor(this.e); 660 | return 1; 661 | case 0xAC: 662 | this.xor(this.h); 663 | return 1; 664 | case 0xAD: 665 | this.xor(this.l); 666 | return 1; 667 | case 0xAE: 668 | this.xor(this.read[this.h][this.l]()); 669 | return 2; 670 | case 0xAF: 671 | this.xor(this.a); 672 | return 1; 673 | case 0xB0: 674 | this.or(this.b); 675 | return 1; 676 | case 0xB1: 677 | this.or(this.c); 678 | return 1; 679 | case 0xB2: 680 | this.or(this.d); 681 | return 1; 682 | case 0xB3: 683 | this.or(this.e); 684 | return 1; 685 | case 0xB4: 686 | this.or(this.h); 687 | return 1; 688 | case 0xB5: 689 | this.or(this.l); 690 | return 1; 691 | case 0xB6: 692 | this.or(this.read[this.h][this.l]()); 693 | return 2; 694 | case 0xB7: 695 | this.or(this.a); 696 | return 1; 697 | case 0xB8: 698 | this.cp(this.b); 699 | return 1; 700 | case 0xB9: 701 | this.cp(this.c); 702 | return 1; 703 | case 0xBA: 704 | this.cp(this.d); 705 | return 1; 706 | case 0xBB: 707 | this.cp(this.e); 708 | return 1; 709 | case 0xBC: 710 | this.cp(this.h); 711 | return 1; 712 | case 0xBD: 713 | this.cp(this.l); 714 | return 1; 715 | case 0xBE: 716 | this.cp(this.read[this.h][this.l]()); 717 | return 2; 718 | case 0xBF: 719 | this.cp(this.a); 720 | return 1; 721 | case 0xC6: 722 | this.add(this.nextByte()); 723 | return 2; 724 | case 0xCE: 725 | this.adc(this.nextByte()); 726 | return 2; 727 | case 0xD6: 728 | this.sub(this.nextByte()); 729 | return 2; 730 | case 0xDE: 731 | this.sbc(this.nextByte()); 732 | return 2; 733 | case 0xE6: 734 | this.and(this.nextByte()); 735 | return 2; 736 | case 0xEE: 737 | this.xor(this.nextByte()); 738 | return 2; 739 | case 0xF6: 740 | this.or(this.nextByte()); 741 | return 2; 742 | case 0xFE: 743 | this.cp(this.nextByte()); 744 | return 2; 745 | 746 | // --- Branch Instructions 747 | case 0x18: 748 | this.pc = this.nextRelative(); 749 | return 3; 750 | case 0x20: 751 | o = this.nextRelative(); 752 | if(!this.zf) { this.pc = o; return 3; } else return 2; 753 | break ; 754 | case 0x28: 755 | o = this.nextRelative(); 756 | if(this.zf) { this.pc = o; return 3; } else return 2; 757 | break ; 758 | case 0x30: 759 | o = this.nextRelative(); 760 | if(!this.cf) { this.pc = o; return 3; } else return 2; 761 | break ; 762 | case 0x38: 763 | o = this.nextRelative(); 764 | if(this.cf) { this.pc = o; return 3; } else return 2; 765 | break ; 766 | case 0xC3: 767 | this.pc = this.nextWord(); 768 | return 4; 769 | case 0xC2: 770 | o = this.nextWord(); if(!this.zf) { this.pc = o; return 4; } else return 3; 771 | break ; 772 | case 0xCA: 773 | o = this.nextWord(); if(this.zf) { this.pc = o; return 4; } else return 3; 774 | break ; 775 | case 0xD2: 776 | o = this.nextWord(); if(!this.cf) { this.pc = o; return 4; } else return 3; 777 | break ; 778 | case 0xDA: 779 | o = this.nextWord(); if(this.cf) { this.pc = o; return 4; } else return 3; 780 | break ; 781 | case 0xE9: 782 | this.pc = this.hl; 783 | return 1; 784 | 785 | // --- RST Instructions 786 | case 0xC7: 787 | this.call(0x00); 788 | return 4; 789 | case 0xCF: 790 | this.call(0x08); 791 | return 4; 792 | case 0xD7: 793 | this.call(0x10); 794 | return 4; 795 | case 0xDF: 796 | this.call(0x18); 797 | return 4; 798 | case 0xE7: 799 | this.call(0x20); 800 | return 4; 801 | case 0xEF: 802 | this.call(0x28); 803 | return 4; 804 | case 0xF7: 805 | this.call(0x30); 806 | return 4; 807 | case 0xFF: 808 | this.call(0x38); 809 | return 4; 810 | 811 | // --- Call instructions 812 | case 0xCD: 813 | this.call(this.nextWord()); 814 | return 6; 815 | case 0xC4: 816 | o = this.nextWord(); if(!this.zf) { this.call(o); return 6; } else return 3; 817 | break ; 818 | case 0xCC: 819 | o = this.nextWord(); if(this.zf) { this.call(o); return 6; } else return 3; 820 | break ; 821 | case 0xD4: 822 | o = this.nextWord(); if(!this.cf) { this.call(o); return 6; } else return 3; 823 | break ; 824 | case 0xDC: 825 | o = this.nextWord(); if(this.cf) { this.call(o); return 6; } else return 3; 826 | break ; 827 | 828 | // --- Return instructions 829 | case 0xC9: 830 | this.ret(); 831 | return 4; 832 | case 0xD9: 833 | this.ret(); 834 | this.irq_master = true; 835 | this.invalidate(); 836 | return 4; 837 | case 0xC0: 838 | if(!this.zf) { this.ret(); return 5; } else return 2; 839 | break ; 840 | case 0xC8: 841 | if(this.zf) { this.ret(); return 5; } else return 2; 842 | break ; 843 | case 0xD0: 844 | if(!this.cf) { this.ret(); return 5; } else return 2; 845 | break ; 846 | case 0xD8: 847 | if(this.cf) { this.ret(); return 5; } else return 2; 848 | break ; 849 | 850 | // --- Halt instructions 851 | case 0x10: 852 | // Put the CPU in halt mode 853 | if( this.prepareSpeed ) 854 | this.setCPUSpeed(!this.doubleSpeed); 855 | 856 | // Note: this SHOULD fall through 857 | case 0x76: 858 | if( !this.irq_master ) 859 | this.delayByte(); 860 | this.halted = true; 861 | this.invalidate(); 862 | return 1; 863 | 864 | // --- Increment/Decrement block 865 | case 0x04: 866 | this.b = this.inc(this.b); 867 | return 1; 868 | case 0x05: 869 | this.b = this.dec(this.b); 870 | return 1; 871 | case 0x0C: 872 | this.c = this.inc(this.c); 873 | return 1; 874 | case 0x0D: 875 | this.c = this.dec(this.c); 876 | return 1; 877 | case 0x14: 878 | this.d = this.inc(this.d); 879 | return 1; 880 | case 0x15: 881 | this.d = this.dec(this.d); 882 | return 1; 883 | case 0x1C: 884 | this.e = this.inc(this.e); 885 | return 1; 886 | case 0x1D: 887 | this.e = this.dec(this.e); 888 | return 1; 889 | case 0x24: 890 | this.h = this.inc(this.h); 891 | return 1; 892 | case 0x25: 893 | this.h = this.dec(this.h); 894 | return 1; 895 | case 0x2C: 896 | this.l = this.inc(this.l); 897 | return 1; 898 | case 0x2D: 899 | this.l = this.dec(this.l); 900 | return 1; 901 | case 0x34: 902 | this.write[this.h][this.l](this.inc(this.read[this.h][this.l]())); 903 | return 3; 904 | case 0x35: 905 | this.write[this.h][this.l](this.dec(this.read[this.h][this.l]())); 906 | return 3; 907 | case 0x3C: 908 | this.a = this.inc(this.a); 909 | return 1; 910 | case 0x3D: 911 | this.a = this.dec(this.a); 912 | return 1; 913 | case 0x03: 914 | this.incBC(); 915 | return 2; 916 | case 0x0B: 917 | this.decBC(); 918 | return 2; 919 | case 0x13: 920 | this.incDE(); 921 | return 2; 922 | case 0x1B: 923 | this.decDE(); 924 | return 2; 925 | case 0x23: 926 | this.incHL(); 927 | return 2; 928 | case 0x2B: 929 | this.decHL(); 930 | return 2; 931 | case 0x33: 932 | this.sp = (this.sp + 1) & 0xFFFF; 933 | return 2; 934 | case 0x3B: 935 | this.sp = (this.sp - 1) & 0xFFFF; 936 | return 2; 937 | 938 | // --- Stack operations 939 | case 0xC1: 940 | this.c = this.pop(); this.b = this.pop(); 941 | return 3; 942 | case 0xC5: 943 | this.push(this.b); this.push(this.c); 944 | return 4; 945 | case 0xD1: 946 | this.e = this.pop(); this.d = this.pop(); 947 | return 3; 948 | case 0xD5: 949 | this.push(this.d); this.push(this.e); 950 | return 4; 951 | case 0xE1: 952 | this.l = this.pop(); this.h = this.pop(); 953 | return 3; 954 | case 0xE5: 955 | this.push(this.h); this.push(this.l); 956 | return 4; 957 | case 0xF1: 958 | this.f = this.pop(); this.a = this.pop(); 959 | return 3; 960 | case 0xF5: 961 | this.push(this.a); this.push(this.f); 962 | return 4; 963 | 964 | // --- Shifter Instructions 965 | case 0x07: 966 | this.rlca(); 967 | return 1; 968 | case 0x0F: 969 | this.rrca(); 970 | return 1; 971 | case 0x17: 972 | this.rla(); 973 | return 1; 974 | case 0x1F: 975 | this.rra(); 976 | return 1; 977 | 978 | // --- 16bit ALU 979 | case 0x09: 980 | this.add16(this.bc); 981 | return 2; 982 | case 0x19: 983 | this.add16(this.de); 984 | return 2; 985 | case 0x29: 986 | this.add16(this.hl); 987 | return 2; 988 | case 0x39: 989 | this.add16(this.sp); 990 | return 2; 991 | case 0xE8: 992 | this.sp = this.addSP(); 993 | return 4; 994 | case 0xF8: 995 | o = this.addSP(); 996 | this.l = o & 0xFF; 997 | this.h = o >> 8; 998 | return 3; 999 | 1000 | // --- Flags 1001 | case 0x37: 1002 | this.cf = true; 1003 | this.nf = false; 1004 | this.hf = false; 1005 | return 1; 1006 | case 0x3F: 1007 | this.cf = !this.cf; 1008 | this.nf = false; 1009 | this.hf = false; 1010 | return 1; 1011 | case 0xF3: 1012 | this.irq_master = false; 1013 | this.invalidate(); 1014 | return 1; 1015 | case 0xFB: 1016 | this.irq_master = true; 1017 | this.invalidate(); 1018 | return 1; 1019 | 1020 | // --- Special registers 1021 | case 0x27: 1022 | this.daa(); 1023 | return 1; 1024 | case 0x2F: 1025 | this.cpl(); 1026 | return 1; 1027 | case 0xCB: 1028 | return this.stepExtended(); 1029 | 1030 | // --- Invalid instruction 1031 | default: 1032 | this.halted = true; 1033 | this.invalidate(); 1034 | throw "The system crashed."; 1035 | } 1036 | } 1037 | } 1038 | --------------------------------------------------------------------------------