Major updates

Added in gfiles and updated Alloy.
This commit is contained in:
TheEmeraldStarr 2020-09-10 13:02:04 -07:00
parent fa247936c1
commit 638b3eaa1d
681 changed files with 56525 additions and 37 deletions

View file

@ -49,49 +49,26 @@
</script>
<div class="d-flex align-items-center order-12" style="height:200px;">
<div class="container text-center justify-content-center align-items-center button" style="color: rgb(255,255,255);filter: blur(0px);background-color: #000000;">
<form id="unblocker-form" action="b" method="get">
<form id="scontainer" action="url" method="get">
<p style="font-size: 46px;">Alloy Proxy</p>
<p style="font-family: 'Montserrat Alternates', sans-serif;">A fast, lightweight proxy developed by Titanium Network.</p>
<div class="input-group">
<div class="input-group-prepend"></div><input class="bg-white border rounded-0 border-warning shadow-lg form-control form-control-lg" type="text" name="url" value="https://google.com" inputmode="url" style="width: 194px;font-family: 'Montserrat Alternates', sans-serif;">
<div class="input-group-append"><button class="btn btn-dark btn-lg bg-dark border rounded-0 border-warning shadow-lg select" type="submit" style="width: 78px;padding: 8px;margin: 0px;height: 48px;font-family: 'Montserrat Alternates', sans-serif;font-size: 16px;">Alloy</button>
<button class="btn btn-dark btn-lg bg-dark border rounded-0 border-warning shadow-lg" type="submit" style="width: 97px;padding: 8px;margin: 0px;height: 48px;font-family: 'Montserrat Alternates', sans-serif;font-size: 14px;">Node (Old)</button>
</div>
<div class="input-group" id="scontainer">
<div class="input-group-prepend"></div><input class="bg-white border rounded-0 border-warning shadow-lg form-control form-control-lg" type="text" id="url" name="url" value="https://google.com" inputmode="url" style="width: 194px;font-family: 'Montserrat Alternates', sans-serif;">
<div class="input-group-append"><button class="btn btn-dark btn-lg bg-dark border rounded-0 border-warning shadow-lg select" type="submit" style="width: 78px;padding: 8px;margin: 0px;height: 48px;font-family: 'Montserrat Alternates', sans-serif;font-size: 16px;">Alloy</button></div>
</div>
<div>
<script>
function $(id) {
return document.getElementById(id);
}
$('unblocker-form').onsubmit = function() {
var url = $('url').value;
if (url.substr(0, 4) != "http") {
url = "http://" + url;
}
window.location.pathname = 'b?url=' + url;
return false;
};
function checkError() {
var search = window.location.search;
var start = search.indexOf('error=');
if (start > -1) {
var stop = search.indexOf('&', start);
if (stop == -1) {
stop = undefined;
// Add active class to the current button (highlight it)
var header = document.getElementById("scontainer");
var btns = header.getElementsByClassName("select");
for (var i = 0; i < btns.length; i++) {
btns[i].addEventListener("click", function() {
var current = document.getElementsByClassName("active");
if (current.length > 0) {
current[0].className = current[0].className.replace(" active", "");
}
// +6 for "error="
var err = search.substr(start + 6, stop);
var $error = $('error');
$error.innerText = $error.textContent = decodeURIComponent(err);
$error.style.display = "block";
}
}
window.onload = function() {
$('url').focus();
checkError();
this.className += " active";
});
}
</script>
</div>

View file

@ -0,0 +1 @@
//window.history.replaceState('', '', '/');

82
public/css/style.css Normal file
View file

@ -0,0 +1,82 @@
.gembed {
width: 100%;
height: 100%;
top: 0;
bottom: 0;
left: 0;
right: 0;
position: fixed;
}
.fbody {
background-color: black;
color: white;
font-family: sans-serif;
}
.glink {
display: block;
text-decoration: none;
color: black;
font: 18px;
border: 1px solid #ddd;
margin-top: -1px;
background-color: #f6f6f6;
padding: 12px;
transition: 0.2s ease;
}
.glink:hover {
background-color: #eee;
text-shadow: 0 0 5px rgba(0,0,0,0.4);
}
#gsearchbar {
outline: none;
width: 374px;
top: 12px;
font-size: 16px;
border: 1px solid rgba(0,0,0,0.3);
padding: 12px;
margin-bottom: 12px;
transition: 0.2s ease;
}
#gsearchbar:focus {
background-color: #eee;
box-shadow: inset 0 0 5px 1px rgba(0,0,0,0.4);
}
.container {
padding: 20px;
}
.box {
margin: auto;
background-color: white;
padding: 10px 10px 15px 10px;
width: 400px;
height: 570px;
}
#glist {
overflow-x: hidden;
overflow-y: scroll;
width: 400px;
height: 520px;
padding: 0;
text-align: center;
}
#glist h2 {
display: block;
text-decoration: none;
color: white;
font: 18px;
border: 1px solid #060606;
margin: -1px 0 0 0;
background-color: #060606;
padding: 12px;
font-weight: bold;
user-select: none;
}

1
public/gfiles/f.html Normal file
View file

@ -0,0 +1 @@
<!DOCTYPE html><html><head><title>Flash Game</title><link rel="stylesheet" href="../css/style.css"><script>function loadswf(){var a,b,c; a="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/"; b=window.location.hash.substring(1)+".swf"; c=document.body; c.innerHTML='<object class="gembed" data="'+a+b+'" type="application/x-shockwave-flash"><param name="wmode" value="direct" /></object>';};</script></head><body onload="loadswf();" class="gbody"></body></html>

View file

@ -0,0 +1,653 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceCPU(IOCore) {
this.IOCore = IOCore;
}
GameBoyAdvanceCPU.prototype.initialize = function () {
this.memory = this.IOCore.memory;
this.wait = this.IOCore.wait;
this.mul64ResultHigh = 0; //Scratch MUL64.
this.mul64ResultLow = 0; //Scratch MUL64.
this.initializeRegisters();
this.ARM = new ARMInstructionSet(this);
this.THUMB = new THUMBInstructionSet(this);
//this.swi = new GameBoyAdvanceSWI(this);
this.IOCore.assignInstructionCoreReferences(this.ARM, this.THUMB);
}
GameBoyAdvanceCPU.prototype.initializeRegisters = function () {
/*
R0-R7 Are known as the low registers.
R8-R12 Are the high registers.
R13 is the stack pointer.
R14 is the link register.
R15 is the program counter.
CPSR is the program status register.
SPSR is the saved program status register.
*/
//Normal R0-R15 Registers:
this.registers = getInt32Array(16);
//Used to copy back the R8-R14 state for normal operations:
this.registersUSR = getInt32Array(7);
//Fast IRQ mode registers (R8-R14):
this.registersFIQ = getInt32Array(7);
//Supervisor mode registers (R13-R14):
this.registersSVC = getInt32Array(2);
//Abort mode registers (R13-R14):
this.registersABT = getInt32Array(2);
//IRQ mode registers (R13-R14):
this.registersIRQ = getInt32Array(2);
//Undefined mode registers (R13-R14):
this.registersUND = getInt32Array(2);
//CPSR Register:
this.branchFlags = ARMCPSRAttributeTable();
this.modeFlags = 0xD3;
//Banked SPSR Registers:
this.SPSR = getUint16Array(5);
this.SPSR[0] = 0xD3; //FIQ
this.SPSR[1] = 0xD3; //IRQ
this.SPSR[2] = 0xD3; //Supervisor
this.SPSR[3] = 0xD3; //Abort
this.SPSR[4] = 0xD3; //Undefined
this.triggeredIRQ = 0; //Pending IRQ found.
//Pre-initialize stack pointers if no BIOS loaded:
if (this.IOCore.SKIPBoot) {
this.HLEReset();
}
//Start in fully bubbled pipeline mode:
this.IOCore.flagBubble();
}
GameBoyAdvanceCPU.prototype.HLEReset = function () {
this.registersSVC[0] = 0x3007FE0;
this.registersIRQ[0] = 0x3007FA0;
this.registers[13] = 0x3007F00;
this.registers[15] = 0x8000000;
this.modeFlags = this.modeFlags | 0x1f;
}
GameBoyAdvanceCPU.prototype.branch = function (branchTo) {
branchTo = branchTo | 0;
//if ((branchTo | 0) > 0x3FFF || this.IOCore.BIOSFound) {
//Branch to new address:
this.registers[15] = branchTo | 0;
//Mark pipeline as invalid:
this.IOCore.flagBubble();
//Next PC fetch has to update the address bus:
this.wait.NonSequentialBroadcastClear();
/*}
else {
//We're branching into BIOS, handle specially:
if ((branchTo | 0) == 0x130) {
//IRQ mode exit handling:
//ROM IRQ handling returns back from its own subroutine back to BIOS at this address.
this.HLEIRQExit();
}
else {
//Reset to start of ROM if no BIOS ROM found:
this.HLEReset();
}
}*/
}
GameBoyAdvanceCPU.prototype.triggerIRQ = function (didFire) {
this.triggeredIRQ = didFire | 0;
this.assertIRQ();
}
GameBoyAdvanceCPU.prototype.assertIRQ = function () {
if ((this.triggeredIRQ | 0) != 0 && (this.modeFlags & 0x80) == 0) {
this.IOCore.flagIRQ();
}
}
GameBoyAdvanceCPU.prototype.getCurrentFetchValue = function () {
if ((this.modeFlags & 0x20) != 0) {
return this.THUMB.getCurrentFetchValue() | 0;
}
else {
return this.ARM.getCurrentFetchValue() | 0;
}
}
GameBoyAdvanceCPU.prototype.enterARM = function () {
this.modeFlags = this.modeFlags & 0xdf;
this.THUMBBitModify(false);
}
GameBoyAdvanceCPU.prototype.enterTHUMB = function () {
this.modeFlags = this.modeFlags | 0x20;
this.THUMBBitModify(true);
}
GameBoyAdvanceCPU.prototype.getLR = function () {
//Get the previous instruction address:
if ((this.modeFlags & 0x20) != 0) {
return this.THUMB.getLR() | 0;
}
else {
return this.ARM.getLR() | 0;
}
}
GameBoyAdvanceCPU.prototype.THUMBBitModify = function (isThumb) {
if (isThumb) {
this.IOCore.flagTHUMB();
}
else {
this.IOCore.deflagTHUMB();
}
}
GameBoyAdvanceCPU.prototype.IRQinARM = function () {
//Mode bits are set to IRQ:
this.switchMode(0x12);
//Save link register:
this.registers[14] = this.ARM.getIRQLR() | 0;
//Disable IRQ:
this.modeFlags = this.modeFlags | 0x80;
//if (this.IOCore.BIOSFound) {
//IRQ exception vector:
this.branch(0x18);
/*}
else {
//HLE the IRQ entrance:
this.HLEIRQEnter();
}*/
//Deflag IRQ from state:
this.IOCore.deflagIRQ();
}
GameBoyAdvanceCPU.prototype.IRQinTHUMB = function () {
//Mode bits are set to IRQ:
this.switchMode(0x12);
//Save link register:
this.registers[14] = this.THUMB.getIRQLR() | 0;
//Disable IRQ:
this.modeFlags = this.modeFlags | 0x80;
//Exception always enter ARM mode:
this.enterARM();
//if (this.IOCore.BIOSFound) {
//IRQ exception vector:
this.branch(0x18);
/*}
else {
//HLE the IRQ entrance:
this.HLEIRQEnter();
}*/
//Deflag IRQ from state:
this.IOCore.deflagIRQ();
}
GameBoyAdvanceCPU.prototype.HLEIRQEnter = function () {
//Get the base address:
var currentAddress = this.registers[0xD] | 0;
//Updating the address bus away from PC fetch:
this.wait.NonSequentialBroadcast();
//Push register(s) into memory:
for (var rListPosition = 0xF; (rListPosition | 0) > -1; rListPosition = ((rListPosition | 0) - 1) | 0) {
if ((0x500F & (1 << (rListPosition | 0))) != 0) {
//Push a register into memory:
currentAddress = ((currentAddress | 0) - 4) | 0;
this.memory.memoryWrite32(currentAddress | 0, this.registers[rListPosition | 0] | 0);
}
}
//Store the updated base address back into register:
this.registers[0xD] = currentAddress | 0;
//Updating the address bus back to PC fetch:
this.wait.NonSequentialBroadcast();
this.registers[0] = 0x4000000;
//Save link register:
this.registers[14] = 0x130;
//Skip BIOS ROM processing:
this.branch(this.read32(0x3FFFFFC) & -0x4);
}
GameBoyAdvanceCPU.prototype.HLEIRQExit = function () {
//Get the base address:
var currentAddress = this.registers[0xD] | 0;
//Updating the address bus away from PC fetch:
this.wait.NonSequentialBroadcast();
//Load register(s) from memory:
for (var rListPosition = 0; (rListPosition | 0) < 0x10; rListPosition = ((rListPosition | 0) + 1) | 0) {
if ((0x500F & (1 << (rListPosition | 0))) != 0) {
//Load a register from memory:
this.registers[rListPosition & 0xF] = this.memory.memoryRead32(currentAddress | 0) | 0;
currentAddress = ((currentAddress | 0) + 4) | 0;
}
}
//Store the updated base address back into register:
this.registers[0xD] = currentAddress | 0;
//Updating the address bus back to PC fetch:
this.wait.NonSequentialBroadcast();
//Return from an exception mode:
var data = this.branchFlags.setSUBFlags(this.registers[0xE] | 0, 4) | 0;
//Restore SPSR to CPSR:
data = data & (-4 >> (this.SPSRtoCPSR() >> 5));
//We performed a branch:
this.branch(data | 0);
}
GameBoyAdvanceCPU.prototype.SWI = function () {
//if (this.IOCore.BIOSFound) {
//Mode bits are set to SWI:
this.switchMode(0x13);
//Save link register:
this.registers[14] = this.getLR() | 0;
//Disable IRQ:
this.modeFlags = this.modeFlags | 0x80;
//Exception always enter ARM mode:
this.enterARM();
//SWI exception vector:
this.branch(0x8);
/*}
else {
if ((this.modeFlags & 0x20) != 0) {
this.THUMB.incrementProgramCounter();
//HLE the SWI command:
this.swi.execute(this.THUMB.getSWICode() | 0);
}
else {
this.ARM.incrementProgramCounter();
//HLE the SWI command:
this.swi.execute(this.ARM.getSWICode() | 0);
}
}*/
}
GameBoyAdvanceCPU.prototype.UNDEFINED = function () {
//Only process undefined instruction if BIOS loaded:
//if (this.IOCore.BIOSFound) {
//Mode bits are set to SWI:
this.switchMode(0x1B);
//Save link register:
this.registers[14] = this.getLR() | 0;
//Disable IRQ:
this.modeFlags = this.modeFlags | 0x80;
//Exception always enter ARM mode:
this.enterARM();
//Undefined exception vector:
this.branch(0x4);
/*}
else {
//Pretend we didn't execute the bad instruction then:
if ((this.modeFlags & 0x20) != 0) {
this.THUMB.incrementProgramCounter();
}
else {
this.ARM.incrementProgramCounter();
}
}*/
}
GameBoyAdvanceCPU.prototype.SPSRtoCPSR = function () {
//Used for leaving an exception and returning to the previous state:
var bank = 1;
switch (this.modeFlags & 0x1f) {
case 0x12: //IRQ
break;
case 0x13: //Supervisor
bank = 2;
break;
case 0x11: //FIQ
bank = 0;
break;
case 0x17: //Abort
bank = 3;
break;
case 0x1B: //Undefined
bank = 4;
break;
default: //User & system lacks SPSR
return this.modeFlags & 0x20;
}
var spsr = this.SPSR[bank | 0] | 0;
this.branchFlags.setNZCV(spsr << 20);
this.switchRegisterBank(spsr & 0x1F);
this.modeFlags = spsr & 0xFF;
this.assertIRQ();
this.THUMBBitModify((spsr & 0x20) != 0);
return spsr & 0x20;
}
GameBoyAdvanceCPU.prototype.switchMode = function (newMode) {
newMode = newMode | 0;
this.CPSRtoSPSR(newMode | 0);
this.switchRegisterBank(newMode | 0);
this.modeFlags = (this.modeFlags & 0xe0) | (newMode | 0);
}
GameBoyAdvanceCPU.prototype.CPSRtoSPSR = function (newMode) {
//Used for entering an exception and saving the previous state:
var spsr = this.modeFlags & 0xFF;
spsr = spsr | (this.branchFlags.getNZCV() >> 20);
switch (newMode | 0) {
case 0x12: //IRQ
this.SPSR[1] = spsr | 0;
break;
case 0x13: //Supervisor
this.SPSR[2] = spsr | 0;
break;
case 0x11: //FIQ
this.SPSR[0] = spsr | 0;
break;
case 0x17: //Abort
this.SPSR[3] = spsr | 0;
break;
case 0x1B: //Undefined
this.SPSR[4] = spsr | 0;
}
}
GameBoyAdvanceCPU.prototype.switchRegisterBank = function (newMode) {
newMode = newMode | 0;
switch (this.modeFlags & 0x1F) {
case 0x10:
case 0x1F:
this.registersUSR[0] = this.registers[8] | 0;
this.registersUSR[1] = this.registers[9] | 0;
this.registersUSR[2] = this.registers[10] | 0;
this.registersUSR[3] = this.registers[11] | 0;
this.registersUSR[4] = this.registers[12] | 0;
this.registersUSR[5] = this.registers[13] | 0;
this.registersUSR[6] = this.registers[14] | 0;
break;
case 0x11:
this.registersFIQ[0] = this.registers[8] | 0;
this.registersFIQ[1] = this.registers[9] | 0;
this.registersFIQ[2] = this.registers[10] | 0;
this.registersFIQ[3] = this.registers[11] | 0;
this.registersFIQ[4] = this.registers[12] | 0;
this.registersFIQ[5] = this.registers[13] | 0;
this.registersFIQ[6] = this.registers[14] | 0;
break;
case 0x12:
this.registersUSR[0] = this.registers[8] | 0;
this.registersUSR[1] = this.registers[9] | 0;
this.registersUSR[2] = this.registers[10] | 0;
this.registersUSR[3] = this.registers[11] | 0;
this.registersUSR[4] = this.registers[12] | 0;
this.registersIRQ[0] = this.registers[13] | 0;
this.registersIRQ[1] = this.registers[14] | 0;
break;
case 0x13:
this.registersUSR[0] = this.registers[8] | 0;
this.registersUSR[1] = this.registers[9] | 0;
this.registersUSR[2] = this.registers[10] | 0;
this.registersUSR[3] = this.registers[11] | 0;
this.registersUSR[4] = this.registers[12] | 0;
this.registersSVC[0] = this.registers[13] | 0;
this.registersSVC[1] = this.registers[14] | 0;
break;
case 0x17:
this.registersUSR[0] = this.registers[8] | 0;
this.registersUSR[1] = this.registers[9] | 0;
this.registersUSR[2] = this.registers[10] | 0;
this.registersUSR[3] = this.registers[11] | 0;
this.registersUSR[4] = this.registers[12] | 0;
this.registersABT[0] = this.registers[13] | 0;
this.registersABT[1] = this.registers[14] | 0;
break;
case 0x1B:
this.registersUSR[0] = this.registers[8] | 0;
this.registersUSR[1] = this.registers[9] | 0;
this.registersUSR[2] = this.registers[10] | 0;
this.registersUSR[3] = this.registers[11] | 0;
this.registersUSR[4] = this.registers[12] | 0;
this.registersUND[0] = this.registers[13] | 0;
this.registersUND[1] = this.registers[14] | 0;
}
switch (newMode | 0) {
case 0x10:
case 0x1F:
this.registers[8] = this.registersUSR[0] | 0;
this.registers[9] = this.registersUSR[1] | 0;
this.registers[10] = this.registersUSR[2] | 0;
this.registers[11] = this.registersUSR[3] | 0;
this.registers[12] = this.registersUSR[4] | 0;
this.registers[13] = this.registersUSR[5] | 0;
this.registers[14] = this.registersUSR[6] | 0;
break;
case 0x11:
this.registers[8] = this.registersFIQ[0] | 0;
this.registers[9] = this.registersFIQ[1] | 0;
this.registers[10] = this.registersFIQ[2] | 0;
this.registers[11] = this.registersFIQ[3] | 0;
this.registers[12] = this.registersFIQ[4] | 0;
this.registers[13] = this.registersFIQ[5] | 0;
this.registers[14] = this.registersFIQ[6] | 0;
break;
case 0x12:
this.registers[8] = this.registersUSR[0] | 0;
this.registers[9] = this.registersUSR[1] | 0;
this.registers[10] = this.registersUSR[2] | 0;
this.registers[11] = this.registersUSR[3] | 0;
this.registers[12] = this.registersUSR[4] | 0;
this.registers[13] = this.registersIRQ[0] | 0;
this.registers[14] = this.registersIRQ[1] | 0;
break;
case 0x13:
this.registers[8] = this.registersUSR[0] | 0;
this.registers[9] = this.registersUSR[1] | 0;
this.registers[10] = this.registersUSR[2] | 0;
this.registers[11] = this.registersUSR[3] | 0;
this.registers[12] = this.registersUSR[4] | 0;
this.registers[13] = this.registersSVC[0] | 0;
this.registers[14] = this.registersSVC[1] | 0;
break;
case 0x17:
this.registers[8] = this.registersUSR[0] | 0;
this.registers[9] = this.registersUSR[1] | 0;
this.registers[10] = this.registersUSR[2] | 0;
this.registers[11] = this.registersUSR[3] | 0;
this.registers[12] = this.registersUSR[4] | 0;
this.registers[13] = this.registersABT[0] | 0;
this.registers[14] = this.registersABT[1] | 0;
break;
case 0x1B:
this.registers[8] = this.registersUSR[0] | 0;
this.registers[9] = this.registersUSR[1] | 0;
this.registers[10] = this.registersUSR[2] | 0;
this.registers[11] = this.registersUSR[3] | 0;
this.registers[12] = this.registersUSR[4] | 0;
this.registers[13] = this.registersUND[0] | 0;
this.registers[14] = this.registersUND[1] | 0;
}
}
if (typeof Math.imul == "function") {
//Math.imul found, insert the optimized path in:
GameBoyAdvanceCPU.prototype.calculateMUL32 = Math.imul;
}
else {
//Math.imul not found, use the compatibility method:
GameBoyAdvanceCPU.prototype.calculateMUL32 = function (rs, rd) {
rs = rs | 0;
rd = rd | 0;
/*
We have to split up the 32 bit multiplication,
as JavaScript does multiplication on the FPU
as double floats, which drops the low bits
rather than the high bits.
*/
var lowMul = (rs & 0xFFFF) * rd;
var highMul = (rs >> 16) * rd;
//Cut off bits above bit 31 and return with proper sign:
return ((highMul << 16) + lowMul) | 0;
}
}
GameBoyAdvanceCPU.prototype.performMUL32 = function (rs, rd) {
rs = rs | 0;
rd = rd | 0;
//Predict the internal cycle time:
if ((rd >>> 8) == 0 || (rd >>> 8) == 0xFFFFFF) {
this.IOCore.wait.CPUInternalSingleCyclePrefetch();
}
else if ((rd >>> 16) == 0 || (rd >>> 16) == 0xFFFF) {
this.IOCore.wait.CPUInternalCyclePrefetch(2);
}
else if ((rd >>> 24) == 0 || (rd >>> 24) == 0xFF) {
this.IOCore.wait.CPUInternalCyclePrefetch(3);
}
else {
this.IOCore.wait.CPUInternalCyclePrefetch(4);
}
return this.calculateMUL32(rs | 0, rd | 0) | 0;
}
GameBoyAdvanceCPU.prototype.performMUL32MLA = function (rs, rd) {
rs = rs | 0;
rd = rd | 0;
//Predict the internal cycle time:
if ((rd >>> 8) == 0 || (rd >>> 8) == 0xFFFFFF) {
this.IOCore.wait.CPUInternalCyclePrefetch(2);
}
else if ((rd >>> 16) == 0 || (rd >>> 16) == 0xFFFF) {
this.IOCore.wait.CPUInternalCyclePrefetch(3);
}
else if ((rd >>> 24) == 0 || (rd >>> 24) == 0xFF) {
this.IOCore.wait.CPUInternalCyclePrefetch(4);
}
else {
this.IOCore.wait.CPUInternalCyclePrefetch(5);
}
return this.calculateMUL32(rs | 0, rd | 0) | 0;
}
GameBoyAdvanceCPU.prototype.performMUL64 = function (rs, rd) {
rs = rs | 0;
rd = rd | 0;
//Predict the internal cycle time:
if ((rd >>> 8) == 0 || (rd >>> 8) == 0xFFFFFF) {
this.IOCore.wait.CPUInternalCyclePrefetch(2);
}
else if ((rd >>> 16) == 0 || (rd >>> 16) == 0xFFFF) {
this.IOCore.wait.CPUInternalCyclePrefetch(3);
}
else if ((rd >>> 24) == 0 || (rd >>> 24) == 0xFF) {
this.IOCore.wait.CPUInternalCyclePrefetch(4);
}
else {
this.IOCore.wait.CPUInternalCyclePrefetch(5);
}
//Solve for the high word (Do FPU double divide to bring down high word into the low word):
this.mul64ResultHigh = Math.floor((rs * rd) / 0x100000000) | 0;
this.mul64ResultLow = this.calculateMUL32(rs | 0, rd | 0) | 0;
}
GameBoyAdvanceCPU.prototype.performMLA64 = function (rs, rd, mlaHigh, mlaLow) {
rs = rs | 0;
rd = rd | 0;
mlaHigh = mlaHigh | 0;
mlaLow = mlaLow | 0;
//Predict the internal cycle time:
if ((rd >>> 8) == 0 || (rd >>> 8) == 0xFFFFFF) {
this.IOCore.wait.CPUInternalCyclePrefetch(3);
}
else if ((rd >>> 16) == 0 || (rd >>> 16) == 0xFFFF) {
this.IOCore.wait.CPUInternalCyclePrefetch(4);
}
else if ((rd >>> 24) == 0 || (rd >>> 24) == 0xFF) {
this.IOCore.wait.CPUInternalCyclePrefetch(5);
}
else {
this.IOCore.wait.CPUInternalCyclePrefetch(6);
}
//Solve for the high word (Do FPU double divide to bring down high word into the low word):
var mulTop = Math.floor((rs * rd) / 0x100000000) | 0;
var dirty = (this.calculateMUL32(rs | 0, rd | 0) >>> 0) + (mlaLow >>> 0);
this.mul64ResultHigh = ((mulTop | 0) + (mlaHigh | 0) + Math.floor(dirty / 0x100000000)) | 0;
this.mul64ResultLow = dirty | 0;
}
GameBoyAdvanceCPU.prototype.performUMUL64 = function (rs, rd) {
rs = rs | 0;
rd = rd | 0;
//Predict the internal cycle time:
if ((rd >>> 8) == 0) {
this.IOCore.wait.CPUInternalCyclePrefetch(2);
}
else if ((rd >>> 16) == 0) {
this.IOCore.wait.CPUInternalCyclePrefetch(3);
}
else if ((rd >>> 24) == 0) {
this.IOCore.wait.CPUInternalCyclePrefetch(4);
}
else {
this.IOCore.wait.CPUInternalCyclePrefetch(5);
}
//Solve for the high word (Do FPU double divide to bring down high word into the low word):
this.mul64ResultHigh = (((rs >>> 0) * (rd >>> 0)) / 0x100000000) | 0;
this.mul64ResultLow = this.calculateMUL32(rs | 0, rd | 0) | 0;
}
GameBoyAdvanceCPU.prototype.performUMLA64 = function (rs, rd, mlaHigh, mlaLow) {
rs = rs | 0;
rd = rd | 0;
mlaHigh = mlaHigh | 0;
mlaLow = mlaLow | 0;
//Predict the internal cycle time:
if ((rd >>> 8) == 0) {
this.IOCore.wait.CPUInternalCyclePrefetch(3);
}
else if ((rd >>> 16) == 0) {
this.IOCore.wait.CPUInternalCyclePrefetch(4);
}
else if ((rd >>> 24) == 0) {
this.IOCore.wait.CPUInternalCyclePrefetch(5);
}
else {
this.IOCore.wait.CPUInternalCyclePrefetch(6);
}
//Solve for the high word (Do FPU double divide to bring down high word into the low word):
var mulTop = Math.floor(((rs >>> 0) * (rd >>> 0)) / 0x100000000) | 0;
var dirty = (this.calculateMUL32(rs | 0, rd | 0) >>> 0) + (mlaLow >>> 0);
this.mul64ResultHigh = ((mulTop | 0) + (mlaHigh | 0) + Math.floor(dirty / 0x100000000)) | 0;
this.mul64ResultLow = dirty | 0;
}
GameBoyAdvanceCPU.prototype.write32 = function (address, data) {
address = address | 0;
data = data | 0;
//Updating the address bus away from PC fetch:
this.IOCore.wait.NonSequentialBroadcast();
this.memory.memoryWrite32(address | 0, data | 0);
//Updating the address bus back to PC fetch:
this.IOCore.wait.NonSequentialBroadcast();
}
GameBoyAdvanceCPU.prototype.write16 = function (address, data) {
address = address | 0;
data = data | 0;
//Updating the address bus away from PC fetch:
this.IOCore.wait.NonSequentialBroadcast();
this.memory.memoryWrite16(address | 0, data | 0);
//Updating the address bus back to PC fetch:
this.IOCore.wait.NonSequentialBroadcast();
}
GameBoyAdvanceCPU.prototype.write8 = function (address, data) {
address = address | 0;
data = data | 0;
//Updating the address bus away from PC fetch:
this.IOCore.wait.NonSequentialBroadcast();
this.memory.memoryWrite8(address | 0, data | 0);
//Updating the address bus back to PC fetch:
this.IOCore.wait.NonSequentialBroadcast();
}
GameBoyAdvanceCPU.prototype.read32 = function (address) {
address = address | 0;
//Updating the address bus away from PC fetch:
this.IOCore.wait.NonSequentialBroadcast();
var data = this.memory.memoryRead32(address | 0) | 0;
//Unaligned access gets data rotated right:
if ((address & 0x3) != 0) {
//Rotate word right:
data = (data << ((4 - (address & 0x3)) << 3)) | (data >>> ((address & 0x3) << 3));
}
//Updating the address bus back to PC fetch:
this.IOCore.wait.NonSequentialBroadcast();
return data | 0;
}
GameBoyAdvanceCPU.prototype.read16 = function (address) {
address = address | 0;
//Updating the address bus away from PC fetch:
this.IOCore.wait.NonSequentialBroadcast();
var data = this.memory.memoryRead16(address | 0) | 0;
//Unaligned access gets data rotated right:
if ((address & 0x1) != 0) {
//Rotate word right:
data = (data << 24) | (data >>> 8);
}
//Updating the address bus back to PC fetch:
this.IOCore.wait.NonSequentialBroadcast();
return data | 0;
}
GameBoyAdvanceCPU.prototype.read8 = function (address) {
address = address | 0;
//Updating the address bus away from PC fetch:
this.IOCore.wait.NonSequentialBroadcast();
var data = this.memory.memoryRead8(address | 0) | 0;
//Updating the address bus back to PC fetch:
this.IOCore.wait.NonSequentialBroadcast();
return data | 0;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,249 @@
"use strict";
/*
Copyright (C) 2012-2014 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function ARMCPSRAttributeTable() {
//"use asm";
var negative = 0;
var zero = 1;
var carry = 0;
var overflow = 0;
function setNegative(toSet) {
toSet = toSet | 0;
negative = toSet | 0;
};
function setNegativeFalse() {
negative = 0;
};
function getNegative() {
return negative | 0;
};
function setZero(toSet) {
toSet = toSet | 0;
zero = toSet | 0;
};
function setZeroTrue() {
zero = 0;
};
function setZeroFalse() {
zero = 1;
};
function getZero() {
return zero | 0;
};
function setOverflowTrue() {
overflow = -1;
};
function setOverflowFalse() {
overflow = 0;
};
function getOverflow() {
return overflow | 0;
};
function setCarry(toSet) {
toSet = toSet | 0;
carry = toSet | 0;
};
function setCarryFalse() {
carry = 0;
};
function getCarry() {
return carry | 0;
};
function getCarryReverse() {
return (~carry) | 0;
};
function checkConditionalCode(execute) {
execute = execute | 0;
/*
Instruction Decode Pattern:
C = Conditional Code Bit;
X = Possible opcode bit;
N = Data Bit, definitely not an opcode bit
OPCODE: CCCCXXXXXXXXXXXXNNNNNNNNXXXXNNNN
For this function, we decode the top 3 bits for the conditional code test:
*/
switch ((execute >>> 29) | 0) {
case 0x4:
if ((zero | 0) == 0) {
execute = -1;
break;
}
case 0x1:
execute = ~carry;
break;
case 0x2:
execute = ~negative;
break;
case 0x3:
execute = ~overflow;
break;
case 0x6:
if ((zero | 0) == 0) {
execute = -1;
break;
}
case 0x5:
execute = negative ^ overflow;
break;
case 0x0:
if ((zero | 0) != 0) {
execute = -1;
break;
}
default:
execute = 0;
}
return execute | 0;
};
function setNZInt(toSet) {
toSet = toSet | 0;
negative = toSet | 0;
zero = toSet | 0;
};
function setNZCV(toSet) {
toSet = toSet | 0;
negative = toSet | 0;
zero = (~toSet) & 0x40000000;
carry = toSet << 2;
overflow = toSet << 3;
};
function getNZCV() {
var toSet = 0;
toSet = negative & 0x80000000;
if ((zero | 0) == 0) {
toSet = toSet | 0x40000000;
}
toSet = toSet | ((carry >>> 31) << 29);
toSet = toSet | ((overflow >>> 31) << 28);
return toSet | 0;
};
function setADDFlags(operand1, operand2) {
//Update flags for an addition operation:
operand1 = operand1 | 0;
operand2 = operand2 | 0;
negative = ((operand1 | 0) + (operand2 | 0)) | 0;
zero = negative | 0;
if ((negative >>> 0) < (operand1 >>> 0)) {
carry = -1;
}
else {
carry = 0;
}
overflow = (~(operand1 ^ operand2)) & (operand1 ^ negative);
return negative | 0;
};
function setADCFlags(operand1, operand2) {
//Update flags for an addition operation:
operand1 = operand1 | 0;
operand2 = operand2 | 0;
negative = ((operand1 | 0) + (operand2 | 0)) | 0;
negative = ((negative | 0) + (carry >>> 31)) | 0;
zero = negative | 0;
if ((negative >>> 0) < (operand1 >>> 0)) {
carry = -1;
}
else if ((negative >>> 0) > (operand1 >>> 0)) {
carry = 0;
}
overflow = (~(operand1 ^ operand2)) & (operand1 ^ negative);
return negative | 0;
};
function setSUBFlags(operand1, operand2) {
//Update flags for a subtraction operation:
operand1 = operand1 | 0;
operand2 = operand2 | 0;
negative = ((operand1 | 0) - (operand2 | 0)) | 0;
zero = negative | 0;
if ((operand1 >>> 0) >= (operand2 >>> 0)) {
carry = -1;
}
else {
carry = 0;
}
overflow = (operand1 ^ operand2) & (operand1 ^ negative);
return negative | 0;
};
function setSBCFlags(operand1, operand2) {
//Update flags for a subtraction operation:
operand1 = operand1 | 0;
operand2 = operand2 | 0;
negative = ((operand1 | 0) - (operand2 | 0)) | 0;
negative = ((negative | 0) - ((~carry) >>> 31)) | 0
zero = negative | 0;
if ((negative >>> 0) < (operand1 >>> 0)) {
carry = -1;
}
else if ((negative >>> 0) > (operand1 >>> 0)) {
carry = 0;
}
overflow = (operand1 ^ operand2) & (operand1 ^ negative);
return negative | 0;
};
function setCMPFlags(operand1, operand2) {
//Update flags for a subtraction operation:
operand1 = operand1 | 0;
operand2 = operand2 | 0;
negative = ((operand1 | 0) - (operand2 | 0)) | 0;
zero = negative | 0;
if ((operand1 >>> 0) >= (operand2 >>> 0)) {
carry = -1;
}
else {
carry = 0;
}
overflow = (operand1 ^ operand2) & (operand1 ^ negative);
};
function setCMNFlags(operand1, operand2) {
//Update flags for an addition operation:
operand1 = operand1 | 0;
operand2 = operand2 | 0;
negative = ((operand1 | 0) + (operand2 | 0)) | 0;
zero = negative | 0;
if ((negative >>> 0) < (operand1 >>> 0)) {
carry = -1;
}
else {
carry = 0;
}
overflow = (~(operand1 ^ operand2)) & (operand1 ^ negative);
};
function BGE() {
//Branch if Negative equal to Overflow
return (negative ^ overflow) | 0;
};
return {
setNegative:setNegative,
setNegativeFalse:setNegativeFalse,
getNegative:getNegative,
setZero:setZero,
setZeroTrue:setZeroTrue,
setZeroFalse:setZeroFalse,
getZero:getZero,
setOverflowTrue:setOverflowTrue,
setOverflowFalse:setOverflowFalse,
getOverflow:getOverflow,
setCarry:setCarry,
setCarryFalse:setCarryFalse,
getCarry:getCarry,
getCarryReverse:getCarryReverse,
checkConditionalCode:checkConditionalCode,
setNZInt:setNZInt,
setNZCV:setNZCV,
getNZCV:getNZCV,
setADDFlags:setADDFlags,
setADCFlags:setADCFlags,
setSUBFlags:setSUBFlags,
setSBCFlags:setSBCFlags,
setCMPFlags:setCMPFlags,
setCMNFlags:setCMNFlags,
BGE:BGE
};
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,278 @@
"use strict";
/*
Copyright (C) 2012-2014 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceCartridge(IOCore) {
this.IOCore = IOCore;
}
GameBoyAdvanceCartridge.prototype.initialize = function () {
this.flash_is128 = false;
this.flash_isAtmel = false;
this.ROM = this.getROMArray(this.IOCore.ROM);
this.ROM16 = getUint16View(this.ROM);
this.ROM32 = getInt32View(this.ROM);
this.decodeName();
this.decodeFlashType();
}
GameBoyAdvanceCartridge.prototype.getROMArray = function (old_array) {
this.ROMLength = Math.min((old_array.length >> 2) << 2, 0x2000000);
this.EEPROMStart = ((this.ROMLength | 0) > 0x1000000) ? Math.max(this.ROMLength | 0, 0x1FFFF00) : 0x1000000;
var newArray = getUint8Array(this.ROMLength | 0);
for (var index = 0; (index | 0) < (this.ROMLength | 0); index = ((index | 0) + 1) | 0) {
newArray[index | 0] = old_array[index | 0] | 0;
}
return newArray;
}
GameBoyAdvanceCartridge.prototype.decodeName = function () {
this.name = "GUID_";
if ((this.ROMLength | 0) >= 0xC0) {
for (var address = 0xAC; (address | 0) < 0xB3; address = ((address | 0) + 1) | 0) {
if ((this.ROM[address | 0] | 0) > 0) {
this.name += String.fromCharCode(this.ROM[address | 0] | 0);
}
else {
this.name += "_";
}
}
}
}
GameBoyAdvanceCartridge.prototype.decodeFlashType = function () {
this.flash_is128 = false;
this.flash_isAtmel = false;
var flash_types = 0;
var F = ("F").charCodeAt(0) & 0xFF;
var L = ("L").charCodeAt(0) & 0xFF;
var A = ("A").charCodeAt(0) & 0xFF;
var S = ("S").charCodeAt(0) & 0xFF;
var H = ("H").charCodeAt(0) & 0xFF;
var underScore = ("_").charCodeAt(0) & 0xFF;
var five = ("5").charCodeAt(0) & 0xFF;
var one = ("1").charCodeAt(0) & 0xFF;
var two = ("2").charCodeAt(0) & 0xFF;
var M = ("M").charCodeAt(0) & 0xFF;
var V = ("V").charCodeAt(0) & 0xFF;
var length = ((this.ROM.length | 0) - 12) | 0;
for (var index = 0; (index | 0) < (length | 0); index = ((index | 0) + 4) | 0) {
if ((this.ROM[index | 0] | 0) == (F | 0)) {
if ((this.ROM[index | 1] | 0) == (L | 0)) {
if ((this.ROM[index | 2] | 0) == (A | 0)) {
if ((this.ROM[index | 3] | 0) == (S | 0)) {
var tempIndex = ((index | 0) + 4) | 0;
if ((this.ROM[tempIndex | 0] | 0) == (H | 0)) {
if ((this.ROM[tempIndex | 1] | 0) == (underScore | 0)) {
if ((this.ROM[tempIndex | 2] | 0) == (V | 0)) {
flash_types |= 1;
}
}
else if ((this.ROM[tempIndex | 1] | 0) == (five | 0)) {
if ((this.ROM[tempIndex | 2] | 0) == (one | 0)) {
if ((this.ROM[tempIndex | 3] | 0) == (two | 0)) {
tempIndex = ((tempIndex | 0) + 4) | 0;
if ((this.ROM[tempIndex | 0] | 0) == (underScore | 0)) {
if ((this.ROM[tempIndex | 1] | 0) == (V | 0)) {
flash_types |= 2;
}
}
}
}
}
else if ((this.ROM[tempIndex | 1] | 0) == (one | 0)) {
if ((this.ROM[tempIndex | 2] | 0) == (M | 0)) {
if ((this.ROM[tempIndex | 3] | 0) == (underScore | 0)) {
tempIndex = ((tempIndex | 0) + 4) | 0;
if ((this.ROM[tempIndex | 0] | 0) == (V | 0)) {
flash_types |= 4;
break;
}
}
}
}
}
}
}
}
}
}
this.flash_is128 = ((flash_types | 0) >= 4);
this.flash_isAtmel = ((flash_types | 0) <= 1);
}
GameBoyAdvanceCartridge.prototype.readROMOnly8 = function (address) {
address = address | 0;
var data = 0;
if ((address | 0) < (this.ROMLength | 0)) {
data = this.ROM[address & 0x1FFFFFF] | 0;
}
return data | 0;
}
if (__LITTLE_ENDIAN__) {
GameBoyAdvanceCartridge.prototype.readROMOnly16 = function (address) {
address = address | 0;
var data = 0;
if ((address | 0) < (this.ROMLength | 0)) {
data = this.ROM16[(address >> 1) & 0xFFFFFF] | 0;
}
return data | 0;
}
GameBoyAdvanceCartridge.prototype.readROMOnly32 = function (address) {
address = address | 0;
var data = 0;
if ((address | 0) < (this.ROMLength | 0)) {
data = this.ROM32[(address >> 2) & 0x7FFFFF] | 0;
}
return data | 0;
}
}
else {
GameBoyAdvanceCartridge.prototype.readROMOnly16 = function (address) {
address = address | 0;
var data = 0;
if ((address | 0) < (this.ROMLength | 0)) {
data = this.ROM[address] | (this.ROM[address | 1] << 8);
}
return data | 0;
}
GameBoyAdvanceCartridge.prototype.readROMOnly32 = function (address) {
address = address | 0;
var data = 0;
if ((address | 0) < (this.ROMLength | 0)) {
data = this.ROM[address] | (this.ROM[address | 1] << 8) | (this.ROM[address | 2] << 16) | (this.ROM[address | 3] << 24);
}
return data | 0;
}
}
GameBoyAdvanceCartridge.prototype.readROM8 = function (address) {
address = address | 0;
var data = 0;
if ((address | 0) > 0xC9) {
//Definitely ROM:
data = this.readROMOnly8(address | 0) | 0;
}
else {
//Possibly GPIO:
data = this.IOCore.saves.readGPIO8(address | 0) | 0;
}
return data | 0;
}
GameBoyAdvanceCartridge.prototype.readROM16 = function (address) {
address = address | 0;
var data = 0;
if ((address | 0) > 0xC9) {
//Definitely ROM:
data = this.readROMOnly16(address | 0) | 0;
}
else {
//Possibly GPIO:
data = this.IOCore.saves.readGPIO16(address | 0) | 0;
}
return data | 0;
}
GameBoyAdvanceCartridge.prototype.readROM32 = function (address) {
address = address | 0;
var data = 0;
if ((address | 0) > 0xC9) {
//Definitely ROM:
data = this.readROMOnly32(address | 0) | 0;
}
else {
//Possibly GPIO:
data = this.IOCore.saves.readGPIO32(address | 0) | 0;
}
return data | 0;
}
GameBoyAdvanceCartridge.prototype.readROM8Space2 = function (address) {
address = address | 0;
var data = 0;
if ((address | 0) >= 0xC4 && (address | 0) < 0xCA) {
//Possibly GPIO:
data = this.IOCore.saves.readGPIO8(address | 0) | 0;
}
else if ((address | 0) >= (this.EEPROMStart | 0)) {
//Possibly EEPROM:
data = this.IOCore.saves.readEEPROM8(address | 0) | 0;
}
else {
//Definitely ROM:
data = this.readROMOnly8(address | 0) | 0;
}
return data | 0;
}
GameBoyAdvanceCartridge.prototype.readROM16Space2 = function (address) {
address = address | 0;
var data = 0;
if ((address | 0) >= 0xC4 && (address | 0) < 0xCA) {
//Possibly GPIO:
data = this.IOCore.saves.readGPIO16(address | 0) | 0;
}
else if ((address | 0) >= (this.EEPROMStart | 0)) {
//Possibly EEPROM:
data = this.IOCore.saves.readEEPROM16(address | 0) | 0;
}
else {
//Definitely ROM:
data = this.readROMOnly16(address | 0) | 0;
}
return data | 0;
}
GameBoyAdvanceCartridge.prototype.readROM32Space2 = function (address) {
address = address | 0;
var data = 0;
if ((address | 0) >= 0xC4 && (address | 0) < 0xCA) {
//Possibly GPIO:
data = this.IOCore.saves.readGPIO32(address | 0) | 0;
}
else if ((address | 0) >= (this.EEPROMStart | 0)) {
//Possibly EEPROM:
data = this.IOCore.saves.readEEPROM32(address | 0) | 0;
}
else {
//Definitely ROM:
data = this.readROMOnly32(address | 0) | 0;
}
return data | 0;
}
GameBoyAdvanceCartridge.prototype.writeROM8 = function (address, data) {
address = address | 0;
data = data | 0;
if ((address | 0) >= 0xC4 && (address | 0) < 0xCA) {
//GPIO Chip (RTC):
this.IOCore.saves.writeGPIO8(address | 0, data | 0);
}
}
GameBoyAdvanceCartridge.prototype.writeROM16 = function (address, data) {
address = address | 0;
data = data | 0;
if ((address | 0) >= 0xC4 && (address | 0) < 0xCA) {
//GPIO Chip (RTC):
this.IOCore.saves.writeGPIO16(address | 0, data | 0);
}
}
GameBoyAdvanceCartridge.prototype.writeROM16DMA = function (address, data) {
address = address | 0;
data = data | 0;
if ((address | 0) >= 0xC4 && (address | 0) < 0xCA) {
//GPIO Chip (RTC):
this.IOCore.saves.writeGPIO16(address | 0, data | 0);
}
else if ((address | 0) >= (this.EEPROMStart | 0)) {
//Possibly EEPROM:
this.IOCore.saves.writeEEPROM16(address | 0, data | 0);
}
}
GameBoyAdvanceCartridge.prototype.writeROM32 = function (address, data) {
address = address | 0;
data = data | 0;
if ((address | 0) >= 0xC4 && (address | 0) < 0xCA) {
//GPIO Chip (RTC):
this.IOCore.saves.writeGPIO32(address | 0, data | 0);
}
}
GameBoyAdvanceCartridge.prototype.nextIRQEventTime = function () {
//Nothing yet implement that would fire an IRQ:
return 0x7FFFFFFF;
}

View file

@ -0,0 +1,98 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceDMA(IOCore) {
this.IOCore = IOCore;
}
GameBoyAdvanceDMA.prototype.initialize = function () {
this.dmaChannel0 = this.IOCore.dmaChannel0;
this.dmaChannel1 = this.IOCore.dmaChannel1;
this.dmaChannel2 = this.IOCore.dmaChannel2;
this.dmaChannel3 = this.IOCore.dmaChannel3;
this.currentMatch = -1;
this.fetch = 0;
}
GameBoyAdvanceDMA.prototype.getCurrentFetchValue = function () {
return this.fetch | 0;
}
GameBoyAdvanceDMA.prototype.gfxHBlankRequest = function () {
//Pass H-Blank signal to all DMA channels:
this.requestDMA(0x4);
}
GameBoyAdvanceDMA.prototype.gfxVBlankRequest = function () {
//Pass V-Blank signal to all DMA channels:
this.requestDMA(0x2);
}
GameBoyAdvanceDMA.prototype.requestDMA = function (DMAType) {
DMAType = DMAType | 0;
this.dmaChannel0.requestDMA(DMAType | 0);
this.dmaChannel1.requestDMA(DMAType | 0);
this.dmaChannel2.requestDMA(DMAType | 0);
this.dmaChannel3.requestDMA(DMAType | 0);
}
GameBoyAdvanceDMA.prototype.findLowestDMA = function () {
if ((this.dmaChannel0.getMatchStatus() | 0) != 0) {
return 0;
}
if ((this.dmaChannel1.getMatchStatus() | 0) != 0) {
return 1;
}
if ((this.dmaChannel2.getMatchStatus() | 0) != 0) {
return 2;
}
if ((this.dmaChannel3.getMatchStatus() | 0) != 0) {
return 3;
}
return 4;
}
GameBoyAdvanceDMA.prototype.update = function () {
var lowestDMAFound = this.findLowestDMA();
if ((lowestDMAFound | 0) < 4) {
//Found an active DMA:
if ((this.currentMatch | 0) == -1) {
this.IOCore.flagDMA();
}
if ((this.currentMatch | 0) != (lowestDMAFound | 0)) {
//Re-broadcasting on address bus, so non-seq:
this.IOCore.wait.NonSequentialBroadcast();
this.currentMatch = lowestDMAFound | 0;
}
}
else if ((this.currentMatch | 0) != -1) {
//No active DMA found:
this.currentMatch = -1;
this.IOCore.deflagDMA();
this.IOCore.updateCoreSpill();
}
}
GameBoyAdvanceDMA.prototype.perform = function () {
//Call the correct channel to process:
switch (this.currentMatch | 0) {
case 0:
this.dmaChannel0.handleDMACopy();
break;
case 1:
this.dmaChannel1.handleDMACopy();
break;
case 2:
this.dmaChannel2.handleDMACopy();
break;
default:
this.dmaChannel3.handleDMACopy();
}
}
GameBoyAdvanceDMA.prototype.updateFetch = function (data) {
data = data | 0;
this.fetch = data | 0;
}
GameBoyAdvanceDMA.prototype.nextEventTime = function () {
var clocks = Math.min(this.dmaChannel0.nextEventTime() | 0, this.dmaChannel1.nextEventTime() | 0, this.dmaChannel2.nextEventTime() | 0, this.dmaChannel3.nextEventTime() | 0) | 0;
return clocks | 0;
}

View file

@ -0,0 +1,469 @@
"use strict";
/*
Copyright (C) 2012-2019 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceEmulator() {
this.settings = {
SKIPBoot:false, //Skip the BIOS boot screen.
audioBufferUnderrunLimit:100, //Audio buffer minimum span amount over x milliseconds.
audioBufferDynamicLimit:32, //Audio buffer dynamic minimum span amount over x milliseconds.
audioBufferSize:300, //Audio buffer maximum span amount over x milliseconds.
emulatorSpeed:1.0, //Speed multiplier of the emulator.
metricCollectionMinimum:500, //How many milliseconds of cycling to count before determining speed.
dynamicSpeed:false, //Whether to actively change the target speed for best user experience.
overclockBlockLimit:200, //Whether to throttle clocks in audio adjustment.
offthreadGfxEnabled:true //Whether to allow offthread graphics rendering if support is present.
};
this.audioFound = 0; //Do we have audio output sink found yet?
this.emulatorStatus = 0x10; //{paused, saves loaded, fault found, loaded}
this.BIOS = []; //Initialize BIOS as not existing.
this.ROM = []; //Initialize BIOS as not existing.
this.audioUpdateState = 1; //Do we need to update the sound core with new info?
this.saveExportHandler = null; //Save export handler attached by GUI.
this.saveImportHandler = null; //Save import handler attached by GUI.
this.speedCallback = null; //Speed report handler attached by GUI.
this.playStatusCallback = null; //Play status change handler attached by GUI.
this.startCallbacks = []; //Some jobs to run at iteration head.
this.endCallbacks = []; //Some jobs to run at iteration end.
this.terminationCallbacks = []; //Some jobs to run if the emulation core is killed.
this.timerIntervalRate = 16; //How often the emulator core is called into (in milliseconds).
this.lastTimestamp = 0; //Track the last time given in milliseconds.
this.dynamicSpeedRefresh = false; //Whether speed is allowed to be changed dynamically in the current cycle.
this.calculateTimings(); //Calculate some multipliers against the core emulator timer.
this.generateCoreExposed(); //Generate a limit API for the core to call this shell object.
}
GameBoyAdvanceEmulator.prototype.generateCoreExposed = function () {
var parentObj = this;
this.coreExposed = {
outputAudio:function (l, r) {
parentObj.outputAudio(l, r);
},
graphicsHandle:null,
appendStartIterationSync:function (callback) {
parentObj.startCallbacks.push(callback);
},
appendEndIterationSync:function (callback) {
parentObj.endCallbacks.push(callback);
},
appendTerminationSync:function (callback) {
parentObj.terminationCallbacks.push(callback);
},
offthreadGfxEnabled:function () {
return !!parentObj.settings.offthreadGfxEnabled;
}
}
}
GameBoyAdvanceEmulator.prototype.play = function () {
if ((this.emulatorStatus | 0) >= 0x10) {
this.emulatorStatus = this.emulatorStatus & 0xF;
if ((this.emulatorStatus & 0x1) == 0 && this.BIOS && this.ROM) {
if ((this.initializeCore() | 0) == 0) {
//Failure to initialize:
this.pause();
return;
}
this.importSave();
}
this.invalidateMetrics();
this.setBufferSpace();
//Report new status back:
this.playStatusCallback(1);
}
}
GameBoyAdvanceEmulator.prototype.pause = function () {
if ((this.emulatorStatus | 0) < 0x10) {
this.exportSave();
this.emulatorStatus = this.emulatorStatus | 0x10;
//Report new status back:
this.playStatusCallback(0);
}
}
GameBoyAdvanceEmulator.prototype.stop = function () {
this.emulatorStatus = this.emulatorStatus & 0x1C;
this.audioUpdateState = 1;
this.pause();
}
GameBoyAdvanceEmulator.prototype.restart = function () {
if ((this.emulatorStatus & 0x1) == 0x1) {
this.emulatorStatus = this.emulatorStatus & 0x1D;
this.exportSave();
if ((this.initializeCore() | 0) == 0) {
//Failure to initialize:
this.pause();
return;
}
this.importSave();
this.audioUpdateState = 1;
this.processNewSpeed(1);
this.setBufferSpace();
}
}
GameBoyAdvanceEmulator.prototype.timerCallback = function (lastTimestamp) {
//Callback passes us a reference timestamp:
this.lastTimestamp = lastTimestamp >>> 0;
switch (this.emulatorStatus | 0) {
//Core initialized and saves loaded:
case 5:
this.iterationStartSequence(); //Run start of iteration stuff.
this.IOCore.enter(this.CPUCyclesTotal | 0); //Step through the emulation core loop.
this.iterationEndSequence(); //Run end of iteration stuff.
break;
//Core initialized, but saves still loading:
case 1:
break;
default:
//Some pending error is preventing execution, so pause:
this.pause();
}
}
GameBoyAdvanceEmulator.prototype.iterationStartSequence = function () {
this.calculateSpeedPercentage(); //Calculate the emulator realtime run speed heuristics.
this.emulatorStatus = this.emulatorStatus | 0x2; //If the end routine doesn't unset this, then we are marked as having crashed.
this.audioUnderrunAdjustment(); //If audio is enabled, look to see how much we should overclock by to maintain the audio buffer.
this.audioPushNewState(); //Check to see if we need to update the audio core for any output changes.
this.runStartJobs(); //Run various callbacks assigned from internal components.
}
GameBoyAdvanceEmulator.prototype.iterationEndSequence = function () {
this.emulatorStatus = this.emulatorStatus & 0x1D; //If core did not throw while running, unset the fatal error flag.
this.clockCyclesSinceStart = ((this.clockCyclesSinceStart | 0) + (this.CPUCyclesTotal | 0)) | 0; //Accumulate tracking.
this.submitAudioBuffer(); //Flush audio buffer to output.
this.runEndJobs(); //Run various callbacks assigned from internal components.
}
GameBoyAdvanceEmulator.prototype.runStartJobs = function () {
var length = this.startCallbacks.length | 0;
//Loop through all jobs:
for (var index = 0; (index | 0) < (length | 0); index = ((index | 0) + 1) | 0) {
//Run job:
this.startCallbacks[index | 0]();
}
}
GameBoyAdvanceEmulator.prototype.runEndJobs = function () {
var length = this.endCallbacks.length | 0;
//Loop through all jobs:
for (var index = 0; (index | 0) < (length | 0); index = ((index | 0) + 1) | 0) {
//Run job:
this.endCallbacks[index | 0]();
}
}
GameBoyAdvanceEmulator.prototype.runTerminationJobs = function () {
var length = this.terminationCallbacks.length | 0;
//Loop through all jobs:
for (var index = 0; (index | 0) < (length | 0); index = ((index | 0) + 1) | 0) {
//Run job:
this.terminationCallbacks[index | 0]();
}
//Remove old jobs:
this.startCallbacks = [];
this.endCallbacks = [];
this.terminationCallbacks = [];
}
GameBoyAdvanceEmulator.prototype.attachROM = function (ROM) {
this.stop();
this.ROM = ROM;
}
GameBoyAdvanceEmulator.prototype.attachBIOS = function (BIOS) {
this.stop();
this.BIOS = BIOS;
}
GameBoyAdvanceEmulator.prototype.getGameName = function () {
if ((this.emulatorStatus & 0x3) == 0x1) {
return this.IOCore.cartridge.name;
}
else {
return "";
}
}
GameBoyAdvanceEmulator.prototype.attachSaveExportHandler = function (handler) {
if (typeof handler == "function") {
this.saveExportHandler = handler;
}
}
GameBoyAdvanceEmulator.prototype.attachSaveImportHandler = function (handler) {
if (typeof handler == "function") {
this.saveImportHandler = handler;
}
}
GameBoyAdvanceEmulator.prototype.attachSpeedHandler = function (handler) {
if (typeof handler == "function") {
this.speedCallback = handler;
}
}
GameBoyAdvanceEmulator.prototype.attachPlayStatusHandler = function (handler) {
if (typeof handler == "function") {
this.playStatusCallback = handler;
}
}
GameBoyAdvanceEmulator.prototype.importSave = function () {
if (this.saveImportHandler) {
var name = this.getGameName();
if (name != "") {
var parentObj = this;
this.emulatorStatus = this.emulatorStatus & 0x1B;
this.saveImportHandler(name, function (save) {
parentObj.emulatorStatus = parentObj.emulatorStatus & 0x1B;
parentObj.saveImportHandler("TYPE_" + name, function (saveType) {
if (save && saveType && (parentObj.emulatorStatus & 0x3) == 0x1) {
var length = save.length | 0;
var convertedSave = getUint8Array(length | 0);
if ((length | 0) > 0) {
for (var index = 0; (index | 0) < (length | 0); index = ((index | 0) + 1) | 0) {
convertedSave[index | 0] = save[index | 0] & 0xFF;
}
//We used to save this code wrong, fix the error in old saves:
if ((saveType.length | 0) != 1) {
//0 is fallthrough "UNKNOWN" aka autodetect type:
parentObj.IOCore.saves.importSave(convertedSave, 0);
}
else {
parentObj.IOCore.saves.importSave(convertedSave, saveType[0] & 0xFF);
}
parentObj.emulatorStatus = parentObj.emulatorStatus | 0x4;
}
}
}, function (){parentObj.emulatorStatus = parentObj.emulatorStatus | 0x4;});
}, function (){parentObj.emulatorStatus = parentObj.emulatorStatus | 0x4;});
return;
}
}
this.emulatorStatus = this.emulatorStatus | 0x4;
}
GameBoyAdvanceEmulator.prototype.exportSave = function () {
if (this.saveExportHandler && (this.emulatorStatus & 0x3) == 0x1) {
var save = this.IOCore.saves.exportSave();
var saveType = this.IOCore.saves.exportSaveType() | 0;
if (save != null) {
this.saveExportHandler(this.IOCore.cartridge.name, save);
this.saveExportHandler("TYPE_" + this.IOCore.cartridge.name, [saveType | 0]);
}
}
}
GameBoyAdvanceEmulator.prototype.setSpeed = function (speed) {
speed = +speed;
//Dynamic Speed overrides custom speed levels:
if (!this.settings.dynamicSpeed) {
this.processNewSpeed(+speed);
}
}
GameBoyAdvanceEmulator.prototype.processNewSpeed = function (speed) {
speed = +speed;
//0.003 for the integer resampler limitations, 0x3F for int math limitations:
speed = +Math.min(Math.max(+speed, 0.003), 0x3F);
if ((+speed) != (+this.settings.emulatorSpeed)) {
this.settings.emulatorSpeed = +speed;
this.calculateTimings();
}
}
GameBoyAdvanceEmulator.prototype.incrementSpeed = function (delta) {
delta = +delta;
this.setSpeed((+delta) + (+this.settings.emulatorSpeed));
}
GameBoyAdvanceEmulator.prototype.getSpeed = function () {
return +this.settings.emulatorSpeed;
}
GameBoyAdvanceEmulator.prototype.invalidateMetrics = function () {
this.clockCyclesSinceStart = 0;
this.metricStart = 0;
}
GameBoyAdvanceEmulator.prototype.resetMetrics = function () {
this.clockCyclesSinceStart = 0;
this.metricStart = this.lastTimestamp >>> 0;
}
GameBoyAdvanceEmulator.prototype.calculateTimings = function () {
this.clocksPerSecond = Math.min((+this.settings.emulatorSpeed) * 0x1000000, 0x3F000000) | 0;
this.clocksPerMilliSecond = +((this.clocksPerSecond | 0) / 1000);
this.CPUCyclesPerIteration = ((+this.clocksPerMilliSecond) * (+this.timerIntervalRate)) | 0;
this.CPUCyclesTotal = this.CPUCyclesPerIteration | 0;
this.initializeAudioLogic();
this.reinitializeAudio();
this.invalidateMetrics();
}
GameBoyAdvanceEmulator.prototype.setIntervalRate = function (intervalRate) {
intervalRate = +intervalRate;
if ((+intervalRate) > 0 && (+intervalRate) < 1000) {
if ((+intervalRate) != (+this.timerIntervalRate)) {
this.timerIntervalRate = +intervalRate;
this.calculateTimings();
}
}
}
GameBoyAdvanceEmulator.prototype.calculateSpeedPercentage = function () {
if ((this.metricStart >>> 0) != 0) {
var timeDiff = Math.max(((this.lastTimestamp >>> 0) - (this.metricStart >>> 0)) | 0, 1) >>> 0;
if ((timeDiff >>> 0) >= (this.settings.metricCollectionMinimum | 0)) {
if (this.speedCallback) {
var result = ((this.clockCyclesSinceStart | 0) * 100000) / ((timeDiff >>> 0) * 0x1000000);
this.speedCallback(+result);
}
//Reset counter for speed check:
this.resetMetrics();
//Do a computation for dynamic speed this iteration:
this.dynamicSpeedRefresh = true;
}
else {
//Postpone any dynamic speed changes this iteration:
this.dynamicSpeedRefresh = false;
}
}
else {
//Reset counter for speed check:
this.resetMetrics();
//Postpone any dynamic speed changes this iteration:
this.dynamicSpeedRefresh = false;
}
}
GameBoyAdvanceEmulator.prototype.initializeCore = function () {
//Wrap up any old internal instance callbacks:
this.runTerminationJobs();
//Setup a new instance of the i/o core:
this.IOCore = new GameBoyAdvanceIO(this.settings.SKIPBoot, this.coreExposed, this.BIOS, this.ROM);
//Call the initalization procedure and get status code:
var allowInit = this.IOCore.initialize() | 0;
//Append status code as play status flag for emulator runtime:
this.emulatorStatus = this.emulatorStatus | allowInit;
return allowInit | 0;
}
GameBoyAdvanceEmulator.prototype.keyDown = function (keyPressed) {
keyPressed = keyPressed | 0;
if ((this.emulatorStatus | 0) < 0x10 && (keyPressed | 0) >= 0 && (keyPressed | 0) <= 9) {
this.IOCore.joypad.keyPress(keyPressed | 0);
}
}
GameBoyAdvanceEmulator.prototype.keyUp = function (keyReleased) {
keyReleased = keyReleased | 0;
if ((this.emulatorStatus | 0) < 0x10 && (keyReleased | 0) >= 0 && (keyReleased | 0) <= 9) {
this.IOCore.joypad.keyRelease(keyReleased | 0);
}
}
GameBoyAdvanceEmulator.prototype.attachGraphicsFrameHandler = function (handler) {
if (typeof handler == "object") {
this.coreExposed.graphicsHandle = handler;
}
}
GameBoyAdvanceEmulator.prototype.attachAudioHandler = function (mixerInputHandler) {
if (mixerInputHandler) {
this.audio = mixerInputHandler;
}
}
GameBoyAdvanceEmulator.prototype.enableAudio = function () {
if ((this.audioFound | 0) == 0 && this.audio) {
this.audioFound = 1; //Set audio to 'found' by default.
//Attempt to enable audio:
var parentObj = this;
this.audio.initialize(2, (this.clocksPerSecond | 0) / (this.audioResamplerFirstPassFactor | 0), Math.max((+this.clocksPerMilliSecond) * (this.settings.audioBufferSize | 0) / (this.audioResamplerFirstPassFactor | 0), 4) | 0, function () {
//Not needed
}, function () {
//We manually check at the start of each timer interval, so not needed here.
}, function () {
//Disable audio in the callback here:
parentObj.disableAudio();
});
this.audio.register();
}
}
GameBoyAdvanceEmulator.prototype.disableAudio = function () {
if ((this.audioFound | 0) != 0) {
this.audio.unregister();
this.audioFound = 0;
}
}
GameBoyAdvanceEmulator.prototype.reinitializeAudio = function () {
if ((this.audioFound | 0) != 0) {
this.disableAudio();
this.enableAudio();
}
}
GameBoyAdvanceEmulator.prototype.initializeAudioLogic = function () {
//Calculate the variables for the preliminary downsampler first:
this.audioResamplerFirstPassFactor = Math.min(Math.floor((this.clocksPerSecond | 0) / 44100), Math.floor(0x7FFFFFFF / 0x3FF)) | 0;
this.audioDownSampleInputDivider = +((2 / 0x3FF) / (this.audioResamplerFirstPassFactor | 0));
this.initializeAudioBuffering();
//Need to push the new resample factor:
this.audioUpdateState = 1;
}
GameBoyAdvanceEmulator.prototype.initializeAudioBuffering = function () {
this.audioDestinationPosition = 0;
this.audioBufferContainAmount = Math.max((+this.clocksPerMilliSecond) * (this.settings.audioBufferUnderrunLimit | 0) / (this.audioResamplerFirstPassFactor | 0), 3) << 1;
this.audioBufferOverclockBlockAmount = Math.max((+this.clocksPerMilliSecond) * (this.settings.overclockBlockLimit | 0) / (this.audioResamplerFirstPassFactor | 0), 3) << 1;
this.audioBufferDynamicContainAmount = Math.max((+this.clocksPerMilliSecond) * (this.settings.audioBufferDynamicLimit | 0) / (this.audioResamplerFirstPassFactor | 0), 2) << 1;
//Underrun logic will request at most 32 milliseconds of runtime per iteration, so set buffer size to 64 ms:
var audioNumSamplesTotal = Math.max(((+this.clocksPerMilliSecond) / (this.audioResamplerFirstPassFactor | 0)) << 6, 4) << 1;
if (!this.audioBuffer || ((audioNumSamplesTotal | 0) > (this.audioBuffer.length | 0))) {
//Only regen buffer if the size is increased:
this.audioBuffer = getFloat32Array(audioNumSamplesTotal | 0);
}
}
GameBoyAdvanceEmulator.prototype.outputAudio = function (downsampleInputLeft, downsampleInputRight) {
downsampleInputLeft = downsampleInputLeft | 0;
downsampleInputRight = downsampleInputRight | 0;
this.audioBuffer[this.audioDestinationPosition | 0] = ((downsampleInputLeft | 0) * (+this.audioDownSampleInputDivider)) - 1;
this.audioDestinationPosition = ((this.audioDestinationPosition | 0) + 1) | 0;
this.audioBuffer[this.audioDestinationPosition | 0] = ((downsampleInputRight | 0) * (+this.audioDownSampleInputDivider)) - 1;
this.audioDestinationPosition = ((this.audioDestinationPosition | 0) + 1) | 0;
}
GameBoyAdvanceEmulator.prototype.submitAudioBuffer = function () {
if ((this.audioFound | 0) != 0) {
this.audio.push(this.audioBuffer, 0, this.audioDestinationPosition | 0);
}
this.audioDestinationPosition = 0;
}
GameBoyAdvanceEmulator.prototype.audioUnderrunAdjustment = function () {
this.CPUCyclesTotal = this.CPUCyclesPerIteration | 0;
if ((this.audioFound | 0) != 0) {
var remainingAmount = this.audio.remainingBuffer();
if (typeof remainingAmount == "number") {
remainingAmount = Math.max(remainingAmount | 0, 0) | 0;
var underrunAmount = ((this.audioBufferContainAmount | 0) - (remainingAmount | 0)) | 0;
if ((underrunAmount | 0) > 0) {
if (this.dynamicSpeedRefresh && this.settings.dynamicSpeed) {
if (((this.audioBufferDynamicContainAmount | 0) - (remainingAmount | 0)) > 0) {
var speed = +this.getSpeed();
speed = Math.max((+speed) - 0.1, 0.003);
this.processNewSpeed(+speed);
}
}
this.CPUCyclesTotal = Math.min(((this.CPUCyclesTotal | 0) + ((underrunAmount >> 1) * (this.audioResamplerFirstPassFactor | 0))) | 0, (+this.clocksPerMilliSecond) << 5) | 0;
}
else {
if (this.dynamicSpeedRefresh && this.settings.dynamicSpeed) {
var speed = +this.getSpeed();
if ((+speed) < 1) {
speed = +Math.min((+speed) + 0.01, 1);
this.processNewSpeed(+speed);
}
}
var overrunAmount = ((remainingAmount | 0) - (this.audioBufferOverclockBlockAmount | 0)) | 0;
if ((overrunAmount | 0) > 0) {
this.CPUCyclesTotal = Math.max(((this.CPUCyclesTotal | 0) - ((overrunAmount >> 1) * (this.audioResamplerFirstPassFactor | 0))) | 0, 0) | 0;
}
}
}
}
}
GameBoyAdvanceEmulator.prototype.audioPushNewState = function () {
if ((this.audioUpdateState | 0) != 0) {
this.IOCore.sound.initializeOutput(this.audioResamplerFirstPassFactor | 0);
this.audioUpdateState = 0;
}
}
GameBoyAdvanceEmulator.prototype.setBufferSpace = function () {
if ((this.audioFound | 0) != 0) {
//Fill the audio system with zeros for buffer stabilization on start:
this.audio.setBufferSpace(this.audioBufferContainAmount | 0);
}
}
GameBoyAdvanceEmulator.prototype.toggleSkipBootROM = function (SKIPBoot) {
this.settings.SKIPBoot = !!SKIPBoot;
}
GameBoyAdvanceEmulator.prototype.toggleDynamicSpeed = function (dynamicSpeed) {
this.settings.dynamicSpeed = !!dynamicSpeed;
this.processNewSpeed(1);
}
GameBoyAdvanceEmulator.prototype.toggleOffthreadGraphics = function (offthreadGfxEnabled) {
this.settings.offthreadGfxEnabled = !!offthreadGfxEnabled;
}

View file

@ -0,0 +1,304 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceGraphics(IOCore) {
//Build references:
this.IOCore = IOCore;
}
GameBoyAdvanceGraphics.prototype.initialize = function () {
this.gfxRenderer = this.IOCore.gfxRenderer;
this.dma = this.IOCore.dma;
this.dmaChannel3 = this.IOCore.dmaChannel3;
this.irq = this.IOCore.irq;
this.wait = this.IOCore.wait;
this.initializeState();
}
GameBoyAdvanceGraphics.prototype.initializeState = function () {
//Initialize Pre-Boot:
this.renderedScanLine = false;
this.statusFlags = 0;
this.IRQFlags = 0;
this.VCounter = 0;
this.currentScanLine = 0;
this.LCDTicks = 0;
if (this.IOCore.SKIPBoot) {
//BIOS entered the ROM at line 0x7C:
this.currentScanLine = 0x7C;
}
}
GameBoyAdvanceGraphics.prototype.addClocks = function (clocks) {
clocks = clocks | 0;
//Call this when clocking the state some more:
this.LCDTicks = ((this.LCDTicks | 0) + (clocks | 0)) | 0;
this.clockLCDState();
}
GameBoyAdvanceGraphics.prototype.clockLCDState = function () {
if ((this.LCDTicks | 0) >= 960) {
this.clockScanLine(); //Line finishes drawing at clock 960.
this.clockLCDStatePostRender(); //Check for hblank and clocking into next line.
}
}
GameBoyAdvanceGraphics.prototype.clockScanLine = function () {
if (!this.renderedScanLine) { //If we rendered the scanline, don't run this again.
this.renderedScanLine = true; //Mark rendering.
if ((this.currentScanLine | 0) < 160) {
this.gfxRenderer.incrementScanLineQueue(); //Tell the gfx JIT to queue another line to draw.
}
}
}
GameBoyAdvanceGraphics.prototype.clockLCDStatePostRender = function () {
if ((this.LCDTicks | 0) >= 1006) {
//HBlank Event Occurred:
this.updateHBlank();
if ((this.LCDTicks | 0) >= 1232) {
//Clocking to next line occurred:
this.clockLCDNextLine();
}
}
}
GameBoyAdvanceGraphics.prototype.clockLCDNextLine = function () {
/*We've now overflowed the LCD scan line state machine counter,
which tells us we need to be on a new scan-line and refresh over.*/
this.renderedScanLine = false; //Unmark line render.
this.statusFlags = this.statusFlags & 0x5; //Un-mark HBlank.
//De-clock for starting on new scan-line:
this.LCDTicks = ((this.LCDTicks | 0) - 1232) | 0; //We start out at the beginning of the next line.
//Increment scanline counter:
this.currentScanLine = ((this.currentScanLine | 0) + 1) | 0; //Increment to the next scan line.
//Handle switching in/out of vblank:
if ((this.currentScanLine | 0) >= 160) {
//Handle special case scan lines of vblank:
switch (this.currentScanLine | 0) {
case 160:
this.updateVBlankStart(); //Update state for start of vblank.
case 161:
this.dmaChannel3.gfxDisplaySyncRequest(); //Display Sync. DMA trigger.
break;
case 162:
this.dmaChannel3.gfxDisplaySyncEnableCheck(); //Display Sync. DMA reset on start of line 162.
break;
case 227:
this.statusFlags = this.statusFlags & 0x6; //Un-mark VBlank on start of last vblank line.
break;
case 228:
this.currentScanLine = 0; //Reset scan-line to zero (First line of draw).
}
}
else if ((this.currentScanLine | 0) > 1) {
this.dmaChannel3.gfxDisplaySyncRequest(); //Display Sync. DMA trigger.
}
this.checkVCounter(); //We're on a new scan line, so check the VCounter for match.
this.isRenderingCheckPreprocess(); //Update a check value.
//Recursive clocking of the LCD state:
this.clockLCDState();
}
GameBoyAdvanceGraphics.prototype.updateHBlank = function () {
if ((this.statusFlags & 0x2) == 0) { //If we were last in HBlank, don't run this again.
this.statusFlags = this.statusFlags | 0x2; //Mark HBlank.
if ((this.IRQFlags & 0x10) != 0) {
this.irq.requestIRQ(0x2); //Check for IRQ.
}
if ((this.currentScanLine | 0) < 160) {
this.dma.gfxHBlankRequest(); //Check for HDMA Trigger.
}
this.isRenderingCheckPreprocess(); //Update a check value.
}
}
GameBoyAdvanceGraphics.prototype.checkVCounter = function () {
if ((this.currentScanLine | 0) == (this.VCounter | 0)) { //Check for VCounter match.
this.statusFlags = this.statusFlags | 0x4;
if ((this.IRQFlags & 0x20) != 0) { //Check for VCounter IRQ.
this.irq.requestIRQ(0x4);
}
}
else {
this.statusFlags = this.statusFlags & 0x3;
}
}
GameBoyAdvanceGraphics.prototype.nextVBlankIRQEventTime = function () {
var nextEventTime = 0x7FFFFFFF;
if ((this.IRQFlags & 0x8) != 0) {
//Only give a time if we're allowed to irq:
nextEventTime = this.nextVBlankEventTime() | 0;
}
return nextEventTime | 0;
}
GameBoyAdvanceGraphics.prototype.nextHBlankEventTime = function () {
var time = this.LCDTicks | 0;
if ((time | 0) < 1006) {
//Haven't reached hblank yet, so hblank offset - current:
time = (1006 - (time | 0)) | 0;
}
else {
//We're in hblank, so it's end clock - current + next scanline hblank offset:
time = (2238 - (time | 0)) | 0;
}
return time | 0;
}
GameBoyAdvanceGraphics.prototype.nextHBlankIRQEventTime = function () {
var nextEventTime = 0x7FFFFFFF;
if ((this.IRQFlags & 0x10) != 0) {
//Only give a time if we're allowed to irq:
nextEventTime = this.nextHBlankEventTime() | 0;
}
return nextEventTime | 0;
}
GameBoyAdvanceGraphics.prototype.nextVCounterIRQEventTime = function () {
var nextEventTime = 0x7FFFFFFF;
if ((this.IRQFlags & 0x20) != 0) {
//Only give a time if we're allowed to irq:
nextEventTime = this.nextVCounterEventTime() | 0;
}
return nextEventTime | 0;
}
GameBoyAdvanceGraphics.prototype.nextVBlankEventTime = function () {
var nextEventTime = this.currentScanLine | 0;
if ((nextEventTime | 0) < 160) {
//Haven't reached vblank yet, so vblank offset - current:
nextEventTime = (160 - (nextEventTime | 0)) | 0;
}
else {
//We're in vblank, so it's end clock - current + next frame vblank offset:
nextEventTime = (388 - (nextEventTime | 0)) | 0;
}
//Convert line count to clocks:
nextEventTime = this.convertScanlineToClocks(nextEventTime | 0) | 0;
//Subtract scanline offset from clocks:
nextEventTime = ((nextEventTime | 0) - (this.LCDTicks | 0)) | 0;
return nextEventTime | 0;
}
GameBoyAdvanceGraphics.prototype.nextHBlankDMAEventTime = function () {
var nextEventTime = this.nextHBlankEventTime() | 0;
if ((this.currentScanLine | 0) > 159 || ((this.currentScanLine | 0) == 159 && (this.LCDTicks | 0) >= 1006)) {
//No HBlank DMA in VBlank:
var linesToSkip = (227 - (this.currentScanLine | 0)) | 0;
linesToSkip = this.convertScanlineToClocks(linesToSkip | 0) | 0;
nextEventTime = ((nextEventTime | 0) + (linesToSkip | 0)) | 0;
}
return nextEventTime | 0;
}
GameBoyAdvanceGraphics.prototype.nextVCounterEventTime = function () {
var nextEventTime = 0x7FFFFFFF;
if ((this.VCounter | 0) <= 227) {
//Only match lines within screen or vblank:
nextEventTime = ((this.VCounter | 0) - (this.currentScanLine | 0)) | 0;
if ((nextEventTime | 0) <= 0) {
nextEventTime = ((nextEventTime | 0) + 228) | 0;
}
nextEventTime = this.convertScanlineToClocks(nextEventTime | 0) | 0;
nextEventTime = ((nextEventTime | 0) - (this.LCDTicks | 0)) | 0;
}
return nextEventTime | 0;
}
GameBoyAdvanceGraphics.prototype.nextDisplaySyncEventTime = function (delay) {
delay = delay | 0;
var nextEventTime = 0x7FFFFFFF;
if ((this.currentScanLine | 0) >= 161 || (delay | 0) != 0) {
//Skip to line 2 metrics:
nextEventTime = (230 - (this.currentScanLine | 0)) | 0;
nextEventTime = this.convertScanlineToClocks(nextEventTime | 0) | 0;
nextEventTime = ((nextEventTime | 0) - (this.LCDTicks | 0)) | 0;
}
else if ((this.currentScanLine | 0) == 0) {
//Doesn't start until line 2:
nextEventTime = (2464 - (this.LCDTicks | 0)) | 0;
}
else {
//Line 2 through line 161:
nextEventTime = (1232 - (this.LCDTicks | 0)) | 0;
}
return nextEventTime | 0;
}
if (typeof Math.imul == "function") {
//Math.imul found, insert the optimized path in:
GameBoyAdvanceGraphics.prototype.convertScanlineToClocks = function (lines) {
lines = lines | 0;
lines = Math.imul(lines | 0, 1232) | 0;
return lines | 0;
}
}
else {
//Math.imul not found, use the compatibility method:
GameBoyAdvanceGraphics.prototype.convertScanlineToClocks = function (lines) {
lines = lines | 0;
lines = ((lines | 0) * 1232) | 0;
return lines | 0;
}
}
GameBoyAdvanceGraphics.prototype.updateVBlankStart = function () {
this.statusFlags = this.statusFlags | 0x1; //Mark VBlank.
if ((this.IRQFlags & 0x8) != 0) { //Check for VBlank IRQ.
this.irq.requestIRQ(0x1);
}
this.gfxRenderer.ensureFraming();
this.dma.gfxVBlankRequest();
}
GameBoyAdvanceGraphics.prototype.isRenderingCheckPreprocess = function () {
var isInVisibleLines = ((this.gfxRenderer.IOData8[0] & 0x80) == 0 && (this.statusFlags & 0x1) == 0);
var isRendering = (isInVisibleLines && (this.statusFlags & 0x2) == 0) ? 2 : 1;
var isOAMRendering = (isInVisibleLines && ((this.statusFlags & 0x2) == 0 || (this.gfxRenderer.IOData8[0] & 0x20) == 0)) ? 2 : 1;
this.wait.updateRenderStatus(isRendering | 0, isOAMRendering | 0);
}
GameBoyAdvanceGraphics.prototype.writeDISPSTAT8_0 = function (data) {
data = data | 0;
this.IOCore.updateCoreClocking();
//VBlank flag read only.
//HBlank flag read only.
//V-Counter flag read only.
//Only LCD IRQ generation enablers can be set here:
this.IRQFlags = data & 0x38;
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceGraphics.prototype.writeDISPSTAT8_1 = function (data) {
data = data | 0;
data = data & 0xFF;
//V-Counter match value:
if ((data | 0) != (this.VCounter | 0)) {
this.IOCore.updateCoreClocking();
this.VCounter = data | 0;
this.checkVCounter();
this.IOCore.updateCoreEventTime();
}
}
GameBoyAdvanceGraphics.prototype.writeDISPSTAT16 = function (data) {
data = data | 0;
this.IOCore.updateCoreClocking();
//VBlank flag read only.
//HBlank flag read only.
//V-Counter flag read only.
//Only LCD IRQ generation enablers can be set here:
this.IRQFlags = data & 0x38;
data = (data >> 8) & 0xFF;
//V-Counter match value:
if ((data | 0) != (this.VCounter | 0)) {
this.VCounter = data | 0;
this.checkVCounter();
}
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceGraphics.prototype.readDISPSTAT8_0 = function () {
this.IOCore.updateGraphicsClocking();
return (this.statusFlags | this.IRQFlags);
}
GameBoyAdvanceGraphics.prototype.readDISPSTAT8_1 = function () {
return this.VCounter | 0;
}
GameBoyAdvanceGraphics.prototype.readDISPSTAT8_2 = function () {
this.IOCore.updateGraphicsClocking();
return this.currentScanLine | 0;
}
GameBoyAdvanceGraphics.prototype.readDISPSTAT16_0 = function () {
this.IOCore.updateGraphicsClocking();
return ((this.VCounter << 8) | this.statusFlags | this.IRQFlags);
}
GameBoyAdvanceGraphics.prototype.readDISPSTAT32 = function () {
this.IOCore.updateGraphicsClocking();
return ((this.currentScanLine << 16) | (this.VCounter << 8) | this.statusFlags | this.IRQFlags);
}

View file

@ -0,0 +1,180 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceIRQ(IOCore) {
//Build references:
this.IOCore = IOCore;
}
GameBoyAdvanceIRQ.prototype.initialize = function () {
this.interruptsEnabled = 0;
this.interruptsRequested = 0;
this.IME = 0;
this.gfxState = this.IOCore.gfxState;
this.timer = this.IOCore.timer;
this.dmaChannel0 = this.IOCore.dmaChannel0;
this.dmaChannel1 = this.IOCore.dmaChannel1;
this.dmaChannel2 = this.IOCore.dmaChannel2;
this.dmaChannel3 = this.IOCore.dmaChannel3;
}
GameBoyAdvanceIRQ.prototype.IRQMatch = function () {
//Used to exit HALT:
return (this.interruptsEnabled & this.interruptsRequested);
}
GameBoyAdvanceIRQ.prototype.checkForIRQFire = function () {
//Tell the CPU core when the emulated hardware is triggering an IRQ:
this.IOCore.cpu.triggerIRQ(this.interruptsEnabled & this.interruptsRequested & this.IME);
}
GameBoyAdvanceIRQ.prototype.requestIRQ = function (irqLineToSet) {
irqLineToSet = irqLineToSet | 0;
this.interruptsRequested = this.interruptsRequested | irqLineToSet;
this.checkForIRQFire();
}
GameBoyAdvanceIRQ.prototype.writeIME = function (data) {
data = data | 0;
this.IOCore.updateCoreClocking();
this.IME = (data << 31) >> 31;
this.checkForIRQFire();
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceIRQ.prototype.writeIE8_0 = function (data) {
data = data | 0;
this.IOCore.updateCoreClocking();
var oldValue = this.interruptsEnabled & 0x3F00;
data = data & 0xFF;
data = data | oldValue;
this.interruptsEnabled = data | 0;
this.checkForIRQFire();
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceIRQ.prototype.writeIE8_1 = function (data) {
data = data | 0;
this.IOCore.updateCoreClocking();
var oldValue = this.interruptsEnabled & 0xFF;
data = (data & 0x3F) << 8;
data = data | oldValue;
this.interruptsEnabled = data | 0;
this.checkForIRQFire();
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceIRQ.prototype.writeIE16 = function (data) {
data = data | 0;
this.IOCore.updateCoreClocking();
this.interruptsEnabled = data & 0x3FFF;
this.checkForIRQFire();
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceIRQ.prototype.writeIF8_0 = function (data) {
data = data | 0;
this.IOCore.updateCoreClocking();
data = ~(data & 0xFF);
this.interruptsRequested = this.interruptsRequested & data;
this.checkForIRQFire();
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceIRQ.prototype.writeIF8_1 = function (data) {
data = data | 0;
this.IOCore.updateCoreClocking();
data = ~((data & 0xFF) << 8);
this.interruptsRequested = this.interruptsRequested & data;
this.checkForIRQFire();
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceIRQ.prototype.writeIF16 = function (data) {
data = data | 0;
this.IOCore.updateCoreClocking();
data = ~data;
this.interruptsRequested = this.interruptsRequested & data;
this.checkForIRQFire();
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceIRQ.prototype.writeIRQ32 = function (data) {
data = data | 0;
this.IOCore.updateCoreClocking();
this.interruptsEnabled = data & 0x3FFF;
data = ~(data >> 16);
this.interruptsRequested = this.interruptsRequested & data;
this.checkForIRQFire();
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceIRQ.prototype.readIME = function () {
var data = this.IME & 0x1;
return data | 0;
}
GameBoyAdvanceIRQ.prototype.readIE8_0 = function () {
var data = this.interruptsEnabled & 0xFF;
return data | 0;
}
GameBoyAdvanceIRQ.prototype.readIE8_1 = function () {
var data = this.interruptsEnabled >> 8;
return data | 0;
}
GameBoyAdvanceIRQ.prototype.readIE16 = function () {
var data = this.interruptsEnabled | 0;
return data | 0;
}
GameBoyAdvanceIRQ.prototype.readIF8_0 = function () {
this.IOCore.updateCoreSpillRetain();
var data = this.interruptsRequested & 0xFF;
return data | 0;
}
GameBoyAdvanceIRQ.prototype.readIF8_1 = function () {
this.IOCore.updateCoreSpillRetain();
var data = this.interruptsRequested >> 8;
return data | 0;
}
GameBoyAdvanceIRQ.prototype.readIF16 = function () {
this.IOCore.updateCoreSpillRetain();
var data = this.interruptsRequested | 0;
return data | 0;
}
GameBoyAdvanceIRQ.prototype.readIRQ32 = function () {
this.IOCore.updateCoreSpillRetain();
var data = (this.interruptsRequested << 16) | this.interruptsEnabled;
return data | 0;
}
GameBoyAdvanceIRQ.prototype.nextEventTime = function () {
var clocks = 0x7FFFFFFF;
if ((this.interruptsEnabled & 0x1) != 0) {
clocks = this.gfxState.nextVBlankIRQEventTime() | 0;
}
if ((this.interruptsEnabled & 0x2) != 0) {
clocks = Math.min(clocks | 0, this.gfxState.nextHBlankIRQEventTime() | 0) | 0;
}
if ((this.interruptsEnabled & 0x4) != 0) {
clocks = Math.min(clocks | 0, this.gfxState.nextVCounterIRQEventTime() | 0) | 0;
}
if ((this.interruptsEnabled & 0x8) != 0) {
clocks = Math.min(clocks | 0, this.timer.nextTimer0IRQEventTime() | 0) | 0;
}
if ((this.interruptsEnabled & 0x10) != 0) {
clocks = Math.min(clocks | 0, this.timer.nextTimer1IRQEventTime() | 0) | 0;
}
if ((this.interruptsEnabled & 0x20) != 0) {
clocks = Math.min(clocks | 0, this.timer.nextTimer2IRQEventTime() | 0) | 0;
}
if ((this.interruptsEnabled & 0x40) != 0) {
clocks = Math.min(clocks | 0, this.timer.nextTimer3IRQEventTime() | 0) | 0;
}
/*if ((this.interruptsEnabled & 0x80) != 0) {
clocks = Math.min(clocks | 0, this.IOCore.serial.nextIRQEventTime() | 0) | 0;
}
if ((this.interruptsEnabled & 0x2000) != 0) {
clocks = Math.min(clocks | 0, this.IOCore.cartridge.nextIRQEventTime() | 0) | 0;
}*/
return clocks | 0;
}
GameBoyAdvanceIRQ.prototype.nextIRQEventTime = function () {
var clocks = 0x7FFFFFFF;
//Checks IME:
if ((this.IME | 0) != 0) {
clocks = this.nextEventTime() | 0;
}
return clocks | 0;
}

View file

@ -0,0 +1,83 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceJoyPad(IOCore) {
this.IOCore = IOCore;
}
GameBoyAdvanceJoyPad.prototype.initialize = function () {
this.keyInput = 0x3FF;
this.keyInterrupt = 0;
}
GameBoyAdvanceJoyPad.prototype.keyPress = function (keyPressed) {
keyPressed = keyPressed | 0;
keyPressed = 1 << (keyPressed | 0);
this.keyInput = this.keyInput & (~keyPressed);
this.checkForMatch();
}
GameBoyAdvanceJoyPad.prototype.keyRelease = function (keyReleased) {
keyReleased = keyReleased | 0;
keyReleased = 1 << (keyReleased | 0);
this.keyInput = this.keyInput | keyReleased;
this.checkForMatch();
}
GameBoyAdvanceJoyPad.prototype.checkForMatch = function () {
if ((this.keyInterrupt & 0x8000) != 0) {
if (((~this.keyInput) & this.keyInterrupt & 0x3FF) == (this.keyInterrupt & 0x3FF)) {
this.IOCore.deflagStop();
this.checkForIRQ();
}
}
else if (((~this.keyInput) & this.keyInterrupt & 0x3FF) != 0) {
this.IOCore.deflagStop();
this.checkForIRQ();
}
}
GameBoyAdvanceJoyPad.prototype.checkForIRQ = function () {
if ((this.keyInterrupt & 0x4000) != 0) {
this.IOCore.irq.requestIRQ(0x1000);
}
}
GameBoyAdvanceJoyPad.prototype.readKeyStatus8_0 = function () {
return this.keyInput & 0xFF;
}
GameBoyAdvanceJoyPad.prototype.readKeyStatus8_1 = function () {
return (this.keyInput >> 8) | 0;
}
GameBoyAdvanceJoyPad.prototype.readKeyStatus16 = function () {
return this.keyInput | 0;
}
GameBoyAdvanceJoyPad.prototype.writeKeyControl8_0 = function (data) {
data = data | 0;
this.keyInterrupt = this.keyInterrupt & 0xC300;
data = data & 0xFF;
this.keyInterrupt = this.keyInterrupt | data;
}
GameBoyAdvanceJoyPad.prototype.writeKeyControl8_1 = function (data) {
data = data | 0;
this.keyInterrupt = this.keyInterrupt & 0xFF;
data = data & 0xC3;
this.keyInterrupt = this.keyInterrupt | (data << 8);
}
GameBoyAdvanceJoyPad.prototype.writeKeyControl16 = function (data) {
data = data | 0;
this.keyInterrupt = data & 0xC3FF;
}
GameBoyAdvanceJoyPad.prototype.readKeyControl8_0 = function () {
return this.keyInterrupt & 0xFF;
}
GameBoyAdvanceJoyPad.prototype.readKeyControl8_1 = function () {
return (this.keyInterrupt >> 8) | 0;
}
GameBoyAdvanceJoyPad.prototype.readKeyControl16 = function () {
return this.keyInterrupt | 0;
}
GameBoyAdvanceJoyPad.prototype.readKeyStatusControl32 = function () {
return this.keyInput | (this.keyInterrupt << 16);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,428 @@
"use strict";
/*
Copyright (C) 2012-2016 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceIO(SKIPBoot, coreExposed, BIOS, ROM) {
//State Machine Tracking:
this.systemStatus = 0;
this.cyclesToIterate = 0;
this.cyclesOveriteratedPreviously = 0;
this.accumulatedClocks = 0;
this.graphicsClocks = 0;
this.timerClocks = 0;
this.serialClocks = 0;
this.nextEventClocks = 0;
//this.BIOSFound = false;
//Do we skip the BIOS Boot Intro?
this.SKIPBoot = !!SKIPBoot;
//References passed to us:
this.coreExposed = coreExposed;
this.BIOS = BIOS;
this.ROM = ROM;
//Build the core object layout:
this.memory = new GameBoyAdvanceMemory(this);
this.dma = new GameBoyAdvanceDMA(this);
this.dmaChannel0 = new GameBoyAdvanceDMA0(this);
this.dmaChannel1 = new GameBoyAdvanceDMA1(this);
this.dmaChannel2 = new GameBoyAdvanceDMA2(this);
this.dmaChannel3 = new GameBoyAdvanceDMA3(this);
this.gfxState = new GameBoyAdvanceGraphics(this);
this.gfxRenderer = new GameBoyAdvanceRendererProxy(this);
this.sound = new GameBoyAdvanceSound(this);
this.timer = new GameBoyAdvanceTimer(this);
this.irq = new GameBoyAdvanceIRQ(this);
this.serial = new GameBoyAdvanceSerial(this);
this.joypad = new GameBoyAdvanceJoyPad(this);
this.cartridge = new GameBoyAdvanceCartridge(this);
this.saves = new GameBoyAdvanceSaves(this);
this.wait = new GameBoyAdvanceWait(this);
this.cpu = new GameBoyAdvanceCPU(this);
}
GameBoyAdvanceIO.prototype.initialize = function () {
var allowInit = 1;
//Now initialize each component:
if ((this.memory.initialize() | 0) == 1) {
//BIOS loaded in OK, so initialize the rest:
this.dma.initialize();
this.dmaChannel0.initialize();
this.dmaChannel1.initialize();
this.dmaChannel2.initialize();
this.dmaChannel3.initialize();
this.gfxState.initialize();
this.gfxRenderer.initialize();
this.sound.initialize();
this.timer.initialize();
this.irq.initialize();
this.serial.initialize();
this.joypad.initialize();
this.cartridge.initialize();
this.saves.initialize();
this.wait.initialize();
this.cpu.initialize();
}
else {
allowInit = 0;
}
return allowInit | 0;
}
GameBoyAdvanceIO.prototype.assignInstructionCoreReferences = function (ARM, THUMB) {
//Passed here once the CPU component is initialized:
this.ARM = ARM;
this.THUMB = THUMB;
}
GameBoyAdvanceIO.prototype.enter = function (CPUCyclesTotal) {
//Find out how many clocks to iterate through this run:
this.cyclesToIterate = ((CPUCyclesTotal | 0) + (this.cyclesOveriteratedPreviously | 0)) | 0;
//An extra check to make sure we don't do stuff if we did too much last run:
if ((this.cyclesToIterate | 0) > 0) {
//Update our core event prediction:
this.updateCoreEventTime();
//If clocks remaining, run iterator:
this.run();
//Spill our core event clocking:
this.updateCoreClocking();
//Ensure audio buffers at least once per iteration:
this.sound.audioJIT();
}
//If we clocked just a little too much, subtract the extra from the next run:
this.cyclesOveriteratedPreviously = this.cyclesToIterate | 0;
}
GameBoyAdvanceIO.prototype.run = function () {
//Clock through the state machine:
while (true) {
//Dispatch to optimized run loops:
switch (this.systemStatus & 0x84) {
case 0:
//ARM instruction set:
this.runARM();
break;
case 0x4:
//THUMB instruction set:
this.runTHUMB();
break;
default:
//End of stepping:
this.deflagIterationEnd();
return;
}
}
}
GameBoyAdvanceIO.prototype.runARM = function () {
//Clock through the state machine:
while (true) {
//Handle the current system state selected:
switch (this.systemStatus | 0) {
case 0: //CPU Handle State (Normal ARM)
this.ARM.executeIteration();
break;
case 1:
case 2: //CPU Handle State (Bubble ARM)
this.ARM.executeBubble();
this.tickBubble();
break;
default: //Handle lesser called / End of stepping
//Dispatch on IRQ/DMA/HALT/STOP/END bit flags
switch (this.systemStatus >> 2) {
case 0x2:
//IRQ Handle State:
this.handleIRQARM();
break;
case 0x4:
case 0x6:
//DMA Handle State
case 0xC:
case 0xE:
//DMA Inside Halt State
this.handleDMA();
break;
case 0x8:
case 0xA:
//Handle Halt State
this.handleHalt();
break;
default: //Handle Stop State
//THUMB flagged stuff falls to here intentionally:
//End of Stepping and/or CPU run loop switch:
if ((this.systemStatus & 0x84) != 0) {
return;
}
this.handleStop();
}
}
}
}
GameBoyAdvanceIO.prototype.runTHUMB = function () {
//Clock through the state machine:
while (true) {
//Handle the current system state selected:
switch (this.systemStatus | 0) {
case 4: //CPU Handle State (Normal THUMB)
this.THUMB.executeIteration();
break;
case 5:
case 6: //CPU Handle State (Bubble THUMB)
this.THUMB.executeBubble();
this.tickBubble();
break;
default: //Handle lesser called / End of stepping
//Dispatch on IRQ/DMA/HALT/STOP/END bit flags
switch (this.systemStatus >> 2) {
case 0x3:
//IRQ Handle State:
this.handleIRQThumb();
break;
case 0x5:
case 0x7:
//DMA Handle State
case 0xD:
case 0xF:
//DMA Inside Halt State
this.handleDMA();
break;
case 0x9:
case 0x11:
//Handle Halt State
this.handleHalt();
break;
default: //Handle Stop State
//ARM flagged stuff falls to here intentionally:
//End of Stepping and/or CPU run loop switch:
if ((this.systemStatus & 0x84) != 0x4) {
return;
}
this.handleStop();
}
}
}
}
GameBoyAdvanceIO.prototype.updateCore = function (clocks) {
clocks = clocks | 0;
//This is used during normal/dma modes of operation:
this.accumulatedClocks = ((this.accumulatedClocks | 0) + (clocks | 0)) | 0;
if ((this.accumulatedClocks | 0) >= (this.nextEventClocks | 0)) {
this.updateCoreSpill();
}
}
GameBoyAdvanceIO.prototype.updateCoreForce = function (clocks) {
clocks = clocks | 0;
//This is used during halt mode of operation:
this.accumulatedClocks = ((this.accumulatedClocks | 0) + (clocks | 0)) | 0;
this.updateCoreSpill();
}
GameBoyAdvanceIO.prototype.updateCoreNegative = function (clocks) {
clocks = clocks | 0;
//This is used during normal/dma modes of operation:
this.accumulatedClocks = ((this.accumulatedClocks | 0) - (clocks | 0)) | 0;
if ((this.accumulatedClocks | 0) >= (this.nextEventClocks | 0)) {
this.updateCoreSpill();
}
}
GameBoyAdvanceIO.prototype.updateCoreSingle = function () {
//This is used during normal/dma modes of operation:
this.accumulatedClocks = ((this.accumulatedClocks | 0) + 1) | 0;
if ((this.accumulatedClocks | 0) >= (this.nextEventClocks | 0)) {
this.updateCoreSpill();
}
}
GameBoyAdvanceIO.prototype.updateCoreSpill = function () {
//Invalidate & recompute new event times:
this.updateCoreClocking();
this.updateCoreEventTime();
}
GameBoyAdvanceIO.prototype.updateCoreSpillRetain = function () {
//Keep the last prediction, just decrement it out, as it's still valid:
this.nextEventClocks = ((this.nextEventClocks | 0) - (this.accumulatedClocks | 0)) | 0;
this.updateCoreClocking();
}
GameBoyAdvanceIO.prototype.updateCoreClocking = function () {
var clocks = this.accumulatedClocks | 0;
//Decrement the clocks per iteration counter:
this.cyclesToIterate = ((this.cyclesToIterate | 0) - (clocks | 0)) | 0;
//Clock all components:
this.gfxState.addClocks(((clocks | 0) - (this.graphicsClocks | 0)) | 0);
this.timer.addClocks(((clocks | 0) - (this.timerClocks | 0)) | 0);
this.serial.addClocks(((clocks | 0) - (this.serialClocks | 0)) | 0);
this.accumulatedClocks = 0;
this.graphicsClocks = 0;
this.timerClocks = 0;
this.serialClocks = 0;
}
GameBoyAdvanceIO.prototype.updateGraphicsClocking = function () {
//Clock gfx component:
this.gfxState.addClocks(((this.accumulatedClocks | 0) - (this.graphicsClocks | 0)) | 0);
this.graphicsClocks = this.accumulatedClocks | 0;
}
GameBoyAdvanceIO.prototype.updateTimerClocking = function () {
//Clock timer component:
this.timer.addClocks(((this.accumulatedClocks | 0) - (this.timerClocks | 0)) | 0);
this.timerClocks = this.accumulatedClocks | 0;
}
GameBoyAdvanceIO.prototype.updateSerialClocking = function () {
//Clock serial component:
this.serial.addClocks(((this.accumulatedClocks | 0) - (this.serialClocks | 0)) | 0);
this.serialClocks = this.accumulatedClocks | 0;
}
GameBoyAdvanceIO.prototype.updateCoreEventTime = function () {
//Predict how many clocks until the next DMA or IRQ event:
this.nextEventClocks = this.cyclesUntilNextEvent() | 0;
}
GameBoyAdvanceIO.prototype.getRemainingCycles = function () {
//Return the number of cycles left until iteration end:
if ((this.cyclesToIterate | 0) < 1) {
//Change our stepper to our end sequence:
this.flagIterationEnd();
return 0;
}
return this.cyclesToIterate | 0;
}
GameBoyAdvanceIO.prototype.handleIRQARM = function () {
if ((this.systemStatus | 0) > 0x8) {
//CPU Handle State (Bubble ARM)
this.ARM.executeBubble();
this.tickBubble();
}
else {
//CPU Handle State (IRQ)
this.cpu.IRQinARM();
}
}
GameBoyAdvanceIO.prototype.handleIRQThumb = function () {
if ((this.systemStatus | 0) > 0xC) {
//CPU Handle State (Bubble THUMB)
this.THUMB.executeBubble();
this.tickBubble();
}
else {
//CPU Handle State (IRQ)
this.cpu.IRQinTHUMB();
}
}
GameBoyAdvanceIO.prototype.handleDMA = function () {
/*
Loop our state status in here as
an optimized iteration, as DMA stepping instances
happen in quick succession of each other, and
aren't often done for one memory word only.
*/
do {
//Perform a DMA read and write:
this.dma.perform();
} while ((this.systemStatus & 0x90) == 0x10);
}
GameBoyAdvanceIO.prototype.handleHalt = function () {
if ((this.irq.IRQMatch() | 0) == 0) {
//Clock up to next IRQ match or DMA:
this.updateCoreForce(this.cyclesUntilNextHALTEvent() | 0);
}
else {
//Exit HALT promptly:
this.deflagHalt();
}
}
GameBoyAdvanceIO.prototype.handleStop = function () {
//Update sound system to add silence to buffer:
this.sound.addClocks(this.getRemainingCycles() | 0);
this.cyclesToIterate = 0;
//Exits when user presses joypad or from an external irq outside of GBA internal.
}
GameBoyAdvanceIO.prototype.cyclesUntilNextHALTEvent = function () {
//Find the clocks to the next HALT leave or DMA event:
var haltClocks = this.irq.nextEventTime() | 0;
var dmaClocks = this.dma.nextEventTime() | 0;
return this.solveClosestTime(haltClocks | 0, dmaClocks | 0) | 0;
}
GameBoyAdvanceIO.prototype.cyclesUntilNextEvent = function () {
//Find the clocks to the next IRQ or DMA event:
var irqClocks = this.irq.nextIRQEventTime() | 0;
var dmaClocks = this.dma.nextEventTime() | 0;
return this.solveClosestTime(irqClocks | 0, dmaClocks | 0) | 0;
}
GameBoyAdvanceIO.prototype.solveClosestTime = function (clocks1, clocks2) {
clocks1 = clocks1 | 0;
clocks2 = clocks2 | 0;
//Find the clocks closest to the next event:
var clocks = this.getRemainingCycles() | 0;
clocks = Math.min(clocks | 0, clocks1 | 0, clocks2 | 0);
return clocks | 0;
}
GameBoyAdvanceIO.prototype.flagBubble = function () {
//Flag a CPU pipeline bubble to step through:
this.systemStatus = this.systemStatus | 0x2;
}
GameBoyAdvanceIO.prototype.tickBubble = function () {
//Tick down a CPU pipeline bubble to step through:
this.systemStatus = ((this.systemStatus | 0) - 1) | 0;
}
GameBoyAdvanceIO.prototype.flagTHUMB = function () {
//Flag a CPU IRQ to step through:
this.systemStatus = this.systemStatus | 0x4;
}
GameBoyAdvanceIO.prototype.deflagTHUMB = function () {
//Deflag a CPU IRQ to step through:
this.systemStatus = this.systemStatus & 0xFB;
}
GameBoyAdvanceIO.prototype.flagIRQ = function () {
//Flag THUMB CPU mode to step through:
this.systemStatus = this.systemStatus | 0x8;
}
GameBoyAdvanceIO.prototype.deflagIRQ = function () {
//Deflag THUMB CPU mode to step through:
this.systemStatus = this.systemStatus & 0xF7;
}
GameBoyAdvanceIO.prototype.flagDMA = function () {
//Flag a DMA event to step through:
this.systemStatus = this.systemStatus | 0x10;
}
GameBoyAdvanceIO.prototype.deflagDMA = function () {
//Deflag a DMA event to step through:
this.systemStatus = this.systemStatus & 0xEF;
}
GameBoyAdvanceIO.prototype.flagHalt = function () {
//Flag a halt event to step through:
this.systemStatus = this.systemStatus | 0x20;
}
GameBoyAdvanceIO.prototype.deflagHalt = function () {
//Deflag a halt event to step through:
this.systemStatus = this.systemStatus & 0xDF;
}
GameBoyAdvanceIO.prototype.flagStop = function () {
//Flag a halt event to step through:
this.systemStatus = this.systemStatus | 0x40;
}
GameBoyAdvanceIO.prototype.deflagStop = function () {
//Deflag a halt event to step through:
this.systemStatus = this.systemStatus & 0xBF;
}
GameBoyAdvanceIO.prototype.flagIterationEnd = function () {
//Flag a run loop kill event to step through:
this.systemStatus = this.systemStatus | 0x80;
}
GameBoyAdvanceIO.prototype.deflagIterationEnd = function () {
//Deflag a run loop kill event to step through:
this.systemStatus = this.systemStatus & 0x7F;
}
GameBoyAdvanceIO.prototype.isStopped = function () {
//Sound system uses this to emulate a unpowered audio output:
return ((this.systemStatus & 0x40) != 0);
}
GameBoyAdvanceIO.prototype.inDMA = function () {
//Save system uses this to detect dma:
return ((this.systemStatus & 0x10) != 0);
}
GameBoyAdvanceIO.prototype.getCurrentFetchValue = function () {
//Last valid value output for bad reads:
var fetch = 0;
if ((this.systemStatus & 0x10) == 0) {
fetch = this.cpu.getCurrentFetchValue() | 0;
}
else {
fetch = this.dma.getCurrentFetchValue() | 0;
}
return fetch | 0;
}

View file

@ -0,0 +1,239 @@
"use strict";
/*
Copyright (C) 2012-2016 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceSaves(IOCore) {
this.cartridge = IOCore.cartridge;
}
GameBoyAdvanceSaves.prototype.initialize = function () {
this.saveType = 0;
this.GPIOChip = new GameBoyAdvanceGPIOChip();
this.UNDETERMINED = new GameBoyAdvanceSaveDeterminer(this);
this.SRAMChip = new GameBoyAdvanceSRAMChip();
this.FLASHChip = new GameBoyAdvanceFLASHChip(this.cartridge.flash_is128, this.cartridge.flash_isAtmel);
this.EEPROMChip = new GameBoyAdvanceEEPROMChip(this.cartridge.IOCore);
this.currentChip = this.UNDETERMINED;
this.referenceSave(this.saveType);
}
GameBoyAdvanceSaves.prototype.referenceSave = function (saveType) {
saveType = saveType | 0;
switch (saveType | 0) {
case 0:
this.currentChip = this.UNDETERMINED;
break;
case 1:
this.currentChip = this.SRAMChip;
break;
case 2:
this.currentChip = this.FLASHChip;
break;
case 3:
this.currentChip = this.EEPROMChip;
}
this.currentChip.initialize();
this.saveType = saveType | 0;
}
GameBoyAdvanceSaves.prototype.importSave = function (saves, saveType) {
saveType = saveType | 0;
this.UNDETERMINED.load(saves);
this.SRAMChip.load(saves);
this.FLASHChip.load(saves);
this.EEPROMChip.load(saves);
this.referenceSave(saveType | 0);
}
GameBoyAdvanceSaves.prototype.importRTC = function (saves) {
this.GPIOChip.loadRTC(saves);
}
GameBoyAdvanceSaves.prototype.importGPIOType = function (gpioType) {
gpioType = gpioType | 0;
this.GPIOChip.loadType(gpioType | 0);
}
GameBoyAdvanceSaves.prototype.exportSave = function () {
return this.currentChip.saves;
}
GameBoyAdvanceSaves.prototype.exportSaveType = function () {
return this.saveType | 0;
}
GameBoyAdvanceSaves.prototype.readGPIO8 = function (address) {
address = address | 0;
var data = 0;
if ((this.GPIOChip.getType() | 0) > 0) {
//GPIO:
data = this.GPIOChip.read8(address | 0) | 0;
}
else {
//ROM:
data = this.cartridge.readROMOnly8(address | 0) | 0;
}
return data | 0;
}
GameBoyAdvanceSaves.prototype.readEEPROM8 = function (address) {
address = address | 0;
var data = 0;
if ((this.saveType | 0) == 3) {
//EEPROM:
data = this.EEPROMChip.read8() | 0;
}
else {
//UNKNOWN:
data = this.UNDETERMINED.readEEPROM8(address | 0) | 0;
}
return data | 0;
}
GameBoyAdvanceSaves.prototype.readGPIO16 = function (address) {
address = address | 0;
var data = 0;
if ((this.GPIOChip.getType() | 0) > 0) {
//GPIO:
data = this.GPIOChip.read16(address | 0) | 0;
}
else {
//ROM:
data = this.cartridge.readROMOnly16(address | 0) | 0;
}
return data | 0;
}
GameBoyAdvanceSaves.prototype.readEEPROM16 = function (address) {
address = address | 0;
var data = 0;
if ((this.saveType | 0) == 3) {
//EEPROM:
data = this.EEPROMChip.read16() | 0;
}
else {
//UNKNOWN:
data = this.UNDETERMINED.readEEPROM16(address | 0) | 0;
}
return data | 0;
}
GameBoyAdvanceSaves.prototype.readGPIO32 = function (address) {
address = address | 0;
var data = 0;
if ((this.GPIOChip.getType() | 0) > 0) {
//GPIO:
data = this.GPIOChip.read32(address | 0) | 0;
}
else {
//ROM:
data = this.cartridge.readROMOnly32(address | 0) | 0;
}
return data | 0;
}
GameBoyAdvanceSaves.prototype.readEEPROM32 = function (address) {
address = address | 0;
var data = 0;
if ((this.saveType | 0) == 3) {
//EEPROM:
data = this.EEPROMChip.read32() | 0;
}
else {
//UNKNOWN:
data = this.UNDETERMINED.readEEPROM32(address | 0) | 0;
}
return data | 0;
}
GameBoyAdvanceSaves.prototype.readSRAM = function (address) {
address = address | 0;
var data = 0;
switch (this.saveType | 0) {
case 0:
//UNKNOWN:
data = this.UNDETERMINED.readSRAM(address | 0) | 0;
break;
case 1:
//SRAM:
data = this.SRAMChip.read(address | 0) | 0;
break;
case 2:
//FLASH:
data = this.FLASHChip.read(address | 0) | 0;
}
return data | 0;
}
GameBoyAdvanceSaves.prototype.writeGPIO8 = function (address, data) {
address = address | 0;
data = data | 0;
if ((this.GPIOChip.getType() | 0) > 0) {
//GPIO:
this.GPIOChip.write8(address | 0, data | 0);
}
else {
//Unknown:
this.UNDETERMINED.writeGPIO8(address | 0, data | 0);
}
}
GameBoyAdvanceSaves.prototype.writeGPIO16 = function (address, data) {
address = address | 0;
data = data | 0;
if ((this.GPIOChip.getType() | 0) > 0) {
//GPIO:
this.GPIOChip.write16(address | 0, data | 0);
}
else {
//Unknown:
this.UNDETERMINED.writeGPIO16(address | 0, data | 0);
}
}
GameBoyAdvanceSaves.prototype.writeEEPROM16 = function (address, data) {
address = address | 0;
data = data | 0;
if ((this.saveType | 0) == 3) {
//EEPROM:
this.EEPROMChip.write16(data | 0);
}
else {
//Unknown:
this.UNDETERMINED.writeEEPROM16(address | 0, data | 0);
}
}
GameBoyAdvanceSaves.prototype.writeGPIO32 = function (address, data) {
address = address | 0;
data = data | 0;
if ((this.GPIOChip.getType() | 0) > 0) {
//GPIO:
this.GPIOChip.write32(address | 0, data | 0);
}
else {
//Unknown:
this.UNDETERMINED.writeGPIO32(address | 0, data | 0);
}
}
GameBoyAdvanceSaves.prototype.writeSRAM = function (address, data) {
address = address | 0;
data = data | 0;
switch (this.saveType | 0) {
case 0:
//Unknown:
this.UNDETERMINED.writeSRAM(address | 0, data | 0);
break;
case 1:
//SRAM:
this.SRAMChip.write(address | 0, data | 0);
break;
case 2:
//FLASH:
this.FLASHChip.write(address | 0, data | 0);
}
}
GameBoyAdvanceSaves.prototype.writeSRAMIfDefined = function (address, data) {
address = address | 0;
data = data | 0;
switch (this.saveType | 0) {
case 0:
//UNKNOWN:
this.SRAMChip.initialize();
case 1:
//SRAM:
this.SRAMChip.write(address | 0, data | 0);
break;
case 2:
//FLASH:
this.FLASHChip.write(address | 0, data | 0);
}
}

View file

@ -0,0 +1,437 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceSerial(IOCore) {
this.IOCore = IOCore;
}
GameBoyAdvanceSerial.prototype.initialize = function () {
this.SIODATA_A = 0xFFFF;
this.SIODATA_B = 0xFFFF;
this.SIODATA_C = 0xFFFF;
this.SIODATA_D = 0xFFFF;
this.SIOShiftClockExternal = 0;
this.SIOShiftClockDivider = 0x40;
this.SIOCNT0_DATA = 0x0C;
this.SIOTransferStarted = false;
this.SIOMULT_PLAYER_NUMBER = 0;
this.SIOCOMMERROR = false;
this.SIOBaudRate = 0;
this.SIOCNT_UART_CTS = false;
this.SIOCNT_UART_MISC = 0;
this.SIOCNT_UART_FIFO = 0;
this.SIOCNT_IRQ = 0;
this.SIOCNT_MODE = 0;
this.SIOCNT_UART_RECV_ENABLE = false;
this.SIOCNT_UART_SEND_ENABLE = false;
this.SIOCNT_UART_PARITY_ENABLE = false;
this.SIOCNT_UART_FIFO_ENABLE = false;
this.SIODATA8 = 0xFFFF;
this.RCNTMode = 0;
this.RCNTIRQ = false;
this.RCNTDataBits = 0;
this.RCNTDataBitFlow = 0;
this.JOYBUS_IRQ = 0;
this.JOYBUS_CNTL_FLAGS = 0;
this.JOYBUS_RECV0 = 0xFF;
this.JOYBUS_RECV1 = 0xFF;
this.JOYBUS_RECV2 = 0xFF;
this.JOYBUS_RECV3 = 0xFF;
this.JOYBUS_SEND0 = 0xFF;
this.JOYBUS_SEND1 = 0xFF;
this.JOYBUS_SEND2 = 0xFF;
this.JOYBUS_SEND3 = 0xFF;
this.JOYBUS_STAT = 0;
this.shiftClocks = 0;
this.serialBitsShifted = 0;
}
GameBoyAdvanceSerial.prototype.SIOMultiplayerBaudRate = [
9600,
38400,
57600,
115200
];
GameBoyAdvanceSerial.prototype.addClocks = function (clocks) {
clocks = clocks | 0;
if ((this.RCNTMode | 0) < 2) {
switch (this.SIOCNT_MODE | 0) {
case 0:
case 1:
if (this.SIOTransferStarted && (this.SIOShiftClockExternal | 0) == 0) {
this.shiftClocks = ((this.shiftClocks | 0) + (clocks | 0)) | 0;
while ((this.shiftClocks | 0) >= (this.SIOShiftClockDivider | 0)) {
this.shiftClocks = ((this.shiftClocks | 0) - (this.SIOShiftClockDivider | 0)) | 0;
this.clockSerial();
}
}
break;
case 2:
if (this.SIOTransferStarted && (this.SIOMULT_PLAYER_NUMBER | 0) == 0) {
this.shiftClocks = ((this.shiftClocks | 0) + (clocks | 0)) | 0;
while ((this.shiftClocks | 0) >= (this.SIOShiftClockDivider | 0)) {
this.shiftClocks = ((this.shiftClocks | 0) - (this.SIOShiftClockDivider | 0)) | 0;
this.clockMultiplayer();
}
}
break;
case 3:
if (this.SIOCNT_UART_SEND_ENABLE && !this.SIOCNT_UART_CTS) {
this.shiftClocks = ((this.shiftClocks | 0) + (clocks | 0)) | 0;
while ((this.shiftClocks | 0) >= (this.SIOShiftClockDivider | 0)) {
this.shiftClocks = ((this.shiftClocks | 0) - (this.SIOShiftClockDivider | 0)) | 0;
this.clockUART();
}
}
}
}
}
GameBoyAdvanceSerial.prototype.clockSerial = function () {
//Emulate as if no slaves connected:
this.serialBitsShifted = ((this.serialBitsShifted | 0) + 1) | 0;
if ((this.SIOCNT_MODE | 0) == 0) {
//8-bit
this.SIODATA8 = ((this.SIODATA8 << 1) | 1) & 0xFFFF;
if ((this.serialBitsShifted | 0) == 8) {
this.SIOTransferStarted = false;
this.serialBitsShifted = 0;
if ((this.SIOCNT_IRQ | 0) != 0) {
//this.IOCore.irq.requestIRQ(0x80);
}
}
}
else {
//32-bit
this.SIODATA_D = ((this.SIODATA_D << 1) & 0xFE) | (this.SIODATA_C >> 7);
this.SIODATA_C = ((this.SIODATA_C << 1) & 0xFE) | (this.SIODATA_B >> 7);
this.SIODATA_B = ((this.SIODATA_B << 1) & 0xFE) | (this.SIODATA_A >> 7);
this.SIODATA_A = ((this.SIODATA_A << 1) & 0xFE) | 1;
if ((this.serialBitsShifted | 0) == 32) {
this.SIOTransferStarted = false;
this.serialBitsShifted = 0;
if ((this.SIOCNT_IRQ | 0) != 0) {
//this.IOCore.irq.requestIRQ(0x80);
}
}
}
}
GameBoyAdvanceSerial.prototype.clockMultiplayer = function () {
//Emulate as if no slaves connected:
this.SIODATA_A = this.SIODATA8 | 0;
this.SIODATA_B = 0xFFFF;
this.SIODATA_C = 0xFFFF;
this.SIODATA_D = 0xFFFF;
this.SIOTransferStarted = false;
this.SIOCOMMERROR = true;
if ((this.SIOCNT_IRQ | 0) != 0) {
//this.IOCore.irq.requestIRQ(0x80);
}
}
GameBoyAdvanceSerial.prototype.clockUART = function () {
this.serialBitsShifted = ((this.serialBitsShifted | 0) + 1) | 0;
if (this.SIOCNT_UART_FIFO_ENABLE) {
if ((this.serialBitsShifted | 0) == 8) {
this.serialBitsShifted = 0;
this.SIOCNT_UART_FIFO = Math.max(((this.SIOCNT_UART_FIFO | 0) - 1) | 0, 0) | 0;
if ((this.SIOCNT_UART_FIFO | 0) == 0 && (this.SIOCNT_IRQ | 0) != 0) {
//this.IOCore.irq.requestIRQ(0x80);
}
}
}
else {
if ((this.serialBitsShifted | 0) == 8) {
this.serialBitsShifted = 0;
if ((this.SIOCNT_IRQ | 0) != 0) {
//this.IOCore.irq.requestIRQ(0x80);
}
}
}
}
GameBoyAdvanceSerial.prototype.writeSIODATA_A0 = function (data) {
data = data | 0;
this.SIODATA_A = (this.SIODATA_A & 0xFF00) | data;
}
GameBoyAdvanceSerial.prototype.readSIODATA_A0 = function () {
return this.SIODATA_A & 0xFF;
}
GameBoyAdvanceSerial.prototype.writeSIODATA_A1 = function (data) {
data = data | 0;
this.SIODATA_A = (this.SIODATA_A & 0xFF) | (data << 8);
}
GameBoyAdvanceSerial.prototype.readSIODATA_A1 = function () {
return this.SIODATA_A >> 8;
}
GameBoyAdvanceSerial.prototype.writeSIODATA_B0 = function (data) {
data = data | 0;
this.SIODATA_B = (this.SIODATA_B & 0xFF00) | data;
}
GameBoyAdvanceSerial.prototype.readSIODATA_B0 = function () {
return this.SIODATA_B & 0xFF;
}
GameBoyAdvanceSerial.prototype.writeSIODATA_B1 = function (data) {
data = data | 0;
this.SIODATA_B = (this.SIODATA_B & 0xFF) | (data << 8);
}
GameBoyAdvanceSerial.prototype.readSIODATA_B1 = function () {
return this.SIODATA_B >> 8;
}
GameBoyAdvanceSerial.prototype.writeSIODATA_C0 = function (data) {
data = data | 0;
this.SIODATA_C = (this.SIODATA_C & 0xFF00) | data;
}
GameBoyAdvanceSerial.prototype.readSIODATA_C0 = function () {
return this.SIODATA_C & 0xFF;
}
GameBoyAdvanceSerial.prototype.writeSIODATA_C1 = function (data) {
data = data | 0;
this.SIODATA_C = (this.SIODATA_C & 0xFF) | (data << 8);
}
GameBoyAdvanceSerial.prototype.readSIODATA_C1 = function () {
return this.SIODATA_C >> 8;
}
GameBoyAdvanceSerial.prototype.writeSIODATA_D0 = function (data) {
data = data | 0;
this.SIODATA_D = (this.SIODATA_D & 0xFF00) | data;
}
GameBoyAdvanceSerial.prototype.readSIODATA_D0 = function () {
return this.SIODATA_D & 0xFF;
}
GameBoyAdvanceSerial.prototype.writeSIODATA_D1 = function (data) {
data = data | 0;
this.SIODATA_D = (this.SIODATA_D & 0xFF) | (data << 8);
}
GameBoyAdvanceSerial.prototype.readSIODATA_D1 = function () {
return this.SIODATA_D >> 8;
}
GameBoyAdvanceSerial.prototype.writeSIOCNT0 = function (data) {
if ((this.RCNTMode | 0) < 0x2) {
switch (this.SIOCNT_MODE | 0) {
//8-Bit:
case 0:
//32-Bit:
case 1:
this.SIOShiftClockExternal = data & 0x1;
this.SIOShiftClockDivider = ((data & 0x2) != 0) ? 0x8 : 0x40;
this.SIOCNT0_DATA = data & 0xB;
if ((data & 0x80) != 0) {
if (!this.SIOTransferStarted) {
this.SIOTransferStarted = true;
this.serialBitsShifted = 0;
this.shiftClocks = 0;
}
}
else {
this.SIOTransferStarted = false;
}
break;
//Multiplayer:
case 2:
this.SIOBaudRate = data & 0x3;
this.SIOShiftClockDivider = this.SIOMultiplayerBaudRate[this.SIOBaudRate | 0] | 0;
this.SIOMULT_PLAYER_NUMBER = (data >> 4) & 0x3;
this.SIOCOMMERROR = ((data & 0x40) != 0);
if ((data & 0x80) != 0) {
if (!this.SIOTransferStarted) {
this.SIOTransferStarted = true;
if ((this.SIOMULT_PLAYER_NUMBER | 0) == 0) {
this.SIODATA_A = 0xFFFF;
this.SIODATA_B = 0xFFFF;
this.SIODATA_C = 0xFFFF;
this.SIODATA_D = 0xFFFF;
}
this.serialBitsShifted = 0;
this.shiftClocks = 0;
}
}
else {
this.SIOTransferStarted = false;
}
break;
//UART:
case 3:
this.SIOBaudRate = data & 0x3;
this.SIOShiftClockDivider = this.SIOMultiplayerBaudRate[this.SIOBaudRate | 0] | 0;
this.SIOCNT_UART_MISC = (data & 0xCF) >> 2;
this.SIOCNT_UART_CTS = ((data & 0x4) != 0);
}
}
}
GameBoyAdvanceSerial.prototype.readSIOCNT0 = function () {
if (this.RCNTMode < 0x2) {
switch (this.SIOCNT_MODE) {
//8-Bit:
case 0:
//32-Bit:
case 1:
return ((this.SIOTransferStarted) ? 0x80 : 0) | 0x74 | this.SIOCNT0_DATA;
//Multiplayer:
case 2:
return ((this.SIOTransferStarted) ? 0x80 : 0) | ((this.SIOCOMMERROR) ? 0x40 : 0) | (this.SIOMULT_PLAYER_NUMBER << 4) | this.SIOBaudRate;
//UART:
case 3:
return (this.SIOCNT_UART_MISC << 2) | ((this.SIOCNT_UART_FIFO == 4) ? 0x30 : 0x20) | this.SIOBaudRate;
}
}
return 0xFF;
}
GameBoyAdvanceSerial.prototype.writeSIOCNT1 = function (data) {
this.SIOCNT_IRQ = data & 0x40;
this.SIOCNT_MODE = (data >> 4) & 0x3;
this.SIOCNT_UART_RECV_ENABLE = ((data & 0x8) != 0);
this.SIOCNT_UART_SEND_ENABLE = ((data & 0x4) != 0);
this.SIOCNT_UART_PARITY_ENABLE = ((data & 0x2) != 0);
this.SIOCNT_UART_FIFO_ENABLE = ((data & 0x1) != 0);
}
GameBoyAdvanceSerial.prototype.readSIOCNT1 = function () {
return (0x80 | this.SIOCNT_IRQ | (this.SIOCNT_MODE << 4) | ((this.SIOCNT_UART_RECV_ENABLE) ? 0x8 : 0) |
((this.SIOCNT_UART_SEND_ENABLE) ? 0x4 : 0) | ((this.SIOCNT_UART_PARITY_ENABLE) ? 0x2 : 0) | ((this.SIOCNT_UART_FIFO_ENABLE) ? 0x2 : 0));
}
GameBoyAdvanceSerial.prototype.writeSIODATA8_0 = function (data) {
data = data | 0;
this.SIODATA8 = (this.SIODATA8 & 0xFF00) | data;
if ((this.RCNTMode | 0) < 0x2 && (this.SIOCNT_MODE | 0) == 3 && this.SIOCNT_UART_FIFO_ENABLE) {
this.SIOCNT_UART_FIFO = Math.min(((this.SIOCNT_UART_FIFO | 0) + 1) | 0, 4) | 0;
}
}
GameBoyAdvanceSerial.prototype.readSIODATA8_0 = function () {
return this.SIODATA8 & 0xFF;
}
GameBoyAdvanceSerial.prototype.writeSIODATA8_1 = function (data) {
data = data | 0;
this.SIODATA8 = (this.SIODATA8 & 0xFF) | (data << 8);
}
GameBoyAdvanceSerial.prototype.readSIODATA8_1 = function () {
return this.SIODATA8 >> 8;
}
GameBoyAdvanceSerial.prototype.writeRCNT0 = function (data) {
if ((this.RCNTMode | 0) == 0x2) {
//General Comm:
var oldDataBits = this.RCNTDataBits | 0;
this.RCNTDataBits = data & 0xF; //Device manually controls SI/SO/SC/SD here.
this.RCNTDataBitFlow = data >> 4;
if (this.RCNTIRQ && ((oldDataBits ^ this.RCNTDataBits) & oldDataBits & 0x4) != 0) {
//SI fell low, trigger IRQ:
//this.IOCore.irq.requestIRQ(0x80);
}
}
}
GameBoyAdvanceSerial.prototype.readRCNT0 = function () {
return (this.RCNTDataBitFlow << 4) | this.RCNTDataBits;
}
GameBoyAdvanceSerial.prototype.writeRCNT1 = function (data) {
this.RCNTMode = data >> 6;
this.RCNTIRQ = ((data & 0x1) != 0);
if ((this.RCNTMode | 0) != 0x2) {
//Force SI/SO/SC/SD to low as we're never "hooked" up:
this.RCNTDataBits = 0;
this.RCNTDataBitFlow = 0;
}
}
GameBoyAdvanceSerial.prototype.readRCNT1 = function () {
return (this.RCNTMode << 6) | ((this.RCNTIRQ) ? 0x3F : 0x3E);
}
GameBoyAdvanceSerial.prototype.writeJOYCNT = function (data) {
this.JOYBUS_IRQ = (data << 25) >> 31;
this.JOYBUS_CNTL_FLAGS &= ~(data & 0x7);
}
GameBoyAdvanceSerial.prototype.readJOYCNT = function () {
return (this.JOYBUS_CNTL_FLAGS | 0x40) | (0xB8 & this.JOYBUS_IRQ);
}
GameBoyAdvanceSerial.prototype.writeJOYBUS_RECV0 = function (data) {
this.JOYBUS_RECV0 = data | 0;
}
GameBoyAdvanceSerial.prototype.readJOYBUS_RECV0 = function () {
this.JOYBUS_STAT = this.JOYBUS_STAT & 0xF7;
return this.JOYBUS_RECV0 | 0;
}
GameBoyAdvanceSerial.prototype.writeJOYBUS_RECV1 = function (data) {
this.JOYBUS_RECV1 = data | 0;
}
GameBoyAdvanceSerial.prototype.readJOYBUS_RECV1 = function () {
this.JOYBUS_STAT = this.JOYBUS_STAT & 0xF7;
return this.JOYBUS_RECV1 | 0;
}
GameBoyAdvanceSerial.prototype.writeJOYBUS_RECV2 = function (data) {
this.JOYBUS_RECV2 = data | 0;
}
GameBoyAdvanceSerial.prototype.readJOYBUS_RECV2 = function () {
this.JOYBUS_STAT = this.JOYBUS_STAT & 0xF7;
return this.JOYBUS_RECV2 | 0;
}
GameBoyAdvanceSerial.prototype.writeJOYBUS_RECV3 = function (data) {
this.JOYBUS_RECV3 = data | 0;
}
GameBoyAdvanceSerial.prototype.readJOYBUS_RECV3 = function () {
this.JOYBUS_STAT = this.JOYBUS_STAT & 0xF7;
return this.JOYBUS_RECV3 | 0;
}
GameBoyAdvanceSerial.prototype.writeJOYBUS_SEND0 = function (data) {
this.JOYBUS_SEND0 = data | 0;
this.JOYBUS_STAT = this.JOYBUS_STAT | 0x2;
}
GameBoyAdvanceSerial.prototype.readJOYBUS_SEND0 = function () {
return this.JOYBUS_SEND0 | 0;
}
GameBoyAdvanceSerial.prototype.writeJOYBUS_SEND1 = function (data) {
this.JOYBUS_SEND1 = data | 0;
this.JOYBUS_STAT = this.JOYBUS_STAT | 0x2;
}
GameBoyAdvanceSerial.prototype.readJOYBUS_SEND1 = function () {
return this.JOYBUS_SEND1 | 0;
}
GameBoyAdvanceSerial.prototype.writeJOYBUS_SEND2 = function (data) {
this.JOYBUS_SEND2 = data | 0;
this.JOYBUS_STAT = this.JOYBUS_STAT | 0x2;
}
GameBoyAdvanceSerial.prototype.readJOYBUS_SEND2 = function () {
return this.JOYBUS_SEND2 | 0;
}
GameBoyAdvanceSerial.prototype.writeJOYBUS_SEND3 = function (data) {
this.JOYBUS_SEND3 = data | 0;
this.JOYBUS_STAT = this.JOYBUS_STAT | 0x2;
}
GameBoyAdvanceSerial.prototype.readJOYBUS_SEND3 = function () {
return this.JOYBUS_SEND3 | 0;
}
GameBoyAdvanceSerial.prototype.writeJOYBUS_STAT = function (data) {
this.JOYBUS_STAT = data | 0;
}
GameBoyAdvanceSerial.prototype.readJOYBUS_STAT = function () {
return 0xC5 | this.JOYBUS_STAT;
}
/*GameBoyAdvanceSerial.prototype.nextIRQEventTime = function (clocks) {
if ((this.SIOCNT_IRQ | 0) != 0 && (this.RCNTMode | 0) < 2) {
switch (this.SIOCNT_MODE | 0) {
case 0:
case 1:
if (this.SIOTransferStarted && (this.SIOShiftClockExternal | 0) == 0) {
return ((((this.SIOCNT_MODE == 1) ? 31 : 7) - this.serialBitsShifted) * this.SIOShiftClockDivider) + (this.SIOShiftClockDivider - this.shiftClocks);
}
else {
return 0x7FFFFFFF;
}
case 2:
if (this.SIOTransferStarted && this.SIOMULT_PLAYER_NUMBER == 0) {
return this.SIOShiftClockDivider - this.shiftClocks;
}
else {
return 0x7FFFFFFF;
}
case 3:
if (this.SIOCNT_UART_SEND_ENABLE && !this.SIOCNT_UART_CTS) {
return (Math.max(((this.SIOCNT_UART_FIFO_ENABLE) ? (this.SIOCNT_UART_FIFO * 8) : 8) - 1, 0) * this.SIOShiftClockDivider) + (this.SIOShiftClockDivider - this.shiftClocks);
}
else {
return 0x7FFFFFFF;
}
}
}
else {
return 0x7FFFFFFF;
}
}*/

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,846 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceTimer(IOCore) {
//Build references:
this.IOCore = IOCore;
}
GameBoyAdvanceTimer.prototype.prescalarLookup = [
0,
0x6,
0x8,
0xA
];
GameBoyAdvanceTimer.prototype.initialize = function () {
this.timer0Counter = 0;
this.timer0Reload = 0;
this.timer0Control = 0;
this.timer0Enabled = false;
this.timer0IRQ = false;
this.timer0Precounter = 0;
this.timer0Prescalar = 1;
this.timer0PrescalarShifted = 0;
this.timer1Counter = 0;
this.timer1Reload = 0;
this.timer1Control = 0;
this.timer1Enabled = false;
this.timer1IRQ = false;
this.timer1Precounter = 0;
this.timer1Prescalar = 1;
this.timer1PrescalarShifted = 0;
this.timer1CountUp = false;
this.timer2Counter = 0;
this.timer2Reload = 0;
this.timer2Control = 0;
this.timer2Enabled = false;
this.timer2IRQ = false;
this.timer2Precounter = 0;
this.timer2Prescalar = 1;
this.timer2PrescalarShifted = 0;
this.timer2CountUp = false;
this.timer3Counter = 0;
this.timer3Reload = 0;
this.timer3Control = 0;
this.timer3Enabled = false;
this.timer3IRQ = false;
this.timer3Precounter = 0;
this.timer3Prescalar = 1;
this.timer3PrescalarShifted = 0;
this.timer3CountUp = false;
this.timer1UseMainClocks = false;
this.timer1UseChainedClocks = false;
this.timer2UseMainClocks = false;
this.timer2UseChainedClocks = false;
this.timer3UseMainClocks = false;
this.timer3UseChainedClocks = false;
}
GameBoyAdvanceTimer.prototype.addClocks = function (clocks) {
clocks = clocks | 0;
//See if timer channels 0 and 1 are enabled:
this.clockSoundTimers(clocks | 0);
//See if timer channel 2 is enabled:
this.clockTimer2(clocks | 0);
//See if timer channel 3 is enabled:
this.clockTimer3(clocks | 0);
}
GameBoyAdvanceTimer.prototype.clockSoundTimers = function (audioClocks) {
audioClocks = audioClocks | 0;
for (var predictedClocks = 0, overflowClocks = 0; (audioClocks | 0) > 0; audioClocks = ((audioClocks | 0) - (predictedClocks | 0)) | 0) {
overflowClocks = this.nextAudioTimerOverflow() | 0;
predictedClocks = Math.min(audioClocks | 0, overflowClocks | 0) | 0;
//See if timer channel 0 is enabled:
this.clockTimer0(predictedClocks | 0);
//See if timer channel 1 is enabled:
this.clockTimer1(predictedClocks | 0);
//Clock audio system up to latest timer:
this.IOCore.sound.addClocks(predictedClocks | 0);
//Only jit if overflow was seen:
if ((overflowClocks | 0) == (predictedClocks | 0)) {
this.IOCore.sound.audioJIT();
}
}
}
GameBoyAdvanceTimer.prototype.clockTimer0 = function (clocks) {
clocks = clocks | 0;
if (this.timer0Enabled) {
this.timer0Precounter = ((this.timer0Precounter | 0) + (clocks | 0)) | 0;
while ((this.timer0Precounter | 0) >= (this.timer0Prescalar | 0)) {
var iterations = Math.min(this.timer0Precounter >> (this.timer0PrescalarShifted | 0), (0x10000 - (this.timer0Counter | 0)) | 0) | 0;
this.timer0Precounter = ((this.timer0Precounter | 0) - ((iterations | 0) << (this.timer0PrescalarShifted | 0))) | 0;
this.timer0Counter = ((this.timer0Counter | 0) + (iterations | 0)) | 0;
if ((this.timer0Counter | 0) > 0xFFFF) {
this.timer0Counter = this.timer0Reload | 0;
this.timer0ExternalTriggerCheck();
this.timer1ClockUpTickCheck();
}
}
}
}
GameBoyAdvanceTimer.prototype.clockTimer1 = function (clocks) {
clocks = clocks | 0;
if (this.timer1UseMainClocks) {
this.timer1Precounter = ((this.timer1Precounter | 0) + (clocks | 0)) | 0;
while ((this.timer1Precounter | 0) >= (this.timer1Prescalar | 0)) {
var iterations = Math.min(this.timer1Precounter >> (this.timer1PrescalarShifted | 0), (0x10000 - (this.timer1Counter | 0)) | 0) | 0;
this.timer1Precounter = ((this.timer1Precounter | 0) - ((iterations | 0) << (this.timer1PrescalarShifted | 0))) | 0;
this.timer1Counter = ((this.timer1Counter | 0) + (iterations | 0)) | 0;
if ((this.timer1Counter | 0) > 0xFFFF) {
this.timer1Counter = this.timer1Reload | 0;
this.timer1ExternalTriggerCheck();
this.timer2ClockUpTickCheck();
}
}
}
}
GameBoyAdvanceTimer.prototype.clockTimer2 = function (clocks) {
clocks = clocks | 0;
if (this.timer2UseMainClocks) {
this.timer2Precounter = ((this.timer2Precounter | 0) + (clocks | 0)) | 0;
while ((this.timer2Precounter | 0) >= (this.timer2Prescalar | 0)) {
var iterations = Math.min(this.timer2Precounter >> (this.timer2PrescalarShifted | 0), (0x10000 - (this.timer2Counter | 0)) | 0) | 0;
this.timer2Precounter = ((this.timer2Precounter | 0) - ((iterations | 0) << (this.timer2PrescalarShifted | 0))) | 0;
this.timer2Counter = ((this.timer2Counter | 0) + (iterations | 0)) | 0;
if ((this.timer2Counter | 0) > 0xFFFF) {
this.timer2Counter = this.timer2Reload | 0;
this.timer2ExternalTriggerCheck();
this.timer3ClockUpTickCheck();
}
}
}
}
GameBoyAdvanceTimer.prototype.clockTimer3 = function (clocks) {
clocks = clocks | 0;
if (this.timer3UseMainClocks) {
this.timer3Precounter = ((this.timer3Precounter | 0) + (clocks | 0)) | 0;
while ((this.timer3Precounter | 0) >= (this.timer3Prescalar | 0)) {
var iterations = Math.min(this.timer3Precounter >> (this.timer3PrescalarShifted | 0), (0x10000 - (this.timer3Counter | 0)) | 0) | 0;
this.timer3Precounter = ((this.timer3Precounter | 0) - ((iterations | 0) << (this.timer3PrescalarShifted | 0))) | 0;
this.timer3Counter = ((this.timer3Counter | 0) + (iterations | 0)) | 0;
if ((this.timer3Counter | 0) > 0xFFFF) {
this.timer3Counter = this.timer3Reload | 0;
this.timer3ExternalTriggerCheck();
}
}
}
}
GameBoyAdvanceTimer.prototype.timer1ClockUpTickCheck = function () {
if (this.timer1UseChainedClocks) {
this.timer1Counter = ((this.timer1Counter | 0) + 1) | 0;
if ((this.timer1Counter | 0) > 0xFFFF) {
this.timer1Counter = this.timer1Reload | 0;
this.timer1ExternalTriggerCheck();
this.timer2ClockUpTickCheck();
}
}
}
GameBoyAdvanceTimer.prototype.timer2ClockUpTickCheck = function () {
if (this.timer2UseChainedClocks) {
this.timer2Counter = ((this.timer2Counter | 0) + 1) | 0;
if ((this.timer2Counter | 0) > 0xFFFF) {
this.timer2Counter = this.timer2Reload | 0;
this.timer2ExternalTriggerCheck();
this.timer3ClockUpTickCheck();
}
}
}
GameBoyAdvanceTimer.prototype.timer3ClockUpTickCheck = function () {
if (this.timer3UseChainedClocks) {
this.timer3Counter = ((this.timer3Counter | 0) + 1) | 0;
if ((this.timer3Counter | 0) > 0xFFFF) {
this.timer3Counter = this.timer3Reload | 0;
this.timer3ExternalTriggerCheck();
}
}
}
GameBoyAdvanceTimer.prototype.timer0ExternalTriggerCheck = function () {
if (this.timer0IRQ) {
this.IOCore.irq.requestIRQ(0x08);
}
this.IOCore.sound.AGBDirectSoundTimer0ClockTick();
}
GameBoyAdvanceTimer.prototype.timer1ExternalTriggerCheck = function () {
if (this.timer1IRQ) {
this.IOCore.irq.requestIRQ(0x10);
}
this.IOCore.sound.AGBDirectSoundTimer1ClockTick();
}
GameBoyAdvanceTimer.prototype.timer2ExternalTriggerCheck = function () {
if (this.timer2IRQ) {
this.IOCore.irq.requestIRQ(0x20);
}
}
GameBoyAdvanceTimer.prototype.timer3ExternalTriggerCheck = function () {
if (this.timer3IRQ) {
this.IOCore.irq.requestIRQ(0x40);
}
}
GameBoyAdvanceTimer.prototype.writeTM0CNT8_0 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.IOCore.sound.audioJIT();
this.timer0Reload = this.timer0Reload & 0xFF00;
data = data & 0xFF;
this.timer0Reload = this.timer0Reload | data;
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.writeTM0CNT8_1 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.IOCore.sound.audioJIT();
this.timer0Reload = this.timer0Reload & 0xFF;
data = data & 0xFF;
this.timer0Reload = this.timer0Reload | (data << 8);
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.writeTM0CNT8_2 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.IOCore.sound.audioJIT();
this.timer0Control = data & 0xFF;
if ((data & 0x80) != 0) {
if (!this.timer0Enabled) {
this.timer0Counter = this.timer0Reload | 0;
this.timer0Enabled = true;
this.timer0Precounter = 0;
}
}
else {
this.timer0Enabled = false;
}
this.timer0IRQ = ((data & 0x40) != 0);
this.timer0PrescalarShifted = this.prescalarLookup[data & 0x03] | 0;
this.timer0Prescalar = 1 << (this.timer0PrescalarShifted | 0);
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.writeTM0CNT16 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.IOCore.sound.audioJIT();
this.timer0Reload = data & 0xFFFF;
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.writeTM0CNT32 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.IOCore.sound.audioJIT();
this.timer0Reload = data & 0xFFFF;
this.timer0Control = data >> 16;
if ((data & 0x800000) != 0) {
if (!this.timer0Enabled) {
this.timer0Counter = this.timer0Reload | 0;
this.timer0Enabled = true;
this.timer0Precounter = 0;
}
}
else {
this.timer0Enabled = false;
}
this.timer0IRQ = ((data & 0x400000) != 0);
this.timer0PrescalarShifted = this.prescalarLookup[(data >> 16) & 0x03] | 0;
this.timer0Prescalar = 1 << (this.timer0PrescalarShifted | 0);
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.readTM0CNT8_0 = function () {
this.IOCore.updateTimerClocking();
return this.timer0Counter & 0xFF;
}
GameBoyAdvanceTimer.prototype.readTM0CNT8_1 = function () {
this.IOCore.updateTimerClocking();
return (this.timer0Counter & 0xFF00) >> 8;
}
GameBoyAdvanceTimer.prototype.readTM0CNT8_2 = function () {
return this.timer0Control & 0xFF;
}
GameBoyAdvanceTimer.prototype.readTM0CNT16 = function () {
this.IOCore.updateTimerClocking();
return this.timer0Counter | 0;
}
GameBoyAdvanceTimer.prototype.readTM0CNT32 = function () {
this.IOCore.updateTimerClocking();
var data = (this.timer0Control & 0xFF) << 16;
data = data | this.timer0Counter;
return data | 0;
}
GameBoyAdvanceTimer.prototype.writeTM1CNT8_0 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.IOCore.sound.audioJIT();
this.timer1Reload = this.timer1Reload & 0xFF00;
data = data & 0xFF;
this.timer1Reload = this.timer1Reload | data;
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.writeTM1CNT8_1 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.IOCore.sound.audioJIT();
this.timer1Reload = this.timer1Reload & 0xFF;
data = data & 0xFF;
this.timer1Reload = this.timer1Reload | (data << 8);
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.writeTM1CNT8_2 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.IOCore.sound.audioJIT();
this.timer1Control = data & 0xFF;
if ((data & 0x80) != 0) {
if (!this.timer1Enabled) {
this.timer1Counter = this.timer1Reload | 0;
this.timer1Enabled = true;
this.timer1Precounter = 0;
}
}
else {
this.timer1Enabled = false;
}
this.timer1IRQ = ((data & 0x40) != 0);
this.timer1CountUp = ((data & 0x4) != 0);
this.timer1PrescalarShifted = this.prescalarLookup[data & 0x03] | 0;
this.timer1Prescalar = 1 << (this.timer1PrescalarShifted | 0);
this.preprocessTimer1();
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.writeTM1CNT16 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.IOCore.sound.audioJIT();
this.timer1Reload = data & 0xFFFF;
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.writeTM1CNT32 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.IOCore.sound.audioJIT();
this.timer1Reload = data & 0xFFFF;
this.timer1Control = data >> 16;
if ((data & 0x800000) != 0) {
if (!this.timer1Enabled) {
this.timer1Counter = this.timer1Reload | 0;
this.timer1Enabled = true;
this.timer1Precounter = 0;
}
}
else {
this.timer1Enabled = false;
}
this.timer1IRQ = ((data & 0x400000) != 0);
this.timer1CountUp = ((data & 0x40000) != 0);
this.timer1PrescalarShifted = this.prescalarLookup[(data >> 16) & 0x03] | 0;
this.timer1Prescalar = 1 << (this.timer1PrescalarShifted | 0);
this.preprocessTimer1();
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.readTM1CNT8_0 = function () {
this.IOCore.updateTimerClocking();
return this.timer1Counter & 0xFF;
}
GameBoyAdvanceTimer.prototype.readTM1CNT8_1 = function () {
this.IOCore.updateTimerClocking();
return (this.timer1Counter & 0xFF00) >> 8;
}
GameBoyAdvanceTimer.prototype.readTM1CNT8_2 = function () {
return this.timer1Control & 0xFF;
}
GameBoyAdvanceTimer.prototype.readTM1CNT16 = function () {
this.IOCore.updateTimerClocking();
return this.timer1Counter | 0;
}
GameBoyAdvanceTimer.prototype.readTM1CNT32 = function () {
this.IOCore.updateTimerClocking();
var data = (this.timer1Control & 0xFF) << 16;
data = data | this.timer1Counter;
return data | 0;
}
GameBoyAdvanceTimer.prototype.writeTM2CNT8_0 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.timer2Reload = this.timer2Reload & 0xFF00;
data = data & 0xFF;
this.timer2Reload = this.timer2Reload | data;
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.writeTM2CNT8_1 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.timer2Reload = this.timer2Reload & 0xFF;
data = data & 0xFF;
this.timer2Reload = this.timer2Reload | (data << 8);
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.writeTM2CNT8_2 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.timer2Control = data & 0xFF;
if ((data & 0x80) != 0) {
if (!this.timer2Enabled) {
this.timer2Counter = this.timer2Reload | 0;
this.timer2Enabled = true;
this.timer2Precounter = 0;
}
}
else {
this.timer2Enabled = false;
}
this.timer2IRQ = ((data & 0x40) != 0);
this.timer2CountUp = ((data & 0x4) != 0);
this.timer2PrescalarShifted = this.prescalarLookup[data & 0x03] | 0;
this.timer2Prescalar = 1 << (this.timer2PrescalarShifted | 0);
this.preprocessTimer2();
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.writeTM2CNT16 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.timer2Reload = data & 0xFFFF;
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.writeTM2CNT32 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.timer2Reload = data & 0xFFFF;
this.timer2Control = data >> 16;
if ((data & 0x800000) != 0) {
if (!this.timer2Enabled) {
this.timer2Counter = this.timer2Reload | 0;
this.timer2Enabled = true;
this.timer2Precounter = 0;
}
}
else {
this.timer2Enabled = false;
}
this.timer2IRQ = ((data & 0x400000) != 0);
this.timer2CountUp = ((data & 0x40000) != 0);
this.timer2PrescalarShifted = this.prescalarLookup[(data >> 16) & 0x03] | 0;
this.timer2Prescalar = 1 << (this.timer2PrescalarShifted | 0);
this.preprocessTimer2();
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.readTM2CNT8_0 = function () {
this.IOCore.updateTimerClocking();
return this.timer2Counter & 0xFF;
}
GameBoyAdvanceTimer.prototype.readTM2CNT8_1 = function () {
this.IOCore.updateTimerClocking();
return (this.timer2Counter & 0xFF00) >> 8;
}
GameBoyAdvanceTimer.prototype.readTM2CNT8_2 = function () {
return this.timer2Control & 0xFF;
}
GameBoyAdvanceTimer.prototype.readTM2CNT16 = function () {
this.IOCore.updateTimerClocking();
return this.timer2Counter | 0;
}
GameBoyAdvanceTimer.prototype.readTM2CNT32 = function () {
this.IOCore.updateTimerClocking();
var data = (this.timer2Control & 0xFF) << 16;
data = data | this.timer2Counter;
return data | 0;
}
GameBoyAdvanceTimer.prototype.writeTM3CNT8_0 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.timer3Reload = this.timer3Reload & 0xFF00;
data = data & 0xFF;
this.timer3Reload = this.timer3Reload | data;
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.writeTM3CNT8_1 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.timer3Reload = this.timer3Reload & 0xFF;
data = data & 0xFF;
this.timer3Reload = this.timer3Reload | (data << 8);
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.writeTM3CNT8_2 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.timer3Control = data & 0xFF;
if ((data & 0x80) != 0) {
if (!this.timer3Enabled) {
this.timer3Counter = this.timer3Reload | 0;
this.timer3Enabled = true;
this.timer3Precounter = 0;
}
}
else {
this.timer3Enabled = false;
}
this.timer3IRQ = ((data & 0x40) != 0);
this.timer3CountUp = ((data & 0x4) != 0);
this.timer3PrescalarShifted = this.prescalarLookup[data & 0x03] | 0;
this.timer3Prescalar = 1 << (this.timer3PrescalarShifted | 0);
this.preprocessTimer3();
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.writeTM3CNT16 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.timer3Reload = data & 0xFFFF;
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.writeTM3CNT32 = function (data) {
data = data | 0;
this.IOCore.updateTimerClocking();
this.timer3Reload = data & 0xFFFF;
this.timer3Control = data >> 16;
if ((data & 0x800000) != 0) {
if (!this.timer3Enabled) {
this.timer3Counter = this.timer3Reload | 0;
this.timer3Enabled = true;
this.timer3Precounter = 0;
}
}
else {
this.timer3Enabled = false;
}
this.timer3IRQ = ((data & 0x400000) != 0);
this.timer3CountUp = ((data & 0x40000) != 0);
this.timer3PrescalarShifted = this.prescalarLookup[(data >> 16) & 0x03] | 0;
this.timer3Prescalar = 1 << (this.timer3PrescalarShifted | 0);
this.preprocessTimer3();
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceTimer.prototype.readTM3CNT8_0 = function () {
this.IOCore.updateTimerClocking();
return this.timer3Counter & 0xFF;
}
GameBoyAdvanceTimer.prototype.readTM3CNT8_1 = function () {
this.IOCore.updateTimerClocking();
return (this.timer3Counter & 0xFF00) >> 8;
}
GameBoyAdvanceTimer.prototype.readTM3CNT8_2 = function () {
return this.timer3Control & 0xFF;
}
GameBoyAdvanceTimer.prototype.readTM3CNT16 = function () {
this.IOCore.updateTimerClocking();
return this.timer3Counter | 0;
}
GameBoyAdvanceTimer.prototype.readTM3CNT32 = function () {
this.IOCore.updateTimerClocking();
var data = (this.timer3Control & 0xFF) << 16;
data = data | this.timer3Counter;
return data | 0;
}
GameBoyAdvanceTimer.prototype.preprocessTimer1 = function () {
this.timer1UseMainClocks = (this.timer1Enabled && !this.timer1CountUp);
this.timer1UseChainedClocks = (this.timer1Enabled && this.timer1CountUp);
}
GameBoyAdvanceTimer.prototype.preprocessTimer2 = function () {
this.timer2UseMainClocks = (this.timer2Enabled && !this.timer2CountUp);
this.timer2UseChainedClocks = (this.timer2Enabled && this.timer2CountUp);
}
GameBoyAdvanceTimer.prototype.preprocessTimer3 = function () {
this.timer3UseMainClocks = (this.timer3Enabled && !this.timer3CountUp);
this.timer3UseChainedClocks = (this.timer3Enabled && this.timer3CountUp);
}
if (typeof Math.imul == "function") {
//Math.imul found, insert the optimized path in:
GameBoyAdvanceTimer.prototype.nextTimer0OverflowBase = function () {
var countUntilReload = (0x10000 - (this.timer0Counter | 0)) | 0;
countUntilReload = Math.imul(countUntilReload | 0, this.timer0Prescalar | 0) | 0;
countUntilReload = ((countUntilReload | 0) - (this.timer0Precounter | 0)) | 0;
return countUntilReload | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer0OverflowSingle = function () {
var eventTime = 0x7FFFFFFF;
if (this.timer0Enabled) {
eventTime = this.nextTimer0OverflowBase() | 0;
}
return eventTime | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer0Overflow = function (numOverflows) {
numOverflows = numOverflows | 0;
var eventTime = 0x7FFFFFFF;
if (this.timer0Enabled) {
numOverflows = ((numOverflows | 0) - 1) | 0;
var countUntilReload = this.nextTimer0OverflowBase() | 0;
var reloadClocks = (0x10000 - (this.timer0Reload | 0)) | 0;
reloadClocks = Math.imul(reloadClocks | 0, this.timer0Prescalar | 0) | 0;
reloadClocks = (reloadClocks | 0) * (numOverflows | 0);
eventTime = Math.min((countUntilReload | 0) + reloadClocks, 0x7FFFFFFF) | 0;
}
return eventTime | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer1OverflowBase = function () {
var countUntilReload = (0x10000 - (this.timer1Counter | 0)) | 0;
countUntilReload = Math.imul(countUntilReload | 0, this.timer1Prescalar | 0) | 0;
countUntilReload = ((countUntilReload | 0) - (this.timer1Precounter | 0)) | 0;
return countUntilReload | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer1Overflow = function (numOverflows) {
numOverflows = numOverflows | 0;
var eventTime = 0x7FFFFFFF;
if (this.timer1Enabled) {
var reloadClocks = (0x10000 - (this.timer1Reload | 0)) | 0;
if (this.timer1CountUp) {
var countUntilReload = (0x10000 - (this.timer1Counter | 0)) | 0;
reloadClocks = (reloadClocks | 0) * (numOverflows | 0);
eventTime = Math.min((countUntilReload | 0) + reloadClocks, 0x7FFFFFFF) | 0;
eventTime = this.nextTimer0Overflow(eventTime | 0) | 0;
}
else {
numOverflows = ((numOverflows | 0) - 1) | 0;
var countUntilReload = this.nextTimer1OverflowBase() | 0;
reloadClocks = Math.imul(reloadClocks | 0, this.timer1Prescalar | 0) | 0;
reloadClocks = reloadClocks * numOverflows;
eventTime = Math.min((countUntilReload | 0) + reloadClocks, 0x7FFFFFFF) | 0;
}
}
return eventTime | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer1OverflowSingle = function () {
var eventTime = 0x7FFFFFFF;
if (this.timer1Enabled) {
if (this.timer1CountUp) {
var countUntilReload = (0x10000 - (this.timer1Counter | 0)) | 0;
eventTime = this.nextTimer0Overflow(countUntilReload | 0) | 0;
}
else {
eventTime = this.nextTimer1OverflowBase() | 0;
}
}
return eventTime | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer2OverflowBase = function () {
var countUntilReload = (0x10000 - (this.timer2Counter | 0)) | 0;
countUntilReload = Math.imul(countUntilReload | 0, this.timer2Prescalar | 0) | 0;
countUntilReload = ((countUntilReload | 0) - (this.timer2Precounter | 0)) | 0;
return countUntilReload | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer2Overflow = function (numOverflows) {
numOverflows = numOverflows | 0;
var eventTime = 0x7FFFFFFF;
if (this.timer2Enabled) {
var reloadClocks = (0x10000 - (this.timer2Reload | 0)) | 0;
if (this.timer2CountUp) {
var countUntilReload = (0x10000 - (this.timer2Counter | 0)) | 0;
reloadClocks = (reloadClocks | 0) * (numOverflows | 0);
eventTime = Math.min((countUntilReload | 0) + reloadClocks, 0x7FFFFFFF) | 0;
eventTime = this.nextTimer1Overflow(eventTime | 0) | 0;
}
else {
numOverflows = ((numOverflows | 0) - 1) | 0;
var countUntilReload = this.nextTimer2OverflowBase() | 0;
reloadClocks = Math.imul(reloadClocks | 0, this.timer2Prescalar | 0) | 0;
reloadClocks = reloadClocks * numOverflows;
eventTime = Math.min((countUntilReload | 0) + reloadClocks, 0x7FFFFFFF) | 0;
}
}
return eventTime | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer2OverflowSingle = function () {
var eventTime = 0x7FFFFFFF;
if (this.timer2Enabled) {
if (this.timer2CountUp) {
var countUntilReload = (0x10000 - (this.timer2Counter | 0)) | 0;
eventTime = this.nextTimer1Overflow(countUntilReload | 0) | 0;
}
else {
eventTime = this.nextTimer2OverflowBase() | 0;
}
}
return eventTime | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer3OverflowSingle = function () {
var eventTime = 0x7FFFFFFF;
if (this.timer3Enabled) {
if (this.timer3CountUp) {
var countUntilReload = (0x10000 - (this.timer3Counter | 0)) | 0;
eventTime = this.nextTimer2Overflow(countUntilReload | 0) | 0;
}
else {
eventTime = (0x10000 - (this.timer3Counter | 0)) | 0;
eventTime = Math.imul(eventTime | 0, this.timer3Prescalar | 0) | 0;
eventTime = ((eventTime | 0) - (this.timer3Precounter | 0)) | 0;
}
}
return eventTime | 0;
}
}
else {
//Math.imul not found, use the compatibility method:
GameBoyAdvanceTimer.prototype.nextTimer0OverflowBase = function () {
var countUntilReload = (0x10000 - (this.timer0Counter | 0)) | 0;
countUntilReload = ((countUntilReload | 0) * (this.timer0Prescalar | 0)) | 0;
countUntilReload = ((countUntilReload | 0) - (this.timer0Precounter | 0)) | 0;
return countUntilReload | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer0OverflowSingle = function () {
var eventTime = 0x7FFFFFFF;
if (this.timer0Enabled) {
eventTime = this.nextTimer0OverflowBase() | 0;
}
return eventTime | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer0Overflow = function (numOverflows) {
numOverflows = numOverflows | 0;
var eventTime = 0x7FFFFFFF;
if (this.timer0Enabled) {
numOverflows = ((numOverflows | 0) - 1) | 0;
var countUntilReload = this.nextTimer0OverflowBase() | 0;
var reloadClocks = (0x10000 - (this.timer0Reload | 0)) | 0;
reloadClocks = ((reloadClocks | 0) * (this.timer0Prescalar | 0)) | 0;
reloadClocks = (reloadClocks | 0) * (numOverflows | 0);
eventTime = Math.min((countUntilReload | 0) + reloadClocks, 0x7FFFFFFF) | 0;
}
return eventTime | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer1OverflowBase = function () {
var countUntilReload = (0x10000 - (this.timer1Counter | 0)) | 0;
countUntilReload = ((countUntilReload | 0) * (this.timer1Prescalar | 0)) | 0;
countUntilReload = ((countUntilReload | 0) - (this.timer1Precounter | 0)) | 0;
return countUntilReload | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer1Overflow = function (numOverflows) {
numOverflows = numOverflows | 0;
var eventTime = 0x7FFFFFFF;
if (this.timer1Enabled) {
var reloadClocks = (0x10000 - (this.timer1Reload | 0)) | 0;
if (this.timer1CountUp) {
var countUntilReload = (0x10000 - (this.timer1Counter | 0)) | 0;
reloadClocks = (reloadClocks | 0) * (numOverflows | 0);
eventTime = Math.min((countUntilReload | 0) + reloadClocks, 0x7FFFFFFF) | 0;
eventTime = this.nextTimer0Overflow(eventTime | 0) | 0;
}
else {
numOverflows = ((numOverflows | 0) - 1) | 0;
var countUntilReload = this.nextTimer1OverflowBase() | 0;
reloadClocks = ((reloadClocks | 0) * (this.timer1Prescalar | 0)) | 0;
reloadClocks = reloadClocks * numOverflows;
eventTime = Math.min((countUntilReload | 0) + reloadClocks, 0x7FFFFFFF) | 0;
}
}
return eventTime | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer1OverflowSingle = function () {
var eventTime = 0x7FFFFFFF;
if (this.timer1Enabled) {
if (this.timer1CountUp) {
var countUntilReload = (0x10000 - (this.timer1Counter | 0)) | 0;
eventTime = this.nextTimer0Overflow(countUntilReload | 0) | 0;
}
else {
eventTime = this.nextTimer1OverflowBase() | 0;
}
}
return eventTime | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer2OverflowBase = function () {
var countUntilReload = (0x10000 - (this.timer2Counter | 0)) | 0;
countUntilReload = ((countUntilReload | 0) * (this.timer2Prescalar | 0)) | 0;
countUntilReload = ((countUntilReload | 0) - (this.timer2Precounter | 0)) | 0;
return countUntilReload | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer2Overflow = function (numOverflows) {
numOverflows = numOverflows | 0;
var eventTime = 0x7FFFFFFF;
if (this.timer2Enabled) {
var reloadClocks = (0x10000 - (this.timer2Reload | 0)) | 0;
if (this.timer2CountUp) {
var countUntilReload = (0x10000 - (this.timer2Counter | 0)) | 0;
reloadClocks = (reloadClocks | 0) * (numOverflows | 0);
eventTime = Math.min((countUntilReload | 0) + reloadClocks, 0x7FFFFFFF) | 0;
eventTime = this.nextTimer1Overflow(eventTime | 0) | 0;
}
else {
numOverflows = ((numOverflows | 0) - 1) | 0;
var countUntilReload = this.nextTimer2OverflowBase() | 0;
reloadClocks = ((reloadClocks | 0) * (this.timer2Prescalar | 0)) | 0;
reloadClocks = reloadClocks * numOverflows;
eventTime = Math.min((countUntilReload | 0) + reloadClocks, 0x7FFFFFFF) | 0;
}
}
return eventTime | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer2OverflowSingle = function () {
var eventTime = 0x7FFFFFFF;
if (this.timer2Enabled) {
if (this.timer2CountUp) {
var countUntilReload = (0x10000 - (this.timer2Counter | 0)) | 0;
eventTime = this.nextTimer1Overflow(countUntilReload | 0) | 0;
}
else {
eventTime = this.nextTimer2OverflowBase() | 0;
}
}
return eventTime | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer3OverflowSingle = function () {
var eventTime = 0x7FFFFFFF;
if (this.timer3Enabled) {
if (this.timer3CountUp) {
var countUntilReload = (0x10000 - (this.timer3Counter | 0)) | 0;
eventTime = this.nextTimer2Overflow(countUntilReload | 0) | 0;
}
else {
eventTime = (0x10000 - (this.timer3Counter | 0)) | 0;
eventTime = ((eventTime | 0) * (this.timer3Prescalar | 0)) | 0;
eventTime = ((eventTime | 0) - (this.timer3Precounter | 0)) | 0;
}
}
return eventTime | 0;
}
}
GameBoyAdvanceTimer.prototype.nextAudioTimerOverflow = function () {
var timer0 = this.nextTimer0OverflowSingle() | 0;
var timer1 = this.nextTimer1OverflowSingle() | 0;
return Math.min(timer0 | 0, timer1 | 0) | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer0IRQEventTime = function () {
var clocks = 0x7FFFFFFF;
if (this.timer0Enabled && this.timer0IRQ) {
clocks = this.nextTimer0OverflowSingle() | 0;
}
return clocks | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer1IRQEventTime = function () {
var clocks = 0x7FFFFFFF;
if (this.timer1Enabled && this.timer1IRQ) {
clocks = this.nextTimer1OverflowSingle() | 0;
}
return clocks | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer2IRQEventTime = function () {
var clocks = 0x7FFFFFFF;
if (this.timer2Enabled && this.timer2IRQ) {
clocks = this.nextTimer2OverflowSingle() | 0;
}
return clocks | 0;
}
GameBoyAdvanceTimer.prototype.nextTimer3IRQEventTime = function () {
var clocks = 0x7FFFFFFF;
if (this.timer3Enabled && this.timer3IRQ) {
clocks = this.nextTimer3OverflowSingle() | 0;
}
return clocks | 0;
}

View file

@ -0,0 +1,484 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceWait(IOCore) {
//Build references:
this.IOCore = IOCore;
}
GameBoyAdvanceWait.prototype.initialize = function () {
this.memory = this.IOCore.memory;
this.cpu = this.IOCore.cpu;
this.WRAMConfiguration = 0xD000020; //WRAM configuration control register current data.
this.WRAMWaitState = 3; //External WRAM wait state.
this.SRAMWaitState = 5; //SRAM wait state.
this.WAITCNT0 = 0; //WAITCNT0 control register data.
this.WAITCNT1 = 0; //WAITCNT1 control register data.
this.POSTBOOT = 0; //POSTBOOT control register data.
this.isRendering = 1; //Are we doing memory during screen draw?
this.isOAMRendering = 1; //Are we doing memory during OAM draw?
this.nonSequential = 0x10; //Non-sequential access bit-flag.
this.buffer = 0; //Tracking of the size of the prebuffer cache.
this.clocks = 0; //Tracking clocks for prebuffer cache.
//Create the wait state address translation cache:
this.waitStateClocks16 = getUint8Array(0x20);
this.waitStateClocks32 = getUint8Array(0x20);
//Wait State 0:
this.setWaitState(0, 0);
//Wait State 1:
this.setWaitState(1, 0);
//Wait State 2:
this.setWaitState(2, 0);
//Initialize out some dynamic references:
this.getROMRead16 = this.getROMRead16NoPrefetch;
this.getROMRead32 = this.getROMRead32NoPrefetch;
this.CPUInternalCyclePrefetch = this.CPUInternalCycleNoPrefetch;
this.CPUInternalSingleCyclePrefetch = this.CPUInternalSingleCycleNoPrefetch;
}
GameBoyAdvanceWait.prototype.getWaitStateFirstAccess = function (data) {
//Get the first access timing:
data = data | 0;
data = data & 0x3;
if ((data | 0) < 0x3) {
data = (5 - (data | 0)) | 0;
}
else {
data = 9;
}
return data | 0;
}
GameBoyAdvanceWait.prototype.getWaitStateSecondAccess = function (region, data) {
//Get the second access timing:
region = region | 0;
data = data | 0;
if ((data & 0x4) == 0) {
data = 0x2 << (region | 0);
data = ((data | 0) + 1) | 0;
}
else {
data = 0x2;
}
return data | 0;
}
GameBoyAdvanceWait.prototype.setWaitState = function (region, data) {
region = region | 0;
data = data | 0;
//Wait State First Access:
var firstAccess = this.getWaitStateFirstAccess(data & 0x3) | 0;
//Wait State Second Access:
var secondAccess = this.getWaitStateSecondAccess(region | 0, data | 0) | 0;
region = region << 1;
//Computing First Access:
//8-16 bit access:
this.waitStateClocks16[0x18 | region] = firstAccess | 0;
this.waitStateClocks16[0x19 | region] = firstAccess | 0;
//32 bit access:
var accessTime = ((firstAccess | 0) + (secondAccess | 0)) | 0;
this.waitStateClocks32[0x18 | region] = accessTime | 0;
this.waitStateClocks32[0x19 | region] = accessTime | 0;
//Computing Second Access:
//8-16 bit access:
this.waitStateClocks16[0x8 | region] = secondAccess | 0;
this.waitStateClocks16[0x9 | region] = secondAccess | 0;
//32 bit access:
this.waitStateClocks32[0x8 | region] = secondAccess << 1;
this.waitStateClocks32[0x9 | region] = secondAccess << 1;
}
GameBoyAdvanceWait.prototype.writeWAITCNT8_0 = function (data) {
data = data | 0;
//Set SRAM Wait State:
if ((data & 0x3) < 0x3) {
this.SRAMWaitState = (5 - (data & 0x3)) | 0;
}
else {
this.SRAMWaitState = 9;
}
data = data & 0xFF;
//Set Wait State 0:
this.setWaitState(0, data >> 2);
//Set Wait State 1:
this.setWaitState(1, data >> 5);
this.WAITCNT0 = data | 0;
}
GameBoyAdvanceWait.prototype.readWAITCNT8_0 = function () {
return this.WAITCNT0 | 0;
}
GameBoyAdvanceWait.prototype.writeWAITCNT8_1 = function (data) {
data = data | 0;
//Set Wait State 2:
this.setWaitState(2, data & 0xFF);
//Set Prefetch Mode:
if ((data & 0x40) == 0) {
//No Prefetch:
this.resetPrebuffer();
this.getROMRead16 = this.getROMRead16NoPrefetch;
this.getROMRead32 = this.getROMRead32NoPrefetch;
this.CPUInternalCyclePrefetch = this.CPUInternalCycleNoPrefetch;
this.CPUInternalSingleCyclePrefetch = this.CPUInternalSingleCycleNoPrefetch;
}
else {
//Prefetch Enabled:
this.getROMRead16 = this.getROMRead16Prefetch;
this.getROMRead32 = this.getROMRead32Prefetch;
this.CPUInternalCyclePrefetch = this.multiClock;
this.CPUInternalSingleCyclePrefetch = this.singleClock;
}
this.WAITCNT1 = data & 0x5F;
}
GameBoyAdvanceWait.prototype.readWAITCNT8_1 = function () {
return this.WAITCNT1 | 0;
}
GameBoyAdvanceWait.prototype.writeWAITCNT16 = function (data) {
this.writeWAITCNT8_0(data | 0);
this.writeWAITCNT8_1(data >> 8);
}
GameBoyAdvanceWait.prototype.readWAITCNT16 = function () {
var data = this.WAITCNT0 | 0;
data = data | (this.WAITCNT1 << 8);
return data | 0;
}
GameBoyAdvanceWait.prototype.writePOSTBOOT = function (data) {
this.POSTBOOT = data & 0xFF;
}
GameBoyAdvanceWait.prototype.readPOSTBOOT = function () {
return this.POSTBOOT | 0;
}
GameBoyAdvanceWait.prototype.writeHALTCNT = function (data) {
data = data | 0;
this.IOCore.updateCoreSpillRetain();
//HALT/STOP mode entrance:
if ((data & 0x80) == 0) {
//Halt:
this.IOCore.flagHalt();
}
else {
//Stop:
this.IOCore.flagStop();
}
}
GameBoyAdvanceWait.prototype.writeHALT16 = function (data) {
data = data | 0;
this.POSTBOOT = data & 0xFF;
this.IOCore.updateCoreSpillRetain();
//HALT/STOP mode entrance:
if ((data & 0x8000) == 0) {
//Halt:
this.IOCore.flagHalt();
}
else {
//Stop:
this.IOCore.flagStop();
}
}
GameBoyAdvanceWait.prototype.writeConfigureWRAM8 = function (address, data) {
address = address | 0;
data = data | 0;
switch (address & 0x3) {
case 0:
this.memory.remapWRAM(data & 0x21);
this.WRAMConfiguration = (this.WRAMConfiguration & 0xFFFFFF00) | (data & 0xFF);
break;
case 1:
this.WRAMConfiguration = (this.WRAMConfiguration & 0xFFFF00FF) | ((data & 0xFF) << 8);
break;
case 2:
this.WRAMConfiguration = (this.WRAMConfiguration & 0xFF00FFFF) | ((data & 0xFF) << 16);
break;
default:
this.WRAMWaitState = (0x10 - (data & 0xF)) | 0;
this.WRAMConfiguration = (this.WRAMConfiguration & 0xFFFFFF) | (data << 24);
}
}
GameBoyAdvanceWait.prototype.writeConfigureWRAM16 = function (address, data) {
address = address | 0;
data = data | 0;
if ((address & 0x2) == 0) {
this.WRAMConfiguration = (this.WRAMConfiguration & 0xFFFF0000) | (data & 0xFFFF);
this.memory.remapWRAM(data & 0x21);
}
else {
this.WRAMConfiguration = (data << 16) | (this.WRAMConfiguration & 0xFFFF);
this.WRAMWaitState = (0x10 - ((data >> 8) & 0xF)) | 0;
}
}
GameBoyAdvanceWait.prototype.writeConfigureWRAM32 = function (data) {
data = data | 0;
this.WRAMConfiguration = data | 0;
this.WRAMWaitState = (0x10 - ((data >> 24) & 0xF)) | 0;
this.memory.remapWRAM(data & 0x21);
}
GameBoyAdvanceWait.prototype.readConfigureWRAM8 = function (address) {
address = address | 0;
var data = 0;
switch (address & 0x3) {
case 0:
data = this.WRAMConfiguration & 0x2F;
break;
case 3:
data = this.WRAMConfiguration >>> 24;
}
return data | 0;
}
GameBoyAdvanceWait.prototype.readConfigureWRAM16 = function (address) {
address = address | 0;
var data = 0;
if ((address & 0x2) == 0) {
data = this.WRAMConfiguration & 0x2F;
}
else {
data = (this.WRAMConfiguration >> 16) & 0xFF00;
}
return data | 0;
}
GameBoyAdvanceWait.prototype.readConfigureWRAM32 = function () {
return this.WRAMConfiguration & 0xFF00002F;
}
GameBoyAdvanceWait.prototype.CPUInternalCycleNoPrefetch = function (clocks) {
clocks = clocks | 0;
//Clock for idle CPU time:
this.IOCore.updateCore(clocks | 0);
//Prebuffer bug:
this.checkPrebufferBug();
}
GameBoyAdvanceWait.prototype.CPUInternalSingleCycleNoPrefetch = function () {
//Clock for idle CPU time:
this.IOCore.updateCoreSingle();
//Not enough time for prebuffer buffering, so skip it.
//Prebuffer bug:
this.checkPrebufferBug();
}
GameBoyAdvanceWait.prototype.checkPrebufferBug = function () {
//Issue a non-sequential cycle for the next read if we did an I-cycle:
var address = this.cpu.registers[15] | 0;
if ((address | 0) >= 0x8000000 && (address | 0) < 0xE000000) {
this.NonSequentialBroadcast();
}
}
GameBoyAdvanceWait.prototype.NonSequentialBroadcast = function () {
//Flag as N cycle:
this.nonSequential = 0x10;
}
GameBoyAdvanceWait.prototype.NonSequentialBroadcastClear = function () {
//PC branched:
this.NonSequentialBroadcast();
this.resetPrebuffer();
}
GameBoyAdvanceWait.prototype.check128kAlignmentBug = function (address) {
address = address | 0;
if ((address & 0x1FFFF) == 0) {
this.NonSequentialBroadcast();
}
}
GameBoyAdvanceWait.prototype.multiClock = function (clocks) {
clocks = clocks | 0;
this.IOCore.updateCore(clocks | 0);
var address = this.cpu.registers[15] | 0;
if ((address | 0) >= 0x8000000 && (address | 0) < 0xE000000) {
if ((this.clocks | 0) < 0xFF) {
this.clocks = ((this.clocks | 0) + (clocks | 0)) | 0;
}
}
else {
this.resetPrebuffer();
}
}
GameBoyAdvanceWait.prototype.singleClock = function () {
this.IOCore.updateCoreSingle();
var address = this.cpu.registers[15] | 0;
if ((address | 0) >= 0x8000000 && (address | 0) < 0xE000000) {
if ((this.clocks | 0) < 0xFF) {
this.clocks = ((this.clocks | 0) + 1) | 0;
}
}
else {
this.resetPrebuffer();
}
}
GameBoyAdvanceWait.prototype.addPrebufferSingleClock = function () {
this.clocks = ((this.clocks | 0) + 1) | 0;
}
GameBoyAdvanceWait.prototype.decrementBufferSingle = function () {
this.buffer = ((this.buffer | 0) - 1) | 0;
}
GameBoyAdvanceWait.prototype.decrementBufferDouble = function () {
this.buffer = ((this.buffer | 0) - 2) | 0;
}
GameBoyAdvanceWait.prototype.resetPrebuffer = function () {
//Reset the buffering:
this.clocks = 0;
this.buffer = 0;
}
GameBoyAdvanceWait.prototype.drainOverdueClocks = function () {
if ((this.clocks | 0) > 0 && (this.buffer | 0) < 8) {
var address = this.cpu.registers[15] >>> 24;
//Convert built up clocks to 16 bit word buffer units:
do {
this.clocks = ((this.clocks | 0) - (this.waitStateClocks16[address | 0] | 0)) | 0;
this.buffer = ((this.buffer | 0) + 1) | 0;
} while ((this.clocks | 0) > 0 && (this.buffer | 0) < 8);
//If we're deficient in clocks, fit them in before the access:
if ((this.clocks | 0) < 0) {
this.IOCore.updateCoreNegative(this.clocks | 0);
this.clocks = 0;
}
}
}
GameBoyAdvanceWait.prototype.computeClocks = function (address) {
address = address | 0;
//Convert built up clocks to 16 bit word buffer units:
while ((this.buffer | 0) < 8 && (this.clocks | 0) >= (this.waitStateClocks16[address | 0] | 0)) {
this.clocks = ((this.clocks | 0) - (this.waitStateClocks16[address | 0] | 0)) | 0;
this.buffer = ((this.buffer | 0) + 1) | 0;
}
}
GameBoyAdvanceWait.prototype.drainOverdueClocksCPU = function () {
if ((this.clocks | 0) < 0) {
//Compute "overdue" clocks:
this.IOCore.updateCoreNegative(this.clocks | 0);
this.clocks = 0;
}
else {
//Buffer satiated, clock 1:
this.IOCore.updateCoreSingle();
}
}
GameBoyAdvanceWait.prototype.doGamePakFetch16 = function (address) {
address = address | 0;
//Fetch 16 bit word into buffer:
this.clocks = ((this.clocks | 0) - (this.waitStateClocks16[address | this.nonSequential] | 0)) | 0;
this.nonSequential = 0;
}
GameBoyAdvanceWait.prototype.doGamePakFetch32 = function (address) {
address = address | 0;
//Fetch 16 bit word into buffer:
this.clocks = ((this.clocks | 0) - (this.waitStateClocks32[address | this.nonSequential] | 0)) | 0;
this.nonSequential = 0;
}
GameBoyAdvanceWait.prototype.getROMRead16Prefetch = function (address) {
//Caching enabled:
address = address | 0;
//Resolve clocks to buffer units:
this.computeClocks(address | 0);
//Need 16 bits minimum buffered:
switch (this.buffer | 0) {
case 0:
//Fetch 16 bit word into buffer:
this.doGamePakFetch16(address | 0);
break;
default:
//Instruction fetch is 1 clock wide minimum:
this.addPrebufferSingleClock();
//Decrement the buffer:
this.decrementBufferSingle();
}
//Clock the state:
this.drainOverdueClocksCPU();
}
GameBoyAdvanceWait.prototype.getROMRead16NoPrefetch = function (address) {
//Caching disabled:
address = address | 0;
this.IOCore.updateCore(this.waitStateClocks16[address | this.nonSequential] | 0);
this.nonSequential = 0;
}
GameBoyAdvanceWait.prototype.getROMRead32Prefetch = function (address) {
//Caching enabled:
address = address | 0;
//Resolve clocks to buffer units:
this.computeClocks(address | 0);
//Need 32 bits minimum buffered:
switch (this.buffer | 0) {
case 0:
//Fetch two 16 bit words into buffer:
this.doGamePakFetch32(address | 0);
break;
case 1:
//Fetch a 16 bit word into buffer:
this.doGamePakFetch16(address | 0);
this.buffer = 0;
break;
default:
//Instruction fetch is 1 clock wide minimum:
this.addPrebufferSingleClock();
//Decrement the buffer:
this.decrementBufferDouble();
}
//Clock the state:
this.drainOverdueClocksCPU();
}
GameBoyAdvanceWait.prototype.getROMRead32NoPrefetch = function (address) {
//Caching disabled:
address = address | 0;
this.IOCore.updateCore(this.waitStateClocks32[address | this.nonSequential] | 0);
this.nonSequential = 0;
}
GameBoyAdvanceWait.prototype.WRAMAccess = function () {
this.multiClock(this.WRAMWaitState | 0);
}
GameBoyAdvanceWait.prototype.WRAMAccess16CPU = function () {
this.IOCore.updateCore(this.WRAMWaitState | 0);
}
GameBoyAdvanceWait.prototype.WRAMAccess32 = function () {
this.multiClock(this.WRAMWaitState << 1);
}
GameBoyAdvanceWait.prototype.WRAMAccess32CPU = function () {
this.IOCore.updateCore(this.WRAMWaitState << 1);
}
GameBoyAdvanceWait.prototype.ROMAccess = function (address) {
address = address | 0;
this.drainOverdueClocks();
this.check128kAlignmentBug(address | 0);
this.IOCore.updateCore(this.waitStateClocks16[(address >> 24) | this.nonSequential] | 0);
this.nonSequential = 0;
}
GameBoyAdvanceWait.prototype.ROMAccess16CPU = function (address) {
address = address | 0;
this.check128kAlignmentBug(address | 0);
this.getROMRead16(address >> 24);
}
GameBoyAdvanceWait.prototype.ROMAccess32 = function (address) {
address = address | 0;
this.drainOverdueClocks();
this.check128kAlignmentBug(address | 0);
this.IOCore.updateCore(this.waitStateClocks32[(address >> 24) | this.nonSequential] | 0);
this.nonSequential = 0;
}
GameBoyAdvanceWait.prototype.ROMAccess32CPU = function (address) {
address = address | 0;
this.check128kAlignmentBug(address | 0);
this.getROMRead32(address >> 24);
}
GameBoyAdvanceWait.prototype.SRAMAccess = function () {
this.multiClock(this.SRAMWaitState | 0);
}
GameBoyAdvanceWait.prototype.SRAMAccessCPU = function () {
this.resetPrebuffer();
this.IOCore.updateCore(this.SRAMWaitState | 0);
}
GameBoyAdvanceWait.prototype.VRAMAccess = function () {
this.multiClock(this.isRendering | 0);
}
GameBoyAdvanceWait.prototype.VRAMAccess16CPU = function () {
this.IOCore.updateCore(this.isRendering | 0);
}
GameBoyAdvanceWait.prototype.VRAMAccess32 = function () {
this.multiClock(this.isRendering << 1);
}
GameBoyAdvanceWait.prototype.VRAMAccess32CPU = function () {
this.IOCore.updateCore(this.isRendering << 1);
}
GameBoyAdvanceWait.prototype.OAMAccess = function () {
this.multiClock(this.isOAMRendering | 0);
}
GameBoyAdvanceWait.prototype.OAMAccessCPU = function () {
this.IOCore.updateCore(this.isOAMRendering | 0);
}
GameBoyAdvanceWait.prototype.updateRenderStatus = function (isRendering, isOAMRendering) {
this.isRendering = isRendering | 0;
this.isOAMRendering = isOAMRendering | 0;
}

View file

@ -0,0 +1,300 @@
"use strict";
/*
Copyright (C) 2012-2019 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
importScripts("../includes/TypedArrayShim.js");
importScripts("Cartridge.js");
importScripts("DMA.js");
importScripts("Emulator.js");
importScripts("Graphics.js");
importScripts("RunLoop.js");
importScripts("Memory.js");
importScripts("IRQ.js");
importScripts("JoyPad.js");
importScripts("Serial.js");
importScripts("Sound.js");
importScripts("Timer.js");
importScripts("Wait.js");
importScripts("CPU.js");
importScripts("Saves.js");
importScripts("sound/FIFO.js");
importScripts("sound/Channel1.js");
importScripts("sound/Channel2.js");
importScripts("sound/Channel3.js");
importScripts("sound/Channel4.js");
importScripts("CPU/ARM.js");
importScripts("CPU/THUMB.js");
importScripts("CPU/CPSR.js");
importScripts("graphics/RendererProxy.js");
importScripts("graphics/RendererShim.js");
importScripts("graphics/Renderer.js");
importScripts("graphics/BGTEXT.js");
importScripts("graphics/BG2FrameBuffer.js");
importScripts("graphics/BGMatrix.js");
importScripts("graphics/AffineBG.js");
importScripts("graphics/ColorEffects.js");
importScripts("graphics/Mosaic.js");
importScripts("graphics/OBJ.js");
importScripts("graphics/OBJWindow.js");
importScripts("graphics/Window.js");
importScripts("graphics/Compositor.js");
importScripts("memory/DMA0.js");
importScripts("memory/DMA1.js");
importScripts("memory/DMA2.js");
importScripts("memory/DMA3.js");
importScripts("cartridge/SaveDeterminer.js");
importScripts("cartridge/SRAM.js");
importScripts("cartridge/FLASH.js");
importScripts("cartridge/EEPROM.js");
importScripts("cartridge/GPIO.js");
var Iodine = new GameBoyAdvanceEmulator();
//Save callbacks waiting to be satisfied:
var saveImportPool = [];
//Graphics Buffers:
var gfxBuffers = [getSharedUint8Array(160 * 240 * 3),
getSharedUint8Array(160 * 240 * 3)];
var gfxCounters = getSharedInt32Array(3);
//Audio Buffers:
var audioBuffer = null;
var audioCounters = null;
var audioBufferSize = 0;
var audioBufferSizeMask = 0;
var audioSamplesRemaining = getSharedInt32Array(1);
//Time Stamp tracking:
var timestamp = getSharedUint32Array(1);
//Interval Timer handle:
var timerHandle = null;
var timerRate = 0;
//Pass the shared array buffers:
try {
postMessage({messageID:0, gfxBuffer1:gfxBuffers[0], gfxBuffer2:gfxBuffers[1], gfxCounters:gfxCounters, audioSamplesRemaining:audioSamplesRemaining, timestamp:timestamp}, [gfxBuffers[0].buffer, gfxBuffers[1].buffer, gfxCounters.buffer, audioSamplesRemaining.buffer, timestamp.buffer]);
}
catch (e) {
postMessage({messageID:0, gfxBuffer1:gfxBuffers[0], gfxBuffer2:gfxBuffers[1], gfxCounters:gfxCounters, audioSamplesRemaining:audioSamplesRemaining, timestamp:timestamp});
}
//Event decoding:
self.onmessage = function (event) {
var data = event.data;
switch (data.messageID | 0) {
case 0:
Iodine.play();
break;
case 1:
Iodine.pause();
break;
case 2:
Iodine.restart();
break;
case 3:
Iodine.setIntervalRate(+data.payload);
changeTimer(data.payload | 0);
break;
case 4:
Iodine.attachGraphicsFrameHandler(graphicsFrameHandler);
break;
case 5:
Iodine.attachAudioHandler(audioHandler);
break;
case 6:
Iodine.enableAudio();
break;
case 7:
Iodine.disableAudio();
break;
case 8:
Iodine.toggleSkipBootROM(!!data.payload);
break;
case 9:
Iodine.toggleDynamicSpeed(!!data.payload);
break;
case 10:
Iodine.attachSpeedHandler(speedHandler);
break;
case 11:
Iodine.keyDown(data.payload | 0);
break;
case 12:
Iodine.keyUp(data.payload | 0);
break;
case 13:
Iodine.incrementSpeed(+data.payload);
break;
case 14:
Iodine.setSpeed(+data.payload);
break;
case 15:
Iodine.attachBIOS(data.payload);
break;
case 16:
Iodine.attachROM(data.payload);
break;
case 17:
Iodine.exportSave();
break;
case 18:
Iodine.attachSaveExportHandler(saveExportHandler);
break;
case 19:
Iodine.attachSaveImportHandler(saveImportHandler);
break;
case 20:
processSaveImportSuccess(data.payload);
break;
case 21:
processSaveImportFail();
break;
case 22:
Iodine.toggleOffthreadGraphics(!!data.payload);
break;
case 23:
Iodine.attachPlayStatusHandler(playStatusHandler);
}
}
var graphicsFrameHandler = {
//Function only called if graphics is THIS thread:
copyBuffer:function (swizzledFrame) {
//Push a frame of graphics to the blitter handle:
//Load the counter values:
var start = gfxCounters[0] | 0; //Written by the other thread.
var end = gfxCounters[1] | 0; //Written by this thread.
//Check if buffer is full:
if ((end | 0) == (((start | 0) + 2) | 0)) {
//Skip copying a frame out:
return;
}
//Copy samples into the ring buffer:
//Hardcoded for 2 buffers for a triple buffer effect:
gfxBuffers[end & 0x1].set(swizzledFrame);
//Increment the ending position counter by 1:
//Atomic to commit the counter to memory:
Atomics.store(gfxCounters, 1, ((end | 0) + 1) | 0);
}
};
//Shim for our audio api:
var audioHandler = {
initialize:function (channels, sampleRate, bufferLimit, call1, call2, call3) {
//Initialize the audio mixer input:
channels = channels | 0;
sampleRate = +sampleRate;
bufferLimit = bufferLimit | 0;
//Generate an audio buffer:
audioBufferSize = ((bufferLimit | 0) * (channels | 0)) | 0;
audioBufferSizeMask = 1;
while ((audioBufferSize | 0) >= (audioBufferSizeMask | 0)) {
audioBufferSizeMask = (audioBufferSizeMask << 1) | 1;
}
audioBufferSize = ((audioBufferSizeMask | 0) + 1) | 0;
audioBuffer = getSharedFloat32Array(audioBufferSize | 0);
audioCounters = getSharedInt32Array(2);
try {
postMessage({messageID:1, channels:channels | 0, sampleRate:+sampleRate, bufferLimit:bufferLimit | 0, audioBuffer:audioBuffer, audioCounters:audioCounters}, [audioBuffer.buffer, audioCounters.buffer]);
}
catch (e) {
postMessage({messageID:1, channels:channels | 0, sampleRate:+sampleRate, bufferLimit:bufferLimit | 0, audioBuffer:audioBuffer, audioCounters:audioCounters});
}
},
push:function (buffer, startPos, endPos) {
startPos = startPos | 0;
endPos = endPos | 0;
//Push audio to the audio mixer input handle:
//Load the counter values:
var start = audioCounters[0] | 0; //Written to by the other thread.
var end = audioCounters[1] | 0; //Written by this thread.
var endCorrected = ((end | 0) & (audioBufferSizeMask | 0)) | 0;
var freeBufferSpace = ((end | 0) - (start | 0)) | 0;
freeBufferSpace = ((audioBufferSize | 0) - (freeBufferSpace | 0)) | 0;
var amountToSend = ((endPos | 0) - (startPos | 0)) | 0;
amountToSend = Math.min(amountToSend | 0, freeBufferSpace | 0) | 0;
endPos = ((startPos | 0) + (amountToSend | 0)) | 0;
//Push audio into buffer:
for (; (startPos | 0) < (endPos | 0); startPos = ((startPos | 0) + 1) | 0) {
audioBuffer[endCorrected | 0] = +buffer[startPos | 0];
endCorrected = ((endCorrected | 0) + 1) | 0;
if ((endCorrected | 0) == (audioBufferSize | 0)) {
endCorrected = 0;
}
}
//Update the cross thread buffering count:
end = ((end | 0) + (amountToSend | 0)) | 0;
//Atomic store to commit writes to memory:
Atomics.store(audioCounters, 1, end | 0);
},
register:function () {
//Register into the audio mixer:
postMessage({messageID:2});
},
unregister:function () {
//Unregister from audio mixer:
postMessage({messageID:3});
},
setBufferSpace:function (spaceContain) {
//Ensure buffering minimum levels for the audio:
postMessage({messageID:4, audioBufferContainAmount:spaceContain | 0});
},
remainingBuffer:function () {
//Report the amount of audio samples in-flight:
var ringBufferCount = this.remainingBufferShared() | 0;
var audioSysCount = audioSamplesRemaining[0] | 0;
return ((ringBufferCount | 0) + (audioSysCount | 0)) | 0;
},
remainingBufferShared:function () {
//Reported the sample count left in the shared buffer:
var start = audioCounters[0] | 0;
var end = audioCounters[1] | 0;
var ringBufferCount = ((end | 0) - (start | 0)) | 0;
return ringBufferCount | 0;
}
};
function saveImportHandler(saveID, saveCallback, noSaveCallback) {
postMessage({messageID:5, saveID:saveID});
saveImportPool.push([saveCallback, noSaveCallback]);
}
function saveExportHandler(saveID, saveData) {
postMessage({messageID:6, saveID:saveID, saveData:saveData});
}
function speedHandler(speed) {
postMessage({messageID:7, speed:speed});
}
function processSaveImportSuccess(saveData) {
saveImportPool.shift()[0](saveData);
}
function processSaveImportFail() {
saveImportPool.shift()[1]();
}
function playStatusHandler(isPlaying) {
isPlaying = isPlaying | 0;
postMessage({messageID:8, playing:(isPlaying | 0)});
if ((isPlaying | 0) == 0) {
if (timerHandle) {
clearInterval(timerHandle);
timerHandle = null;
}
}
else {
if (!timerHandle) {
initTimer(timerRate | 0);
}
}
}
function changeTimer(rate) {
rate = rate | 0;
if (timerHandle) {
clearInterval(timerHandle);
initTimer(rate | 0);
}
timerRate = rate | 0;
}
function initTimer(rate) {
rate = rate | 0;
if ((rate | 0) > 0) {
timerHandle = setInterval(function() {
Iodine.timerCallback(timestamp[0] >>> 0);
}, rate | 0);
}
}

View file

@ -0,0 +1,190 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceEEPROMChip(IOCore) {
this.saves = null;
this.largestSizePossible = 0x200;
this.mode = 0;
this.bitsProcessed = 0;
this.address = 0;
this.buffer = getUint8Array(8);
this.IOCore = IOCore;
//Special note to emulator authors: EEPROM command ending bit "0" can also be a "1"...
}
GameBoyAdvanceEEPROMChip.prototype.initialize = function () {
this.allocate();
}
GameBoyAdvanceEEPROMChip.prototype.allocate = function () {
if (this.saves == null || (this.saves.length | 0) < (this.largestSizePossible | 0)) {
//Allocate the new array:
var newSave = getUint8Array(this.largestSizePossible | 0);
//Init to default value:
for (var index = 0; (index | 0) < (this.largestSizePossible | 0); index = ((index | 0) + 1) | 0) {
newSave[index | 0] = 0xFF;
}
//Copy the old save data out:
if (this.saves != null) {
for (var index = 0; (index | 0) < (this.saves.length | 0); index = ((index | 0) + 1) | 0) {
newSave[index | 0] = this.saves[index | 0] | 0;
}
}
//Assign the new array out:
this.saves = newSave;
}
}
GameBoyAdvanceEEPROMChip.prototype.load = function (save) {
if ((save.length | 0) == 0x200 || (save.length | 0) == 0x2000) {
this.saves = save;
}
}
GameBoyAdvanceEEPROMChip.prototype.read8 = function () {
//Can't do real reading with 8-bit reads:
return 0x1;
}
GameBoyAdvanceEEPROMChip.prototype.read16 = function () {
var data = 1;
switch (this.mode | 0) {
case 0x7:
//Return 4 junk 0 bits:
data = 0;
if ((this.bitsProcessed | 0) < 3) {
//Increment our bits counter:
this.bitsProcessed = ((this.bitsProcessed | 0) + 1) | 0;
}
else {
//Reset our bits counter:
this.bitsProcessed = 0;
//Change mode for the actual reads:
this.mode = 8;
}
break;
case 0x8:
//Return actual serial style data:
var address = ((this.bitsProcessed >> 3) + (this.address | 0)) | 0;
data = (this.saves[address | 0] >> ((0x7 - (this.bitsProcessed & 0x7)) | 0)) & 0x1;
//Check for end of read:
if ((this.bitsProcessed | 0) < 0x3F) {
//Increment our bits counter:
this.bitsProcessed = ((this.bitsProcessed | 0) + 1) | 0;
}
else {
//Finished read and now idle:
this.resetMode();
}
}
return data | 0;
}
GameBoyAdvanceEEPROMChip.prototype.read32 = function () {
//Can't do real reading with 32-bit reads:
return 0x10001;
}
GameBoyAdvanceEEPROMChip.prototype.write16 = function (data) {
data = data | 0;
data = data & 0x1;
//Writes only work in DMA:
switch (this.mode | 0) {
//Idle Mode:
case 0:
this.mode = data | 0;
break;
//Select Mode:
case 0x1:
this.selectMode(data | 0);
break;
//Address Mode (Write):
case 0x2:
//Address Mode (Read):
case 0x3:
this.addressMode(data | 0);
break;
//Write Mode:
case 0x4:
this.writeMode(data | 0);
break;
//Ending bit of addressing:
case 0x5:
case 0x6:
this.endAddressing();
break;
//Read Mode:
default:
this.resetMode();
}
}
GameBoyAdvanceEEPROMChip.prototype.selectMode = function (data) {
data = data | 0;
//Reset our address:
this.address = 0;
//Reset our bits counter:
this.bitsProcessed = 0;
//Read the mode bit:
this.mode = 0x2 | data;
}
GameBoyAdvanceEEPROMChip.prototype.addressMode = function (data) {
data = data | 0;
//Shift in our address bit:
this.address = (this.address << 1) | data;
//Increment our bits counter:
this.bitsProcessed = ((this.bitsProcessed | 0) + 1) | 0;
//Check for how many bits we've shifted in:
switch (this.bitsProcessed | 0) {
//6 bit address mode:
case 0x6:
if ((this.IOCore.dmaChannel3.wordCountShadow | 0) >= (((this.mode | 0) == 2) ? 0x4A : 0xA)) {
this.largestSizePossible = 0x2000;
this.allocate();
break;
}
//14 bit address mode:
case 0xE:
this.changeModeToActive();
}
}
GameBoyAdvanceEEPROMChip.prototype.changeModeToActive = function () {
//Ensure the address range:
this.address &= 0x3FF;
//Addressing in units of 8 bytes:
this.address <<= 3;
//Reset our bits counter:
this.bitsProcessed = 0;
//Change to R/W Mode:
this.mode = ((this.mode | 0) + 2) | 0;
}
GameBoyAdvanceEEPROMChip.prototype.writeMode = function (data) {
data = data | 0;
//Push a bit into the buffer:
this.pushBuffer(data | 0);
//Save on last write bit push:
if ((this.bitsProcessed | 0) == 0x40) {
//64 bits buffered, so copy our buffer to the save data:
this.copyBuffer();
this.mode = 6;
}
}
GameBoyAdvanceEEPROMChip.prototype.pushBuffer = function (data) {
data = data | 0;
//Push a bit through our serial buffer:
var bufferPosition = this.bitsProcessed >> 3;
this.buffer[bufferPosition & 0x7] = ((this.buffer[bufferPosition & 0x7] << 1) & 0xFE) | data;
this.bitsProcessed = ((this.bitsProcessed | 0) + 1) | 0;
}
GameBoyAdvanceEEPROMChip.prototype.copyBuffer = function () {
//Copy 8 bytes from buffer to EEPROM save data starting at address offset:
for (var index = 0; (index | 0) < 8; index = ((index | 0) + 1) | 0) {
this.saves[this.address | index] = this.buffer[index & 0x7] & 0xFF;
}
}
GameBoyAdvanceEEPROMChip.prototype.endAddressing = function () {
this.mode = ((this.mode | 0) + 2) | 0;
}
GameBoyAdvanceEEPROMChip.prototype.resetMode = function () {
//Reset back to idle:
this.mode = 0;
}

View file

@ -0,0 +1,210 @@
"use strict";
/*
Copyright (C) 2012-2013 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceFLASHChip(is128, isAteml) {
this.largestSizePossible = (!!is128) ? 0x20000 : 0x10000;
this.notATMEL = !isAteml;
this.saves = null;
this.BANKOffset = 0;
this.flashCommandUnlockStage = 0;
this.flashCommand = 0;
this.writeBytesLeft = 0;
}
GameBoyAdvanceFLASHChip.prototype.initialize = function () {
this.allocate();
}
GameBoyAdvanceFLASHChip.prototype.allocate = function () {
if (this.saves == null || (this.saves.length | 0) < (this.largestSizePossible | 0)) {
//Allocate the new array:
var newSave = getUint8Array(this.largestSizePossible | 0);
//Init to default value:
for (var index = 0; (index | 0) < (this.largestSizePossible | 0); index = ((index | 0) + 1) | 0) {
newSave[index | 0] = 0xFF;
}
//Copy the old save data out:
if (this.saves != null) {
for (var index = 0; (index | 0) < (this.saves.length | 0); index = ((index | 0) + 1) | 0) {
newSave[index | 0] = this.saves[index | 0] | 0;
}
}
//Assign the new array out:
this.saves = newSave;
}
}
GameBoyAdvanceFLASHChip.prototype.load = function (save) {
if ((save.length | 0) == 0x10000 || (save.length | 0) == 0x20000) {
this.saves = save;
if ((save.length | 0) == 0x20000) {
this.notATMEL = true;
}
}
}
GameBoyAdvanceFLASHChip.prototype.read = function (address) {
address = address | 0;
var data = 0;
if ((this.flashCommand | 0) != 2 || (address | 0) > 1) {
data = this.saves[address | this.BANKOffset] | 0;
}
else {
if ((address | 0) == 0) {
if (this.notATMEL) {
data = ((this.largestSizePossible | 0) == 0x20000) ? 0x62 : 0xBF;
}
else {
data = 0x1F;
}
}
else {
if (this.notATMEL) {
data = ((this.largestSizePossible | 0) == 0x20000) ? 0x13 : 0xD4;
}
else {
data = 0x3D;
}
}
}
return data | 0;
}
GameBoyAdvanceFLASHChip.prototype.write = function (address, data) {
address = address | 0;
data = data | 0;
switch (this.writeBytesLeft | 0) {
case 0:
this.writeControlBits(address | 0, data | 0);
break;
case 0x80:
var addressToErase = (address & 0xFF80) | this.BANKOffset;
for (var index = 0; (index | 0) < 0x80; index = ((index | 0) + 1) | 0) {
this.saves[addressToErase | index] = 0xFF;
}
default:
this.writeByte(address | 0, data | 0);
}
}
GameBoyAdvanceFLASHChip.prototype.writeControlBits = function (address, data) {
address = address | 0;
data = data | 0;
switch (address | 0) {
case 0:
this.sectorEraseOrBankSwitch(address | 0, data | 0);
break;
case 0x5555:
this.controlWriteStage2(data | 0);
break;
case 0x2AAA:
this.controlWriteStageIncrement(data | 0);
break;
default:
this.sectorErase(address | 0, data | 0);
}
}
GameBoyAdvanceFLASHChip.prototype.writeByte = function (address, data) {
address = address | 0;
data = data | 0;
this.saves[address | this.BANKOffset] = data | 0;
this.writeBytesLeft = ((this.writeBytesLeft | 0) - 1) | 0;
}
GameBoyAdvanceFLASHChip.prototype.selectBank = function (bankNumber) {
bankNumber = bankNumber | 0;
this.BANKOffset = (bankNumber & 0x1) << 16;
this.largestSizePossible = Math.max((0x10000 + (this.BANKOffset | 0)) | 0, this.largestSizePossible | 0) | 0;
this.notATMEL = true;
this.allocate();
}
GameBoyAdvanceFLASHChip.prototype.controlWriteStage2 = function (data) {
data = data | 0;
if ((data | 0) == 0xAA) {
//Initial Command:
this.flashCommandUnlockStage = 1;
}
else if ((this.flashCommandUnlockStage | 0) == 2) {
switch (data | 0) {
case 0x10:
//Command Erase Chip:
if ((this.flashCommand | 0) == 1) {
for (var index = 0; (index | 0) < (this.largestSizePossible | 0); index = ((index | 0) + 1) | 0) {
this.saves[index | 0] = 0xFF;
}
}
this.flashCommand = 0;
break;
case 0x80:
//Command Erase:
this.flashCommand = 1;
break;
case 0x90:
//Command ID:
this.flashCommand = 2;
break;
case 0xA0:
//Command Write:
this.writeCommandTrigger();
break;
case 0xB0:
//Command Bank Switch:
this.flashCommand = 3;
break;
default:
this.flashCommand = 0;
}
//Reset the command state:
this.flashCommandUnlockStage = 0;
}
else if ((data | 0) == 0xF0) {
//Command Clear:
this.flashCommand = 0;
this.flashCommandUnlockStage = 0;
this.notATMEL = true;
}
}
GameBoyAdvanceFLASHChip.prototype.writeCommandTrigger = function () {
if ((this.flashCommandUnlockStage | 0) == 2) {
if (this.notATMEL) {
this.writeBytesLeft = 1;
}
else {
this.writeBytesLeft = 0x80;
}
}
}
GameBoyAdvanceFLASHChip.prototype.sectorErase = function (address, data) {
address = (address << 12) >> 12;
data = data | 0;
if ((this.flashCommand | 0) == 1 && (this.flashCommandUnlockStage | 0) == 2 && ((data | 0) == 0x30)) {
var addressEnd = ((address | this.BANKOffset) + 0x1000) | 0;
for (var index = address | this.BANKOffset; (index | 0) < (addressEnd | 0); index = ((index | 0) + 1) | 0) {
this.saves[index | 0] = 0xFF;
}
this.notATMEL = true;
}
this.flashCommand = 0;
this.flashCommandUnlockStage = 0;
}
GameBoyAdvanceFLASHChip.prototype.sectorEraseOrBankSwitch = function (address, data) {
address = address | 0;
data = data | 0;
if ((this.flashCommandUnlockStage | 0) == 2) {
this.sectorErase(address | 0, data | 0);
}
else if ((this.flashCommand | 0) == 3 && (this.flashCommandUnlockStage | 0) == 0) {
this.selectBank(data & 0x1);
}
this.flashCommand = 0;
this.flashCommandUnlockStage = 0;
}
GameBoyAdvanceFLASHChip.prototype.controlWriteStageIncrement = function (data) {
if ((data | 0) == 0x55 && (this.flashCommandUnlockStage | 0) == 1) {
this.flashCommandUnlockStage = ((this.flashCommandUnlockStage | 0) + 1) | 0;
}
else {
this.flashCommandUnlockStage = 0;
}
}

View file

@ -0,0 +1,56 @@
"use strict";
/*
Copyright (C) 2012-2016 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceGPIOChip() {
this.type = 0;
this.data = 0;
this.direction = 0;
this.readWrite = 0;
}
GameBoyAdvanceGPIOChip.prototype.getType = function () {
return this.type | 0;
}
GameBoyAdvanceGPIOChip.prototype.setType = function (type) {
type = type | 0;
this.type = type | 0;
}
GameBoyAdvanceGPIOChip.prototype.read = function (address) {
address = address | 0;
var data = 0;
if (this.readWrite | 0) {
switch (address & 0xF) {
case 0x4:
this.readTick();
data = this.data | 0;
break;
case 0x6:
data = this.direction | 0;
break;
case 0x8:
data = this.readWrite | 0;
}
}
return data | 0;
}
GameBoyAdvanceGPIOChip.prototype.write = function (address, data) {
address = address | 0;
data = data | 0;
switch (address & 0xF) {
case 0x4:
this.data = data & 0xF;
this.writeTick(data | 0);
break;
case 0x6:
this.direction = data & 0xF;
break;
case 0x8:
this.readWrite = data & 0x1;
}
}

View file

@ -0,0 +1,85 @@
"use strict";
/*
Copyright (C) 2012-2013 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceSRAMChip() {
this.saves = null;
this.TILTChip = null;
this.TILTChipUnlocked = 0;
}
GameBoyAdvanceSRAMChip.prototype.initialize = function () {
if (this.saves == null || (this.saves.length | 0) != 0x8000) {
this.saves = getUint8Array(0x8000);
}
}
GameBoyAdvanceSRAMChip.prototype.load = function (save) {
if ((save.length | 0) == 0x8000) {
this.saves = save;
}
}
GameBoyAdvanceSRAMChip.prototype.read = function (address) {
address = address | 0;
var data = 0;
if ((address | 0) < 0x8000 || (this.TILTChipUnlocked | 0) != 3) {
data = this.saves[address & 0x7FFF] | 0;
}
else {
switch (address | 0) {
case 0x8200:
data = this.TILTChip.readXLow() | 0;
break;
case 0x8300:
data = this.TILTChip.readXHigh() | 0;
break;
case 0x8400:
data = this.TILTChip.readYLow() | 0;
break;
case 0x8500:
data = this.TILTChip.readYHigh() | 0;
break;
default:
data = this.saves[address & 0x7FFF] | 0;
}
}
return data | 0;
}
GameBoyAdvanceSRAMChip.prototype.write = function (address, data) {
address = address | 0;
data = data | 0;
if ((address | 0) < 0x8000 || (this.TILTChipUnlocked | 0) >= 4) {
//Normal SRAM write:
this.saves[address & 0x7FFF] = data | 0;
}
else {
switch (address | 0) {
case 0x8000:
if ((data | 0) == 0x55) { //Magic Combo.
this.TILTChipUnlocked |= 0x1; //Tilt unlock stage 1.
}
else {
this.TILTChipUnlocked |= 0x4; //Definitely not using a tilt sensor.
}
break;
case 0x8100:
if ((data | 0) == 0xAA) { //Magic Combo.
this.TILTChipUnlocked |= 0x2; //Tilt unlock stage 2.
}
else {
this.TILTChipUnlocked |= 0x4; //Definitely not using a tilt sensor.
}
break;
default:
//Check for mirroring while not tilt chip:
if ((this.TILTChipUnlocked | 0) == 0) {
this.saves[address & 0x7FFF] = data | 0;
this.TILTChipUnlocked |= 0x4; //Definitely not using a tilt sensor.
}
}
}
}

View file

@ -0,0 +1,158 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceSaveDeterminer(saveCore) {
this.saves = null;
this.saveCore = saveCore;
this.possible = 0x7;
}
GameBoyAdvanceSaveDeterminer.prototype.flags = {
SRAM: 1,
FLASH: 2,
EEPROM: 4
}
GameBoyAdvanceSaveDeterminer.prototype.initialize = function () {
}
GameBoyAdvanceSaveDeterminer.prototype.load = function (save) {
this.saves = save;
var length = save.length | 0;
switch (length | 0) {
case 0x200:
case 0x2000:
this.possible = this.flags.EEPROM | 0;
break;
case 0x8000:
this.possible = this.flags.SRAM | 0;
break;
case 0x10000:
case 0x20000:
this.possible = this.flags.FLASH | 0;
}
this.checkDetermination();
}
GameBoyAdvanceSaveDeterminer.prototype.checkDetermination = function () {
switch (this.possible) {
case 0x1:
this.saveCore.referenceSave(0x1);
break;
case 0x2:
this.saveCore.referenceSave(0x2);
break;
case 0x4:
this.saveCore.referenceSave(0x3);
}
}
GameBoyAdvanceSaveDeterminer.prototype.readSRAM = function (address) {
address = address | 0;
var data = 0;
//Is not EEPROM:
this.possible &= ~this.flags.EEPROM;
if (this.saves != null) {
if ((this.possible & this.flags.FLASH) == (this.flags.FLASH | 0) || (this.possible & this.flags.SRAM) == (this.flags.SRAM | 0)) {
//Read is the same between SRAM and FLASH for the most part:
data = this.saves[(address | 0) % (this.saves.length | 0)] | 0;
}
}
return data | 0;
}
GameBoyAdvanceSaveDeterminer.prototype.writeGPIO8 = function (address, data) {
address = address | 0;
data = data | 0;
//GPIO (TODO):
}
GameBoyAdvanceSaveDeterminer.prototype.writeGPIO16 = function (address, data) {
address = address | 0;
data = data | 0;
//GPIO (TODO):
}
GameBoyAdvanceSaveDeterminer.prototype.writeGPIO32 = function (address, data) {
address = address | 0;
data = data | 0;
//GPIO (TODO):
}
GameBoyAdvanceSaveDeterminer.prototype.writeEEPROM16 = function (address, data) {
address = address | 0;
data = data | 0;
if ((this.possible & this.flags.EEPROM) == (this.flags.EEPROM | 0)) {
//EEPROM:
this.possible = this.flags.EEPROM | 0;
this.checkDetermination();
this.saveCore.writeEEPROM16(address | 0, data | 0);
}
}
GameBoyAdvanceSaveDeterminer.prototype.readEEPROM8 = function (address) {
address = address | 0;
var data = 0;
if ((this.possible & this.flags.EEPROM) == (this.flags.EEPROM | 0)) {
//EEPROM:
this.possible = this.flags.EEPROM | 0;
this.checkDetermination();
return this.saveCore.readEEPROM8(address | 0) | 0;
}
}
GameBoyAdvanceSaveDeterminer.prototype.readEEPROM16 = function (address) {
address = address | 0;
var data = 0;
if ((this.possible & this.flags.EEPROM) == (this.flags.EEPROM | 0)) {
//EEPROM:
this.possible = this.flags.EEPROM | 0;
this.checkDetermination();
return this.saveCore.readEEPROM16(address | 0) | 0;
}
}
GameBoyAdvanceSaveDeterminer.prototype.readEEPROM32 = function (address) {
address = address | 0;
var data = 0;
if ((this.possible & this.flags.EEPROM) == (this.flags.EEPROM | 0)) {
//EEPROM:
this.possible = this.flags.EEPROM | 0;
this.checkDetermination();
return this.saveCore.readEEPROM32(address | 0) | 0;
}
}
GameBoyAdvanceSaveDeterminer.prototype.writeSRAM = function (address, data) {
address = address | 0;
data = data | 0;
//Is not EEPROM:
this.possible &= ~this.flags.EEPROM;
if ((this.possible & this.flags.FLASH) == (this.flags.FLASH | 0)) {
if ((this.possible & this.flags.SRAM) == (this.flags.SRAM | 0)) {
if ((address | 0) == 0x5555) {
if ((data | 0) == 0xAA) {
//FLASH
this.possible = this.flags.FLASH | 0;
}
else {
//SRAM
this.possible = this.flags.SRAM | 0;
}
}
}
else {
if ((address | 0) == 0x5555) {
if ((data | 0) == 0xAA) {
//FLASH
this.possible = this.flags.FLASH | 0;
}
else {
//Is not Flash:
this.possible &= ~this.flags.FLASH;
}
}
}
}
else if ((this.possible & this.flags.SRAM) == (this.flags.SRAM | 0)) {
//SRAM
this.possible = this.flags.SRAM | 0;
}
this.checkDetermination();
this.saveCore.writeSRAMIfDefined(address | 0, data | 0);
}

View file

@ -0,0 +1,392 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceAffineBGRenderer(gfx, BGLayer) {
BGLayer = BGLayer | 0;
this.gfx = gfx;
this.BGLayer = BGLayer | 0;
this.offset = ((this.BGLayer << 8) + 0x100) | 0;
}
if (__VIEWS_SUPPORTED__) {
GameBoyAdvanceAffineBGRenderer.prototype.initialize = function () {
this.bg2MatrixRenderer = this.gfx.bg2MatrixRenderer;
this.bg3MatrixRenderer = this.gfx.bg3MatrixRenderer;
this.bg2FrameBufferRenderer = this.gfx.bg2FrameBufferRenderer;
this.scratchBuffer = getInt32ViewCustom(this.gfx.buffer, this.offset | 0, ((this.offset | 0) + 240) | 0);
this.BGdx = 0x100;
this.BGdmx = 0;
this.BGdy = 0;
this.BGdmy = 0x100;
this.BGReferenceX = 0;
this.BGReferenceY = 0;
this.pb = 0;
this.pd = 0;
this.doMosaic = 0;
this.priorityPreprocess(0);
this.offsetReferenceCounters();
}
if (typeof Math.imul == "function") {
//Math.imul found, insert the optimized path in:
GameBoyAdvanceAffineBGRenderer.prototype.renderScanLine2M = function (line) {
line = line | 0;
var x = this.pb | 0;
var y = this.pd | 0;
if ((this.doMosaic | 0) != 0) {
//Correct line number for mosaic:
var mosaicY = this.gfx.mosaicRenderer.getMosaicYOffset(line | 0) | 0;
x = ((x | 0) - Math.imul(this.BGdmx | 0, mosaicY | 0)) | 0;
y = ((y | 0) - Math.imul(this.BGdmy | 0, mosaicY | 0)) | 0;
}
for (var position = 0; (position | 0) < 240; position = ((position | 0) + 1) | 0, x = ((x | 0) + (this.BGdx | 0)) | 0, y = ((y | 0) + (this.BGdy | 0)) | 0) {
//Fetch pixel:
this.scratchBuffer[position | 0] = this.priorityFlag | this.bg2MatrixRenderer.fetchPixel(x >> 8, y >> 8);
}
if ((this.doMosaic | 0) != 0) {
//Pixelize the line horizontally:
this.gfx.mosaicRenderer.renderMosaicHorizontal(this.offset | 0);
}
}
GameBoyAdvanceAffineBGRenderer.prototype.renderScanLine3M = function (line) {
line = line | 0;
var x = this.pb | 0;
var y = this.pd | 0;
if ((this.doMosaic | 0) != 0) {
//Correct line number for mosaic:
var mosaicY = this.gfx.mosaicRenderer.getMosaicYOffset(line | 0) | 0;
x = ((x | 0) - Math.imul(this.BGdmx | 0, mosaicY | 0)) | 0;
y = ((y | 0) - Math.imul(this.BGdmy | 0, mosaicY | 0)) | 0;
}
for (var position = 0; (position | 0) < 240; position = ((position | 0) + 1) | 0, x = ((x | 0) + (this.BGdx | 0)) | 0, y = ((y | 0) + (this.BGdy | 0)) | 0) {
//Fetch pixel:
this.scratchBuffer[position | 0] = this.priorityFlag | this.bg3MatrixRenderer.fetchPixel(x >> 8, y >> 8);
}
if ((this.doMosaic | 0) != 0) {
//Pixelize the line horizontally:
this.gfx.mosaicRenderer.renderMosaicHorizontal(this.offset | 0);
}
}
GameBoyAdvanceAffineBGRenderer.prototype.renderScanLine2F = function (line) {
line = line | 0;
var x = this.pb | 0;
var y = this.pd | 0;
if ((this.doMosaic | 0) != 0) {
//Correct line number for mosaic:
var mosaicY = this.gfx.mosaicRenderer.getMosaicYOffset(line | 0) | 0;
x = ((x | 0) - Math.imul(this.BGdmx | 0, mosaicY | 0)) | 0;
y = ((y | 0) - Math.imul(this.BGdmy | 0, mosaicY | 0)) | 0;
}
for (var position = 0; (position | 0) < 240; position = ((position | 0) + 1) | 0, x = ((x | 0) + (this.BGdx | 0)) | 0, y = ((y | 0) + (this.BGdy | 0)) | 0) {
//Fetch pixel:
this.scratchBuffer[position | 0] = this.priorityFlag | this.bg2FrameBufferRenderer.fetchPixel(x >> 8, y >> 8);
}
if ((this.doMosaic | 0) != 0) {
//Pixelize the line horizontally:
this.gfx.mosaicRenderer.renderMosaicHorizontal(this.offset | 0);
}
}
GameBoyAdvanceAffineBGRenderer.prototype.offsetReferenceCounters = function () {
var end = this.gfx.lastUnrenderedLine | 0;
this.pb = Math.imul(((this.pb | 0) + (this.BGdmx | 0)) | 0, end | 0) | 0;
this.pd = Math.imul(((this.pd | 0) + (this.BGdmy | 0)) | 0, end | 0) | 0;
}
}
else {
//Math.imul not found, use the compatibility method:
GameBoyAdvanceAffineBGRenderer.prototype.renderScanLine2M = function (line) {
var x = this.pb;
var y = this.pd;
if (this.doMosaic != 0) {
//Correct line number for mosaic:
var mosaicY = this.gfx.mosaicRenderer.getMosaicYOffset(line);
x -= this.BGdmx * mosaicY;
y -= this.BGdmy * mosaicY;
}
for (var position = 0; position < 240; ++position, x += this.BGdx, y += this.BGdy) {
//Fetch pixel:
this.scratchBuffer[position] = this.priorityFlag | this.bg2MatrixRenderer.fetchPixel(x >> 8, y >> 8);
}
if (this.doMosaic != 0) {
//Pixelize the line horizontally:
this.gfx.mosaicRenderer.renderMosaicHorizontal(this.offset);
}
}
GameBoyAdvanceAffineBGRenderer.prototype.renderScanLine3M = function (line) {
var x = this.pb;
var y = this.pd;
if (this.doMosaic != 0) {
//Correct line number for mosaic:
var mosaicY = this.gfx.mosaicRenderer.getMosaicYOffset(line);
x -= this.BGdmx * mosaicY;
y -= this.BGdmy * mosaicY;
}
for (var position = 0; position < 240; ++position, x += this.BGdx, y += this.BGdy) {
//Fetch pixel:
this.scratchBuffer[position] = this.priorityFlag | this.bg3MatrixRenderer.fetchPixel(x >> 8, y >> 8);
}
if (this.doMosaic != 0) {
//Pixelize the line horizontally:
this.gfx.mosaicRenderer.renderMosaicHorizontal(this.offset);
}
}
GameBoyAdvanceAffineBGRenderer.prototype.renderScanLine2F = function (line) {
var x = this.pb;
var y = this.pd;
if (this.doMosaic != 0) {
//Correct line number for mosaic:
var mosaicY = this.gfx.mosaicRenderer.getMosaicYOffset(line);
x -= this.BGdmx * mosaicY;
y -= this.BGdmy * mosaicY;
}
for (var position = 0; position < 240; ++position, x += this.BGdx, y += this.BGdy) {
//Fetch pixel:
this.scratchBuffer[position] = this.priorityFlag | this.bg2FrameBufferRenderer.fetchPixel(x >> 8, y >> 8);
}
if (this.doMosaic != 0) {
//Pixelize the line horizontally:
this.gfx.mosaicRenderer.renderMosaicHorizontal(this.offset);
}
}
GameBoyAdvanceAffineBGRenderer.prototype.offsetReferenceCounters = function () {
var end = this.gfx.lastUnrenderedLine | 0;
this.pb = (((this.pb | 0) + (this.BGdmx | 0)) * (end | 0)) | 0;
this.pd = (((this.pd | 0) + (this.BGdmy | 0)) * (end | 0)) | 0;
}
}
}
else {
GameBoyAdvanceAffineBGRenderer.prototype.initialize = function () {
this.bg2MatrixRenderer = this.gfx.bg2MatrixRenderer;
this.bg3MatrixRenderer = this.gfx.bg3MatrixRenderer;
this.bg2FrameBufferRenderer = this.gfx.bg2FrameBufferRenderer;
this.scratchBuffer = this.gfx.buffer;
this.BGdx = 0x100;
this.BGdmx = 0;
this.BGdy = 0;
this.BGdmy = 0x100;
this.BGReferenceX = 0;
this.BGReferenceY = 0;
this.pb = 0;
this.pd = 0;
this.doMosaic = 0;
this.priorityPreprocess(0);
this.offsetReferenceCounters();
}
GameBoyAdvanceAffineBGRenderer.prototype.renderScanLine2M = function (line) {
var x = this.pb;
var y = this.pd;
if (this.doMosaic != 0) {
//Correct line number for mosaic:
var mosaicY = this.gfx.mosaicRenderer.getMosaicYOffset(line);
x -= this.BGdmx * mosaicY;
y -= this.BGdmy * mosaicY;
}
for (var position = 0; position < 240; ++position, x += this.BGdx, y += this.BGdy) {
//Fetch pixel:
this.scratchBuffer[this.offset + position] = this.priorityFlag | this.bg2MatrixRenderer.fetchPixel(x >> 8, y >> 8);
}
if (this.doMosaic != 0) {
//Pixelize the line horizontally:
this.gfx.mosaicRenderer.renderMosaicHorizontal(this.offset);
}
}
GameBoyAdvanceAffineBGRenderer.prototype.renderScanLine3M = function (line) {
var x = this.pb;
var y = this.pd;
if (this.doMosaic != 0) {
//Correct line number for mosaic:
var mosaicY = this.gfx.mosaicRenderer.getMosaicYOffset(line);
x -= this.BGdmx * mosaicY;
y -= this.BGdmy * mosaicY;
}
for (var position = 0; position < 240; ++position, x += this.BGdx, y += this.BGdy) {
//Fetch pixel:
this.scratchBuffer[this.offset + position] = this.priorityFlag | this.bg3MatrixRenderer.fetchPixel(x >> 8, y >> 8);
}
if (this.doMosaic != 0) {
//Pixelize the line horizontally:
this.gfx.mosaicRenderer.renderMosaicHorizontal(this.offset);
}
}
GameBoyAdvanceAffineBGRenderer.prototype.renderScanLine2F = function (line) {
var x = this.pb;
var y = this.pd;
if (this.doMosaic != 0) {
//Correct line number for mosaic:
var mosaicY = this.gfx.mosaicRenderer.getMosaicYOffset(line);
x -= this.BGdmx * mosaicY;
y -= this.BGdmy * mosaicY;
}
for (var position = 0; position < 240; ++position, x += this.BGdx, y += this.BGdy) {
//Fetch pixel:
this.scratchBuffer[this.offset + position] = this.priorityFlag | this.bg2FrameBufferRenderer.fetchPixel(x >> 8, y >> 8);
}
if (this.doMosaic != 0) {
//Pixelize the line horizontally:
this.gfx.mosaicRenderer.renderMosaicHorizontal(this.offset);
}
}
GameBoyAdvanceAffineBGRenderer.prototype.offsetReferenceCounters = function () {
var end = this.gfx.lastUnrenderedLine | 0;
this.pb = (((this.pb | 0) + (this.BGdmx | 0)) * (end | 0)) | 0;
this.pd = (((this.pd | 0) + (this.BGdmy | 0)) * (end | 0)) | 0;
}
}
GameBoyAdvanceAffineBGRenderer.prototype.incrementReferenceCounters = function () {
this.pb = ((this.pb | 0) + (this.BGdmx | 0)) | 0;
this.pd = ((this.pd | 0) + (this.BGdmy | 0)) | 0;
}
GameBoyAdvanceAffineBGRenderer.prototype.resetReferenceCounters = function () {
this.pb = this.BGReferenceX | 0;
this.pd = this.BGReferenceY | 0;
}
GameBoyAdvanceAffineBGRenderer.prototype.setMosaicEnable = function (doMosaic) {
doMosaic = doMosaic | 0;
this.doMosaic = doMosaic | 0;
}
GameBoyAdvanceAffineBGRenderer.prototype.priorityPreprocess = function (BGPriority) {
BGPriority = BGPriority | 0;
this.priorityFlag = (BGPriority << 23) | (1 << (this.BGLayer | 0x10));
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGPA8_0 = function (data) {
data = data | 0;
this.BGdx = (this.BGdx & 0xFFFFFF00) | data;
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGPA8_1 = function (data) {
data = data | 0;
data = (data << 24) >> 16;
this.BGdx = data | (this.BGdx & 0xFF);
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGPA16 = function (data) {
data = data | 0;
this.BGdx = (data << 16) >> 16;
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGPB8_0 = function (data) {
data = data | 0;
this.BGdmx = (this.BGdmx & 0xFFFFFF00) | data;
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGPB8_1 = function (data) {
data = data | 0;
data = (data << 24) >> 16;
this.BGdmx = data | (this.BGdmx & 0xFF);
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGPB16 = function (data) {
data = data | 0;
this.BGdmx = (data << 16) >> 16;
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGPAB32 = function (data) {
data = data | 0;
this.BGdx = (data << 16) >> 16;
this.BGdmx = data >> 16;
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGPC8_0 = function (data) {
data = data | 0;
this.BGdy = (this.BGdy & 0xFFFFFF00) | data;
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGPC8_1 = function (data) {
data = data | 0;
data = (data << 24) >> 16;
this.BGdy = data | (this.BGdy & 0xFF);
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGPC16 = function (data) {
data = data | 0;
this.BGdy = (data << 16) >> 16;
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGPD8_0 = function (data) {
data = data | 0;
this.BGdmy = (this.BGdmy & 0xFFFFFF00) | data;
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGPD8_1 = function (data) {
data = data | 0;
data = (data << 24) >> 16;
this.BGdmy = data | (this.BGdmy & 0xFF);
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGPD16 = function (data) {
data = data | 0;
this.BGdmy = (data << 16) >> 16;
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGPCD32 = function (data) {
data = data | 0;
this.BGdy = (data << 16) >> 16;
this.BGdmy = data >> 16;
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGX8_0 = function (data) {
data = data | 0;
this.BGReferenceX = (this.BGReferenceX & 0xFFFFFF00) | data;
//Writing to the x reference doesn't reset the counters during draw!
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGX8_1 = function (data) {
data = data | 0;
this.BGReferenceX = (data << 8) | (this.BGReferenceX & 0xFFFF00FF);
//Writing to the x reference doesn't reset the counters during draw!
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGX8_2 = function (data) {
data = data | 0;
this.BGReferenceX = (data << 16) | (this.BGReferenceX & 0xFF00FFFF);
//Writing to the x reference doesn't reset the counters during draw!
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGX8_3 = function (data) {
data = data | 0;
data = (data << 28) >> 4;
this.BGReferenceX = data | (this.BGReferenceX & 0xFFFFFF);
//Writing to the x reference doesn't reset the counters during draw!
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGX16_0 = function (data) {
data = data | 0;
this.BGReferenceX = (this.BGReferenceX & 0xFFFF0000) | data;
//Writing to the x reference doesn't reset the counters during draw!
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGX16_1 = function (data) {
data = data | 0;
data = (data << 20) >> 4;
this.BGReferenceX = (this.BGReferenceX & 0xFFFF) | data;
//Writing to the x reference doesn't reset the counters during draw!
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGX32 = function (data) {
data = data | 0;
this.BGReferenceX = (data << 4) >> 4;
//Writing to the x reference doesn't reset the counters during draw!
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGY8_0 = function (data) {
data = data | 0;
this.BGReferenceY = (this.BGReferenceY & 0xFFFFFF00) | data;
this.resetReferenceCounters();
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGY8_1 = function (data) {
data = data | 0;
this.BGReferenceY = (data << 8) | (this.BGReferenceY & 0xFFFF00FF);
this.resetReferenceCounters();
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGY8_2 = function (data) {
data = data | 0;
this.BGReferenceY = (data << 16) | (this.BGReferenceY & 0xFF00FFFF);
this.resetReferenceCounters();
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGY8_3 = function (data) {
data = data | 0;
data = (data << 28) >> 4;
this.BGReferenceY = data | (this.BGReferenceY & 0xFFFFFF);
this.resetReferenceCounters();
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGY16_0 = function (data) {
data = data | 0;
this.BGReferenceY = (this.BGReferenceY & 0xFFFF0000) | data;
this.resetReferenceCounters();
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGY16_1 = function (data) {
data = data | 0;
data = (data << 20) >> 4;
this.BGReferenceY = (this.BGReferenceY & 0xFFFF) | data;
this.resetReferenceCounters();
}
GameBoyAdvanceAffineBGRenderer.prototype.writeBGY32 = function (data) {
data = data | 0;
this.BGReferenceY = (data << 4) >> 4;
this.resetReferenceCounters();
}

View file

@ -0,0 +1,162 @@
"use strict";
/*
Copyright (C) 2012-2016 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceBG2FrameBufferRenderer(gfx) {
this.gfx = gfx;
}
GameBoyAdvanceBG2FrameBufferRenderer.prototype.initialize = function () {
this.palette = this.gfx.palette256;
this.VRAM = this.gfx.VRAM;
this.VRAM16 = this.gfx.VRAM16;
this.fetchPixel = this.fetchMode3Pixel;
this.frameSelect = 0;
}
GameBoyAdvanceBG2FrameBufferRenderer.prototype.selectMode = function (mode) {
mode = mode | 0;
switch (mode | 0) {
case 3:
this.fetchPixel = this.fetchMode3Pixel;
break;
case 4:
this.fetchPixel = this.fetchMode4Pixel;
break;
case 5:
this.fetchPixel = this.fetchMode5Pixel;
}
}
if (__LITTLE_ENDIAN__) {
if (typeof Math.imul == "function") {
//Math.imul found, insert the optimized path in:
GameBoyAdvanceBG2FrameBufferRenderer.prototype.fetchMode3Pixel = function (x, y) {
x = x | 0;
y = y | 0;
//Output pixel:
if ((x >>> 0) < 240 && (y >>> 0) < 160) {
var address = (Math.imul(y | 0, 240) + (x | 0)) | 0;
return this.VRAM16[address & 0xFFFF] & 0x7FFF;
}
//Out of range, output transparency:
return 0x3800000;
}
GameBoyAdvanceBG2FrameBufferRenderer.prototype.fetchMode5Pixel = function (x, y) {
x = x | 0;
y = y | 0;
//Output pixel:
if ((x >>> 0) < 160 && (y >>> 0) < 128) {
var address = ((this.frameSelect | 0) + Math.imul(y | 0, 160) + (x | 0)) | 0;
return this.VRAM16[address & 0xFFFF] & 0x7FFF;
}
//Out of range, output transparency:
return 0x3800000;
}
}
else {
//Math.imul not found, use the compatibility method:
GameBoyAdvanceBG2FrameBufferRenderer.prototype.fetchMode3Pixel = function (x, y) {
x = x | 0;
y = y | 0;
//Output pixel:
if ((x >>> 0) < 240 && (y >>> 0) < 160) {
var address = (((y * 240) | 0) + (x | 0)) | 0;
return this.VRAM16[address & 0xFFFF] & 0x7FFF;
}
//Out of range, output transparency:
return 0x3800000;
}
GameBoyAdvanceBG2FrameBufferRenderer.prototype.fetchMode5Pixel = function (x, y) {
x = x | 0;
y = y | 0;
//Output pixel:
if ((x >>> 0) < 160 && (y >>> 0) < 128) {
var address = ((this.frameSelect | 0) + ((y * 160) | 0) + (x | 0)) | 0;
return this.VRAM16[address & 0xFFFF] & 0x7FFF;
}
//Out of range, output transparency:
return 0x3800000;
}
}
}
else {
if (typeof Math.imul == "function") {
//Math.imul found, insert the optimized path in:
GameBoyAdvanceBG2FrameBufferRenderer.prototype.fetchMode3Pixel = function (x, y) {
x = x | 0;
y = y | 0;
//Output pixel:
if ((x >>> 0) < 240 && (y >>> 0) < 160) {
var address = (Math.imul(y | 0, 240) + (x | 0)) << 1;
return ((this.VRAM[address | 1] << 8) | this.VRAM[address | 0]) & 0x7FFF;
}
//Out of range, output transparency:
return 0x3800000;
}
GameBoyAdvanceBG2FrameBufferRenderer.prototype.fetchMode5Pixel = function (x, y) {
x = x | 0;
y = y | 0;
//Output pixel:
if ((x >>> 0) < 160 && (y >>> 0) < 128) {
var address = ((this.frameSelect | 0) + ((Math.imul(y | 0, 160) + (x | 0)) << 1)) | 0;
return ((this.VRAM[address | 1] << 8) | this.VRAM[address | 0]) & 0x7FFF;
}
//Out of range, output transparency:
return 0x3800000;
}
}
else {
//Math.imul not found, use the compatibility method:
GameBoyAdvanceBG2FrameBufferRenderer.prototype.fetchMode3Pixel = function (x, y) {
//Output pixel:
if ((x >>> 0) < 240 && (y >>> 0) < 160) {
var address = ((y * 240) + x) << 1;
return ((this.VRAM[address | 1] << 8) | this.VRAM[address]) & 0x7FFF;
}
//Out of range, output transparency:
return 0x3800000;
}
GameBoyAdvanceBG2FrameBufferRenderer.prototype.fetchMode5Pixel = function (x, y) {
//Output pixel:
if ((x >>> 0) < 160 && (y >>> 0) < 128) {
var address = this.frameSelect + (((y * 160) + x) << 1);
return ((this.VRAM[address | 1] << 8) | this.VRAM[address]) & 0x7FFF;
}
//Out of range, output transparency:
return 0x3800000;
}
}
}
if (typeof Math.imul == "function") {
//Math.imul found, insert the optimized path in:
GameBoyAdvanceBG2FrameBufferRenderer.prototype.fetchMode4Pixel = function (x, y) {
x = x | 0;
y = y | 0;
//Output pixel:
if ((x >>> 0) < 240 && (y >>> 0) < 160) {
var address = ((this.frameSelect | 0) + (Math.imul(y | 0, 240) | 0) + (x | 0)) | 0;
return this.palette[this.VRAM[address | 0] & 0xFF] | 0;
}
//Out of range, output transparency:
return 0x3800000;
}
}
else {
//Math.imul not found, use the compatibility method:
GameBoyAdvanceBG2FrameBufferRenderer.prototype.fetchMode4Pixel = function (x, y) {
//Output pixel:
if ((x >>> 0) < 240 && (y >>> 0) < 160) {
return this.palette[this.VRAM[this.frameSelect + (y * 240) + x]];
}
//Out of range, output transparency:
return 0x3800000;
}
}
GameBoyAdvanceBG2FrameBufferRenderer.prototype.writeFrameSelect = function (frameSelect) {
frameSelect = frameSelect >> 31;
this.frameSelect = frameSelect & 0xA000;
}

View file

@ -0,0 +1,99 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceBGMatrixRenderer(gfx) {
this.gfx = gfx;
}
GameBoyAdvanceBGMatrixRenderer.prototype.initialize = function () {
this.VRAM = this.gfx.VRAM;
this.palette = this.gfx.palette256;
this.screenSizePreprocess(0);
this.screenBaseBlockPreprocess(0);
this.characterBaseBlockPreprocess(0);
this.displayOverflowProcess(0);
}
if (typeof Math.imul == "function") {
//Math.imul found, insert the optimized path in:
GameBoyAdvanceBGMatrixRenderer.prototype.fetchTile = function (x, y) {
//Compute address for tile VRAM to address:
x = x | 0;
y = y | 0;
var tileNumber = ((x | 0) + Math.imul(y | 0, this.mapSize | 0)) | 0;
return this.VRAM[((tileNumber | 0) + (this.BGScreenBaseBlock | 0)) & 0xFFFF] | 0;
}
}
else {
//Math.imul not found, use the compatibility method:
GameBoyAdvanceBGMatrixRenderer.prototype.fetchTile = function (x, y) {
//Compute address for tile VRAM to address:
var tileNumber = x + (y * this.mapSize);
return this.VRAM[(tileNumber + this.BGScreenBaseBlock) & 0xFFFF];
}
}
GameBoyAdvanceBGMatrixRenderer.prototype.computeScreenAddress = function (x, y) {
//Compute address for character VRAM to address:
x = x | 0;
y = y | 0;
var address = this.fetchTile(x >> 3, y >> 3) << 6;
address = ((address | 0) + (this.BGCharacterBaseBlock | 0)) | 0;
address = ((address | 0) + ((y & 0x7) << 3)) | 0;
address = ((address | 0) + (x & 0x7)) | 0;
return address | 0;
}
GameBoyAdvanceBGMatrixRenderer.prototype.fetchPixelOverflow = function (x, y) {
//Fetch the pixel:
x = x | 0;
y = y | 0;
//Output pixel:
var address = this.computeScreenAddress(x & this.mapSizeComparer, y & this.mapSizeComparer) | 0;
return this.palette[this.VRAM[address & 0xFFFF] & 0xFF] | 0;
}
GameBoyAdvanceBGMatrixRenderer.prototype.fetchPixelNoOverflow = function (x, y) {
//Fetch the pixel:
x = x | 0;
y = y | 0;
//Output pixel:
if ((x | 0) != (x & this.mapSizeComparer) || (y | 0) != (y & this.mapSizeComparer)) {
//Overflow Handling:
//Out of bounds with no overflow allowed:
return 0x3800000;
}
var address = this.computeScreenAddress(x | 0, y | 0) | 0;
return this.palette[this.VRAM[address & 0xFFFF] & 0xFF] | 0;
}
GameBoyAdvanceBGMatrixRenderer.prototype.screenBaseBlockPreprocess = function (BGScreenBaseBlock) {
BGScreenBaseBlock = BGScreenBaseBlock | 0;
this.BGScreenBaseBlock = BGScreenBaseBlock << 11;
}
GameBoyAdvanceBGMatrixRenderer.prototype.characterBaseBlockPreprocess = function (BGCharacterBaseBlock) {
BGCharacterBaseBlock = BGCharacterBaseBlock | 0;
this.BGCharacterBaseBlock = BGCharacterBaseBlock << 14;
}
GameBoyAdvanceBGMatrixRenderer.prototype.screenSizePreprocess = function (BGScreenSize) {
BGScreenSize = BGScreenSize | 0;
this.mapSize = 0x10 << (BGScreenSize | 0);
this.mapSizeComparer = ((this.mapSize << 3) - 1) | 0;
}
GameBoyAdvanceBGMatrixRenderer.prototype.displayOverflowPreprocess = function (doOverflow) {
doOverflow = doOverflow | 0;
if ((doOverflow | 0) != (this.BGDisplayOverflow | 0)) {
this.displayOverflowProcess(doOverflow | 0);
}
}
GameBoyAdvanceBGMatrixRenderer.prototype.displayOverflowProcess = function (doOverflow) {
doOverflow = doOverflow | 0;
this.BGDisplayOverflow = doOverflow | 0;
if ((doOverflow | 0) != 0) {
this.fetchPixel = this.fetchPixelOverflow;
}
else {
this.fetchPixel = this.fetchPixelNoOverflow;
}
}

View file

@ -0,0 +1,577 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceBGTEXTRenderer(gfx, BGLayer) {
BGLayer = BGLayer | 0;
this.gfx = gfx;
this.BGLayer = BGLayer | 0;
}
if (__VIEWS_SUPPORTED__) {
GameBoyAdvanceBGTEXTRenderer.prototype.initialize = function () {
this.VRAM = this.gfx.VRAM;
this.VRAM16 = this.gfx.VRAM16;
this.VRAM32 = this.gfx.VRAM32;
this.palette16 = this.gfx.palette16;
this.palette256 = this.gfx.palette256;
this.offset = ((this.BGLayer << 8) + 0x100) | 0;
this.scratchBuffer = getInt32ViewCustom(this.gfx.buffer, this.offset | 0, ((this.offset | 0) + 248) | 0);
this.tileFetched = getInt32ViewCustom(this.gfx.buffer, ((this.offset | 0) + 0xF8) | 0, ((this.offset | 0) + 0x100) | 0);
this.BGXCoord = 0;
this.BGYCoord = 0;
this.do256 = 0;
this.doMosaic = 0;
this.screenSizePreprocess(0);
this.priorityPreprocess(0);
this.screenBaseBlockPreprocess(0);
this.characterBaseBlockPreprocess(0);
}
GameBoyAdvanceBGTEXTRenderer.prototype.renderWholeTiles8BIT = function (xTileStart, yTileStart, yTileOffset) {
xTileStart = xTileStart | 0;
yTileStart = yTileStart | 0;
yTileOffset = yTileOffset | 0;
//Process full 8 pixels at a time:
for (var position = (8 - (this.BGXCoord & 0x7)) | 0; (position | 0) < 240; position = ((position | 0) + 8) | 0) {
//Fetch tile attributes:
//Get 8 pixels of data:
this.process8BitVRAM(this.fetchTile(yTileStart | 0, xTileStart | 0) | 0, yTileOffset | 0);
//Copy the buffered tile to line:
this.scratchBuffer[position | 0] = this.tileFetched[0] | 0;
this.scratchBuffer[((position | 0) + 1) | 0] = this.tileFetched[1] | 0;
this.scratchBuffer[((position | 0) + 2) | 0] = this.tileFetched[2] | 0;
this.scratchBuffer[((position | 0) + 3) | 0] = this.tileFetched[3] | 0;
this.scratchBuffer[((position | 0) + 4) | 0] = this.tileFetched[4] | 0;
this.scratchBuffer[((position | 0) + 5) | 0] = this.tileFetched[5] | 0;
this.scratchBuffer[((position | 0) + 6) | 0] = this.tileFetched[6] | 0;
this.scratchBuffer[((position | 0) + 7) | 0] = this.tileFetched[7] | 0;
//Increment a tile counter:
xTileStart = ((xTileStart | 0) + 1) | 0;
}
}
GameBoyAdvanceBGTEXTRenderer.prototype.renderWholeTiles4BIT = function (xTileStart, yTileStart, yTileOffset) {
xTileStart = xTileStart | 0;
yTileStart = yTileStart | 0;
yTileOffset = yTileOffset | 0;
//Process full 8 pixels at a time:
for (var position = (8 - (this.BGXCoord & 0x7)) | 0; (position | 0) < 240; position = ((position | 0) + 8) | 0) {
//Fetch tile attributes:
//Get 8 pixels of data:
this.process4BitVRAM(this.fetchTile(yTileStart | 0, xTileStart | 0) | 0, yTileOffset | 0);
//Copy the buffered tile to line:
this.scratchBuffer[position | 0] = this.tileFetched[0] | 0;
this.scratchBuffer[((position | 0) + 1) | 0] = this.tileFetched[1] | 0;
this.scratchBuffer[((position | 0) + 2) | 0] = this.tileFetched[2] | 0;
this.scratchBuffer[((position | 0) + 3) | 0] = this.tileFetched[3] | 0;
this.scratchBuffer[((position | 0) + 4) | 0] = this.tileFetched[4] | 0;
this.scratchBuffer[((position | 0) + 5) | 0] = this.tileFetched[5] | 0;
this.scratchBuffer[((position | 0) + 6) | 0] = this.tileFetched[6] | 0;
this.scratchBuffer[((position | 0) + 7) | 0] = this.tileFetched[7] | 0;
//Increment a tile counter:
xTileStart = ((xTileStart | 0) + 1) | 0;
}
}
GameBoyAdvanceBGTEXTRenderer.prototype.fetchVRAMStart = function () {
//Handle the the first tile of the scan-line specially:
var pixelPipelinePosition = this.BGXCoord & 0x7;
switch (pixelPipelinePosition | 0) {
case 0:
this.scratchBuffer[0] = this.tileFetched[0] | 0;
case 1:
this.scratchBuffer[(1 - (pixelPipelinePosition | 0)) | 0] = this.tileFetched[1] | 0;
case 2:
this.scratchBuffer[(2 - (pixelPipelinePosition | 0)) | 0] = this.tileFetched[2] | 0;
case 3:
this.scratchBuffer[(3 - (pixelPipelinePosition | 0)) | 0] = this.tileFetched[3] | 0;
case 4:
this.scratchBuffer[(4 - (pixelPipelinePosition | 0)) | 0] = this.tileFetched[4] | 0;
case 5:
this.scratchBuffer[(5 - (pixelPipelinePosition | 0)) | 0] = this.tileFetched[5] | 0;
case 6:
this.scratchBuffer[(6 - (pixelPipelinePosition | 0)) | 0] = this.tileFetched[6] | 0;
default:
this.scratchBuffer[(7 - (pixelPipelinePosition | 0)) | 0] = this.tileFetched[7] | 0;
}
}
}
else {
GameBoyAdvanceBGTEXTRenderer.prototype.initialize = function () {
this.VRAM = this.gfx.VRAM;
this.VRAM16 = this.gfx.VRAM16;
this.VRAM32 = this.gfx.VRAM32;
this.palette16 = this.gfx.palette16;
this.palette256 = this.gfx.palette256;
this.offset = (this.BGLayer << 8) + 0x100;
this.offsetEnd = this.offset + 240;
this.scratchBuffer = this.gfx.buffer;
this.tileFetched = getInt32Array(8);
this.BGXCoord = 0;
this.BGYCoord = 0;
this.do256 = 0;
this.doMosaic = 0;
this.screenSizePreprocess(0);
this.priorityPreprocess(0);
this.screenBaseBlockPreprocess(0);
this.characterBaseBlockPreprocess(0);
}
GameBoyAdvanceBGTEXTRenderer.prototype.renderWholeTiles8BIT = function (xTileStart, yTileStart, yTileOffset) {
//Process full 8 pixels at a time:
for (var position = 8 - (this.BGXCoord & 0x7) + this.offset; position < this.offsetEnd;) {
//Fetch tile attributes:
//Get 8 pixels of data:
this.process8BitVRAM(this.fetchTile(yTileStart, xTileStart++), yTileOffset);
//Copy the buffered tile to line:
this.scratchBuffer[position++] = this.tileFetched[0];
this.scratchBuffer[position++] = this.tileFetched[1];
this.scratchBuffer[position++] = this.tileFetched[2];
this.scratchBuffer[position++] = this.tileFetched[3];
this.scratchBuffer[position++] = this.tileFetched[4];
this.scratchBuffer[position++] = this.tileFetched[5];
this.scratchBuffer[position++] = this.tileFetched[6];
this.scratchBuffer[position++] = this.tileFetched[7];
}
}
GameBoyAdvanceBGTEXTRenderer.prototype.renderWholeTiles4BIT = function (xTileStart, yTileStart, yTileOffset) {
//Process full 8 pixels at a time:
for (var position = 8 - (this.BGXCoord & 0x7) + this.offset; position < this.offsetEnd;) {
//Fetch tile attributes:
//Get 8 pixels of data:
this.process4BitVRAM(this.fetchTile(yTileStart, xTileStart++), yTileOffset);
//Copy the buffered tile to line:
this.scratchBuffer[position++] = this.tileFetched[0];
this.scratchBuffer[position++] = this.tileFetched[1];
this.scratchBuffer[position++] = this.tileFetched[2];
this.scratchBuffer[position++] = this.tileFetched[3];
this.scratchBuffer[position++] = this.tileFetched[4];
this.scratchBuffer[position++] = this.tileFetched[5];
this.scratchBuffer[position++] = this.tileFetched[6];
this.scratchBuffer[position++] = this.tileFetched[7];
}
}
GameBoyAdvanceBGTEXTRenderer.prototype.fetchVRAMStart = function () {
//Handle the the first tile of the scan-line specially:
var pixelPipelinePosition = this.BGXCoord & 0x7;
var offset = pixelPipelinePosition - this.offset;
switch (pixelPipelinePosition | 0) {
case 0:
this.scratchBuffer[offset] = this.tileFetched[0];
case 1:
this.scratchBuffer[1 - offset] = this.tileFetched[1];
case 2:
this.scratchBuffer[2 - offset] = this.tileFetched[2];
case 3:
this.scratchBuffer[3 - offset] = this.tileFetched[3];
case 4:
this.scratchBuffer[4 - offset] = this.tileFetched[4];
case 5:
this.scratchBuffer[5 - offset] = this.tileFetched[5];
case 6:
this.scratchBuffer[6 - offset] = this.tileFetched[6];
default:
this.scratchBuffer[7 - offset] = this.tileFetched[7];
}
}
}
GameBoyAdvanceBGTEXTRenderer.prototype.renderScanLine = function (line) {
line = line | 0;
if ((this.doMosaic | 0) != 0) {
//Correct line number for mosaic:
line = ((line | 0) - (this.gfx.mosaicRenderer.getMosaicYOffset(line | 0) | 0)) | 0;
}
var yTileOffset = ((line | 0) + (this.BGYCoord | 0)) & 0x7;
var yTileStart = ((line | 0) + (this.BGYCoord | 0)) >> 3;
var xTileStart = this.BGXCoord >> 3;
//Render the tiles:
if ((this.do256 | 0) != 0) {
//8-bit palette mode:
this.render8BITLine(yTileStart | 0, xTileStart | 0, yTileOffset | 0);
}
else {
//4-bit palette mode:
this.render4BITLine(yTileStart | 0, xTileStart | 0, yTileOffset | 0);
}
if ((this.doMosaic | 0) != 0) {
//Pixelize the line horizontally:
this.gfx.mosaicRenderer.renderMosaicHorizontal(this.offset | 0);
}
}
GameBoyAdvanceBGTEXTRenderer.prototype.render8BITLine = function (yTileStart, xTileStart, yTileOffset) {
yTileStart = yTileStart | 0;
xTileStart = xTileStart | 0;
yTileOffset = yTileOffset | 0;
//Fetch tile attributes:
var chrData = this.fetchTile(yTileStart | 0, xTileStart | 0) | 0;
xTileStart = ((xTileStart | 0) + 1) | 0;
//Get 8 pixels of data:
this.process8BitVRAM(chrData | 0, yTileOffset | 0);
//Copy the buffered tile to line:
this.fetchVRAMStart();
//Render the rest of the tiles fast:
this.renderWholeTiles8BIT(xTileStart | 0, yTileStart | 0, yTileOffset | 0);
}
GameBoyAdvanceBGTEXTRenderer.prototype.render4BITLine = function (yTileStart, xTileStart, yTileOffset) {
//Fetch tile attributes:
var chrData = this.fetchTile(yTileStart | 0, xTileStart | 0) | 0;
xTileStart = ((xTileStart | 0) + 1) | 0;
//Get 8 pixels of data:
this.process4BitVRAM(chrData | 0, yTileOffset | 0);
//Copy the buffered tile to line:
this.fetchVRAMStart();
//Render the rest of the tiles fast:
this.renderWholeTiles4BIT(xTileStart | 0, yTileStart | 0, yTileOffset | 0);
}
if (__LITTLE_ENDIAN__) {
GameBoyAdvanceBGTEXTRenderer.prototype.fetchTile = function (yTileStart, xTileStart) {
yTileStart = yTileStart | 0;
xTileStart = xTileStart | 0;
//Find the tile code to locate the tile block:
var address = ((this.computeTileNumber(yTileStart | 0, xTileStart | 0) | 0) + (this.BGScreenBaseBlock | 0)) | 0;
return this.VRAM16[address & 0x7FFF] | 0;
}
}
else {
GameBoyAdvanceBGTEXTRenderer.prototype.fetchTile = function (yTileStart, xTileStart) {
//Find the tile code to locate the tile block:
var address = ((this.computeTileNumber(yTileStart, xTileStart) + this.BGScreenBaseBlock) << 1) & 0xFFFF;
return (this.VRAM[address | 1] << 8) | this.VRAM[address];
}
}
GameBoyAdvanceBGTEXTRenderer.prototype.computeTileNumber = function (yTile, xTile) {
//Return the true tile number:
yTile = yTile | 0;
xTile = xTile | 0;
var tileNumber = xTile & 0x1F;
switch (this.tileMode | 0) {
//1x1
case 0:
tileNumber = tileNumber | ((yTile & 0x1F) << 5);
break;
//2x1
case 1:
tileNumber = tileNumber | (((xTile & 0x20) | (yTile & 0x1F)) << 5);
break;
//1x2
case 2:
tileNumber = tileNumber | ((yTile & 0x3F) << 5);
break;
//2x2
default:
tileNumber = tileNumber | (((xTile & 0x20) | (yTile & 0x1F)) << 5) | ((yTile & 0x20) << 6);
}
return tileNumber | 0;
}
GameBoyAdvanceBGTEXTRenderer.prototype.process4BitVRAM = function (chrData, yOffset) {
//16 color tile mode:
chrData = chrData | 0;
yOffset = yOffset | 0;
//Parse flip attributes, grab palette, and then output pixel:
var address = (chrData & 0x3FF) << 3;
address = ((address | 0) + (this.BGCharacterBaseBlock | 0)) | 0;
if ((chrData & 0x800) == 0) {
//No vertical flip:
address = ((address | 0) + (yOffset | 0)) | 0;
}
else {
//Vertical flip:
address = ((address | 0) + 7) | 0;
address = ((address | 0) - (yOffset | 0)) | 0;
}
//Copy out our pixels:
this.render4BitVRAM(chrData >> 8, address | 0);
}
if (__LITTLE_ENDIAN__) {
GameBoyAdvanceBGTEXTRenderer.prototype.render4BitVRAM = function (chrData, address) {
chrData = chrData | 0;
address = address | 0;
//Unrolled data tile line fetch:
if ((address | 0) < 0x4000) {
//Tile address valid:
var paletteOffset = chrData & 0xF0;
var data = this.VRAM32[address | 0] | 0;
if ((chrData & 0x4) == 0) {
//Normal Horizontal:
this.tileFetched[0] = this.palette16[paletteOffset | (data & 0xF)] | this.priorityFlag;
this.tileFetched[1] = this.palette16[paletteOffset | ((data >> 4) & 0xF)] | this.priorityFlag;
this.tileFetched[2] = this.palette16[paletteOffset | ((data >> 8) & 0xF)] | this.priorityFlag;
this.tileFetched[3] = this.palette16[paletteOffset | ((data >> 12) & 0xF)] | this.priorityFlag;
this.tileFetched[4] = this.palette16[paletteOffset | ((data >> 16) & 0xF)] | this.priorityFlag;
this.tileFetched[5] = this.palette16[paletteOffset | ((data >> 20) & 0xF)] | this.priorityFlag;
this.tileFetched[6] = this.palette16[paletteOffset | ((data >> 24) & 0xF)] | this.priorityFlag;
this.tileFetched[7] = this.palette16[paletteOffset | (data >>> 28)] | this.priorityFlag;
}
else {
//Flipped Horizontally:
this.tileFetched[0] = this.palette16[paletteOffset | (data >>> 28)] | this.priorityFlag;
this.tileFetched[1] = this.palette16[paletteOffset | ((data >> 24) & 0xF)] | this.priorityFlag;
this.tileFetched[2] = this.palette16[paletteOffset | ((data >> 20) & 0xF)] | this.priorityFlag;
this.tileFetched[3] = this.palette16[paletteOffset | ((data >> 16) & 0xF)] | this.priorityFlag;
this.tileFetched[4] = this.palette16[paletteOffset | ((data >> 12) & 0xF)] | this.priorityFlag;
this.tileFetched[5] = this.palette16[paletteOffset | ((data >> 8) & 0xF)] | this.priorityFlag;
this.tileFetched[6] = this.palette16[paletteOffset | ((data >> 4) & 0xF)] | this.priorityFlag;
this.tileFetched[7] = this.palette16[paletteOffset | (data & 0xF)] | this.priorityFlag;
}
}
else {
//Tile address invalid:
this.addressInvalidRender();
}
}
}
else {
GameBoyAdvanceBGTEXTRenderer.prototype.render4BitVRAM = function (chrData, address) {
address <<= 2;
//Unrolled data tile line fetch:
if (address < 0x10000) {
//Tile address valid:
var paletteOffset = chrData & 0xF0;
var data = this.VRAM[address];
if ((chrData & 0x4) == 0) {
//Normal Horizontal:
this.tileFetched[0] = this.palette16[paletteOffset | (data & 0xF)] | this.priorityFlag;
this.tileFetched[1] = this.palette16[paletteOffset | (data >> 4)] | this.priorityFlag;
data = this.VRAM[address | 1];
this.tileFetched[2] = this.palette16[paletteOffset | (data & 0xF)] | this.priorityFlag;
this.tileFetched[3] = this.palette16[paletteOffset | (data >> 4)] | this.priorityFlag;
data = this.VRAM[address | 2];
this.tileFetched[4] = this.palette16[paletteOffset | (data & 0xF)] | this.priorityFlag;
this.tileFetched[5] = this.palette16[paletteOffset | (data >> 4)] | this.priorityFlag;
data = this.VRAM[address | 3];
this.tileFetched[6] = this.palette16[paletteOffset | (data & 0xF)] | this.priorityFlag;
this.tileFetched[7] = this.palette16[paletteOffset | (data >> 4)] | this.priorityFlag;
}
else {
//Flipped Horizontally:
this.tileFetched[7] = this.palette16[paletteOffset | (data & 0xF)] | this.priorityFlag;
this.tileFetched[6] = this.palette16[paletteOffset | (data >> 4)] | this.priorityFlag;
data = this.VRAM[address | 1];
this.tileFetched[5] = this.palette16[paletteOffset | (data & 0xF)] | this.priorityFlag;
this.tileFetched[4] = this.palette16[paletteOffset | (data >> 4)] | this.priorityFlag;
data = this.VRAM[address | 2];
this.tileFetched[3] = this.palette16[paletteOffset | (data & 0xF)] | this.priorityFlag;
this.tileFetched[2] = this.palette16[paletteOffset | (data >> 4)] | this.priorityFlag;
data = this.VRAM[address | 3];
this.tileFetched[1] = this.palette16[paletteOffset | (data & 0xF)] | this.priorityFlag;
this.tileFetched[0] = this.palette16[paletteOffset | (data >> 4)] | this.priorityFlag;
}
}
else {
//Tile address invalid:
this.addressInvalidRender();
}
}
}
/*
If there was 64 bit typed array support,
then process8BitVRAM, render8BitVRAMNormal,
and render8BitVRAMFlipped could be optimized further.
Namely make one fetch for tile data instead of two,
and cancel a y-offset shift.
*/
GameBoyAdvanceBGTEXTRenderer.prototype.process8BitVRAM = function (chrData, yOffset) {
//16 color tile mode:
chrData = chrData | 0;
yOffset = yOffset | 0;
//Parse flip attributes, grab palette, and then output pixel:
var address = (chrData & 0x3FF) << 4;
address = ((address | 0) + (this.BGCharacterBaseBlock | 0)) | 0;
//Copy out our pixels:
switch (chrData & 0xC00) {
//No Flip:
case 0:
address = ((address | 0) + (yOffset << 1)) | 0;
this.render8BitVRAMNormal(address | 0);
break;
//Horizontal Flip:
case 0x400:
address = ((address | 0) + (yOffset << 1)) | 0;
this.render8BitVRAMFlipped(address | 0);
break;
//Vertical Flip:
case 0x800:
address = ((address | 0) + 14) | 0;
address = ((address | 0) - (yOffset << 1)) | 0;
this.render8BitVRAMNormal(address | 0);
break;
//Horizontal & Vertical Flip:
default:
address = ((address | 0) + 14) | 0;
address = ((address | 0) - (yOffset << 1)) | 0;
this.render8BitVRAMFlipped(address | 0);
}
}
if (__LITTLE_ENDIAN__) {
GameBoyAdvanceBGTEXTRenderer.prototype.render8BitVRAMNormal = function (address) {
address = address | 0;
if ((address | 0) < 0x4000) {
//Tile address valid:
//Normal Horizontal:
var data = this.VRAM32[address | 0] | 0;
this.tileFetched[0] = this.palette256[data & 0xFF] | this.priorityFlag;
this.tileFetched[1] = this.palette256[(data >> 8) & 0xFF] | this.priorityFlag;
this.tileFetched[2] = this.palette256[(data >> 16) & 0xFF] | this.priorityFlag;
this.tileFetched[3] = this.palette256[data >>> 24] | this.priorityFlag;
data = this.VRAM32[address | 1] | 0;
this.tileFetched[4] = this.palette256[data & 0xFF] | this.priorityFlag;
this.tileFetched[5] = this.palette256[(data >> 8) & 0xFF] | this.priorityFlag;
this.tileFetched[6] = this.palette256[(data >> 16) & 0xFF] | this.priorityFlag;
this.tileFetched[7] = this.palette256[data >>> 24] | this.priorityFlag;
}
else {
//Tile address invalid:
this.addressInvalidRender();
}
}
GameBoyAdvanceBGTEXTRenderer.prototype.render8BitVRAMFlipped = function (address) {
address = address | 0;
if ((address | 0) < 0x4000) {
//Tile address valid:
//Flipped Horizontally:
var data = this.VRAM32[address | 0] | 0;
this.tileFetched[4] = this.palette256[data >>> 24] | this.priorityFlag;
this.tileFetched[5] = this.palette256[(data >> 16) & 0xFF] | this.priorityFlag;
this.tileFetched[6] = this.palette256[(data >> 8) & 0xFF] | this.priorityFlag;
this.tileFetched[7] = this.palette256[data & 0xFF] | this.priorityFlag;
data = this.VRAM32[address | 1] | 0;
this.tileFetched[0] = this.palette256[data >>> 24] | this.priorityFlag;
this.tileFetched[1] = this.palette256[(data >> 16) & 0xFF] | this.priorityFlag;
this.tileFetched[2] = this.palette256[(data >> 8) & 0xFF] | this.priorityFlag;
this.tileFetched[3] = this.palette256[data & 0xFF] | this.priorityFlag;
}
else {
//Tile address invalid:
this.addressInvalidRender();
}
}
}
else {
GameBoyAdvanceBGTEXTRenderer.prototype.render8BitVRAMNormal = function (address) {
address <<= 2;
if (address < 0x10000) {
//Tile address valid:
//Normal Horizontal:
this.tileFetched[0] = this.palette256[this.VRAM[address]] | this.priorityFlag;
this.tileFetched[1] = this.palette256[this.VRAM[address | 1]] | this.priorityFlag;
this.tileFetched[2] = this.palette256[this.VRAM[address | 2]] | this.priorityFlag;
this.tileFetched[3] = this.palette256[this.VRAM[address | 3]] | this.priorityFlag;
this.tileFetched[4] = this.palette256[this.VRAM[address | 4]] | this.priorityFlag;
this.tileFetched[5] = this.palette256[this.VRAM[address | 5]] | this.priorityFlag;
this.tileFetched[6] = this.palette256[this.VRAM[address | 6]] | this.priorityFlag;
this.tileFetched[7] = this.palette256[this.VRAM[address | 7]] | this.priorityFlag;
}
else {
//Tile address invalid:
this.addressInvalidRender();
}
}
GameBoyAdvanceBGTEXTRenderer.prototype.render8BitVRAMFlipped = function (address) {
address <<= 2;
if (address < 0x10000) {
//Tile address valid:
//Flipped Horizontally:
this.tileFetched[7] = this.palette256[this.VRAM[address]] | this.priorityFlag;
this.tileFetched[6] = this.palette256[this.VRAM[address | 1]] | this.priorityFlag;
this.tileFetched[5] = this.palette256[this.VRAM[address | 2]] | this.priorityFlag;
this.tileFetched[4] = this.palette256[this.VRAM[address | 3]] | this.priorityFlag;
this.tileFetched[3] = this.palette256[this.VRAM[address | 4]] | this.priorityFlag;
this.tileFetched[2] = this.palette256[this.VRAM[address | 5]] | this.priorityFlag;
this.tileFetched[1] = this.palette256[this.VRAM[address | 6]] | this.priorityFlag;
this.tileFetched[0] = this.palette256[this.VRAM[address | 7]] | this.priorityFlag;
}
else {
//Tile address invalid:
this.addressInvalidRender();
}
}
}
GameBoyAdvanceBGTEXTRenderer.prototype.addressInvalidRender = function () {
//In GBA mode on NDS, we display transparency on invalid tiles:
var data = this.gfx.transparency | this.priorityFlag;
this.tileFetched[0] = data | 0;
this.tileFetched[1] = data | 0;
this.tileFetched[2] = data | 0;
this.tileFetched[3] = data | 0;
this.tileFetched[4] = data | 0;
this.tileFetched[5] = data | 0;
this.tileFetched[6] = data | 0;
this.tileFetched[7] = data | 0;
}
GameBoyAdvanceBGTEXTRenderer.prototype.setMosaicEnable = function (doMosaic) {
doMosaic = doMosaic | 0;
this.doMosaic = doMosaic | 0;
}
GameBoyAdvanceBGTEXTRenderer.prototype.paletteModeSelect = function (do256) {
do256 = do256 | 0;
this.do256 = do256 | 0;
}
GameBoyAdvanceBGTEXTRenderer.prototype.screenSizePreprocess = function (BGScreenSize) {
BGScreenSize = BGScreenSize | 0;
this.tileMode = BGScreenSize | 0;
}
GameBoyAdvanceBGTEXTRenderer.prototype.priorityPreprocess = function (BGPriority) {
BGPriority = BGPriority | 0;
this.priorityFlag = (BGPriority << 23) | (1 << (this.BGLayer | 0x10));
}
GameBoyAdvanceBGTEXTRenderer.prototype.screenBaseBlockPreprocess = function (BGScreenBaseBlock) {
BGScreenBaseBlock = BGScreenBaseBlock | 0;
this.BGScreenBaseBlock = BGScreenBaseBlock << 10;
}
GameBoyAdvanceBGTEXTRenderer.prototype.characterBaseBlockPreprocess = function (BGCharacterBaseBlock) {
BGCharacterBaseBlock = BGCharacterBaseBlock | 0;
this.BGCharacterBaseBlock = BGCharacterBaseBlock << 12;
}
GameBoyAdvanceBGTEXTRenderer.prototype.writeBGCNT8_0 = function (data) {
data = data | 0;
this.setMosaicEnable(data & 0x40);
this.paletteModeSelect(data & 0x80);
this.priorityPreprocess(data & 0x3);
this.characterBaseBlockPreprocess((data & 0xC) >> 2);
}
GameBoyAdvanceBGTEXTRenderer.prototype.writeBGCNT8_1 = function (data) {
data = data | 0;
this.screenSizePreprocess((data & 0xC0) >> 6);
this.screenBaseBlockPreprocess(data & 0x1F);
}
GameBoyAdvanceBGTEXTRenderer.prototype.writeBGCNT16 = function (data) {
data = data | 0;
this.setMosaicEnable(data & 0x40);
this.paletteModeSelect(data & 0x80);
this.priorityPreprocess(data & 0x3);
this.characterBaseBlockPreprocess((data & 0xC) >> 2);
this.screenSizePreprocess((data & 0xC000) >> 14);
this.screenBaseBlockPreprocess((data >> 8) & 0x1F);
}
GameBoyAdvanceBGTEXTRenderer.prototype.writeBGHOFS8_0 = function (data) {
data = data | 0;
this.BGXCoord = (this.BGXCoord & 0x100) | data;
}
GameBoyAdvanceBGTEXTRenderer.prototype.writeBGHOFS8_1 = function (data) {
data = data | 0;
this.BGXCoord = (data << 8) | (this.BGXCoord & 0xFF);
}
GameBoyAdvanceBGTEXTRenderer.prototype.writeBGHOFS16 = function (data) {
data = data | 0;
this.BGXCoord = data | 0;
}
GameBoyAdvanceBGTEXTRenderer.prototype.writeBGVOFS8_0 = function (data) {
data = data | 0;
this.BGYCoord = (this.BGYCoord & 0x100) | data;
}
GameBoyAdvanceBGTEXTRenderer.prototype.writeBGVOFS8_1 = function (data) {
data = data | 0;
this.BGYCoord = (data << 8) | (this.BGYCoord & 0xFF);
}
GameBoyAdvanceBGTEXTRenderer.prototype.writeBGVOFS16 = function (data) {
data = data | 0;
this.BGYCoord = data | 0;
}
GameBoyAdvanceBGTEXTRenderer.prototype.writeBGOFS32 = function (data) {
data = data | 0;
this.BGXCoord = data & 0x1FF;
this.BGYCoord = data >> 16;
}

View file

@ -0,0 +1,347 @@
"use strict";
/*
Copyright (C) 2012-2016 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceColorEffectsRenderer(buffer) {
this.effectsTarget1 = 0;
this.colorEffectsType = 0;
this.effectsTarget2 = 0;
this.initialize(buffer);
}
if (typeof SIMD == "object" && typeof SIMD.Int32x4 == "function") {
//SIMD support found, insert the optimized SIMD path in:
GameBoyAdvanceColorEffectsRenderer.prototype.initialize = function (buffer) {
this.alphaBlendAmountTarget1 = SIMD.Int32x4.splat(0);
this.alphaBlendAmountTarget2 = SIMD.Int32x4.splat(0);
this.brightnessEffectAmount = SIMD.Int32x4.splat(0);
this.brightnessEffectAmountReverse = SIMD.Int32x4.splat(0x10);
this.buffer = buffer;
}
GameBoyAdvanceColorEffectsRenderer.prototype.pixelMask = SIMD.Int32x4.splat(0x1F);
GameBoyAdvanceColorEffectsRenderer.prototype.temporaryPixelBuffer = new Int32Array(4);
GameBoyAdvanceColorEffectsRenderer.prototype.alphaBlend = function (topPixel, lowerPixel) {
topPixel = topPixel | 0;
lowerPixel = lowerPixel | 0;
var p1 = SIMD.Int32x4(topPixel >> 10, topPixel >> 5, topPixel, 0);
var p2 = SIMD.Int32x4(lowerPixel >> 10, lowerPixel >> 5, lowerPixel, 0);
p1 = SIMD.Int32x4.and(p1, this.pixelMask);
p2 = SIMD.Int32x4.and(p2, this.pixelMask);
p1 = SIMD.Int32x4.mul(p1, this.alphaBlendAmountTarget1);
p2 = SIMD.Int32x4.mul(p2, this.alphaBlendAmountTarget2);
var presult = SIMD.Int32x4.add(p1, p2);
presult = SIMD.Int32x4.shiftRightByScalar(presult, 4);
var selectMask = SIMD.Int32x4.lessThanOrEqual(presult, this.pixelMask);
presult = SIMD.Int32x4.select(selectMask, presult, this.pixelMask);
SIMD.Int32x4.store(this.temporaryPixelBuffer, 0, presult);
return (this.temporaryPixelBuffer[0] << 10) | (this.temporaryPixelBuffer[1] << 5) | this.temporaryPixelBuffer[2];
}
GameBoyAdvanceColorEffectsRenderer.prototype.brightnessIncrease = function (topPixel) {
topPixel = topPixel | 0;
var p1 = SIMD.Int32x4(topPixel >> 10, topPixel >> 5, topPixel, 0);
p1 = SIMD.Int32x4.and(p1, this.pixelMask);
var pTemp = SIMD.Int32x4.sub(this.pixelMask, p1);
pTemp = SIMD.Int32x4.mul(pTemp, this.brightnessEffectAmount);
pTemp = SIMD.Int32x4.shiftRightByScalar(pTemp, 4);
p1 = SIMD.Int32x4.add(p1, pTemp);
SIMD.Int32x4.store(this.temporaryPixelBuffer, 0, p1);
return (this.temporaryPixelBuffer[0] << 10) | (this.temporaryPixelBuffer[1] << 5) | this.temporaryPixelBuffer[2];
}
GameBoyAdvanceColorEffectsRenderer.prototype.brightnessDecrease = function (topPixel) {
topPixel = topPixel | 0;
var p1 = SIMD.Int32x4(topPixel >> 10, topPixel >> 5, topPixel, 0);
p1 = SIMD.Int32x4.and(p1, this.pixelMask);
p1 = SIMD.Int32x4.mul(p1, this.brightnessEffectAmountReverse);
p1 = SIMD.Int32x4.shiftRightByScalar(p1, 4);
SIMD.Int32x4.store(this.temporaryPixelBuffer, 0, p1);
return (this.temporaryPixelBuffer[0] << 10) | (this.temporaryPixelBuffer[1] << 5) | this.temporaryPixelBuffer[2];
}
GameBoyAdvanceColorEffectsRenderer.prototype.writeBLDALPHA8_0 = function (data) {
data = data | 0;
var alphaBlendAmountTarget1Scratch = data & 0x1F;
this.alphaBlendAmountTarget1 = SIMD.Int32x4.splat(Math.min(alphaBlendAmountTarget1Scratch | 0, 0x10) | 0);
}
GameBoyAdvanceColorEffectsRenderer.prototype.writeBLDALPHA8_1 = function (data) {
data = data | 0;
var alphaBlendAmountTarget2Scratch = data & 0x1F;
this.alphaBlendAmountTarget2 = SIMD.Int32x4.splat(Math.min(alphaBlendAmountTarget2Scratch | 0, 0x10) | 0);
}
GameBoyAdvanceColorEffectsRenderer.prototype.writeBLDALPHA16 = function (data) {
data = data | 0;
var alphaBlendAmountTarget1Scratch = data & 0x1F;
this.alphaBlendAmountTarget1 = SIMD.Int32x4.splat(Math.min(alphaBlendAmountTarget1Scratch | 0, 0x10) | 0);
var alphaBlendAmountTarget2Scratch = (data >> 8) & 0x1F;
this.alphaBlendAmountTarget2 = SIMD.Int32x4.splat(Math.min(alphaBlendAmountTarget2Scratch | 0, 0x10) | 0);
}
GameBoyAdvanceColorEffectsRenderer.prototype.writeBLDCNT32 = function (data) {
data = data | 0;
//Select target 1 and color effects mode:
this.effectsTarget1 = (data & 0x3F) << 16;
this.colorEffectsType = (data >> 6) & 0x3;
//Select target 2:
this.effectsTarget2 = (data & 0x3F00) << 8;
var alphaBlendAmountTarget1Scratch = (data >> 16) & 0x1F;
this.alphaBlendAmountTarget1 = SIMD.Int32x4.splat(Math.min(alphaBlendAmountTarget1Scratch | 0, 0x10) | 0);
var alphaBlendAmountTarget2Scratch = (data >> 24) & 0x1F;
this.alphaBlendAmountTarget2 = SIMD.Int32x4.splat(Math.min(alphaBlendAmountTarget2Scratch | 0, 0x10) | 0);
}
GameBoyAdvanceColorEffectsRenderer.prototype.writeBLDY8 = function (data) {
data = data | 0;
var data = Math.min(data & 0x1F, 0x10) | 0;
this.brightnessEffectAmount = SIMD.Int32x4.splat(data | 0);
this.brightnessEffectAmountReverse = SIMD.Int32x4.splat((0x10 - (data | 0)) | 0);
}
GameBoyAdvanceColorEffectsRenderer.prototype.processFullNormalEffectsNoSprites = function () {
for (var index = 0; (index | 0) < 240; index = ((index | 0) + 4) | 0) {
this.buffer[index | 0] = this.processPixelNormal(this.buffer[index | 0x700] | 0, this.buffer[index | 0x800] | 0);
this.buffer[index | 1] = this.processPixelNormal(this.buffer[index | 0x701] | 0, this.buffer[index | 0x801] | 0);
this.buffer[index | 2] = this.processPixelNormal(this.buffer[index | 0x702] | 0, this.buffer[index | 0x802] | 0);
this.buffer[index | 3] = this.processPixelNormal(this.buffer[index | 0x703] | 0, this.buffer[index | 0x803] | 0);
}
}
GameBoyAdvanceColorEffectsRenderer.prototype.processWindowNormalEffectsNoSprites = function (xStart, xEnd) {
xStart = xStart | 0;
xEnd = xEnd | 0;
while ((xStart | 0) < (xEnd | 0)) {
this.buffer[xStart | 0] = this.processPixelNormal(this.buffer[xStart | 0x700] | 0, this.buffer[xStart | 0x800] | 0);
xStart = ((xStart | 0) + 1) | 0;
}
}
GameBoyAdvanceColorEffectsRenderer.prototype.processFullNormalEffectsWithSprites = function () {
for (var index = 0; (index | 0) < 240; index = ((index | 0) + 4) | 0) {
this.buffer[index | 0] = this.processPixelTestFull(this.buffer[index | 0x700] | 0, this.buffer[index | 0x800] | 0);
this.buffer[index | 1] = this.processPixelTestFull(this.buffer[index | 0x701] | 0, this.buffer[index | 0x801] | 0);
this.buffer[index | 2] = this.processPixelTestFull(this.buffer[index | 0x702] | 0, this.buffer[index | 0x802] | 0);
this.buffer[index | 3] = this.processPixelTestFull(this.buffer[index | 0x703] | 0, this.buffer[index | 0x803] | 0);
}
}
GameBoyAdvanceColorEffectsRenderer.prototype.processWindowNormalEffectsWithSprites = function (xStart, xEnd) {
xStart = xStart | 0;
xEnd = xEnd | 0;
while ((xStart | 0) < (xEnd | 0)) {
this.buffer[xStart | 0] = this.processPixelTestFull(this.buffer[xStart | 0x700] | 0, this.buffer[xStart | 0x800] | 0);
xStart = ((xStart | 0) + 1) | 0;
}
}
GameBoyAdvanceColorEffectsRenderer.prototype.processFullNoEffectsWithSprites = function () {
for (var index = 0; (index | 0) < 240; index = ((index | 0) + 4) | 0) {
this.buffer[index | 0] = this.processPixelTestSprite(this.buffer[index | 0x700] | 0, this.buffer[index | 0x800] | 0);
this.buffer[index | 1] = this.processPixelTestSprite(this.buffer[index | 0x701] | 0, this.buffer[index | 0x801] | 0);
this.buffer[index | 2] = this.processPixelTestSprite(this.buffer[index | 0x702] | 0, this.buffer[index | 0x802] | 0);
this.buffer[index | 3] = this.processPixelTestSprite(this.buffer[index | 0x703] | 0, this.buffer[index | 0x803] | 0);
}
}
GameBoyAdvanceColorEffectsRenderer.prototype.processWindowNoEffectsWithSprites = function (xStart, xEnd) {
xStart = xStart | 0;
xEnd = xEnd | 0;
while ((xStart | 0) < (xEnd | 0)) {
this.buffer[xStart | 0] = this.processPixelTestSprite(this.buffer[xStart | 0x700] | 0, this.buffer[xStart | 0x800] | 0);
xStart = ((xStart | 0) + 1) | 0;
}
}
GameBoyAdvanceColorEffectsRenderer.prototype.processPixelTestFull = function (lowerPixel, currentPixel) {
lowerPixel = lowerPixel | 0;
currentPixel = currentPixel | 0;
if ((currentPixel & 0x400000) == 0) {
return this.processPixelNormal(lowerPixel | 0, currentPixel | 0) | 0;
}
else {
return this.processPixelSprite(lowerPixel | 0, currentPixel | 0) | 0;
}
}
GameBoyAdvanceColorEffectsRenderer.prototype.processPixelTestSprite = function (lowerPixel, currentPixel) {
lowerPixel = lowerPixel | 0;
currentPixel = currentPixel | 0;
if ((currentPixel & 0x400000) == 0) {
return currentPixel | 0;
}
else {
return this.processPixelSprite(lowerPixel | 0, currentPixel | 0) | 0;
}
}
}
else {
//No SIMD support found, use the scalar path instead:
GameBoyAdvanceColorEffectsRenderer.prototype.initialize = function (buffer) {
this.alphaBlendAmountTarget1 = 0;
this.alphaBlendAmountTarget2 = 0;
this.brightnessEffectAmount = 0;
}
if (typeof Math.imul == "function") {
//Math.imul found, insert the optimized path in:
GameBoyAdvanceColorEffectsRenderer.prototype.alphaBlend = function (topPixel, lowerPixel) {
topPixel = topPixel | 0;
lowerPixel = lowerPixel | 0;
var b1 = (topPixel >> 10) & 0x1F;
var g1 = (topPixel >> 5) & 0x1F;
var r1 = topPixel & 0x1F;
var b2 = (lowerPixel >> 10) & 0x1F;
var g2 = (lowerPixel >> 5) & 0x1F;
var r2 = lowerPixel & 0x1F;
b1 = Math.imul(b1 | 0, this.alphaBlendAmountTarget1 | 0) | 0;
g1 = Math.imul(g1 | 0, this.alphaBlendAmountTarget1 | 0) | 0;
r1 = Math.imul(r1 | 0, this.alphaBlendAmountTarget1 | 0) | 0;
b2 = Math.imul(b2 | 0, this.alphaBlendAmountTarget2 | 0) | 0;
g2 = Math.imul(g2 | 0, this.alphaBlendAmountTarget2 | 0) | 0;
r2 = Math.imul(r2 | 0, this.alphaBlendAmountTarget2 | 0) | 0;
//Keep this not inlined in the return, firefox 22 grinds on it:
var b = Math.min(((b1 | 0) + (b2 | 0)) >> 4, 0x1F) | 0;
var g = Math.min(((g1 | 0) + (g2 | 0)) >> 4, 0x1F) | 0;
var r = Math.min(((r1 | 0) + (r2 | 0)) >> 4, 0x1F) | 0;
return (b << 10) | (g << 5) | r;
}
GameBoyAdvanceColorEffectsRenderer.prototype.brightnessIncrease = function (topPixel) {
topPixel = topPixel | 0;
var b1 = (topPixel >> 10) & 0x1F;
var g1 = (topPixel >> 5) & 0x1F;
var r1 = topPixel & 0x1F;
b1 = ((b1 | 0) + (Math.imul((0x1F - (b1 | 0)) | 0, this.brightnessEffectAmount | 0) >> 4)) | 0;
g1 = ((g1 | 0) + (Math.imul((0x1F - (g1 | 0)) | 0, this.brightnessEffectAmount | 0) >> 4)) | 0;
r1 = ((r1 | 0) + (Math.imul((0x1F - (r1 | 0)) | 0, this.brightnessEffectAmount | 0) >> 4)) | 0;
return (b1 << 10) | (g1 << 5) | r1;
}
GameBoyAdvanceColorEffectsRenderer.prototype.brightnessDecrease = function (topPixel) {
topPixel = topPixel | 0;
var b1 = (topPixel >> 10) & 0x1F;
var g1 = (topPixel >> 5) & 0x1F;
var r1 = topPixel & 0x1F;
var decreaseMultiplier = (0x10 - (this.brightnessEffectAmount | 0)) | 0;
b1 = Math.imul(b1 | 0, decreaseMultiplier | 0) >> 4;
g1 = Math.imul(g1 | 0, decreaseMultiplier | 0) >> 4;
r1 = Math.imul(r1 | 0, decreaseMultiplier | 0) >> 4;
return (b1 << 10) | (g1 << 5) | r1;
}
}
else {
//Math.imul not found, use the compatibility method:
GameBoyAdvanceColorEffectsRenderer.prototype.alphaBlend = function (topPixel, lowerPixel) {
topPixel = topPixel | 0;
lowerPixel = lowerPixel | 0;
var b1 = (topPixel >> 10) & 0x1F;
var g1 = (topPixel >> 5) & 0x1F;
var r1 = (topPixel & 0x1F);
var b2 = (lowerPixel >> 10) & 0x1F;
var g2 = (lowerPixel >> 5) & 0x1F;
var r2 = lowerPixel & 0x1F;
b1 = b1 * this.alphaBlendAmountTarget1;
g1 = g1 * this.alphaBlendAmountTarget1;
r1 = r1 * this.alphaBlendAmountTarget1;
b2 = b2 * this.alphaBlendAmountTarget2;
g2 = g2 * this.alphaBlendAmountTarget2;
r2 = r2 * this.alphaBlendAmountTarget2;
return (Math.min((b1 + b2) >> 4, 0x1F) << 10) | (Math.min((g1 + g2) >> 4, 0x1F) << 5) | Math.min((r1 + r2) >> 4, 0x1F);
}
GameBoyAdvanceColorEffectsRenderer.prototype.brightnessIncrease = function (topPixel) {
topPixel = topPixel | 0;
var b1 = (topPixel >> 10) & 0x1F;
var g1 = (topPixel >> 5) & 0x1F;
var r1 = topPixel & 0x1F;
b1 += ((0x1F - b1) * this.brightnessEffectAmount) >> 4;
g1 += ((0x1F - g1) * this.brightnessEffectAmount) >> 4;
r1 += ((0x1F - r1) * this.brightnessEffectAmount) >> 4;
return (b1 << 10) | (g1 << 5) | r1;
}
GameBoyAdvanceColorEffectsRenderer.prototype.brightnessDecrease = function (topPixel) {
topPixel = topPixel | 0;
var b1 = (topPixel >> 10) & 0x1F;
var g1 = (topPixel >> 5) & 0x1F;
var r1 = topPixel & 0x1F;
var decreaseMultiplier = 0x10 - this.brightnessEffectAmount;
b1 = (b1 * decreaseMultiplier) >> 4;
g1 = (g1 * decreaseMultiplier) >> 4;
r1 = (r1 * decreaseMultiplier) >> 4;
return (b1 << 10) | (g1 << 5) | r1;
}
}
GameBoyAdvanceColorEffectsRenderer.prototype.writeBLDALPHA8_0 = function (data) {
data = data | 0;
var alphaBlendAmountTarget1Scratch = data & 0x1F;
this.alphaBlendAmountTarget1 = Math.min(alphaBlendAmountTarget1Scratch | 0, 0x10) | 0;
}
GameBoyAdvanceColorEffectsRenderer.prototype.writeBLDALPHA8_1 = function (data) {
data = data | 0;
var alphaBlendAmountTarget2Scratch = data & 0x1F;
this.alphaBlendAmountTarget2 = Math.min(alphaBlendAmountTarget2Scratch | 0, 0x10) | 0;
}
GameBoyAdvanceColorEffectsRenderer.prototype.writeBLDALPHA16 = function (data) {
data = data | 0;
var alphaBlendAmountTarget1Scratch = data & 0x1F;
this.alphaBlendAmountTarget1 = Math.min(alphaBlendAmountTarget1Scratch | 0, 0x10) | 0;
var alphaBlendAmountTarget2Scratch = (data >> 8) & 0x1F;
this.alphaBlendAmountTarget2 = Math.min(alphaBlendAmountTarget2Scratch | 0, 0x10) | 0;
}
GameBoyAdvanceColorEffectsRenderer.prototype.writeBLDCNT32 = function (data) {
data = data | 0;
//Select target 1 and color effects mode:
this.effectsTarget1 = (data & 0x3F) << 16;
this.colorEffectsType = (data >> 6) & 0x3;
//Select target 2:
this.effectsTarget2 = (data & 0x3F00) << 8;
var alphaBlendAmountTarget1Scratch = (data >> 16) & 0x1F;
this.alphaBlendAmountTarget1 = Math.min(alphaBlendAmountTarget1Scratch | 0, 0x10) | 0;
var alphaBlendAmountTarget2Scratch = (data >> 24) & 0x1F;
this.alphaBlendAmountTarget2 = Math.min(alphaBlendAmountTarget2Scratch | 0, 0x10) | 0;
}
GameBoyAdvanceColorEffectsRenderer.prototype.writeBLDY8 = function (data) {
data = data | 0;
this.brightnessEffectAmount = Math.min(data & 0x1F, 0x10) | 0;
}
}
GameBoyAdvanceColorEffectsRenderer.prototype.processPixelNormal = function (lowerPixel, topPixel) {
lowerPixel = lowerPixel | 0;
topPixel = topPixel | 0;
if (((topPixel | 0) & (this.effectsTarget1 | 0)) != 0) {
switch (this.colorEffectsType | 0) {
case 1:
if (((lowerPixel | 0) & (this.effectsTarget2 | 0)) != 0 && (topPixel | 0) != (lowerPixel | 0)) {
return this.alphaBlend(topPixel | 0, lowerPixel | 0) | 0;
}
break;
case 2:
return this.brightnessIncrease(topPixel | 0) | 0;
case 3:
return this.brightnessDecrease(topPixel | 0) | 0;
}
}
return topPixel | 0;
}
GameBoyAdvanceColorEffectsRenderer.prototype.processPixelSprite = function (lowerPixel, topPixel) {
lowerPixel = lowerPixel | 0;
topPixel = topPixel | 0;
if (((lowerPixel | 0) & (this.effectsTarget2 | 0)) != 0) {
return this.alphaBlend(topPixel | 0, lowerPixel | 0) | 0;
}
else if (((topPixel | 0) & (this.effectsTarget1 | 0)) != 0) {
switch (this.colorEffectsType | 0) {
case 2:
return this.brightnessIncrease(topPixel | 0) | 0;
case 3:
return this.brightnessDecrease(topPixel | 0) | 0;
}
}
return topPixel | 0;
}
GameBoyAdvanceColorEffectsRenderer.prototype.writeBLDCNT8_0 = function (data) {
data = data | 0;
//Select target 1 and color effects mode:
this.effectsTarget1 = (data & 0x3F) << 16;
this.colorEffectsType = data >> 6;
}
GameBoyAdvanceColorEffectsRenderer.prototype.writeBLDCNT8_1 = function (data) {
data = data | 0;
//Select target 2:
this.effectsTarget2 = (data & 0x3F) << 16;
}
GameBoyAdvanceColorEffectsRenderer.prototype.writeBLDCNT16 = function (data) {
data = data | 0;
//Select target 1 and color effects mode:
this.effectsTarget1 = (data & 0x3F) << 16;
this.colorEffectsType = (data >> 6) & 0x3;
//Select target 2:
this.effectsTarget2 = (data & 0x3F00) << 8;
}

View file

@ -0,0 +1,949 @@
"use strict";
/*
Copyright (C) 2012-2016 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceCompositor(gfx) {
this.gfx = gfx;
this.doEffects = 0;
}
function GameBoyAdvanceWindowCompositor(gfx) {
this.gfx = gfx;
this.doEffects = 0;
}
function GameBoyAdvanceOBJWindowCompositor(gfx) {
this.gfx = gfx;
this.doEffects = 0;
}
GameBoyAdvanceCompositor.prototype.initialize = GameBoyAdvanceWindowCompositor.prototype.initialize = function () {
this.buffer = this.gfx.buffer;
this.colorEffectsRenderer = this.gfx.colorEffectsRenderer;
}
GameBoyAdvanceOBJWindowCompositor.prototype.initialize = function () {
this.buffer = this.gfx.buffer;
this.colorEffectsRenderer = this.gfx.colorEffectsRenderer;
this.OBJWindowBuffer = this.gfx.objRenderer.scratchWindowBuffer;
}
GameBoyAdvanceCompositor.prototype.preprocess = GameBoyAdvanceWindowCompositor.prototype.preprocess = GameBoyAdvanceOBJWindowCompositor.prototype.preprocess = function (doEffects) {
doEffects = doEffects | 0;
this.doEffects = doEffects | 0;
}
GameBoyAdvanceOBJWindowCompositor.prototype.renderScanLine = GameBoyAdvanceCompositor.prototype.renderScanLine = function (layers) {
layers = layers | 0;
if ((this.doEffects | 0) == 0) {
this.renderNormalScanLine(layers | 0);
}
else {
this.renderScanLineWithEffects(layers | 0);
}
}
GameBoyAdvanceWindowCompositor.prototype.renderScanLine = function (xStart, xEnd, layers) {
xStart = xStart | 0;
xEnd = xEnd | 0;
layers = layers | 0;
if ((this.doEffects | 0) == 0) {
this.renderNormalScanLine(xStart | 0, xEnd | 0, layers | 0);
}
else {
this.renderScanLineWithEffects(xStart | 0, xEnd | 0, layers | 0);
}
}
//Check for SIMD support:
if (typeof SIMD == "object" && typeof SIMD.Int32x4 == "function") {
GameBoyAdvanceCompositor.prototype.mask0 = SIMD.Int32x4.splat(0);
GameBoyAdvanceCompositor.prototype.mask1 = SIMD.Int32x4.splat(0x2000000);
GameBoyAdvanceCompositor.prototype.mask2 = SIMD.Int32x4.splat(0x3800000);
GameBoyAdvanceCompositor.prototype.mask3 = SIMD.Int32x4.splat(0x1800000);
GameBoyAdvanceCompositor.prototype.mask4 = SIMD.Bool32x4.splat(true);
}
function generateIodineGBAGFXCompositors() {
function generateCompositors() {
function generateLoop(compositeType, doEffects, layers) {
function generateLoopHead(useVectorized, compositeType) {
function generateLocalScopeInit(useVectorized, layers, alreadyDeclared) {
function checkDeclared(alreadyDeclared) {
var code = "";
if (!alreadyDeclared) {
code += "var ";
}
return code;
}
//Declare the necessary temporary variables:
var code = "";
if (useVectorized) {
//Declare the necessary temporary variables:
code +=
"var backdrop = SIMD.Int32x4.splat(this.gfx.backdrop | 0);";
switch (layers) {
case 0:
//Don't need any if no layers to process:
break;
default:
//Need this temp for more than one layer:
code +=
checkDeclared(alreadyDeclared) + "workingPixel = this.mask0;" +
"var test1 = this.mask4;" +
"var test2 = this.mask4;";
case 0x1:
case 0x2:
case 0x4:
case 0x8:
case 0x10:
//Need these temps for one or more layers:
code +=
checkDeclared(alreadyDeclared) + "currentPixel = this.mask0;" +
checkDeclared(alreadyDeclared) + "lowerPixel = this.mask0;";
}
}
else {
switch (layers) {
case 0:
//Don't need any if no layers to process:
break;
default:
//Need this temp for more than one layer:
code +=
checkDeclared(alreadyDeclared) + "workingPixel = 0;";
case 0x1:
case 0x2:
case 0x4:
case 0x8:
case 0x10:
//Need these temps for one or more layers:
code +=
checkDeclared(alreadyDeclared) + "currentPixel = 0;" +
checkDeclared(alreadyDeclared) + "lowerPixel = 0;";
}
}
return code;
}
function generateLoopBody(useVectorized, doEffects, layers) {
function getSingleLayerPrefix(useVectorized) {
//Pass initialization if processing only 1 layer:
var code = "";
if (useVectorized) {
code +=
"lowerPixel = backdrop;";
}
else {
code +=
"lowerPixel = this.gfx.backdrop | 0;";
}
return code;
}
function getMultiLayerPrefix(useVectorized) {
//Pass initialization if processing more than 1 layer:
var code = "";
if (useVectorized) {
code +=
"lowerPixel = backdrop;" +
"currentPixel = lowerPixel;";
}
else {
code +=
"lowerPixel = this.gfx.backdrop | 0;" +
"currentPixel = lowerPixel | 0;";
}
return code;
}
function generateLayerCompareSingle(useVectorized, layerOffset) {
//Only 1 layer specified to be rendered:
var code = "";
if (useVectorized) {
code +=
"currentPixel = SIMD.Int32x4.load(this.buffer, xStart | " + layerOffset + ");" +
"currentPixel = SIMD.Int32x4.select(" +
"SIMD.Int32x4.notEqual(" +
"this.mask0," +
"SIMD.Int32x4.and(currentPixel, this.mask1)" +
")," +
"lowerPixel," +
"currentPixel" +
");";
}
else {
code +=
"currentPixel = this.buffer[xStart | " + layerOffset + "] | 0;" +
"if ((currentPixel & 0x2000000) != 0) {" +
"currentPixel = lowerPixel | 0;" +
"}";
}
return code;
}
function generateLayerCompare(useVectorized, layerOffset) {
//Code unit to be used when rendering more than 1 layer:
var code = "";
if (useVectorized) {
code +=
"workingPixel = SIMD.Int32x4.load(this.buffer, xStart | " + layerOffset + ");" +
"test1 = SIMD.Int32x4.lessThanOrEqual(" +
"SIMD.Int32x4.and(workingPixel, this.mask2)," +
"SIMD.Int32x4.and(currentPixel, this.mask3)" +
");" +
"lowerPixel = SIMD.Int32x4.select(test1, currentPixel, lowerPixel);" +
"currentPixel = SIMD.Int32x4.select(test1, workingPixel, currentPixel);" +
"test2 = SIMD.Int32x4.lessThanOrEqual(" +
"SIMD.Int32x4.and(workingPixel, this.mask2)," +
"SIMD.Int32x4.and(lowerPixel, this.mask3)" +
");" +
"lowerPixel = SIMD.Int32x4.select(" +
"test1," +
"lowerPixel," +
"SIMD.Int32x4.select(test2, workingPixel, lowerPixel)" +
");";
}
else {
code +=
"workingPixel = this.buffer[xStart | " + layerOffset + "] | 0;" +
"if ((workingPixel & 0x3800000) <= (currentPixel & 0x1800000)) {" +
"lowerPixel = currentPixel | 0;" +
"currentPixel = workingPixel | 0;" +
"}" +
"else if ((workingPixel & 0x3800000) <= (lowerPixel & 0x1800000)) {" +
"lowerPixel = workingPixel | 0;" +
"}";
}
return code;
}
function getColorEffects0Layers(useVectorized, doEffects) {
//Handle checks for color effects here:
var code = "";
if (useVectorized) {
//No layers:
if (doEffects) {
//Color effects enabled:
code +=
"SIMD.Int32x4.store(this.buffer, xStart | 0x700, this.mask0);" +
"SIMD.Int32x4.store(this.buffer, xStart | 0x800, backdrop);";
}
else {
//No effects enabled:
code +=
"SIMD.Int32x4.store(this.buffer, xStart | 0, backdrop);";
}
}
else {
//No layers:
if (doEffects) {
//Color effects enabled:
code +=
"this.buffer[xStart | 0] = this.colorEffectsRenderer.processPixelNormal(0, this.gfx.backdrop | 0) | 0;";
}
else {
//No effects enabled:
code +=
"this.buffer[xStart | 0] = this.gfx.backdrop | 0;"
}
}
return code;
}
function getColorEffectsNoSprites(useVectorized, doEffects) {
//Handle checks for color effects here:
var code = "";
if (useVectorized) {
//Rendering with no sprite layer:
if (doEffects) {
//Color effects enabled:
code +=
"SIMD.Int32x4.store(this.buffer, xStart | 0x700, lowerPixel);" +
"SIMD.Int32x4.store(this.buffer, xStart | 0x800, currentPixel);";
}
else {
//No effects enabled:
code +=
"SIMD.Int32x4.store(this.buffer, xStart | 0, currentPixel);";
}
}
else {
//Rendering with no sprite layer:
if (doEffects) {
//Color effects enabled:
code +=
"this.buffer[xStart | 0] = this.colorEffectsRenderer.processPixelNormal(lowerPixel | 0, currentPixel | 0) | 0;";
}
else {
//No effects enabled:
code +=
"this.buffer[xStart | 0] = currentPixel | 0;";
}
}
return code;
}
function getColorEffectsWithSprites(useVectorized, doEffects) {
//Handle checks for color effects here:
var code = "";
if (useVectorized) {
//Rendering with a sprite layer:
code +=
"SIMD.Int32x4.store(this.buffer, xStart | 0x700, lowerPixel);" +
"SIMD.Int32x4.store(this.buffer, xStart | 0x800, currentPixel);";
}
else {
//Rendering with a sprite layer:
code +=
"if ((currentPixel & 0x400000) == 0) {";
if (doEffects) {
//Color effects enabled:
code +=
"this.buffer[xStart | 0] = this.colorEffectsRenderer.processPixelNormal(lowerPixel | 0, currentPixel | 0) | 0;";
}
else {
//No effects enabled:
code +=
"this.buffer[xStart | 0] = currentPixel | 0;";
}
code +=
"}" +
"else {" +
//Must handle for semi-transparent sprite case:
"this.buffer[xStart | 0] = this.colorEffectsRenderer.processPixelSprite(lowerPixel | 0, currentPixel | 0) | 0;" +
"}";
}
return code;
}
function generatePass(useVectorized, doEffects, layers) {
var code = "";
//Special case each possible layer combination:
switch (layers) {
case 0:
//Backdrop only:
//Color Effects Post Processing:
code += getColorEffects0Layers(useVectorized, doEffects);
break;
case 1:
//Generate temps:
code += getSingleLayerPrefix(useVectorized);
//BG0:
code += generateLayerCompareSingle(useVectorized, 0x100);
//Color Effects Post Processing:
code += getColorEffectsNoSprites(useVectorized, doEffects);
break;
case 2:
//Generate temps:
code += getSingleLayerPrefix(useVectorized);
//BG1:
code += generateLayerCompareSingle(useVectorized, 0x200);
//Color Effects Post Processing:
code += getColorEffectsNoSprites(useVectorized, doEffects);
break;
case 3:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG1:
code += generateLayerCompare(useVectorized, 0x200);
//BG0:
code += generateLayerCompare(useVectorized, 0x100);
//Color Effects Post Processing:
code += getColorEffectsNoSprites(useVectorized, doEffects);
break;
case 4:
//Generate temps:
code += getSingleLayerPrefix(useVectorized);
//BG2:
code += generateLayerCompareSingle(useVectorized, 0x300);
//Color Effects Post Processing:
code += getColorEffectsNoSprites(useVectorized, doEffects);
break;
case 5:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG2:
code += generateLayerCompare(useVectorized, 0x300);
//BG0:
code += generateLayerCompare(useVectorized, 0x100);
//Color Effects Post Processing:
code += getColorEffectsNoSprites(useVectorized, doEffects);
break;
case 6:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG2:
code += generateLayerCompare(useVectorized, 0x300);
//BG1:
code += generateLayerCompare(useVectorized, 0x200);
//Color Effects Post Processing:
code += getColorEffectsNoSprites(useVectorized, doEffects);
break;
case 7:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG2:
code += generateLayerCompare(useVectorized, 0x300);
//BG1:
code += generateLayerCompare(useVectorized, 0x200);
//BG0:
code += generateLayerCompare(useVectorized, 0x100);
//Color Effects Post Processing:
code += getColorEffectsNoSprites(useVectorized, doEffects);
break;
case 8:
//Generate temps:
code += getSingleLayerPrefix(useVectorized);
//BG2:
code += generateLayerCompareSingle(useVectorized, 0x400);
//Color Effects Post Processing:
code += getColorEffectsNoSprites(useVectorized, doEffects);
break;
case 9:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG3:
code += generateLayerCompare(useVectorized, 0x400);
//BG0:
code += generateLayerCompare(useVectorized, 0x100);
//Color Effects Post Processing:
code += getColorEffectsNoSprites(useVectorized, doEffects);
break;
case 0xA:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG3:
code += generateLayerCompare(useVectorized, 0x400);
//BG1:
code += generateLayerCompare(useVectorized, 0x200);
//Color Effects Post Processing:
code += getColorEffectsNoSprites(useVectorized, doEffects);
break;
case 0xB:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG3:
code += generateLayerCompare(useVectorized, 0x400);
//BG1:
code += generateLayerCompare(useVectorized, 0x200);
//BG0:
code += generateLayerCompare(useVectorized, 0x100);
//Color Effects Post Processing:
code += getColorEffectsNoSprites(useVectorized, doEffects);
break;
case 0xC:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG3:
code += generateLayerCompare(useVectorized, 0x400);
//BG2:
code += generateLayerCompare(useVectorized, 0x300);
//Color Effects Post Processing:
code += getColorEffectsNoSprites(useVectorized, doEffects);
break;
case 0xD:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG3:
code += generateLayerCompare(useVectorized, 0x400);
//BG2:
code += generateLayerCompare(useVectorized, 0x300);
//BG0:
code += generateLayerCompare(useVectorized, 0x100);
//Color Effects Post Processing:
code += getColorEffectsNoSprites(useVectorized, doEffects);
break;
case 0xE:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG3:
code += generateLayerCompare(useVectorized, 0x400);
//BG2:
code += generateLayerCompare(useVectorized, 0x300);
//BG1:
code += generateLayerCompare(useVectorized, 0x200);
//Color Effects Post Processing:
code += getColorEffectsNoSprites(useVectorized, doEffects);
break;
case 0xF:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG3:
code += generateLayerCompare(useVectorized, 0x400);
//BG2:
code += generateLayerCompare(useVectorized, 0x300);
//BG1:
code += generateLayerCompare(useVectorized, 0x200);
//BG0:
code += generateLayerCompare(useVectorized, 0x100);
//Color Effects Post Processing:
code += getColorEffectsNoSprites(useVectorized, doEffects);
break;
case 0x10:
//Generate temps:
code += getSingleLayerPrefix(useVectorized);
//OBJ:
code += generateLayerCompareSingle(useVectorized, 0x500);
//Color Effects Post Processing:
code += getColorEffectsWithSprites(useVectorized, doEffects);
break;
case 0x11:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG0:
code += generateLayerCompare(useVectorized, 0x100);
//OBJ:
code += generateLayerCompare(useVectorized, 0x500);
//Color Effects Post Processing:
code += getColorEffectsWithSprites(useVectorized, doEffects);
break;
case 0x12:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG1:
code += generateLayerCompare(useVectorized, 0x200);
//OBJ:
code += generateLayerCompare(useVectorized, 0x500);
//Color Effects Post Processing:
code += getColorEffectsWithSprites(useVectorized, doEffects);
break;
case 0x13:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG1:
code += generateLayerCompare(useVectorized, 0x200);
//BG0:
code += generateLayerCompare(useVectorized, 0x100);
//OBJ:
code += generateLayerCompare(useVectorized, 0x500);
//Color Effects Post Processing:
code += getColorEffectsWithSprites(useVectorized, doEffects);
break;
case 0x14:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG2:
code += generateLayerCompare(useVectorized, 0x300);
//OBJ:
code += generateLayerCompare(useVectorized, 0x500);
//Color Effects Post Processing:
code += getColorEffectsWithSprites(useVectorized, doEffects);
break;
case 0x15:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG2:
code += generateLayerCompare(useVectorized, 0x300);
//BG0:
code += generateLayerCompare(useVectorized, 0x100);
//OBJ:
code += generateLayerCompare(useVectorized, 0x500);
//Color Effects Post Processing:
code += getColorEffectsWithSprites(useVectorized, doEffects);
break;
case 0x16:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG2:
code += generateLayerCompare(useVectorized, 0x300);
//BG1:
code += generateLayerCompare(useVectorized, 0x200);
//OBJ:
code += generateLayerCompare(useVectorized, 0x500);
//Color Effects Post Processing:
code += getColorEffectsWithSprites(useVectorized, doEffects);
break;
case 0x17:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG2:
code += generateLayerCompare(useVectorized, 0x300);
//BG1:
code += generateLayerCompare(useVectorized, 0x200);
//BG0:
code += generateLayerCompare(useVectorized, 0x100);
//OBJ:
code += generateLayerCompare(useVectorized, 0x500);
//Color Effects Post Processing:
code += getColorEffectsWithSprites(useVectorized, doEffects);
break;
case 0x18:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG3:
code += generateLayerCompare(useVectorized, 0x400);
//OBJ:
code += generateLayerCompare(useVectorized, 0x500);
//Color Effects Post Processing:
code += getColorEffectsWithSprites(useVectorized, doEffects);
break;
case 0x19:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG3:
code += generateLayerCompare(useVectorized, 0x400);
//BG0:
code += generateLayerCompare(useVectorized, 0x100);
//OBJ:
code += generateLayerCompare(useVectorized, 0x500);
//Color Effects Post Processing:
code += getColorEffectsWithSprites(useVectorized, doEffects);
break;
case 0x1A:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG3:
code += generateLayerCompare(useVectorized, 0x400);
//BG1:
code += generateLayerCompare(useVectorized, 0x200);
//OBJ:
code += generateLayerCompare(useVectorized, 0x500);
//Color Effects Post Processing:
code += getColorEffectsWithSprites(useVectorized, doEffects);
break;
case 0x1B:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG3:
code += generateLayerCompare(useVectorized, 0x400);
//BG1:
code += generateLayerCompare(useVectorized, 0x200);
//BG0:
code += generateLayerCompare(useVectorized, 0x100);
//OBJ:
code += generateLayerCompare(useVectorized, 0x500);
//Color Effects Post Processing:
code += getColorEffectsWithSprites(useVectorized, doEffects);
break;
case 0x1C:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG3:
code += generateLayerCompare(useVectorized, 0x400);
//BG2:
code += generateLayerCompare(useVectorized, 0x300);
//OBJ:
code += generateLayerCompare(useVectorized, 0x500);
//Color Effects Post Processing:
code += getColorEffectsWithSprites(useVectorized, doEffects);
break;
case 0x1D:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG3:
code += generateLayerCompare(useVectorized, 0x400);
//BG2:
code += generateLayerCompare(useVectorized, 0x300);
//BG0:
code += generateLayerCompare(useVectorized, 0x100);
//OBJ:
code += generateLayerCompare(useVectorized, 0x500);
//Color Effects Post Processing:
code += getColorEffectsWithSprites(useVectorized, doEffects);
break;
case 0x1E:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG3:
code += generateLayerCompare(useVectorized, 0x400);
//BG2:
code += generateLayerCompare(useVectorized, 0x300);
//BG1:
code += generateLayerCompare(useVectorized, 0x200);
//OBJ:
code += generateLayerCompare(useVectorized, 0x500);
//Color Effects Post Processing:
code += getColorEffectsWithSprites(useVectorized, doEffects);
break;
default:
//Generate temps:
code += getMultiLayerPrefix(useVectorized);
//BG3:
code += generateLayerCompare(useVectorized, 0x400);
//BG2:
code += generateLayerCompare(useVectorized, 0x300);
//BG1:
code += generateLayerCompare(useVectorized, 0x200);
//BG0:
code += generateLayerCompare(useVectorized, 0x100);
//OBJ:
code += generateLayerCompare(useVectorized, 0x500);
//Color Effects Post Processing:
code += getColorEffectsWithSprites(useVectorized, doEffects);
}
return code;
}
//Build the code to put inside a loop:
return generatePass(useVectorized, doEffects, layers);
}
function generateSIMDColorEffectsExternalCall(useVectorized, layers, compositeType) {
var code = "";
if (useVectorized) {
switch (compositeType) {
case 0:
//Check if we're processing the sprite layer:
if (layers < 0x10) {
//Don't need color effects processing for the else case:
if (doEffects) {
//Effects handling:
code +=
";this.colorEffectsRenderer.processFullNormalEffectsNoSprites()";
}
}
else {
if (doEffects) {
//Effects + semi-transparency handling:
code +=
";this.colorEffectsRenderer.processFullNormalEffectsWithSprites()";
}
else {
//Sprite semi-transparency handling:
code +=
";this.colorEffectsRenderer.processFullNoEffectsWithSprites()";
}
}
break;
case 1:
//Check if we're processing the sprite layer:
if (layers < 0x10) {
//Don't need color effects processing for the else case:
if (doEffects) {
//Effects handling:
code +=
";this.colorEffectsRenderer.processWindowNormalEffectsNoSprites(xStartCopy | 0, xEnd & -4)";
}
}
else {
if (doEffects) {
//Effects + semi-transparency handling:
code +=
";this.colorEffectsRenderer.processWindowNormalEffectsWithSprites(xStartCopy | 0, xEnd & -4)";
}
else {
//Sprite semi-transparency handling:
code +=
";this.colorEffectsRenderer.processWindowNoEffectsWithSprites(xStartCopy | 0, xEnd & -4)";
}
}
break;
}
}
return code;
}
var code = "";
switch (compositeType) {
//Loop for normal compositor:
case 0:
if (useVectorized) {
code +=
generateLocalScopeInit(true, layers, false) +
"for (var xStart = 0; (xStart | 0) < 240; xStart = ((xStart | 0) + 4) | 0) {" +
generateLoopBody(true, doEffects, layers) +
"}";
}
else {
code +=
generateLocalScopeInit(false, layers, false) +
"for (var xStart = 0; (xStart | 0) < 240; xStart = ((xStart | 0) + 1) | 0) {" +
generateLoopBody(false, doEffects, layers) +
"}";
}
break;
//Loop for window compositor:
case 1:
code +=
"xStart = xStart | 0;" +
"xEnd = xEnd | 0;";
if (useVectorized) {
code +=
generateLocalScopeInit(false, layers, false) +
"while ((xStart | 0) < (xEnd | 0) && (xStart | 0) <= (xStart | 0x3)) {" +
generateLoopBody(false, doEffects, layers) +
"xStart = ((xStart | 0) + 1) | 0;" +
"}" +
"var xStartCopy = xStart | 0;" +
generateLocalScopeInit(true, layers, true) +
"while ((xStart | 0) < (xEnd & -4)) {" +
generateLoopBody(true, doEffects, layers) +
"xStart = ((xStart | 0) + 4) | 0;" +
"}" +
generateLocalScopeInit(false, layers, true) +
"while ((xStart | 0) < (xEnd | 0)) {" +
generateLoopBody(false, doEffects, layers) +
"xStart = ((xStart | 0) + 1) | 0;" +
"}";
}
else {
code +=
generateLocalScopeInit(false, layers, false) +
"while ((xStart | 0) < (xEnd | 0)) {" +
generateLoopBody(false, doEffects, layers) +
"xStart = ((xStart | 0) + 1) | 0;" +
"}";
}
break;
//Loop for OBJ window compositor:
case 2:
code +=
generateLocalScopeInit(false, layers, false) +
"for (var xStart = 0; (xStart | 0) < 240; xStart = ((xStart | 0) + 1) | 0) {" +
"if ((this.OBJWindowBuffer[xStart | 0] | 0) < 0x3800000) {" +
generateLoopBody(false, doEffects, layers) +
"}" +
"}";
}
code += generateSIMDColorEffectsExternalCall(useVectorized, layers, compositeType);
return code;
}
//Build the loop:
return generateLoopHead(typeof SIMD == "object" && typeof SIMD.Int32x4 == "function", compositeType);
}
function generateCompositor(compositeType, doEffects) {
//Get function suffix we'll use depending on color effects usage:
var effectsPrefix = (doEffects) ? "special" : "normal";
//Loop through all possible combinations of layers:
for (var layers = 0; layers < 0x20; layers++) {
//Codegen the loop:
var code = generateLoop(compositeType, doEffects, layers);
//Compile the code and assign to appropriate compositor object:
switch (compositeType) {
case 0:
//Normal compositor:
GameBoyAdvanceCompositor.prototype[effectsPrefix + layers] = Function(code);
break;
case 1:
//Window compositor:
GameBoyAdvanceWindowCompositor.prototype[effectsPrefix + layers] = Function("xStart", "xEnd", code);
break;
default:
//OBJ window compositor:
GameBoyAdvanceOBJWindowCompositor.prototype[effectsPrefix + layers] = Function(code);
}
}
}
//Build the functions for each of the three compositors:
for (var compositeType = 0; compositeType < 3; compositeType++) {
//Build for the no special effects processing case:
generateCompositor(compositeType, false);
//Build for the special effects processing case:
generateCompositor(compositeType, true);
}
}
function generateDispatches() {
function generateDispatch(coordsSpecified, doEffects) {
function generateDispatchBlock(coordsSpecified, doEffects) {
function generateDispatchHead(coordsSpecified) {
//Initialize some local variables:
var code = "";
if (coordsSpecified) {
code +=
"xStart = xStart | 0;" +
"xEnd = xEnd | 0;";
}
code +=
"layers = layers | 0;";
return code;
}
function generateDispatchBody(coordsSpecified, doEffects) {
function generateSwitchHead(bodyCode) {
//We're building a switch statement:
var code =
"switch (layers | 0) {" +
bodyCode +
"}";
return code;
}
function generateSwitchBody(coordsSpecified, doEffects) {
function generateCases(coordsSpecified, doEffects) {
function generateCase(layers, coordsSpecified, doEffects) {
function generateCaseHead(layers, bodyCode) {
//Building the case label:
var code = "";
if (layers < 0x1F) {
//Not the last case in the list, so specify number:
code +=
"case " + layers + ":" +
"{" +
bodyCode + ";" +
"break" +
"};";
}
else {
//Last case in the list, so place it as default case:
code +=
"default:" +
"{" +
bodyCode +
"}";
}
return code;
}
function generateCaseBody(layers, coordsSpecified, doEffects) {
//Build the function call:
var code =
"this.";
if (doEffects) {
//Special effects:
code +=
"special";
}
else {
//No special effects:
code +=
"normal";
}
code +=
layers +
"(";
if (coordsSpecified) {
//Passing some xcoords as parameters:
code +=
"xStart | 0, xEnd | 0";
}
code +=
")";
return code;
}
//Build the full case unit:
return generateCaseHead(layers, generateCaseBody(layers, coordsSpecified, doEffects));
}
function generateList(coordsSpecified, doEffects) {
var code = "";
//Loop through all combinations to build:
for (var layers = 0; layers < 0x20; layers++) {
//Build a case for the specified combination:
code += generateCase(layers, coordsSpecified, doEffects);
}
return code;
}
//Build the entire switch:
return generateList(coordsSpecified, doEffects);
}
//Build the entire switch:
return generateCases(coordsSpecified, doEffects);
}
//Build the switch block:
return generateSwitchHead(generateSwitchBody(coordsSpecified, doEffects));
}
function generateDispatchCode(coordsSpecified, doEffects) {
//Build the dispatch block:
var code = "";
code += generateDispatchHead(coordsSpecified);
code += generateDispatchBody(coordsSpecified, doEffects);
return code;
}
return generateDispatchCode(coordsSpecified, doEffects);
}
function generateDispatchFunc(coordsSpecified, code) {
if (coordsSpecified) {
return Function("xStart", "xEnd", "layers", code);
}
else {
return Function("layers", code);
}
}
//Generate the function:
return generateDispatchFunc(coordsSpecified, generateDispatchBlock(coordsSpecified, doEffects));
}
//Build the functions for each of the six dispatches:
GameBoyAdvanceOBJWindowCompositor.prototype.renderNormalScanLine = GameBoyAdvanceCompositor.prototype.renderNormalScanLine = generateDispatch(false, false);
GameBoyAdvanceWindowCompositor.prototype.renderNormalScanLine = generateDispatch(true, false);
GameBoyAdvanceOBJWindowCompositor.prototype.renderScanLineWithEffects = GameBoyAdvanceCompositor.prototype.renderScanLineWithEffects = generateDispatch(false, true);
GameBoyAdvanceWindowCompositor.prototype.renderScanLineWithEffects = generateDispatch(true, true);
}
//Build and compile the compositors for every possible mode/layer/effect combination:
generateCompositors();
//Build and compile the dispatches for every possible mode/effect combination:
generateDispatches();
}
generateIodineGBAGFXCompositors();

View file

@ -0,0 +1,93 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceMosaicRenderer(buffer) {
this.BGMosaicHSize = 0;
this.BGMosaicVSize = 0;
this.OBJMosaicHSize = 0;
this.OBJMosaicVSize = 0;
this.buffer = buffer;
}
GameBoyAdvanceMosaicRenderer.prototype.attachOBJBuffer = function (objBuffer) {
//Function only called if no typed array view support:
this.objBuffer = objBuffer;
}
GameBoyAdvanceMosaicRenderer.prototype.renderMosaicHorizontal = function (offset) {
offset = offset | 0;
var currentPixel = 0;
var mosaicBlur = ((this.BGMosaicHSize | 0) + 1) | 0;
if ((mosaicBlur | 0) > 1) { //Don't perform a useless loop.
for (var position = 0; (position | 0) < 240; position = ((position | 0) + 1) | 0) {
if ((((position | 0) % (mosaicBlur | 0)) | 0) == 0) {
currentPixel = this.buffer[position | offset] | 0;
}
else {
this.buffer[position | offset] = currentPixel | 0;
}
}
}
}
if (__VIEWS_SUPPORTED__) {
GameBoyAdvanceMosaicRenderer.prototype.renderOBJMosaicHorizontal = function (xOffset, xSize) {
xOffset = xOffset | 0;
xSize = xSize | 0;
var currentPixel = 0x3800000;
var mosaicBlur = ((this.OBJMosaicHSize | 0) + 1) | 0;
if ((mosaicBlur | 0) > 1) { //Don't perform a useless loop.
for (var position = ((xOffset | 0) % (mosaicBlur | 0)) | 0; (position | 0) < (xSize | 0); position = ((position | 0) + 1) | 0) {
if ((((position | 0) % (mosaicBlur | 0)) | 0) == 0) {
currentPixel = this.buffer[position | 0x600] | 0;
}
this.buffer[position | 0x600] = currentPixel | 0;
}
}
}
}
else {
GameBoyAdvanceMosaicRenderer.prototype.renderOBJMosaicHorizontal = function (xOffset, xSize) {
xOffset = xOffset | 0;
xSize = xSize | 0;
var currentPixel = 0x3800000;
var mosaicBlur = ((this.OBJMosaicHSize | 0) + 1) | 0;
if ((mosaicBlur | 0) > 1) { //Don't perform a useless loop.
for (var position = ((xOffset | 0) % (mosaicBlur | 0)) | 0; (position | 0) < (xSize | 0); position = ((position | 0) + 1) | 0) {
if ((((position | 0) % (mosaicBlur | 0)) | 0) == 0) {
currentPixel = this.objBuffer[position | 0] | 0;
}
this.objBuffer[position | 0] = currentPixel | 0;
}
}
}
}
GameBoyAdvanceMosaicRenderer.prototype.getMosaicYOffset = function (line) {
line = line | 0;
return ((line | 0) % (((this.BGMosaicVSize | 0) + 1) | 0)) | 0;
}
GameBoyAdvanceMosaicRenderer.prototype.getOBJMosaicYOffset = function (line) {
line = line | 0;
return ((line | 0) % (((this.OBJMosaicVSize | 0) + 1) | 0)) | 0;
}
GameBoyAdvanceMosaicRenderer.prototype.writeMOSAIC8_0 = function (data) {
data = data | 0;
this.BGMosaicHSize = data & 0xF;
this.BGMosaicVSize = data >> 4;
}
GameBoyAdvanceMosaicRenderer.prototype.writeMOSAIC8_1 = function (data) {
data = data | 0;
this.OBJMosaicHSize = data & 0xF;
this.OBJMosaicVSize = data >> 4;
}
GameBoyAdvanceMosaicRenderer.prototype.writeMOSAIC16 = function (data) {
data = data | 0;
this.BGMosaicHSize = data & 0xF;
this.BGMosaicVSize = (data >> 4) & 0xF;
this.OBJMosaicHSize = (data >> 8) & 0xF;
this.OBJMosaicVSize = data >> 12;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,41 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceOBJWindowRenderer(compositor) {
//Get a layer compositor that we'll send our parameters for windowing to:
this.compositor = compositor;
}
GameBoyAdvanceOBJWindowRenderer.prototype.initialize = function () {
//Initialize the compositor:
this.compositor.initialize();
//Layer masking & color effects control:
this.WINOBJOutside = 0;
//Need to update the color effects status in the compositor:
this.preprocess();
}
GameBoyAdvanceOBJWindowRenderer.prototype.renderScanLine = function (line, toRender) {
line = line | 0;
toRender = toRender | 0;
//Windowing can disable out further layers:
toRender = toRender & this.WINOBJOutside;
//Windowing occurs where there is a non-transparent "obj-win" sprite:
this.compositor.renderScanLine(toRender | 0);
}
GameBoyAdvanceOBJWindowRenderer.prototype.writeWINOBJIN8 = function (data) {
data = data | 0;
//Layer masking & color effects control:
this.WINOBJOutside = data | 0;
//Need to update the color effects status in the compositor:
this.preprocess();
}
GameBoyAdvanceOBJWindowRenderer.prototype.preprocess = function () {
//Update the color effects status in the compositor:
this.compositor.preprocess(this.WINOBJOutside & 0x20);
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,919 @@
"use strict";
/*
Copyright (C) 2012-2016 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function getGameBoyAdvanceGraphicsRenderer(coreExposed, skippingBIOS) {
if (!coreExposed.offthreadGfxEnabled() || typeof SharedArrayBuffer != "function" || typeof Atomics != "object") {
return new GameBoyAdvanceGraphicsRenderer(coreExposed, skippingBIOS);
}
else {
try {
//Some browsers don't allow webworkers via file:///
return new GameBoyAdvanceGraphicsRendererShim(coreExposed, skippingBIOS);
}
catch (error) {
return new GameBoyAdvanceGraphicsRenderer(coreExposed, skippingBIOS);
}
}
}
function GameBoyAdvanceGraphicsRendererShim(coreExposed, skippingBIOS) {
this.coreExposed = coreExposed;
this.initializeWorker(skippingBIOS);
this.appendAtomicSync();
this.initializeBuffers();
this.shareStaticBuffers();
this.shareDynamicBuffers();
}
GameBoyAdvanceGraphicsRendererShim.prototype.initializeWorker = function (skippingBIOS) {
skippingBIOS = !!skippingBIOS;
//Apparently running on localhost is nearly impossible for webworkers in a cross-browser manner:
var loc = location.href;
var loc = loc.split("/");
loc = loc.slice(0, loc.length - 1).join("/");
try {
if (typeof WorkerGlobalScope === 'undefined' || !(self instanceof WorkerGlobalScope)) {
//Use the catch block:
throw null;
}
//Firefox:
var loc2 = loc + "/graphics/Worker.js";
this.worker = new Worker(loc2);
}
catch (e) {
//Google Chrome:
var loc3 = loc + "/IodineGBA/core/graphics/Worker.js";
this.worker = new Worker(loc3);
}
this.worker.postMessage({
messageID:1,
skippingBIOS:!!skippingBIOS
});
}
GameBoyAdvanceGraphicsRendererShim.prototype.initializeBuffers = function () {
//Graphics Buffers:
this.gfxCommandBufferLength = 0x80000;
this.gfxCommandBufferMask = ((this.gfxCommandBufferLength | 0) - 1) | 0;
this.gfxCommandBuffer = getSharedInt32Array(this.gfxCommandBufferLength | 0);
this.gfxCommandCounters = getSharedInt32Array(3);
this.gfxLineCounter = getSharedInt32Array(1);
this.start = 0;
this.end = 0;
this.linesPassed = 0;
this.OAMRAM = getUint8Array(0x400);
this.OAMRAM16 = getUint16View(this.OAMRAM);
this.OAMRAM32 = getInt32View(this.OAMRAM);
this.paletteRAM = getUint8Array(0x400);
this.VRAM = getUint8Array(0x18000);
this.VRAM16 = getUint16View(this.VRAM);
this.VRAM32 = getInt32View(this.VRAM);
this.paletteRAM16 = getUint16View(this.paletteRAM);
this.paletteRAM32 = getInt32View(this.paletteRAM);
}
GameBoyAdvanceGraphicsRendererShim.prototype.increaseCommandBufferCapacity = function () {
//Tell the other thread to break for receiving the new buffer:
Atomics.store(this.gfxCommandCounters, 2, 1);
//Double the size to the next power of 2:
this.gfxCommandBufferLength = this.gfxCommandBufferLength << 1;
this.gfxCommandBufferMask = ((this.gfxCommandBufferLength | 0) - 1) | 0;
this.gfxCommandBuffer = getSharedInt32Array(this.gfxCommandBufferLength | 0);
this.gfxCommandCounters = getSharedInt32Array(3);
this.start = 0;
this.end = 0;
//Share our new buffers:
this.shareDynamicBuffers();
}
GameBoyAdvanceGraphicsRendererShim.prototype.appendAtomicSync = function () {
//Command buffer counters get synchronized with emulator runtime head/end for efficiency:
var parentObj = this;
this.coreExposed.appendStartIterationSync(function () {
parentObj.synchronizeReader();
});
this.coreExposed.appendEndIterationSync(function () {
parentObj.synchronizeWriter();
});
this.coreExposed.appendTerminationSync(function () {
//Core instance being replaced, kill the worker thread:
parentObj.worker.terminate();
});
}
GameBoyAdvanceGraphicsRendererShim.prototype.shareStaticBuffers = function () {
try {
this.worker.postMessage({
messageID:0,
gfxBuffers:gfxBuffers,
gfxCounters:gfxCounters,
gfxLineCounter:this.gfxLineCounter
}, [
gfxBuffers[0].buffer,
gfxBuffers[1].buffer,
gfxCounters.buffer,
this.gfxLineCounter.buffer
]);
}
catch (e) {
this.worker.postMessage({
messageID:0,
gfxBuffers:gfxBuffers,
gfxCounters:gfxCounters,
gfxLineCounter:this.gfxLineCounter
});
}
}
GameBoyAdvanceGraphicsRendererShim.prototype.shareDynamicBuffers = function () {
try {
this.worker.postMessage({
messageID:2,
gfxCommandBuffer:this.gfxCommandBuffer,
gfxCommandCounters:this.gfxCommandCounters
}, [
this.gfxCommandBuffer.buffer,
this.gfxCommandCounters.buffer
]);
}
catch (e) {
this.worker.postMessage({
messageID:2,
gfxCommandBuffer:this.gfxCommandBuffer,
gfxCommandCounters:this.gfxCommandCounters
});
}
//Wake up the producer "GPU" thread:
Atomics.notify(gfxCounters, 2, 1);
}
GameBoyAdvanceGraphicsRendererShim.prototype.pushCommand = function (command, data) {
command = command | 0;
data = data | 0;
//Check if buffer is full:
this.checkCommandBufferFull();
//Get the write offset into the ring buffer:
var endCorrected = this.end & this.gfxCommandBufferMask;
//Push command into buffer:
this.gfxCommandBuffer[endCorrected | 0] = command | 0;
//Push data into buffer:
this.gfxCommandBuffer[endCorrected | 1] = data | 0;
//Update the cross thread buffering count:
this.end = ((this.end | 0) + 2) | 0;
}
GameBoyAdvanceGraphicsRendererShim.prototype.checkCommandBufferFull = function () {
if ((this.start | 0) == (((this.end | 0) - (this.gfxCommandBufferLength | 0)) | 0)) {
//Give the reader our updated counter and tell it to run:
this.synchronizeWriter();
//Increase buffer size:
this.increaseCommandBufferCapacity();
//Reload reader counter value:
this.synchronizeReader();
}
}
GameBoyAdvanceGraphicsRendererShim.prototype.synchronizeWriter = function () {
//Store command buffer writer counter value:
Atomics.store(this.gfxCommandCounters, 1, this.end | 0);
Atomics.store(this.gfxLineCounter, 0, this.linesPassed | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.synchronizeReader = function () {
//Load command buffer reader counter value:
this.start = Atomics.load(this.gfxCommandCounters, 0) | 0;
}
GameBoyAdvanceGraphicsRendererShim.prototype.pushVRAM16 = function (address, data) {
address = address | 0;
data = data | 0;
address = address & 0xFFFF;
this.pushCommand(0x10000 | address, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.pushVRAM32 = function (address, data) {
address = address | 0;
data = data | 0;
address = address & 0x7FFF;
this.pushCommand(0x20000 | address, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.pushPAL16 = function (address, data) {
address = address | 0;
data = data | 0;
address = address & 0x1FF;
this.pushCommand(0x30000 | address, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.pushPAL32 = function (address, data) {
address = address | 0;
data = data | 0;
address = address & 0xFF;
this.pushCommand(0x40000 | address, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.pushOAM16 = function (address, data) {
address = address | 0;
data = data | 0;
address = address & 0x1FF;
this.pushCommand(0x50000 | address, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.pushOAM32 = function (address, data) {
address = address | 0;
data = data | 0;
address = address & 0xFF;
this.pushCommand(0x60000 | address, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.incrementScanLineQueue = function () {
//Increment scan line command:
this.pushCommand(0, 0);
//Increment how many scanlines we've pushed out:
this.linesPassed = ((this.linesPassed | 0) + 1) | 0;
}
GameBoyAdvanceGraphicsRendererShim.prototype.ensureFraming = function () {
//Vertical blank synchronization command:
this.pushCommand(0, 1);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeDISPCNT8_0 = function (data) {
data = data | 0;
this.pushCommand(1, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeDISPCNT8_1 = function (data) {
data = data | 0;
this.pushCommand(2, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeDISPCNT8_2 = function (data) {
data = data | 0;
this.pushCommand(3, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeDISPCNT16 = function (data) {
data = data | 0;
this.pushCommand(4, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeDISPCNT32 = function (data) {
data = data | 0;
this.pushCommand(5, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG0CNT8_0 = function (data) {
data = data | 0;
this.pushCommand(6, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG0CNT8_1 = function (data) {
data = data | 0;
this.pushCommand(7, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG0CNT16 = function (data) {
data = data | 0;
this.pushCommand(8, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG1CNT8_0 = function (data) {
data = data | 0;
this.pushCommand(9, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG1CNT8_1 = function (data) {
data = data | 0;
this.pushCommand(10, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG1CNT16 = function (data) {
data = data | 0;
this.pushCommand(11, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG0BG1CNT32 = function (data) {
data = data | 0;
this.pushCommand(12, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2CNT8_0 = function (data) {
data = data | 0;
this.pushCommand(13, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2CNT8_1 = function (data) {
data = data | 0;
this.pushCommand(14, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2CNT16 = function (data) {
data = data | 0;
this.pushCommand(15, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3CNT8_0 = function (data) {
data = data | 0;
this.pushCommand(16, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3CNT8_1 = function (data) {
data = data | 0;
this.pushCommand(17, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3CNT16 = function (data) {
data = data | 0;
this.pushCommand(18, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2BG3CNT32 = function (data) {
data = data | 0;
this.pushCommand(19, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG0HOFS8_0 = function (data) {
data = data | 0;
this.pushCommand(20, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG0HOFS8_1 = function (data) {
data = data | 0;
this.pushCommand(21, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG0HOFS16 = function (data) {
data = data | 0;
this.pushCommand(22, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG0VOFS8_0 = function (data) {
data = data | 0;
this.pushCommand(23, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG0VOFS8_1 = function (data) {
data = data | 0;
this.pushCommand(24, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG0VOFS16 = function (data) {
data = data | 0;
this.pushCommand(25, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG0OFS32 = function (data) {
data = data | 0;
this.pushCommand(26, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG1HOFS8_0 = function (data) {
data = data | 0;
this.pushCommand(27, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG1HOFS8_1 = function (data) {
data = data | 0;
this.pushCommand(28, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG1HOFS16 = function (data) {
data = data | 0;
this.pushCommand(29, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG1VOFS8_0 = function (data) {
data = data | 0;
this.pushCommand(30, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG1VOFS8_1 = function (data) {
data = data | 0;
this.pushCommand(31, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG1VOFS16 = function (data) {
data = data | 0;
this.pushCommand(32, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG1OFS32 = function (data) {
data = data | 0;
this.pushCommand(33, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2HOFS8_0 = function (data) {
data = data | 0;
this.pushCommand(34, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2HOFS8_1 = function (data) {
data = data | 0;
this.pushCommand(35, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2HOFS16 = function (data) {
data = data | 0;
this.pushCommand(36, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2VOFS8_0 = function (data) {
data = data | 0;
this.pushCommand(37, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2VOFS8_1 = function (data) {
data = data | 0;
this.pushCommand(38, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2VOFS16 = function (data) {
data = data | 0;
this.pushCommand(39, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2OFS32 = function (data) {
data = data | 0;
this.pushCommand(40, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3HOFS8_0 = function (data) {
data = data | 0;
this.pushCommand(41, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3HOFS8_1 = function (data) {
data = data | 0;
this.pushCommand(42, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3HOFS16 = function (data) {
data = data | 0;
this.pushCommand(43, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3VOFS8_0 = function (data) {
data = data | 0;
this.pushCommand(44, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3VOFS8_1 = function (data) {
data = data | 0;
this.pushCommand(45, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3VOFS16 = function (data) {
data = data | 0;
this.pushCommand(46, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3OFS32 = function (data) {
data = data | 0;
this.pushCommand(47, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2PA8_0 = function (data) {
data = data | 0;
this.pushCommand(48, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2PA8_1 = function (data) {
data = data | 0;
this.pushCommand(49, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2PA16 = function (data) {
data = data | 0;
this.pushCommand(50, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2PB8_0 = function (data) {
data = data | 0;
this.pushCommand(51, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2PB8_1 = function (data) {
data = data | 0;
this.pushCommand(52, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2PB16 = function (data) {
data = data | 0;
this.pushCommand(53, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2PAB32 = function (data) {
data = data | 0;
this.pushCommand(54, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2PC8_0 = function (data) {
data = data | 0;
this.pushCommand(55, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2PC8_1 = function (data) {
data = data | 0;
this.pushCommand(56, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2PC16 = function (data) {
data = data | 0;
this.pushCommand(57, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2PD8_0 = function (data) {
data = data | 0;
this.pushCommand(58, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2PD8_1 = function (data) {
data = data | 0;
this.pushCommand(59, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2PD16 = function (data) {
data = data | 0;
this.pushCommand(60, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2PCD32 = function (data) {
data = data | 0;
this.pushCommand(61, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3PA8_0 = function (data) {
data = data | 0;
this.pushCommand(62, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3PA8_1 = function (data) {
data = data | 0;
this.pushCommand(63, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3PA16 = function (data) {
data = data | 0;
this.pushCommand(64, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3PB8_0 = function (data) {
data = data | 0;
this.pushCommand(65, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3PB8_1 = function (data) {
data = data | 0;
this.pushCommand(66, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3PB16 = function (data) {
data = data | 0;
this.pushCommand(67, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3PAB32 = function (data) {
data = data | 0;
this.pushCommand(68, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3PC8_0 = function (data) {
data = data | 0;
this.pushCommand(69, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3PC8_1 = function (data) {
data = data | 0;
this.pushCommand(70, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3PC16 = function (data) {
data = data | 0;
this.pushCommand(71, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3PD8_0 = function (data) {
data = data | 0;
this.pushCommand(72, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3PD8_1 = function (data) {
data = data | 0;
this.pushCommand(73, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3PD16 = function (data) {
data = data | 0;
this.pushCommand(74, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3PCD32 = function (data) {
data = data | 0;
this.pushCommand(75, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2X8_0 = function (data) {
data = data | 0;
this.pushCommand(76, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2X8_1 = function (data) {
data = data | 0;
this.pushCommand(77, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2X8_2 = function (data) {
data = data | 0;
this.pushCommand(78, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2X8_3 = function (data) {
data = data | 0;
this.pushCommand(79, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2X16_0 = function (data) {
data = data | 0;
this.pushCommand(80, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2X16_1 = function (data) {
data = data | 0;
this.pushCommand(81, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2X32 = function (data) {
data = data | 0;
this.pushCommand(82, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2Y8_0 = function (data) {
data = data | 0;
this.pushCommand(83, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2Y8_1 = function (data) {
data = data | 0;
this.pushCommand(84, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2Y8_2 = function (data) {
data = data | 0;
this.pushCommand(85, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2Y8_3 = function (data) {
data = data | 0;
this.pushCommand(86, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2Y16_0 = function (data) {
data = data | 0;
this.pushCommand(87, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2Y16_1 = function (data) {
data = data | 0;
this.pushCommand(88, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG2Y32 = function (data) {
data = data | 0;
this.pushCommand(89, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3X8_0 = function (data) {
data = data | 0;
this.pushCommand(90, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3X8_1 = function (data) {
data = data | 0;
this.pushCommand(91, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3X8_2 = function (data) {
data = data | 0;
this.pushCommand(92, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3X8_3 = function (data) {
data = data | 0;
this.pushCommand(93, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3X16_0 = function (data) {
data = data | 0;
this.pushCommand(94, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3X16_1 = function (data) {
data = data | 0;
this.pushCommand(95, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3X32 = function (data) {
data = data | 0;
this.pushCommand(96, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3Y8_0 = function (data) {
data = data | 0;
this.pushCommand(97, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3Y8_1 = function (data) {
data = data | 0;
this.pushCommand(98, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3Y8_2 = function (data) {
data = data | 0;
this.pushCommand(99, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3Y8_3 = function (data) {
data = data | 0;
this.pushCommand(100, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3Y16_0 = function (data) {
data = data | 0;
this.pushCommand(101, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3Y16_1 = function (data) {
data = data | 0;
this.pushCommand(102, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBG3Y32 = function (data) {
data = data | 0;
this.pushCommand(103, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWIN0XCOORDRight8 = function (data) {
data = data | 0;
this.pushCommand(104, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWIN0XCOORDLeft8 = function (data) {
data = data | 0;
this.pushCommand(105, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWIN0XCOORD16 = function (data) {
data = data | 0;
this.pushCommand(106, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWIN1XCOORDRight8 = function (data) {
data = data | 0;
this.pushCommand(107, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWIN1XCOORDLeft8 = function (data) {
data = data | 0;
this.pushCommand(108, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWIN1XCOORD16 = function (data) {
data = data | 0;
this.pushCommand(109, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWINXCOORD32 = function (data) {
data = data | 0;
this.pushCommand(110, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWIN0YCOORDBottom8 = function (data) {
data = data | 0;
this.pushCommand(111, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWIN0YCOORDTop8 = function (data) {
data = data | 0;
this.pushCommand(112, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWIN0YCOORD16 = function (data) {
data = data | 0;
this.pushCommand(113, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWIN1YCOORDBottom8 = function (data) {
data = data | 0;
this.pushCommand(114, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWIN1YCOORDTop8 = function (data) {
data = data | 0;
this.pushCommand(115, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWIN1YCOORD16 = function (data) {
data = data | 0;
this.pushCommand(116, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWINYCOORD32 = function (data) {
data = data | 0;
this.pushCommand(117, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWIN0IN8 = function (data) {
data = data | 0;
this.pushCommand(118, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWIN1IN8 = function (data) {
data = data | 0;
this.pushCommand(119, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWININ16 = function (data) {
data = data | 0;
this.pushCommand(120, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWINOUT8 = function (data) {
data = data | 0;
this.pushCommand(121, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWINOBJIN8 = function (data) {
data = data | 0;
this.pushCommand(122, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWINOUT16 = function (data) {
data = data | 0;
this.pushCommand(123, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeWINCONTROL32 = function (data) {
data = data | 0;
this.pushCommand(124, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeMOSAIC8_0 = function (data) {
data = data | 0;
this.pushCommand(125, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeMOSAIC8_1 = function (data) {
data = data | 0;
this.pushCommand(126, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeMOSAIC16 = function (data) {
data = data | 0;
this.pushCommand(127, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBLDCNT8_0 = function (data) {
data = data | 0;
this.pushCommand(128, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBLDCNT8_1 = function (data) {
data = data | 0;
this.pushCommand(129, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBLDCNT16 = function (data) {
data = data | 0;
this.pushCommand(130, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBLDALPHA8_0 = function (data) {
data = data | 0;
this.pushCommand(131, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBLDALPHA8_1 = function (data) {
data = data | 0;
this.pushCommand(132, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBLDALPHA16 = function (data) {
data = data | 0;
this.pushCommand(133, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBLDCNT32 = function (data) {
data = data | 0;
this.pushCommand(134, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeBLDY8 = function (data) {
data = data | 0;
this.pushCommand(135, data | 0);
}
if (__LITTLE_ENDIAN__) {
GameBoyAdvanceGraphicsRendererShim.prototype.writeVRAM8 =
GameBoyAdvanceGraphicsRendererShim.prototype.writeVRAM16 = function (address, data) {
address = address | 0;
data = data | 0;
this.VRAM16[address & 0xFFFF] = data & 0xFFFF;
this.pushVRAM16(address | 0, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeVRAM32 = function (address, data) {
address = address | 0;
data = data | 0;
this.VRAM32[address & 0x7FFF] = data | 0;
this.pushVRAM32(address | 0, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.readVRAM16 = function (address) {
address = address | 0;
return this.VRAM16[address & 0xFFFF] | 0;
}
GameBoyAdvanceGraphicsRendererShim.prototype.readVRAM32 = function (address) {
address = address | 0;
return this.VRAM32[address & 0x7FFF] | 0;
}
GameBoyAdvanceGraphicsRendererShim.prototype.writePalette16 = function (address, data) {
data = data | 0;
address = address | 0;
this.paletteRAM16[address & 0x1FF] = data & 0xFFFF;
this.pushPAL16(address | 0, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writePalette32 = function (address, data) {
data = data | 0;
address = address | 0;
this.paletteRAM32[address & 0xFF] = data | 0;
this.pushPAL32(address | 0, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.readPalette16 = function (address) {
address = address | 0;
return this.paletteRAM16[address & 0x1FF] | 0;
}
GameBoyAdvanceGraphicsRendererShim.prototype.readPalette32 = function (address) {
address = address | 0;
return this.paletteRAM32[address & 0xFF] | 0;
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeOAM16 = function (address, data) {
address = address | 0;
data = data | 0;
this.OAMRAM16[address & 0x1FF] = data & 0xFFFF;
this.pushOAM16(address | 0, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeOAM32 = function (address, data) {
address = address | 0;
data = data | 0;
this.OAMRAM32[address & 0xFF] = data | 0;
this.pushOAM32(address | 0, data | 0);
}
GameBoyAdvanceGraphicsRendererShim.prototype.readOAM16 = function (address) {
address = address | 0;
return this.OAMRAM16[address & 0x1FF] | 0;
}
GameBoyAdvanceGraphicsRendererShim.prototype.readOAM32 = function (address) {
address = address | 0;
return this.OAMRAM32[address & 0xFF] | 0;
}
}
else {
GameBoyAdvanceGraphicsRendererShim.prototype.writeVRAM8 =
GameBoyAdvanceGraphicsRendererShim.prototype.writeVRAM16 = function (address, data) {
address <<= 1;
address &= 0x1FFFE;
this.VRAM[address] = data & 0xFF;
this.VRAM[address + 1] = (data >> 8) & 0xFF;
this.pushVRAM16(address, data);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeVRAM32 = function (address, data) {
address <<= 2;
address &= 0x1FFFC;
this.VRAM[address] = data & 0xFF;
this.VRAM[address + 1] = (data >> 8) & 0xFF;
this.VRAM[address + 2] = (data >> 16) & 0xFF;
this.VRAM[address + 3] = data >>> 24;
this.pushVRAM32(address, data);
}
GameBoyAdvanceGraphicsRendererShim.prototype.readVRAM16 = function (address) {
address <<= 1;
address &= 0x1FFFE;
return this.VRAM[address] | (this.VRAM[address + 1] << 8);
}
GameBoyAdvanceGraphicsRendererShim.prototype.readVRAM32 = function (address) {
address <<= 2;
address &= 0x1FFFC;
return this.VRAM[address] | (this.VRAM[address + 1] << 8) | (this.VRAM[address + 2] << 16) | (this.VRAM[address + 3] << 24);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writePalette16 = function (address, data) {
this.paletteRAM[address << 1] = data & 0xFF;
this.paletteRAM[(address << 1) + 1] = data >> 8;
this.pushPAL16(address, data);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writePalette32 = function (address, data) {
address <<= 2;
this.paletteRAM[address] = data & 0xFF;
this.paletteRAM[address | 1] = (data >> 8) & 0xFF;
this.paletteRAM[address | 2] = (data >> 16) & 0xFF;
this.paletteRAM[address | 3] = data >>> 24;
address >>= 2;
this.pushPAL32(address, data);
}
GameBoyAdvanceGraphicsRendererShim.prototype.readPalette16 = function (address) {
address &= 0x3FE;
return this.paletteRAM[address] | (this.paletteRAM[address | 1] << 8);
}
GameBoyAdvanceGraphicsRendererShim.prototype.readPalette32 = function (address) {
address &= 0x3FC;
return this.paletteRAM[address] | (this.paletteRAM[address | 1] << 8) | (this.paletteRAM[address | 2] << 16) | (this.paletteRAM[address | 3] << 24);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeOAM16 = function (address, data) {
address &= 0x1FF;
this.OAMRAM[address << 1] = data & 0xFF;
this.OAMRAM[(address << 1) | 1] = data >> 8;
this.pushOAM16(address, data);
}
GameBoyAdvanceGraphicsRendererShim.prototype.writeOAM32 = function (address, data) {
address &= 0xFF;
address <<= 2;
this.OAMRAM[address] = data & 0xFF;
this.OAMRAM[address + 1] = (data >> 8) & 0xFF;
this.OAMRAM[address + 2] = (data >> 16) & 0xFF;
this.OAMRAM[address + 3] = data >>> 24;
address >>= 2;
this.pushOAM32(address, data);
}
GameBoyAdvanceGraphicsRendererShim.prototype.readOAM16 = function (address) {
address &= 0x1FF;
address <<= 1;
return this.OAMRAM[address] | (this.OAMRAM[address | 1] << 8);
}
GameBoyAdvanceGraphicsRendererShim.prototype.readOAM32 = function (address) {
address &= 0xFF;
address <<= 2;
return this.OAMRAM[address] | (this.OAMRAM[address | 1] << 8) | (this.OAMRAM[address | 2] << 16) | (this.OAMRAM[address | 3] << 24);
}
}
GameBoyAdvanceGraphicsRendererShim.prototype.readVRAM8 = function (address) {
address = address | 0;
return this.VRAM[address & 0x1FFFF] | 0;
}
GameBoyAdvanceGraphicsRendererShim.prototype.readOAM = function (address) {
address = address | 0;
return this.OAMRAM[address & 0x3FF] | 0;
}
GameBoyAdvanceGraphicsRendererShim.prototype.readPalette8 = function (address) {
address = address | 0;
return this.paletteRAM[address & 0x3FF] | 0;
}

View file

@ -0,0 +1,116 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceWindowRenderer(compositor) {
//Get a layer compositor that we'll send our parameters for windowing to:
this.compositor = compositor;
}
GameBoyAdvanceWindowRenderer.prototype.initialize = function () {
//Initialize the compositor:
this.compositor.initialize();
//Right windowing coordinate:
this.WINXCoordRight = 0;
//Left windowing coordinate:
this.WINXCoordLeft = 0;
//Bottom windowing coordinate:
this.WINYCoordBottom = 0;
//Top windowing coordinate:
this.WINYCoordTop = 0;
//Layer masking & color effects control:
this.windowDisplayControl = 0;
//Need to update the color effects status in the compositor:
this.preprocess();
}
GameBoyAdvanceWindowRenderer.prototype.renderScanLine = function (line, toRender) {
line = line | 0;
toRender = toRender | 0;
//Windowing can disable out further layers:
toRender = toRender & this.windowDisplayControl;
//Check if we're doing windowing for the current line:
if (this.checkYRange(line | 0)) {
//Windowing is active for the current line:
var right = this.WINXCoordRight | 0;
var left = this.WINXCoordLeft | 0;
if ((left | 0) <= (right | 0)) {
//Windowing is left to right like expected:
left = Math.min(left | 0, 240) | 0;
right = Math.min(right | 0, 240) | 0;
//Render left coordinate to right coordinate:
this.compositor.renderScanLine(left | 0, right | 0, toRender | 0);
}
else {
//Invalid horizontal windowing coordinates, so invert horizontal windowing range:
left = Math.min(left | 0, 240) | 0;
right = Math.min(right | 0, 240) | 0;
//Render pixel 0 to right coordinate:
this.compositor.renderScanLine(0, right | 0, toRender | 0);
//Render left coordinate to last pixel:
this.compositor.renderScanLine(left | 0, 240, toRender | 0);
}
}
}
GameBoyAdvanceWindowRenderer.prototype.checkYRange = function (line) {
line = line | 0;
var bottom = this.WINYCoordBottom | 0;
var top = this.WINYCoordTop | 0;
if ((top | 0) <= (bottom | 0)) {
//Windowing is top to bottom like expected:
return ((line | 0) >= (top | 0) && (line | 0) < (bottom | 0));
}
else {
//Invalid vertical windowing coordinates, so invert vertical windowing range:
return ((line | 0) < (top | 0) || (line | 0) >= (bottom | 0));
}
}
GameBoyAdvanceWindowRenderer.prototype.preprocess = function () {
//Update the color effects status in the compositor:
this.compositor.preprocess(this.windowDisplayControl & 0x20);
}
GameBoyAdvanceWindowRenderer.prototype.writeWINXCOORDRight8 = function (data) {
data = data | 0;
//Right windowing coordinate:
this.WINXCoordRight = data | 0;
}
GameBoyAdvanceWindowRenderer.prototype.writeWINXCOORDLeft8 = function (data) {
data = data | 0;
//Left windowing coordinate:
this.WINXCoordLeft = data | 0;
}
GameBoyAdvanceWindowRenderer.prototype.writeWINYCOORDBottom8 = function (data) {
data = data | 0;
//Bottom windowing coordinate:
this.WINYCoordBottom = data | 0;
}
GameBoyAdvanceWindowRenderer.prototype.writeWINYCOORDTop8 = function (data) {
data = data | 0;
//Top windowing coordinate:
this.WINYCoordTop = data | 0;
}
GameBoyAdvanceWindowRenderer.prototype.writeWINXCOORD16 = function (data) {
data = data | 0;
//Right windowing coordinate:
this.WINXCoordRight = data & 0xFF;
//Left windowing coordinate:
this.WINXCoordLeft = data >> 8;
}
GameBoyAdvanceWindowRenderer.prototype.writeWINYCOORD16 = function (data) {
data = data | 0;
//Bottom windowing coordinate:
this.WINYCoordBottom = data & 0xFF;
//Top windowing coordinate:
this.WINYCoordTop = data >> 8;
}
GameBoyAdvanceWindowRenderer.prototype.writeWININ8 = function (data) {
data = data | 0;
//Layer masking & color effects control:
this.windowDisplayControl = data | 0;
//Need to update the color effects status in the compositor:
this.preprocess();
}

View file

@ -0,0 +1,587 @@
"use strict";
/*
Copyright (C) 2012-2016 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
importScripts("../../includes/TypedArrayShim.js");
importScripts("Renderer.js");
importScripts("BGTEXT.js");
importScripts("BG2FrameBuffer.js");
importScripts("BGMatrix.js");
importScripts("AffineBG.js");
importScripts("ColorEffects.js");
importScripts("Mosaic.js");
importScripts("OBJ.js");
importScripts("OBJWindow.js");
importScripts("Window.js");
importScripts("Compositor.js");
var renderer = null;
var gfxBuffers = null;
var gfxCounters = null;
var gfxCommandBuffer = null;
var gfxCommandCounters = null;
var gfxCommandBufferMask = 1;
var gfxLineCounter = null;
var gfxLinesCPU = 0;
var gfxLinesGPU = 0;
self.onmessage = function (event) {
var data = event.data;
switch (data.messageID | 0) {
case 0:
assignStaticBuffers(data.gfxBuffers, data.gfxCounters, data.gfxLineCounter);
break;
case 1:
initializeRenderer(!!data.skippingBIOS);
break;
default:
assignDynamicBuffers(data.gfxCommandBuffer, data.gfxCommandCounters);
waitForVSync();
}
}
function copyBuffer(swizzledFrame) {
//Push a frame of graphics to the blitter handle:
//Load the counter values:
var start = Atomics.load(gfxCounters, 0) | 0; //Written by the other thread.
var end = gfxCounters[1] | 0; //Written by this thread.
//Check if buffer is full:
if ((end | 0) == (((start | 0) + 2) | 0)) {
//Skip copying a frame out:
return;
}
//Copy samples into the ring buffer:
//Hardcoded for 2 buffers for a triple buffer effect:
gfxBuffers[end & 0x1].set(swizzledFrame);
//Increment the ending position counter by 1:
//Atomic to commit the counter to memory:
Atomics.store(gfxCounters, 1, ((end | 0) + 1) | 0);
}
function waitForVSync() {
//Only breaks if the buffer gets resized:
while ((Atomics.load(gfxCommandCounters, 2) | 0) == 0) {
//Process the current buffer:
processCommands();
//Main thread calls wake to unblock us here:
Atomics.wait(gfxCounters, 2, 0);
}
//Empty old buffer before getting a new buffer to refer to:
processCommands();
}
function initializeRenderer(skippingBIOS) {
skippingBIOS = !!skippingBIOS;
renderer = new GameBoyAdvanceGraphicsRendererOffthread(!!skippingBIOS);
}
function assignStaticBuffers(gfxb, gfxc, cmdl) {
gfxBuffers = gfxb;
gfxCounters = gfxc;
gfxLineCounter = cmdl;
}
function assignDynamicBuffers(cmdb, cmdc) {
gfxCommandBuffer = cmdb;
gfxCommandCounters = cmdc;
var gfxCommandBufferLength = gfxCommandBuffer.length | 0;
gfxCommandBufferMask = ((gfxCommandBufferLength | 0) - 1) | 0;
}
function processCommands() {
//Load the counter values:
var start = gfxCommandCounters[0] | 0; //Written by this thread.
var end = Atomics.load(gfxCommandCounters, 1) | 0; //Written by the other thread.
gfxLinesCPU = Atomics.load(gfxLineCounter, 0) | 0; //Keep atomic; IonMonkey thinks this is dead code if it's removed.
//Don't process if nothing to process:
if ((end | 0) == (start | 0)) {
//Buffer is empty:
return;
}
//Dispatch commands:
var startCorrected = start & gfxCommandBufferMask;
var endCorrected = end & gfxCommandBufferMask;
do {
//Read a command:
dispatchCommand(gfxCommandBuffer[startCorrected | 0] | 0, gfxCommandBuffer[startCorrected | 1] | 0);
//Increment by two since we're reading the command code and corresponding data after it:
startCorrected = ((startCorrected | 0) + 2) & gfxCommandBufferMask;
} while ((startCorrected | 0) != (endCorrected | 0));
//Update the starting position counter to match the end position:
Atomics.store(gfxCommandCounters, 0, end | 0);
}
function dispatchCommand(command, data) {
command = command | 0;
data = data | 0;
/*
We squeeze some address bits as a portion of the command code.
The top bits will be the actual command, the bottom ones will be the address,
unless of course the top bits are zero, for which then it's purely a command code:
*/
switch (command >> 16) {
//IO:
case 0:
dispatchIOCommand(command | 0, data | 0);
break;
//VRAM 16-BIT:
case 1:
renderer.writeVRAM16(command & 0xFFFF, data | 0);
break;
//VRAM 32-BIT:
case 2:
renderer.writeVRAM32(command & 0x7FFF, data | 0);
break;
//Palette 16-BIT:
case 3:
renderer.writePalette16(command & 0x1FF, data | 0);
break;
//Palette 32-BIT:
case 4:
renderer.writePalette32(command & 0xFF, data | 0);
break;
//OAM 16-BIT:
case 5:
renderer.writeOAM16(command & 0x1FF, data | 0);
break;
//OAM 32-BIT:
default:
renderer.writeOAM32(command & 0xFF, data | 0);
}
}
function dispatchIOCommand(command, data) {
command = command | 0;
data = data | 0;
switch (command | 0) {
case 0:
decodeInternalCommand(data | 0);
break;
case 1:
renderer.writeDISPCNT8_0(data | 0);
break;
case 2:
renderer.writeDISPCNT8_1(data | 0);
break;
case 3:
renderer.writeDISPCNT8_2(data | 0);
break;
case 4:
renderer.writeDISPCNT16(data | 0);
break;
case 5:
renderer.writeDISPCNT32(data | 0);
break;
case 6:
renderer.writeBG0CNT8_0(data | 0);
break;
case 7:
renderer.writeBG0CNT8_1(data | 0);
break;
case 8:
renderer.writeBG0CNT16(data | 0);
break;
case 9:
renderer.writeBG1CNT8_0(data | 0);
break;
case 10:
renderer.writeBG1CNT8_1(data | 0);
break;
case 11:
renderer.writeBG1CNT16(data | 0);
break;
case 12:
renderer.writeBG0BG1CNT32(data | 0);
break;
case 13:
renderer.writeBG2CNT8_0(data | 0);
break;
case 14:
renderer.writeBG2CNT8_1(data | 0);
break;
case 15:
renderer.writeBG2CNT16(data | 0);
break;
case 16:
renderer.writeBG3CNT8_0(data | 0);
break;
case 17:
renderer.writeBG3CNT8_1(data | 0);
break;
case 18:
renderer.writeBG3CNT16(data | 0);
break;
case 19:
renderer.writeBG2BG3CNT32(data | 0);
break;
case 20:
renderer.writeBG0HOFS8_0(data | 0);
break;
case 21:
renderer.writeBG0HOFS8_1(data | 0);
break;
case 22:
renderer.writeBG0HOFS16(data | 0);
break;
case 23:
renderer.writeBG0VOFS8_0(data | 0);
break;
case 24:
renderer.writeBG0VOFS8_1(data | 0);
break;
case 25:
renderer.writeBG0VOFS16(data | 0);
break;
case 26:
renderer.writeBG0OFS32(data | 0);
break;
case 27:
renderer.writeBG1HOFS8_0(data | 0);
break;
case 28:
renderer.writeBG1HOFS8_1(data | 0);
break;
case 29:
renderer.writeBG1HOFS16(data | 0);
break;
case 30:
renderer.writeBG1VOFS8_0(data | 0);
break;
case 31:
renderer.writeBG1VOFS8_1(data | 0);
break;
case 32:
renderer.writeBG1VOFS16(data | 0);
break;
case 33:
renderer.writeBG1OFS32(data | 0);
break;
case 34:
renderer.writeBG2HOFS8_0(data | 0);
break;
case 35:
renderer.writeBG2HOFS8_1(data | 0);
break;
case 36:
renderer.writeBG2HOFS16(data | 0);
break;
case 37:
renderer.writeBG2VOFS8_0(data | 0);
break;
case 38:
renderer.writeBG2VOFS8_1(data | 0);
break;
case 39:
renderer.writeBG2VOFS16(data | 0);
break;
case 40:
renderer.writeBG2OFS32(data | 0);
break;
case 41:
renderer.writeBG3HOFS8_0(data | 0);
break;
case 42:
renderer.writeBG3HOFS8_1(data | 0);
break;
case 43:
renderer.writeBG3HOFS16(data | 0);
break;
case 44:
renderer.writeBG3VOFS8_0(data | 0);
break;
case 45:
renderer.writeBG3VOFS8_1(data | 0);
break;
case 46:
renderer.writeBG3VOFS16(data | 0);
break;
case 47:
renderer.writeBG3OFS32(data | 0);
break;
case 48:
renderer.writeBG2PA8_0(data | 0);
break;
case 49:
renderer.writeBG2PA8_1(data | 0);
break;
case 50:
renderer.writeBG2PA16(data | 0);
break;
case 51:
renderer.writeBG2PB8_0(data | 0);
break;
case 52:
renderer.writeBG2PB8_1(data | 0);
break;
case 53:
renderer.writeBG2PB16(data | 0);
break;
case 54:
renderer.writeBG2PAB32(data | 0);
break;
case 55:
renderer.writeBG2PC8_0(data | 0);
break;
case 56:
renderer.writeBG2PC8_1(data | 0);
break;
case 57:
renderer.writeBG2PC16(data | 0);
break;
case 58:
renderer.writeBG2PD8_0(data | 0);
break;
case 59:
renderer.writeBG2PD8_1(data | 0);
break;
case 60:
renderer.writeBG2PD16(data | 0);
break;
case 61:
renderer.writeBG2PCD32(data | 0);
break;
case 62:
renderer.writeBG3PA8_0(data | 0);
break;
case 63:
renderer.writeBG3PA8_1(data | 0);
break;
case 64:
renderer.writeBG3PA16(data | 0);
break;
case 65:
renderer.writeBG3PB8_0(data | 0);
break;
case 66:
renderer.writeBG3PB8_1(data | 0);
break;
case 67:
renderer.writeBG3PB16(data | 0);
break;
case 68:
renderer.writeBG3PAB32(data | 0);
break;
case 69:
renderer.writeBG3PC8_0(data | 0);
break;
case 70:
renderer.writeBG3PC8_1(data | 0);
break;
case 71:
renderer.writeBG3PC16(data | 0);
break;
case 72:
renderer.writeBG3PD8_0(data | 0);
break;
case 73:
renderer.writeBG3PD8_1(data | 0);
break;
case 74:
renderer.writeBG3PD16(data | 0);
break;
case 75:
renderer.writeBG3PCD32(data | 0);
break;
case 76:
renderer.writeBG2X8_0(data | 0);
break;
case 77:
renderer.writeBG2X8_1(data | 0);
break;
case 78:
renderer.writeBG2X8_2(data | 0);
break;
case 79:
renderer.writeBG2X8_3(data | 0);
break;
case 80:
renderer.writeBG2X16_0(data | 0);
break;
case 81:
renderer.writeBG2X16_1(data | 0);
break;
case 82:
renderer.writeBG2X32(data | 0);
break;
case 83:
renderer.writeBG2Y8_0(data | 0);
break;
case 84:
renderer.writeBG2Y8_1(data | 0);
break;
case 85:
renderer.writeBG2Y8_2(data | 0);
break;
case 86:
renderer.writeBG2Y8_3(data | 0);
break;
case 87:
renderer.writeBG2Y16_0(data | 0);
break;
case 88:
renderer.writeBG2Y16_1(data | 0);
break;
case 89:
renderer.writeBG2Y32(data | 0);
break;
case 90:
renderer.writeBG3X8_0(data | 0);
break;
case 91:
renderer.writeBG3X8_1(data | 0);
break;
case 92:
renderer.writeBG3X8_2(data | 0);
break;
case 93:
renderer.writeBG3X8_3(data | 0);
break;
case 94:
renderer.writeBG3X16_0(data | 0);
break;
case 95:
renderer.writeBG3X16_1(data | 0);
break;
case 96:
renderer.writeBG3X32(data | 0);
break;
case 97:
renderer.writeBG3Y8_0(data | 0);
break;
case 98:
renderer.writeBG3Y8_1(data | 0);
break;
case 99:
renderer.writeBG3Y8_2(data | 0);
break;
case 100:
renderer.writeBG3Y8_3(data | 0);
break;
case 101:
renderer.writeBG3Y16_0(data | 0);
break;
case 102:
renderer.writeBG3Y16_1(data | 0);
break;
case 103:
renderer.writeBG3Y32(data | 0);
break;
case 104:
renderer.writeWIN0XCOORDRight8(data | 0);
break;
case 105:
renderer.writeWIN0XCOORDLeft8(data | 0);
break;
case 106:
renderer.writeWIN0XCOORD16(data | 0);
break;
case 107:
renderer.writeWIN1XCOORDRight8(data | 0);
break;
case 108:
renderer.writeWIN1XCOORDLeft8(data | 0);
break;
case 109:
renderer.writeWIN1XCOORD16(data | 0);
break;
case 110:
renderer.writeWINXCOORD32(data | 0);
break;
case 111:
renderer.writeWIN0YCOORDBottom8(data | 0);
break;
case 112:
renderer.writeWIN0YCOORDTop8(data | 0);
break;
case 113:
renderer.writeWIN0YCOORD16(data | 0);
break;
case 114:
renderer.writeWIN1YCOORDBottom8(data | 0);
break;
case 115:
renderer.writeWIN1YCOORDTop8(data | 0);
break;
case 116:
renderer.writeWIN1YCOORD16(data | 0);
break;
case 117:
renderer.writeWINYCOORD32(data | 0);
break;
case 118:
renderer.writeWIN0IN8(data | 0);
break;
case 119:
renderer.writeWIN1IN8(data | 0);
break;
case 120:
renderer.writeWININ16(data | 0);
break;
case 121:
renderer.writeWINOUT8(data | 0);
break;
case 122:
renderer.writeWINOBJIN8(data | 0);
break;
case 123:
renderer.writeWINOUT16(data | 0);
break;
case 124:
renderer.writeWINCONTROL32(data | 0);
break;
case 125:
renderer.writeMOSAIC8_0(data | 0);
break;
case 126:
renderer.writeMOSAIC8_1(data | 0);
break;
case 127:
renderer.writeMOSAIC16(data | 0);
break;
case 128:
renderer.writeBLDCNT8_0(data | 0);
break;
case 129:
renderer.writeBLDCNT8_1(data | 0);
break;
case 130:
renderer.writeBLDCNT16(data | 0);
break;
case 131:
renderer.writeBLDALPHA8_0(data | 0);
break;
case 132:
renderer.writeBLDALPHA8_1(data | 0);
break;
case 133:
renderer.writeBLDALPHA16(data | 0);
break;
case 134:
renderer.writeBLDCNT32(data | 0);
break;
default:
renderer.writeBLDY8(data | 0);
}
}
function decodeInternalCommand(data) {
data = data | 0;
switch (data | 0) {
case 0:
//Check to see if we need to skip rendering to catch up:
if ((((gfxLinesCPU | 0) - (gfxLinesGPU | 0)) | 0) < 480) {
//Render a scanline:
renderer.renderScanLine();
}
else {
//Update some internal counters to maintain state:
renderer.updateReferenceCounters();
}
//Clock the scanline counter:
renderer.incrementScanLine();
//Increment how many scanlines we've received out:
gfxLinesGPU = ((gfxLinesGPU | 0) + 1) | 0;
break;
default:
//Check to see if we need to skip rendering to catch up:
if ((((gfxLinesCPU | 0) - (gfxLinesGPU | 0)) | 0) < 480) {
//Push out a frame of graphics:
renderer.prepareFrame();
}
}
}

View file

@ -0,0 +1,401 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceDMA0(IOCore) {
this.IOCore = IOCore;
}
GameBoyAdvanceDMA0.prototype.DMA_ENABLE_TYPE = [ //DMA Channel 0 Mapping:
0x1,
0x2,
0x4,
0x40
];
GameBoyAdvanceDMA0.prototype.initialize = function () {
this.enabled = 0;
this.pending = 0;
this.source = 0;
this.sourceShadow = 0;
this.destination = 0;
this.destinationShadow = 0;
this.wordCount = 0;
this.wordCountShadow = 0;
this.irqFlagging = 0;
this.dmaType = 0;
this.is32Bit = 0;
this.repeat = 0;
this.sourceControl = 0;
this.destinationControl = 0;
this.DMACore = this.IOCore.dma;
this.memory = this.IOCore.memory;
this.gfxState = this.IOCore.gfxState;
this.irq = this.IOCore.irq;
}
GameBoyAdvanceDMA0.prototype.validateDMASource = function (address) {
address = address | 0;
if ((address | 0) >= 0x2000000) {
if ((address | 0) <= 0x7FFFFFF || (address | 0) >= 0xE000000) {
this.source = address | 0;
}
}
}
GameBoyAdvanceDMA0.prototype.validateDMADestination = function (address) {
address = address | 0;
if ((address | 0) <= 0x7FFFFFF) {
this.destination = address | 0;
}
}
GameBoyAdvanceDMA0.prototype.writeDMASource8_0 = function (data) {
data = data | 0;
var source = this.source & 0xFFFFF00;
data = data & 0xFF;
source = source | data;
this.validateDMASource(source | 0);
}
GameBoyAdvanceDMA0.prototype.writeDMASource8_1 = function (data) {
data = data | 0;
var source = this.source & 0xFFF00FF;
data = data & 0xFF;
source = source | (data << 8);
this.validateDMASource(source | 0);
}
GameBoyAdvanceDMA0.prototype.writeDMASource8_2 = function (data) {
data = data | 0;
var source = this.source & 0xF00FFFF;
data = data & 0xFF;
source = source | (data << 16);
this.validateDMASource(source | 0);
}
GameBoyAdvanceDMA0.prototype.writeDMASource8_3 = function (data) {
data = data | 0;
var source = this.source & 0xFFFFFF;
data = data & 0xF;
source = source | (data << 24);
this.validateDMASource(source | 0);
}
GameBoyAdvanceDMA0.prototype.writeDMASource16_0 = function (data) {
data = data | 0;
var source = this.source & 0xFFF0000;
data = data & 0xFFFF;
source = source | data;
this.validateDMASource(source | 0);
}
GameBoyAdvanceDMA0.prototype.writeDMASource16_1 = function (data) {
data = data | 0;
var source = this.source & 0xFFFF;
data = data & 0xFFF;
source = source | (data << 16);
this.validateDMASource(source | 0);
}
GameBoyAdvanceDMA0.prototype.writeDMASource32 = function (data) {
data = data | 0;
var source = data & 0xFFFFFFF;
this.validateDMASource(source | 0);
}
GameBoyAdvanceDMA0.prototype.writeDMADestination8_0 = function (data) {
data = data | 0;
var destination = this.destination & 0xFFFFF00;
data = data & 0xFF;
destination = destination | data;
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA0.prototype.writeDMADestination8_1 = function (data) {
data = data | 0;
var destination = this.destination & 0xFFF00FF;
data = data & 0xFF;
destination = destination | (data << 8);
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA0.prototype.writeDMADestination8_2 = function (data) {
data = data | 0;
var destination = this.destination & 0xF00FFFF;
data = data & 0xFF;
destination = destination | (data << 16);
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA0.prototype.writeDMADestination8_3 = function (data) {
data = data | 0;
var destination = this.destination & 0xFFFFFF;
data = data & 0xF;
destination = destination | (data << 24);
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA0.prototype.writeDMADestination16_0 = function (data) {
data = data | 0;
var destination = this.destination & 0xFFF0000;
data = data & 0xFFFF;
destination = destination | data;
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA0.prototype.writeDMADestination16_1 = function (data) {
data = data | 0;
var destination = this.destination & 0xFFFF;
data = data & 0xFFF;
destination = destination | (data << 16);
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA0.prototype.writeDMADestination32 = function (data) {
data = data | 0;
var destination = data & 0xFFFFFFF;
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA0.prototype.writeDMAWordCount8_0 = function (data) {
data = data | 0;
this.wordCount = this.wordCount & 0x3F00;
data = data & 0xFF;
this.wordCount = this.wordCount | data;
}
GameBoyAdvanceDMA0.prototype.writeDMAWordCount8_1 = function (data) {
data = data | 0;
this.wordCount = this.wordCount & 0xFF;
data = data & 0x3F;
this.wordCount = this.wordCount | (data << 8);
}
GameBoyAdvanceDMA0.prototype.writeDMAWordCount16 = function (data) {
data = data | 0;
this.wordCount = data & 0x3FFF;
}
GameBoyAdvanceDMA0.prototype.writeDMAControl8_0 = function (data) {
data = data | 0;
this.destinationControl = (data >> 5) & 0x3;
this.sourceControl = this.sourceControl & 0x2;
this.sourceControl = this.sourceControl | ((data >> 7) & 0x1);
}
GameBoyAdvanceDMA0.prototype.writeDMAControl8_1 = function (data) {
data = data | 0;
//Spill state machine clocks:
this.IOCore.updateCoreClocking();
this.sourceControl = (this.sourceControl & 0x1) | ((data & 0x1) << 1);
this.repeat = data & 0x2;
this.is32Bit = data & 0x4;
this.dmaType = (data >> 4) & 0x3;
this.irqFlagging = data & 0x40;
this.enableDMAChannel(data & 0x80);
//Calculate next event:
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceDMA0.prototype.writeDMAControl16 = function (data) {
data = data | 0;
//Spill state machine clocks:
this.IOCore.updateCoreClocking();
this.destinationControl = (data >> 5) & 0x3;
this.sourceControl = (data >> 7) & 0x3;
this.repeat = (data >> 8) & 0x2;
this.is32Bit = (data >> 8) & 0x4;
this.dmaType = (data >> 12) & 0x3;
this.irqFlagging = (data >> 8) & 0x40;
this.enableDMAChannel(data & 0x8000);
//Calculate next event:
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceDMA0.prototype.writeDMAControl32 = function (data) {
data = data | 0;
this.writeDMAWordCount16(data | 0);
this.writeDMAControl16(data >> 16);
}
GameBoyAdvanceDMA0.prototype.readDMAControl8_0 = function () {
var data = this.destinationControl << 5;
data = data | ((this.sourceControl & 0x1) << 7);
return data | 0;
}
GameBoyAdvanceDMA0.prototype.readDMAControl8_1 = function () {
var data = this.sourceControl >> 1;
data = data | this.repeat;
data = data | this.is32Bit;
data = data | (this.dmaType << 4);
data = data | this.irqFlagging;
if ((this.enabled | 0) != 0) {
data = data | 0x80;
}
return data | 0;
}
GameBoyAdvanceDMA0.prototype.readDMAControl16 = function () {
var data = this.destinationControl << 5;
data = data | (this.sourceControl << 7);
data = data | (this.repeat << 8);
data = data | (this.is32Bit << 8);
data = data | (this.dmaType << 12);
data = data | (this.irqFlagging << 8);
if ((this.enabled | 0) != 0) {
data = data | 0x8000;
}
return data | 0;
}
GameBoyAdvanceDMA0.prototype.getMatchStatus = function () {
return this.enabled & this.pending;
}
GameBoyAdvanceDMA0.prototype.requestDMA = function (DMAType) {
DMAType = DMAType | 0;
if ((this.enabled & DMAType) != 0) {
this.pending = DMAType | 0;
this.DMACore.update();
}
}
GameBoyAdvanceDMA0.prototype.enableDMAChannel = function (enabled) {
enabled = enabled | 0;
if ((enabled | 0) != 0) {
//If DMA was previously disabled, reload control registers:
if ((this.enabled | 0) == 0) {
//Flag immediate DMA transfers for processing now:
this.pending = 0x1;
//Shadow copy the word count:
this.wordCountShadow = this.wordCount | 0;
//Shadow copy the source address:
this.sourceShadow = this.source | 0;
//Shadow copy the destination address:
this.destinationShadow = this.destination | 0;
}
//DMA type changed:
this.enabled = this.DMA_ENABLE_TYPE[this.dmaType | 0] | 0;
this.pending = this.pending & this.enabled;
}
else {
//DMA Disabled:
this.enabled = 0;
}
//Run some DMA channel activity checks:
this.DMACore.update();
}
GameBoyAdvanceDMA0.prototype.handleDMACopy = function () {
//Get the addesses:
var source = this.sourceShadow | 0;
var destination = this.destinationShadow | 0;
//Transfer Data:
if ((this.is32Bit | 0) == 4) {
//32-bit Transfer:
this.copy32(source | 0, destination | 0);
}
else {
//16-bit Transfer:
this.copy16(source | 0, destination | 0);
}
}
GameBoyAdvanceDMA0.prototype.copy16 = function (source, destination) {
source = source | 0;
destination = destination | 0;
var data = this.memory.memoryReadDMA16(source | 0) | 0;
this.memory.memoryWriteDMA16(destination | 0, data | 0);
this.decrementWordCount(source | 0, destination | 0, 2);
this.DMACore.updateFetch(data | (data << 16));
}
GameBoyAdvanceDMA0.prototype.copy32 = function (source, destination) {
source = source | 0;
destination = destination | 0;
var data = this.memory.memoryReadDMA32(source | 0) | 0;
this.memory.memoryWriteDMA32(destination | 0, data | 0);
this.decrementWordCount(source | 0, destination | 0, 4);
this.DMACore.updateFetch(data | 0);
}
GameBoyAdvanceDMA0.prototype.decrementWordCount = function (source, destination, transferred) {
source = source | 0;
destination = destination | 0;
transferred = transferred | 0;
//Decrement the word count:
var wordCountShadow = ((this.wordCountShadow | 0) - 1) & 0x3FFF;
if ((wordCountShadow | 0) == 0) {
//DMA transfer ended, handle accordingly:
wordCountShadow = this.finalizeDMA(source | 0, destination | 0, transferred | 0) | 0;
}
else {
//Update addresses:
this.incrementDMAAddresses(source | 0, destination | 0, transferred | 0);
}
//Save the new word count:
this.wordCountShadow = wordCountShadow | 0;
}
GameBoyAdvanceDMA0.prototype.finalizeDMA = function (source, destination, transferred) {
source = source | 0;
destination = destination | 0;
transferred = transferred | 0;
var wordCountShadow = 0;
//Reset pending requests:
this.pending = 0;
//Check Repeat Status:
if ((this.repeat | 0) == 0 || (this.enabled | 0) == 0x1) {
//Disable the enable bit:
this.enabled = 0;
}
else {
//Reload word count:
wordCountShadow = this.wordCount | 0;
}
//Run the DMA channel checks:
this.DMACore.update();
//Check to see if we should flag for IRQ:
this.checkIRQTrigger();
//Update addresses:
this.finalDMAAddresses(source | 0, destination | 0, transferred | 0);
return wordCountShadow | 0;
}
GameBoyAdvanceDMA0.prototype.checkIRQTrigger = function () {
if ((this.irqFlagging | 0) != 0) {
this.irq.requestIRQ(0x100);
}
}
GameBoyAdvanceDMA0.prototype.finalDMAAddresses = function (source, destination, transferred) {
source = source | 0;
destination = destination | 0;
transferred = transferred | 0;
//Update source address:
switch (this.sourceControl | 0) {
case 0: //Increment
case 3: //Forbidden (VBA has it increment)
this.sourceShadow = ((source | 0) + (transferred | 0)) | 0;
break;
case 1: //Decrement
this.sourceShadow = ((source | 0) - (transferred | 0)) | 0;
}
//Update destination address:
switch (this.destinationControl | 0) {
case 0: //Increment
this.destinationShadow = ((destination | 0) + (transferred | 0)) | 0;
break;
case 1: //Decrement
this.destinationShadow = ((destination | 0) - (transferred | 0)) | 0;
break;
case 3: //Reload
this.destinationShadow = this.destination | 0;
}
}
GameBoyAdvanceDMA0.prototype.incrementDMAAddresses = function (source, destination, transferred) {
source = source | 0;
destination = destination | 0;
transferred = transferred | 0;
//Update source address:
switch (this.sourceControl | 0) {
case 0: //Increment
case 3: //Forbidden (VBA has it increment)
this.sourceShadow = ((source | 0) + (transferred | 0)) | 0;
break;
case 1:
this.sourceShadow = ((source | 0) - (transferred | 0)) | 0;
}
//Update destination address:
switch (this.destinationControl | 0) {
case 0: //Increment
case 3: //Increment
this.destinationShadow = ((destination | 0) + (transferred | 0)) | 0;
break;
case 1: //Decrement
this.destinationShadow = ((destination | 0) - (transferred | 0)) | 0;
}
}
GameBoyAdvanceDMA0.prototype.nextEventTime = function () {
var clocks = 0x7FFFFFFF;
switch (this.enabled | 0) {
//V_BLANK
case 0x2:
clocks = this.gfxState.nextVBlankEventTime() | 0;
break;
//H_BLANK:
case 0x4:
clocks = this.gfxState.nextHBlankDMAEventTime() | 0;
}
return clocks | 0;
}

View file

@ -0,0 +1,473 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceDMA1(IOCore) {
this.IOCore = IOCore;
}
GameBoyAdvanceDMA1.prototype.DMA_ENABLE_TYPE = [ //DMA Channel 1 Mapping:
0x1,
0x2,
0x4,
0x8
];
GameBoyAdvanceDMA1.prototype.initialize = function () {
this.enabled = 0;
this.pending = 0;
this.source = 0;
this.sourceShadow = 0;
this.destination = 0;
this.destinationShadow = 0;
this.wordCount = 0;
this.wordCountShadow = 0;
this.irqFlagging = 0;
this.dmaType = 0;
this.is32Bit = 0;
this.repeat = 0;
this.sourceControl = 0;
this.destinationControl = 0;
this.DMACore = this.IOCore.dma;
this.memory = this.IOCore.memory;
this.gfxState = this.IOCore.gfxState;
this.irq = this.IOCore.irq;
this.sound = this.IOCore.sound;
this.wait = this.IOCore.wait;
}
GameBoyAdvanceDMA1.prototype.validateDMASource = function (address) {
address = address | 0;
if ((address | 0) >= 0x2000000) {
this.source = address | 0;
}
}
GameBoyAdvanceDMA1.prototype.validateDMADestination = function (address) {
address = address | 0;
if ((address | 0) <= 0x7FFFFFF) {
this.destination = address | 0;
}
}
GameBoyAdvanceDMA1.prototype.writeDMASource8_0 = function (data) {
data = data | 0;
var source = this.source & 0xFFFFF00;
data = data & 0xFF;
source = source | data;
this.validateDMASource(source | 0);
}
GameBoyAdvanceDMA1.prototype.writeDMASource8_1 = function (data) {
data = data | 0;
var source = this.source & 0xFFF00FF;
data = data & 0xFF;
source = source | (data << 8);
this.validateDMASource(source | 0)
}
GameBoyAdvanceDMA1.prototype.writeDMASource8_2 = function (data) {
data = data | 0;
var source = this.source & 0xF00FFFF;
data = data & 0xFF;
source = ource | (data << 16);
this.validateDMASource(source | 0)
}
GameBoyAdvanceDMA1.prototype.writeDMASource8_3 = function (data) {
data = data | 0;
var source = this.source & 0xFFFFFF;
data = data & 0xF;
source = source | (data << 24);
this.validateDMASource(source | 0)
}
GameBoyAdvanceDMA1.prototype.writeDMASource16_0 = function (data) {
data = data | 0;
var source = this.source & 0xFFF0000;
data = data & 0xFFFF;
source = source | data;
this.validateDMASource(source | 0)
}
GameBoyAdvanceDMA1.prototype.writeDMASource16_1 = function (data) {
data = data | 0;
var source = this.source & 0xFFFF;
data = data & 0xFFF;
source = source | (data << 16);
this.validateDMASource(source | 0)
}
GameBoyAdvanceDMA1.prototype.writeDMASource32 = function (data) {
data = data | 0;
var source = data & 0xFFFFFFF;
this.validateDMASource(source | 0)
}
GameBoyAdvanceDMA1.prototype.writeDMADestination8_0 = function (data) {
data = data | 0;
var destination = this.destination & 0xFFFFF00;
data = data & 0xFF;
destination = destination | data;
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA1.prototype.writeDMADestination8_1 = function (data) {
data = data | 0;
var destination = this.destination & 0xFFF00FF;
data = data & 0xFF;
destination = destination | (data << 8);
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA1.prototype.writeDMADestination8_2 = function (data) {
data = data | 0;
var destination = this.destination & 0xF00FFFF;
data = data & 0xFF;
destination = destination | (data << 16);
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA1.prototype.writeDMADestination8_3 = function (data) {
data = data | 0;
var destination = this.destination & 0xFFFFFF;
data = data & 0xF;
destination = destination | (data << 24);
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA1.prototype.writeDMADestination16_0 = function (data) {
data = data | 0;
var destination = this.destination & 0xFFF0000;
data = data & 0xFFFF;
destination = destination | data;
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA1.prototype.writeDMADestination16_1 = function (data) {
data = data | 0;
var destination = this.destination & 0xFFFF;
data = data & 0xFFF;
destination = destination | (data << 16);
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA1.prototype.writeDMADestination32 = function (data) {
data = data | 0;
var destination = data & 0xFFFFFFF;
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA1.prototype.writeDMAWordCount8_0 = function (data) {
data = data | 0;
this.wordCount = this.wordCount & 0x3F00;
data = data & 0xFF;
this.wordCount = this.wordCount | data;
}
GameBoyAdvanceDMA1.prototype.writeDMAWordCount8_1 = function (data) {
data = data | 0;
this.wordCount = this.wordCount & 0xFF;
data = data & 0x3F;
this.wordCount = this.wordCount | (data << 8);
}
GameBoyAdvanceDMA1.prototype.writeDMAWordCount16 = function (data) {
data = data | 0;
this.wordCount = data & 0x3FFF;
}
GameBoyAdvanceDMA1.prototype.writeDMAControl8_0 = function (data) {
data = data | 0;
this.destinationControl = (data >> 5) & 0x3;
this.sourceControl = this.sourceControl & 0x2;
this.sourceControl = this.sourceControl | ((data >> 7) & 0x1);
}
GameBoyAdvanceDMA1.prototype.writeDMAControl8_1 = function (data) {
data = data | 0;
//Spill state machine clocks:
this.IOCore.updateCoreClocking();
this.sourceControl = (this.sourceControl & 0x1) | ((data & 0x1) << 1);
this.repeat = data & 0x2;
this.is32Bit = data & 0x4;
this.dmaType = (data >> 4) & 0x3;
this.irqFlagging = data & 0x40;
this.enableDMAChannel(data & 0x80);
//Calculate next event:
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceDMA1.prototype.writeDMAControl16 = function (data) {
data = data | 0;
//Spill state machine clocks:
this.IOCore.updateCoreClocking();
this.destinationControl = (data >> 5) & 0x3;
this.sourceControl = (data >> 7) & 0x3;
this.repeat = (data >> 8) & 0x2;
this.is32Bit = (data >> 8) & 0x4;
this.dmaType = (data >> 12) & 0x3;
this.irqFlagging = (data >> 8) & 0x40;
this.enableDMAChannel(data & 0x8000);
//Calculate next event:
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceDMA1.prototype.writeDMAControl32 = function (data) {
data = data | 0;
this.writeDMAWordCount16(data | 0);
this.writeDMAControl16(data >> 16);
}
GameBoyAdvanceDMA1.prototype.readDMAControl8_0 = function () {
var data = this.destinationControl << 5;
data = data | ((this.sourceControl & 0x1) << 7);
return data | 0;
}
GameBoyAdvanceDMA1.prototype.readDMAControl8_1 = function () {
var data = this.sourceControl >> 1;
data = data | this.repeat;
data = data | this.is32Bit;
data = data | (this.dmaType << 4);
data = data | this.irqFlagging;
if ((this.enabled | 0) != 0) {
data = data | 0x80;
}
return data | 0;
}
GameBoyAdvanceDMA1.prototype.readDMAControl16 = function () {
var data = this.destinationControl << 5;
data = data | (this.sourceControl << 7);
data = data | (this.repeat << 8);
data = data | (this.is32Bit << 8);
data = data | (this.dmaType << 12);
data = data | (this.irqFlagging << 8);
if ((this.enabled | 0) != 0) {
data = data | 0x8000;
}
return data | 0;
}
GameBoyAdvanceDMA1.prototype.getMatchStatus = function () {
return this.enabled & this.pending;
}
GameBoyAdvanceDMA1.prototype.soundFIFOARequest = function () {
this.requestDMA(0x8);
}
GameBoyAdvanceDMA1.prototype.requestDMA = function (DMAType) {
DMAType = DMAType | 0;
if ((this.enabled & DMAType) != 0) {
this.pending = DMAType | 0;
this.DMACore.update();
}
}
GameBoyAdvanceDMA1.prototype.enableDMAChannel = function (enabled) {
enabled = enabled | 0;
if ((enabled | 0) != 0) {
//If DMA was previously disabled, reload control registers:
if ((this.enabled | 0) == 0) {
switch (this.dmaType | 0) {
case 0x3:
//Direct Sound DMA Hardwired To Wordcount Of 4:
this.wordCountShadow = 0x4;
break;
case 0:
//Flag immediate DMA transfers for processing now:
this.pending = 0x1;
default:
//Shadow copy the word count:
this.wordCountShadow = this.wordCount | 0;
}
//Shadow copy the source address:
this.sourceShadow = this.source | 0;
//Shadow copy the destination address:
this.destinationShadow = this.destination | 0;
}
//DMA type changed:
this.enabled = this.DMA_ENABLE_TYPE[this.dmaType | 0] | 0;
this.pending = this.pending & this.enabled;
//Assert the FIFO A DMA request signal:
this.sound.checkFIFOAPendingSignal();
}
else {
//DMA Disabled:
this.enabled = 0;
}
//Run some DMA channel activity checks:
this.DMACore.update();
}
GameBoyAdvanceDMA1.prototype.handleDMACopy = function () {
//Get the source addess:
var source = this.sourceShadow | 0;
//Transfer Data:
if ((this.enabled | 0) == 0x8) {
//32-bit Transfer:
this.copySound(source | 0);
}
else {
//Get the destination address:
var destination = this.destinationShadow | 0;
if ((this.is32Bit | 0) == 4) {
//32-bit Transfer:
this.copy32(source | 0, destination | 0);
}
else {
//16-bit Transfer:
this.copy16(source | 0, destination | 0);
}
}
}
GameBoyAdvanceDMA1.prototype.copy16 = function (source, destination) {
source = source | 0;
destination = destination | 0;
var data = this.memory.memoryReadDMAFull16(source | 0) | 0;
this.memory.memoryWriteDMA16(destination | 0, data | 0);
this.decrementWordCount(source | 0, destination | 0, 2);
this.DMACore.updateFetch(data | (data << 16));
}
GameBoyAdvanceDMA1.prototype.copy32 = function (source, destination) {
source = source | 0;
destination = destination | 0;
var data = this.memory.memoryReadDMAFull32(source | 0) | 0;
this.memory.memoryWriteDMA32(destination | 0, data | 0);
this.decrementWordCount(source | 0, destination | 0, 4);
this.DMACore.updateFetch(data | 0);
}
GameBoyAdvanceDMA1.prototype.copySound = function (source) {
source = source | 0;
var data = this.memory.memoryReadDMAFull32(source | 0) | 0;
this.wait.singleClock();
this.IOCore.updateTimerClocking();
this.sound.writeFIFOA32(data | 0);
this.soundDMAUpdate(source | 0);
this.DMACore.updateFetch(data | 0);
}
GameBoyAdvanceDMA1.prototype.decrementWordCount = function (source, destination, transferred) {
source = source | 0;
destination = destination | 0;
transferred = transferred | 0;
//Decrement the word count:
var wordCountShadow = ((this.wordCountShadow | 0) - 1) & 0x3FFF;
if ((wordCountShadow | 0) == 0) {
//DMA transfer ended, handle accordingly:
wordCountShadow = this.finalizeDMA(source | 0, destination | 0, transferred | 0) | 0;
}
else {
//Update addresses:
this.incrementDMAAddresses(source | 0, destination | 0, transferred | 0);
}
//Save the new word count:
this.wordCountShadow = wordCountShadow | 0;
}
GameBoyAdvanceDMA1.prototype.soundDMAUpdate = function (source) {
source = source | 0;
//Decrement the word count:
this.wordCountShadow = ((this.wordCountShadow | 0) - 1) & 0x3FFF;
if ((this.wordCountShadow | 0) == 0) {
//DMA transfer ended, handle accordingly:
//Reset pending requests:
this.pending = 0;
//Check Repeat Status:
if ((this.repeat | 0) == 0) {
//Disable the enable bit:
this.enabled = 0;
}
else {
//Repeating the dma:
//Direct Sound DMA Hardwired To Wordcount Of 4:
this.wordCountShadow = 0x4;
}
//Assert the FIFO A DMA request signal:
this.sound.checkFIFOAPendingSignal();
//Run the DMA channel checks:
this.DMACore.update();
//Check to see if we should flag for IRQ:
this.checkIRQTrigger();
}
//Update source address:
switch (this.sourceControl | 0) {
case 0: //Increment
case 3: //Forbidden (VBA has it increment)
this.sourceShadow = ((source | 0) + 4) | 0;
break;
case 1:
this.sourceShadow = ((source | 0) - 4) | 0;
}
}
GameBoyAdvanceDMA1.prototype.finalizeDMA = function (source, destination, transferred) {
source = source | 0;
destination = destination | 0;
transferred = transferred | 0;
var wordCountShadow = 0;
//Reset pending requests:
this.pending = 0;
//Check Repeat Status:
if ((this.repeat | 0) == 0 || (this.enabled | 0) == 0x1) {
//Disable the enable bit:
this.enabled = 0;
}
else {
//Repeating the dma:
//Reload word count:
wordCountShadow = this.wordCount | 0;
}
//Assert the FIFO A DMA request signal:
this.sound.checkFIFOAPendingSignal();
//Run the DMA channel checks:
this.DMACore.update();
//Check to see if we should flag for IRQ:
this.checkIRQTrigger();
//Update addresses:
this.finalDMAAddresses(source | 0, destination | 0, transferred | 0);
return wordCountShadow | 0;
}
GameBoyAdvanceDMA1.prototype.checkIRQTrigger = function () {
if ((this.irqFlagging | 0) != 0) {
this.irq.requestIRQ(0x200);
}
}
GameBoyAdvanceDMA1.prototype.finalDMAAddresses = function (source, destination, transferred) {
source = source | 0;
destination = destination | 0;
transferred = transferred | 0;
//Update source address:
switch (this.sourceControl | 0) {
case 0: //Increment
case 3: //Forbidden (VBA has it increment)
this.sourceShadow = ((source | 0) + (transferred | 0)) | 0;
break;
case 1: //Decrement
this.sourceShadow = ((source | 0) - (transferred | 0)) | 0;
}
//Update destination address:
switch (this.destinationControl | 0) {
case 0: //Increment
this.destinationShadow = ((destination | 0) + (transferred | 0)) | 0;
break;
case 1: //Decrement
this.destinationShadow = ((destination | 0) - (transferred | 0)) | 0;
break;
case 3: //Reload
this.destinationShadow = this.destination | 0;
}
}
GameBoyAdvanceDMA1.prototype.incrementDMAAddresses = function (source, destination, transferred) {
source = source | 0;
destination = destination | 0;
transferred = transferred | 0;
//Update source address:
switch (this.sourceControl | 0) {
case 0: //Increment
case 3: //Forbidden (VBA has it increment)
this.sourceShadow = ((source | 0) + (transferred | 0)) | 0;
break;
case 1:
this.sourceShadow = ((source | 0) - (transferred | 0)) | 0;
}
//Update destination address:
switch (this.destinationControl | 0) {
case 0: //Increment
case 3: //Increment
this.destinationShadow = ((destination | 0) + (transferred | 0)) | 0;
break;
case 1: //Decrement
this.destinationShadow = ((destination | 0) - (transferred | 0)) | 0;
}
}
GameBoyAdvanceDMA1.prototype.nextEventTime = function () {
var clocks = 0x7FFFFFFF;
switch (this.enabled | 0) {
//V_BLANK
case 0x2:
clocks = this.gfxState.nextVBlankEventTime() | 0;
break;
//H_BLANK:
case 0x4:
clocks = this.gfxState.nextHBlankDMAEventTime() | 0;
break;
//FIFO_A:
case 0x8:
clocks = this.sound.nextFIFOAEventTime() | 0;
}
return clocks | 0;
}

View file

@ -0,0 +1,472 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceDMA2(IOCore) {
this.IOCore = IOCore;
}
GameBoyAdvanceDMA2.prototype.DMA_ENABLE_TYPE = [ //DMA Channel 2 Mapping:
0x1,
0x2,
0x4,
0x10
];
GameBoyAdvanceDMA2.prototype.initialize = function () {
this.enabled = 0;
this.pending = 0;
this.source = 0;
this.sourceShadow = 0;
this.destination = 0;
this.destinationShadow = 0;
this.wordCount = 0;
this.wordCountShadow = 0;
this.irqFlagging = 0;
this.dmaType = 0;
this.is32Bit = 0;
this.repeat = 0;
this.sourceControl = 0;
this.destinationControl = 0;
this.DMACore = this.IOCore.dma;
this.memory = this.IOCore.memory;
this.gfxState = this.IOCore.gfxState;
this.irq = this.IOCore.irq;
this.sound = this.IOCore.sound;
this.wait = this.IOCore.wait;
}
GameBoyAdvanceDMA2.prototype.validateDMASource = function (address) {
address = address | 0;
if ((address | 0) >= 0x2000000) {
this.source = address | 0;
}
}
GameBoyAdvanceDMA2.prototype.validateDMADestination = function (address) {
address = address | 0;
if ((address | 0) <= 0x7FFFFFF) {
this.destination = address | 0;
}
}
GameBoyAdvanceDMA2.prototype.writeDMASource8_0 = function (data) {
data = data | 0;
var source = this.source & 0xFFFFF00;
data = data & 0xFF;
source = source | data;
this.validateDMASource(source | 0);
}
GameBoyAdvanceDMA2.prototype.writeDMASource8_1 = function (data) {
data = data | 0;
var source = this.source & 0xFFF00FF;
data = data & 0xFF;
source = source | (data << 8);
this.validateDMASource(source | 0)
}
GameBoyAdvanceDMA2.prototype.writeDMASource8_2 = function (data) {
data = data | 0;
var source = this.source & 0xF00FFFF;
data = data & 0xFF;
source = ource | (data << 16);
this.validateDMASource(source | 0)
}
GameBoyAdvanceDMA2.prototype.writeDMASource8_3 = function (data) {
data = data | 0;
var source = this.source & 0xFFFFFF;
data = data & 0xF;
source = source | (data << 24);
this.validateDMASource(source | 0)
}
GameBoyAdvanceDMA2.prototype.writeDMASource16_0 = function (data) {
data = data | 0;
var source = this.source & 0xFFF0000;
data = data & 0xFFFF;
source = source | data;
this.validateDMASource(source | 0)
}
GameBoyAdvanceDMA2.prototype.writeDMASource16_1 = function (data) {
data = data | 0;
var source = this.source & 0xFFFF;
data = data & 0xFFF;
source = source | (data << 16);
this.validateDMASource(source | 0)
}
GameBoyAdvanceDMA2.prototype.writeDMASource32 = function (data) {
data = data | 0;
var source = data & 0xFFFFFFF;
this.validateDMASource(source | 0)
}
GameBoyAdvanceDMA2.prototype.writeDMADestination8_0 = function (data) {
data = data | 0;
var destination = this.destination & 0xFFFFF00;
data = data & 0xFF;
destination = destination | data;
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA2.prototype.writeDMADestination8_1 = function (data) {
data = data | 0;
var destination = this.destination & 0xFFF00FF;
data = data & 0xFF;
destination = destination | (data << 8);
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA2.prototype.writeDMADestination8_2 = function (data) {
data = data | 0;
var destination = this.destination & 0xF00FFFF;
data = data & 0xFF;
destination = destination | (data << 16);
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA2.prototype.writeDMADestination8_3 = function (data) {
data = data | 0;
var destination = this.destination & 0xFFFFFF;
data = data & 0xF;
destination = destination | (data << 24);
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA2.prototype.writeDMADestination16_0 = function (data) {
data = data | 0;
var destination = this.destination & 0xFFF0000;
data = data & 0xFFFF;
destination = destination | data;
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA2.prototype.writeDMADestination16_1 = function (data) {
data = data | 0;
var destination = this.destination & 0xFFFF;
data = data & 0xFFF;
destination = destination | (data << 16);
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA2.prototype.writeDMADestination32 = function (data) {
data = data | 0;
var destination = data & 0xFFFFFFF;
this.validateDMADestination(destination | 0);
}
GameBoyAdvanceDMA2.prototype.writeDMAWordCount8_0 = function (data) {
data = data | 0;
this.wordCount = this.wordCount & 0x3F00;
data = data & 0xFF;
this.wordCount = this.wordCount | data;
}
GameBoyAdvanceDMA2.prototype.writeDMAWordCount8_1 = function (data) {
data = data | 0;
this.wordCount = this.wordCount & 0xFF;
data = data & 0x3F;
this.wordCount = this.wordCount | (data << 8);
}
GameBoyAdvanceDMA2.prototype.writeDMAWordCount16 = function (data) {
data = data | 0;
this.wordCount = data & 0x3FFF;
}
GameBoyAdvanceDMA2.prototype.writeDMAControl8_0 = function (data) {
data = data | 0;
this.destinationControl = (data >> 5) & 0x3;
this.sourceControl = this.sourceControl & 0x2;
this.sourceControl = this.sourceControl | ((data >> 7) & 0x1);
}
GameBoyAdvanceDMA2.prototype.writeDMAControl8_1 = function (data) {
data = data | 0;
//Spill state machine clocks:
this.IOCore.updateCoreClocking();
this.sourceControl = (this.sourceControl & 0x1) | ((data & 0x1) << 1);
this.repeat = data & 0x2;
this.is32Bit = data & 0x4;
this.dmaType = (data >> 4) & 0x3;
this.irqFlagging = data & 0x40;
this.enableDMAChannel(data & 0x80);
//Calculate next event:
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceDMA2.prototype.writeDMAControl16 = function (data) {
data = data | 0;
//Spill state machine clocks:
this.IOCore.updateCoreClocking();
this.destinationControl = (data >> 5) & 0x3;
this.sourceControl = (data >> 7) & 0x3;
this.repeat = (data >> 8) & 0x2;
this.is32Bit = (data >> 8) & 0x4;
this.dmaType = (data >> 12) & 0x3;
this.irqFlagging = (data >> 8) & 0x40;
this.enableDMAChannel(data & 0x8000);
//Calculate next event:
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceDMA2.prototype.writeDMAControl32 = function (data) {
data = data | 0;
this.writeDMAWordCount16(data | 0);
this.writeDMAControl16(data >> 16);
}
GameBoyAdvanceDMA2.prototype.readDMAControl8_0 = function () {
var data = this.destinationControl << 5;
data = data | ((this.sourceControl & 0x1) << 7);
return data | 0;
}
GameBoyAdvanceDMA2.prototype.readDMAControl8_1 = function () {
var data = this.sourceControl >> 1;
data = data | this.repeat;
data = data | this.is32Bit;
data = data | (this.dmaType << 4);
data = data | this.irqFlagging;
if ((this.enabled | 0) != 0) {
data = data | 0x80;
}
return data | 0;
}
GameBoyAdvanceDMA2.prototype.readDMAControl16 = function () {
var data = this.destinationControl << 5;
data = data | (this.sourceControl << 7);
data = data | (this.repeat << 8);
data = data | (this.is32Bit << 8);
data = data | (this.dmaType << 12);
data = data | (this.irqFlagging << 8);
if ((this.enabled | 0) != 0) {
data = data | 0x8000;
}
return data | 0;
}
GameBoyAdvanceDMA2.prototype.getMatchStatus = function () {
return this.enabled & this.pending;
}
GameBoyAdvanceDMA2.prototype.soundFIFOBRequest = function () {
this.requestDMA(0x10);
}
GameBoyAdvanceDMA2.prototype.requestDMA = function (DMAType) {
DMAType = DMAType | 0;
if ((this.enabled & DMAType) != 0) {
this.pending = DMAType | 0;
this.DMACore.update();
}
}
GameBoyAdvanceDMA2.prototype.enableDMAChannel = function (enabled) {
enabled = enabled | 0;
if ((enabled | 0) != 0) {
//If DMA was previously disabled, reload control registers:
if ((this.enabled | 0) == 0) {
switch (this.dmaType | 0) {
case 0x3:
//Direct Sound DMA Hardwired To Wordcount Of 4:
this.wordCountShadow = 0x4;
break;
case 0:
//Flag immediate DMA transfers for processing now:
this.pending = 0x1;
default:
//Shadow copy the word count:
this.wordCountShadow = this.wordCount | 0;
}
//Shadow copy the source address:
this.sourceShadow = this.source | 0;
//Shadow copy the destination address:
this.destinationShadow = this.destination | 0;
}
//DMA type changed:
this.enabled = this.DMA_ENABLE_TYPE[this.dmaType | 0] | 0;
//this.pending = this.pending & this.enabled;
//Assert the FIFO A DMA request signal:
this.sound.checkFIFOBPendingSignal();
}
else {
//DMA Disabled:
this.enabled = 0;
}
//Run some DMA channel activity checks:
this.DMACore.update();
}
GameBoyAdvanceDMA2.prototype.handleDMACopy = function () {
//Get the source addess:
var source = this.sourceShadow | 0;
//Transfer Data:
if ((this.enabled | 0) == 0x10) {
//32-bit Transfer:
this.copySound(source | 0);
}
else {
//Get the destination address:
var destination = this.destinationShadow | 0;
if ((this.is32Bit | 0) == 4) {
//32-bit Transfer:
this.copy32(source | 0, destination | 0);
}
else {
//16-bit Transfer:
this.copy16(source | 0, destination | 0);
}
}
}
GameBoyAdvanceDMA2.prototype.copy16 = function (source, destination) {
source = source | 0;
destination = destination | 0;
var data = this.memory.memoryReadDMAFull16(source | 0) | 0;
this.memory.memoryWriteDMA16(destination | 0, data | 0);
this.decrementWordCount(source | 0, destination | 0, 2);
this.DMACore.updateFetch(data | (data << 16));
}
GameBoyAdvanceDMA2.prototype.copy32 = function (source, destination) {
source = source | 0;
destination = destination | 0;
var data = this.memory.memoryReadDMAFull32(source | 0) | 0;
this.memory.memoryWriteDMA32(destination | 0, data | 0);
this.decrementWordCount(source | 0, destination | 0, 4);
this.DMACore.updateFetch(data | 0);
}
GameBoyAdvanceDMA2.prototype.copySound = function (source) {
source = source | 0;
var data = this.memory.memoryReadDMAFull32(source | 0) | 0;
this.wait.singleClock();
this.IOCore.updateTimerClocking();
this.sound.writeFIFOB32(data | 0);
this.soundDMAUpdate(source | 0);
this.DMACore.updateFetch(data | 0);
}
GameBoyAdvanceDMA2.prototype.decrementWordCount = function (source, destination, transferred) {
source = source | 0;
destination = destination | 0;
transferred = transferred | 0;
//Decrement the word count:
var wordCountShadow = ((this.wordCountShadow | 0) - 1) & 0x3FFF;
if ((wordCountShadow | 0) == 0) {
//DMA transfer ended, handle accordingly:
wordCountShadow = this.finalizeDMA(source | 0, destination | 0, transferred | 0) | 0;
}
else {
//Update addresses:
this.incrementDMAAddresses(source | 0, destination | 0, transferred | 0);
}
//Save the new word count:
this.wordCountShadow = wordCountShadow | 0;
}
GameBoyAdvanceDMA2.prototype.soundDMAUpdate = function (source) {
source = source | 0;
//Decrement the word count:
this.wordCountShadow = ((this.wordCountShadow | 0) - 1) & 0x3FFF;
if ((this.wordCountShadow | 0) == 0) {
//DMA transfer ended, handle accordingly:
//Reset pending requests:
this.pending = 0;
//Check Repeat Status:
if ((this.repeat | 0) == 0) {
//Disable the enable bit:
this.enabled = 0;
}
else {
//Repeating the dma:
//Direct Sound DMA Hardwired To Wordcount Of 4:
this.wordCountShadow = 0x4;
}
//Assert the FIFO B DMA request signal:
this.sound.checkFIFOBPendingSignal();
//Run the DMA channel checks:
this.DMACore.update();
//Check to see if we should flag for IRQ:
this.checkIRQTrigger();
}
//Update source address:
switch (this.sourceControl | 0) {
case 0: //Increment
case 3: //Forbidden (VBA has it increment)
this.sourceShadow = ((source | 0) + 4) | 0;
break;
case 1:
this.sourceShadow = ((source | 0) - 4) | 0;
}
}
GameBoyAdvanceDMA2.prototype.finalizeDMA = function (source, destination, transferred) {
source = source | 0;
destination = destination | 0;
transferred = transferred | 0;
var wordCountShadow = 0;
//Reset pending requests:
this.pending = 0;
//Check Repeat Status:
if ((this.repeat | 0) == 0 || (this.enabled | 0) == 0x1) {
//Disable the enable bit:
this.enabled = 0;
}
else {
//Repeating the dma:
//Reload word count:
wordCountShadow = this.wordCount | 0;
}
//Assert the FIFO B DMA request signal:
this.sound.checkFIFOBPendingSignal();
//Run the DMA channel checks:
this.DMACore.update();
//Check to see if we should flag for IRQ:
this.checkIRQTrigger();
//Update addresses:
this.finalDMAAddresses(source | 0, destination | 0, transferred | 0);
return wordCountShadow | 0;
}
GameBoyAdvanceDMA2.prototype.checkIRQTrigger = function () {
if ((this.irqFlagging | 0) != 0) {
this.irq.requestIRQ(0x400);
}
}
GameBoyAdvanceDMA2.prototype.finalDMAAddresses = function (source, destination, transferred) {
source = source | 0;
destination = destination | 0;
transferred = transferred | 0;
//Update source address:
switch (this.sourceControl | 0) {
case 0: //Increment
case 3: //Forbidden (VBA has it increment)
this.sourceShadow = ((source | 0) + (transferred | 0)) | 0;
break;
case 1: //Decrement
this.sourceShadow = ((source | 0) - (transferred | 0)) | 0;
}
//Update destination address:
switch (this.destinationControl | 0) {
case 0: //Increment
this.destinationShadow = ((destination | 0) + (transferred | 0)) | 0;
break;
case 1: //Decrement
this.destinationShadow = ((destination | 0) - (transferred | 0)) | 0;
break;
case 3: //Reload
this.destinationShadow = this.destination | 0;
}
}
GameBoyAdvanceDMA2.prototype.incrementDMAAddresses = function (source, destination, transferred) {
source = source | 0;
destination = destination | 0;
transferred = transferred | 0;
//Update source address:
switch (this.sourceControl | 0) {
case 0: //Increment
case 3: //Forbidden (VBA has it increment)
this.sourceShadow = ((source | 0) + (transferred | 0)) | 0;
break;
case 1:
this.sourceShadow = ((source | 0) - (transferred | 0)) | 0;
}
//Update destination address:
switch (this.destinationControl | 0) {
case 0: //Increment
case 3: //Increment
this.destinationShadow = ((destination | 0) + (transferred | 0)) | 0;
break;
case 1: //Decrement
this.destinationShadow = ((destination | 0) - (transferred | 0)) | 0;
}
}
GameBoyAdvanceDMA2.prototype.nextEventTime = function () {
var clocks = 0x7FFFFFFF;
switch (this.enabled | 0) {
//V_BLANK
case 0x2:
clocks = this.gfxState.nextVBlankEventTime() | 0;
break;
//H_BLANK:
case 0x4:
clocks = this.gfxState.nextHBlankDMAEventTime() | 0;
break;
//FIFO_B:
case 0x10:
clocks = this.sound.nextFIFOBEventTime() | 0;
}
return clocks | 0;
}

View file

@ -0,0 +1,418 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceDMA3(IOCore) {
this.IOCore = IOCore;
}
GameBoyAdvanceDMA3.prototype.DMA_ENABLE_TYPE = [ //DMA Channel 3 Mapping:
0x1,
0x2,
0x4,
0x20
];
GameBoyAdvanceDMA3.prototype.initialize = function () {
this.enabled = 0;
this.pending = 0;
this.source = 0;
this.sourceShadow = 0;
this.destination = 0;
this.destinationShadow = 0;
this.wordCount = 0;
this.wordCountShadow = 0;
this.irqFlagging = 0;
this.dmaType = 0;
this.is32Bit = 0;
this.repeat = 0;
this.sourceControl = 0;
this.destinationControl = 0;
this.gamePakDMA = 0;
this.displaySyncEnableDelay = 0;
this.DMACore = this.IOCore.dma;
this.memory = this.IOCore.memory;
this.gfxState = this.IOCore.gfxState;
this.irq = this.IOCore.irq;
}
GameBoyAdvanceDMA3.prototype.validateDMASource = function (address) {
address = address | 0;
if ((address | 0) >= 0x2000000) {
this.source = address | 0;
}
}
GameBoyAdvanceDMA3.prototype.writeDMASource8_0 = function (data) {
data = data | 0;
var source = this.source & 0xFFFFF00;
data = data & 0xFF;
source = source | data;
this.validateDMASource(source | 0);
}
GameBoyAdvanceDMA3.prototype.writeDMASource8_1 = function (data) {
data = data | 0;
var source = this.source & 0xFFF00FF;
data = data & 0xFF;
source = source | (data << 8);
this.validateDMASource(source | 0);
}
GameBoyAdvanceDMA3.prototype.writeDMASource8_2 = function (data) {
data = data | 0;
var source = this.source & 0xF00FFFF;
data = data & 0xFF;
source = source | (data << 16);
this.validateDMASource(source | 0);
}
GameBoyAdvanceDMA3.prototype.writeDMASource8_3 = function (data) {
data = data | 0;
var source = this.source & 0xFFFFFF;
data = data & 0xF;
source = source | (data << 24);
this.validateDMASource(source | 0);
}
GameBoyAdvanceDMA3.prototype.writeDMASource16_0 = function (data) {
data = data | 0;
var source = this.source & 0xFFF0000;
data = data & 0xFFFF;
source = source | data;
this.validateDMASource(source | 0);
}
GameBoyAdvanceDMA3.prototype.writeDMASource16_1 = function (data) {
data = data | 0;
var source = this.source & 0xFFFF;
data = data & 0xFFF;
source = source | (data << 16);
this.validateDMASource(source | 0);
}
GameBoyAdvanceDMA3.prototype.writeDMASource32 = function (data) {
data = data | 0;
var source = data & 0xFFFFFFF;
this.validateDMASource(source | 0);
}
GameBoyAdvanceDMA3.prototype.writeDMADestination8_0 = function (data) {
data = data | 0;
this.destination = this.destination & 0xFFFFF00;
data = data & 0xFF;
this.destination = this.destination | data;
}
GameBoyAdvanceDMA3.prototype.writeDMADestination8_1 = function (data) {
data = data | 0;
this.destination = this.destination & 0xFFF00FF;
data = data & 0xFF;
this.destination = this.destination | (data << 8);
}
GameBoyAdvanceDMA3.prototype.writeDMADestination8_2 = function (data) {
data = data | 0;
this.destination = this.destination & 0xF00FFFF;
data = data & 0xFF;
this.destination = this.destination | (data << 16);
}
GameBoyAdvanceDMA3.prototype.writeDMADestination8_3 = function (data) {
data = data | 0;
this.destination = this.destination & 0xFFFFFF;
data = data & 0xF;
this.destination = this.destination | (data << 24);
}
GameBoyAdvanceDMA3.prototype.writeDMADestination16_0 = function (data) {
data = data | 0;
this.destination = this.destination & 0xFFF0000;
data = data & 0xFFFF;
this.destination = this.destination | data;
}
GameBoyAdvanceDMA3.prototype.writeDMADestination16_1 = function (data) {
data = data | 0;
this.destination = this.destination & 0xFFFF;
data = data & 0xFFF;
this.destination = this.destination | (data << 16);
}
GameBoyAdvanceDMA3.prototype.writeDMADestination32 = function (data) {
data = data | 0;
this.destination = data & 0xFFFFFFF;
}
GameBoyAdvanceDMA3.prototype.writeDMAWordCount8_0 = function (data) {
data = data | 0;
this.wordCount = this.wordCount & 0xFF00;
data = data & 0xFF;
this.wordCount = this.wordCount | data;
}
GameBoyAdvanceDMA3.prototype.writeDMAWordCount8_1 = function (data) {
data = data | 0;
this.wordCount = this.wordCount & 0xFF;
data = data & 0xFF;
this.wordCount = this.wordCount | (data << 8);
}
GameBoyAdvanceDMA3.prototype.writeDMAWordCount16 = function (data) {
data = data | 0;
this.wordCount = data & 0xFFFF;
}
GameBoyAdvanceDMA3.prototype.writeDMAControl8_0 = function (data) {
data = data | 0;
this.destinationControl = (data >> 5) & 0x3;
this.sourceControl = this.sourceControl & 0x2;
this.sourceControl = this.sourceControl | ((data >> 7) & 0x1);
}
GameBoyAdvanceDMA3.prototype.writeDMAControl8_1 = function (data) {
data = data | 0;
//Spill state machine clocks:
this.IOCore.updateCoreClocking();
this.sourceControl = (this.sourceControl & 0x1) | ((data & 0x1) << 1);
this.repeat = data & 0x2;
this.is32Bit = data & 0x4;
this.gamePakDMA = data & 0x8;
this.dmaType = (data >> 4) & 0x3;
this.irqFlagging = data & 0x40;
this.enableDMAChannel(data & 0x80);
//Calculate next event:
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceDMA3.prototype.writeDMAControl16 = function (data) {
data = data | 0;
//Spill state machine clocks:
this.IOCore.updateCoreClocking();
this.destinationControl = (data >> 5) & 0x3;
this.sourceControl = (data >> 7) & 0x3;
this.repeat = (data >> 8) & 0x2;
this.is32Bit = (data >> 8) & 0x4;
this.gamePakDMA = (data >> 8) & 0x8;
this.dmaType = (data >> 12) & 0x3;
this.irqFlagging = (data >> 8) & 0x40;
this.enableDMAChannel(data & 0x8000);
//Calculate next event:
this.IOCore.updateCoreEventTime();
}
GameBoyAdvanceDMA3.prototype.writeDMAControl32 = function (data) {
data = data | 0;
this.writeDMAWordCount16(data | 0);
this.writeDMAControl16(data >> 16);
}
GameBoyAdvanceDMA3.prototype.readDMAControl8_0 = function () {
var data = this.destinationControl << 5;
data = data | ((this.sourceControl & 0x1) << 7);
return data | 0;
}
GameBoyAdvanceDMA3.prototype.readDMAControl8_1 = function () {
var data = this.sourceControl >> 1;
data = data | this.repeat;
data = data | this.is32Bit;
data = data | this.gamePakDMA;
data = data | (this.dmaType << 4);
data = data | this.irqFlagging;
if ((this.enabled | 0) != 0) {
data = data | 0x80;
}
return data | 0;
}
GameBoyAdvanceDMA3.prototype.readDMAControl16 = function () {
var data = this.destinationControl << 5;
data = data | (this.sourceControl << 7);
data = data | (this.repeat << 8);
data = data | (this.is32Bit << 8);
data = data | (this.gamePakDMA << 8);
data = data | (this.dmaType << 12);
data = data | (this.irqFlagging << 8);
if ((this.enabled | 0) != 0) {
data = data | 0x8000;
}
return data | 0;
}
GameBoyAdvanceDMA3.prototype.getMatchStatus = function () {
return this.enabled & this.pending;
}
GameBoyAdvanceDMA3.prototype.gfxDisplaySyncRequest = function () {
this.requestDMA(0x20 ^ this.displaySyncEnableDelay);
}
GameBoyAdvanceDMA3.prototype.gfxDisplaySyncEnableCheck = function () {
//Reset the display sync & reassert DMA enable line:
if ((this.enabled | 0) == 0x20) {
if ((this.displaySyncEnableDelay | 0) == 0x20) {
this.displaySyncEnableDelay = 0;
}
else {
this.enabled = 0;
this.DMACore.update();
}
}
}
GameBoyAdvanceDMA3.prototype.requestDMA = function (DMAType) {
DMAType = DMAType | 0;
if ((this.enabled & DMAType) != 0) {
this.pending = DMAType | 0;
this.DMACore.update();
}
}
GameBoyAdvanceDMA3.prototype.enableDMAChannel = function (enabled) {
enabled = enabled | 0;
if ((enabled | 0) != 0) {
//If DMA was previously disabled, reload control registers:
if ((this.enabled | 0) == 0) {
switch (this.dmaType | 0) {
case 0:
//Flag immediate DMA transfers for processing now:
this.pending = 0x1;
break;
case 0x3:
//Trigger display sync DMA shadow enable and auto-check on line 162:
this.displaySyncEnableDelay = 0x20;
}
//Shadow copy the word count:
this.wordCountShadow = this.wordCount | 0;
//Shadow copy the source address:
this.sourceShadow = this.source | 0;
//Shadow copy the destination address:
this.destinationShadow = this.destination | 0;
}
//DMA type changed:
this.enabled = this.DMA_ENABLE_TYPE[this.dmaType | 0] | 0;
this.pending = this.pending & this.enabled;
}
else {
//DMA Disabled:
this.enabled = 0;
}
//Run some DMA channel activity checks:
this.DMACore.update();
}
GameBoyAdvanceDMA3.prototype.handleDMACopy = function () {
//Get the addesses:
var source = this.sourceShadow | 0;
var destination = this.destinationShadow | 0;
//Transfer Data:
if ((this.is32Bit | 0) == 4) {
//32-bit Transfer:
this.copy32(source | 0, destination | 0);
}
else {
//16-bit Transfer:
this.copy16(source | 0, destination | 0);
}
}
GameBoyAdvanceDMA3.prototype.copy16 = function (source, destination) {
source = source | 0;
destination = destination | 0;
var data = this.memory.memoryReadDMAFull16(source | 0) | 0;
this.memory.memoryWriteDMAFull16(destination | 0, data | 0);
this.decrementWordCount(source | 0, destination | 0, 2);
this.DMACore.updateFetch(data | (data << 16));
}
GameBoyAdvanceDMA3.prototype.copy32 = function (source, destination) {
source = source | 0;
destination = destination | 0;
var data = this.memory.memoryReadDMAFull32(source | 0) | 0;
this.memory.memoryWriteDMAFull32(destination | 0, data | 0);
this.decrementWordCount(source | 0, destination | 0, 4);
this.DMACore.updateFetch(data | 0);
}
GameBoyAdvanceDMA3.prototype.decrementWordCount = function (source, destination, transferred) {
source = source | 0;
destination = destination | 0;
transferred = transferred | 0;
//Decrement the word count:
var wordCountShadow = ((this.wordCountShadow | 0) - 1) & 0xFFFF;
if ((wordCountShadow | 0) == 0) {
//DMA transfer ended, handle accordingly:
wordCountShadow = this.finalizeDMA(source | 0, destination | 0, transferred | 0) | 0;
}
else {
//Update addresses:
this.incrementDMAAddresses(source | 0, destination | 0, transferred | 0);
}
//Save the new word count:
this.wordCountShadow = wordCountShadow | 0;
}
GameBoyAdvanceDMA3.prototype.finalizeDMA = function (source, destination, transferred) {
source = source | 0;
destination = destination | 0;
transferred = transferred | 0;
var wordCountShadow = 0;
//Reset pending requests:
this.pending = 0;
//Check Repeat Status:
if ((this.repeat | 0) == 0 || (this.enabled | 0) == 0x1) {
//Disable the enable bit:
this.enabled = 0;
}
else {
//Reload word count:
wordCountShadow = this.wordCount | 0;
}
//Run the DMA channel checks:
this.DMACore.update();
//Check to see if we should flag for IRQ:
this.checkIRQTrigger();
//Update addresses:
this.finalDMAAddresses(source | 0, destination | 0, transferred | 0);
return wordCountShadow | 0;
}
GameBoyAdvanceDMA3.prototype.checkIRQTrigger = function () {
if ((this.irqFlagging | 0) != 0) {
this.irq.requestIRQ(0x800);
}
}
GameBoyAdvanceDMA3.prototype.finalDMAAddresses = function (source, destination, transferred) {
source = source | 0;
destination = destination | 0;
transferred = transferred | 0;
//Update source address:
switch (this.sourceControl | 0) {
case 0: //Increment
case 3: //Forbidden (VBA has it increment)
this.sourceShadow = ((source | 0) + (transferred | 0)) | 0;
break;
case 1: //Decrement
this.sourceShadow = ((source | 0) - (transferred | 0)) | 0;
}
//Update destination address:
switch (this.destinationControl | 0) {
case 0: //Increment
this.destinationShadow = ((destination | 0) + (transferred | 0)) | 0;
break;
case 1: //Decrement
this.destinationShadow = ((destination | 0) - (transferred | 0)) | 0;
break;
case 3: //Reload
this.destinationShadow = this.destination | 0;
}
}
GameBoyAdvanceDMA3.prototype.incrementDMAAddresses = function (source, destination, transferred) {
source = source | 0;
destination = destination | 0;
transferred = transferred | 0;
//Update source address:
switch (this.sourceControl | 0) {
case 0: //Increment
case 3: //Forbidden (VBA has it increment)
this.sourceShadow = ((source | 0) + (transferred | 0)) | 0;
break;
case 1:
this.sourceShadow = ((source | 0) - (transferred | 0)) | 0;
}
//Update destination address:
switch (this.destinationControl | 0) {
case 0: //Increment
case 3: //Increment
this.destinationShadow = ((destination | 0) + (transferred | 0)) | 0;
break;
case 1: //Decrement
this.destinationShadow = ((destination | 0) - (transferred | 0)) | 0;
}
}
GameBoyAdvanceDMA3.prototype.nextEventTime = function () {
var clocks = 0x7FFFFFFF;
switch (this.enabled | 0) {
//V_BLANK
case 0x2:
clocks = this.gfxState.nextVBlankEventTime() | 0;
break;
//H_BLANK:
case 0x4:
clocks = this.gfxState.nextHBlankDMAEventTime() | 0;
break;
//DISPLAY_SYNC:
case 0x20:
clocks = this.gfxState.nextDisplaySyncEventTime(this.displaySyncEnableDelay | 0) | 0;
}
return clocks | 0;
}

View file

@ -0,0 +1,298 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceChannel1Synth(sound) {
this.sound = sound;
this.currentSampleLeft = 0;
this.currentSampleRight = 0;
this.SweepFault = false;
this.lastTimeSweep = 0;
this.timeSweep = 0;
this.frequencySweepDivider = 0;
this.decreaseSweep = false;
this.nr11 = 0;
this.CachedDuty = 0xF0000000;
this.totalLength = 0x40;
this.nr12 = 0;
this.envelopeVolume = 0;
this.frequency = 0;
this.FrequencyTracker = 0x8000;
this.nr14 = 0;
this.consecutive = true;
this.ShadowFrequency = 0x8000;
this.canPlay = false;
this.Enabled = 0;
this.envelopeSweeps = 0;
this.envelopeSweepsLast = -1;
this.FrequencyCounter = 0;
this.DutyTracker = 0;
this.Swept = false;
this.leftEnable = 0;
this.rightEnable = 0;
}
GameBoyAdvanceChannel1Synth.prototype.disabled = function () {
//Clear NR10:
this.nr10 = 0;
this.SweepFault = false;
this.lastTimeSweep = 0;
this.timeSweep = 0;
this.frequencySweepDivider = 0;
this.decreaseSweep = false;
//Clear NR11:
this.nr11 = 0;
this.CachedDuty = 0xF0000000;
this.totalLength = 0x40;
//Clear NR12:
this.nr12 = 0;
this.envelopeVolume = 0;
//Clear NR13:
this.frequency = 0;
this.FrequencyTracker = 0x8000;
//Clear NR14:
this.nr14 = 0;
this.consecutive = true;
this.ShadowFrequency = 0x8000;
this.canPlay = false;
this.Enabled = 0;
this.envelopeSweeps = 0;
this.envelopeSweepsLast = -1;
this.FrequencyCounter = 0;
this.DutyTracker = 0;
}
GameBoyAdvanceChannel1Synth.prototype.clockAudioLength = function () {
if ((this.totalLength | 0) > 1) {
this.totalLength = ((this.totalLength | 0) - 1) | 0;
}
else if ((this.totalLength | 0) == 1) {
this.totalLength = 0;
this.enableCheck();
this.sound.unsetNR52(0xFE); //Channel #1 On Flag Off
}
}
GameBoyAdvanceChannel1Synth.prototype.enableCheck = function () {
if ((this.consecutive || (this.totalLength | 0) > 0) && !this.SweepFault && this.canPlay) {
this.Enabled = 0xF;
}
else {
this.Enabled = 0;
}
}
GameBoyAdvanceChannel1Synth.prototype.volumeEnableCheck = function () {
this.canPlay = ((this.nr12 | 0) > 7);
this.enableCheck();
}
GameBoyAdvanceChannel1Synth.prototype.outputLevelCache = function () {
var duty = this.CachedDuty >> (this.DutyTracker | 0);
var envelopeVolume = this.envelopeVolume & this.Enabled & duty;
this.currentSampleLeft = this.leftEnable & envelopeVolume;
this.currentSampleRight = this.rightEnable & envelopeVolume;
}
GameBoyAdvanceChannel1Synth.prototype.setChannelOutputEnable = function (data) {
data = data | 0;
//Set by NR51 handler:
this.rightEnable = (data << 31) >> 31;
this.leftEnable = (data << 27) >> 31;
}
GameBoyAdvanceChannel1Synth.prototype.clockAudioSweep = function () {
//Channel 1:
if (!this.SweepFault && (this.timeSweep | 0) > 0) {
this.timeSweep = ((this.timeSweep | 0) - 1) | 0
if ((this.timeSweep | 0) == 0) {
this.runAudioSweep();
}
}
}
GameBoyAdvanceChannel1Synth.prototype.runAudioSweep = function () {
//Channel 1:
if ((this.lastTimeSweep | 0) > 0) {
if ((this.frequencySweepDivider | 0) > 0) {
this.Swept = true;
if (this.decreaseSweep) {
this.ShadowFrequency = ((this.ShadowFrequency | 0) - (this.ShadowFrequency >> (this.frequencySweepDivider | 0))) | 0;
this.frequency = this.ShadowFrequency & 0x7FF;
this.FrequencyTracker = (0x800 - (this.frequency | 0)) << 4;
}
else {
this.ShadowFrequency = ((this.ShadowFrequency | 0) + (this.ShadowFrequency >> (this.frequencySweepDivider | 0))) | 0;
this.frequency = this.ShadowFrequency | 0;
if ((this.ShadowFrequency | 0) <= 0x7FF) {
this.FrequencyTracker = (0x800 - (this.frequency | 0)) << 4;
//Run overflow check twice:
if ((((this.ShadowFrequency | 0) + (this.ShadowFrequency >> (this.frequencySweepDivider | 0))) | 0) > 0x7FF) {
this.SweepFault = true;
this.enableCheck();
this.sound.unsetNR52(0xFE); //Channel #1 On Flag Off
}
}
else {
this.frequency &= 0x7FF;
this.SweepFault = true;
this.enableCheck();
this.sound.unsetNR52(0xFE); //Channel #1 On Flag Off
}
}
this.timeSweep = this.lastTimeSweep | 0;
}
else {
//Channel has sweep disabled and timer becomes a length counter:
this.SweepFault = true;
this.enableCheck();
}
}
}
GameBoyAdvanceChannel1Synth.prototype.audioSweepPerformDummy = function () {
//Channel 1:
if ((this.frequencySweepDivider | 0) > 0) {
if (!this.decreaseSweep) {
var channel1ShadowFrequency = ((this.ShadowFrequency | 0) + (this.ShadowFrequency >> (this.frequencySweepDivider | 0))) | 0;
if ((channel1ShadowFrequency | 0) <= 0x7FF) {
//Run overflow check twice:
if ((((channel1ShadowFrequency | 0) + (channel1ShadowFrequency >> (this.frequencySweepDivider | 0))) | 0) > 0x7FF) {
this.SweepFault = true;
this.enableCheck();
this.sound.unsetNR52(0xFE); //Channel #1 On Flag Off
}
}
else {
this.SweepFault = true;
this.enableCheck();
this.sound.unsetNR52(0xFE); //Channel #1 On Flag Off
}
}
}
}
GameBoyAdvanceChannel1Synth.prototype.clockAudioEnvelope = function () {
if ((this.envelopeSweepsLast | 0) > -1) {
if ((this.envelopeSweeps | 0) > 0) {
this.envelopeSweeps = ((this.envelopeSweeps | 0) - 1) | 0;
}
else {
if (!this.envelopeType) {
if ((this.envelopeVolume | 0) > 0) {
this.envelopeVolume = ((this.envelopeVolume | 0) - 1) | 0;
this.envelopeSweeps = this.envelopeSweepsLast | 0;
}
else {
this.envelopeSweepsLast = -1;
}
}
else if ((this.envelopeVolume | 0) < 0xF) {
this.envelopeVolume = ((this.envelopeVolume | 0) + 1) | 0;
this.envelopeSweeps = this.envelopeSweepsLast | 0;
}
else {
this.envelopeSweepsLast = -1;
}
}
}
}
GameBoyAdvanceChannel1Synth.prototype.computeAudioChannel = function () {
if ((this.FrequencyCounter | 0) == 0) {
this.FrequencyCounter = this.FrequencyTracker | 0;
this.DutyTracker = ((this.DutyTracker | 0) + 4) & 0x1C;
}
}
GameBoyAdvanceChannel1Synth.prototype.readSOUND1CNT8_0 = function () {
//NR10:
return this.nr10 | 0;
}
GameBoyAdvanceChannel1Synth.prototype.writeSOUND1CNT8_0 = function (data) {
data = data | 0;
//NR10:
if (this.decreaseSweep && (data & 0x08) == 0) {
if (this.Swept) {
this.SweepFault = true;
}
}
this.lastTimeSweep = (data & 0x70) >> 4;
this.frequencySweepDivider = data & 0x07;
this.decreaseSweep = ((data & 0x08) != 0);
this.nr10 = data & 0xFF;
this.enableCheck();
}
GameBoyAdvanceChannel1Synth.prototype.readSOUND1CNT8_2 = function () {
//NR11:
return this.nr11 | 0;
}
GameBoyAdvanceChannel1Synth.prototype.writeSOUND1CNT8_2 = function (data) {
data = data | 0;
//NR11:
switch ((data >> 6) & 0x3) {
case 0:
this.CachedDuty = 0xF0000000;
break;
case 1:
this.CachedDuty = 0xF000000F;
break;
case 2:
this.CachedDuty = 0xFFF0000F;
break;
default:
this.CachedDuty = 0x0FFFFFF0;
}
this.totalLength = (0x40 - (data & 0x3F)) | 0;
this.nr11 = data & 0xFF;
this.enableCheck();
}
GameBoyAdvanceChannel1Synth.prototype.readSOUND1CNT8_3 = function () {
//NR12:
return this.nr12 | 0;
}
GameBoyAdvanceChannel1Synth.prototype.writeSOUND1CNT8_3 = function (data) {
data = data | 0;
//NR12:
this.envelopeType = ((data & 0x08) != 0);
this.nr12 = data & 0xFF;
this.volumeEnableCheck();
}
GameBoyAdvanceChannel1Synth.prototype.writeSOUND1CNT_X0 = function (data) {
data = data | 0;
//NR13:
this.frequency = (this.frequency & 0x700) | (data & 0xFF);
this.FrequencyTracker = (0x800 - (this.frequency | 0)) << 4;
}
GameBoyAdvanceChannel1Synth.prototype.readSOUND1CNTX8 = function () {
//NR14:
return this.nr14 | 0;
}
GameBoyAdvanceChannel1Synth.prototype.writeSOUND1CNT_X1 = function (data) {
data = data | 0;
//NR14:
this.consecutive = ((data & 0x40) == 0);
this.frequency = ((data & 0x7) << 8) | (this.frequency & 0xFF);
this.FrequencyTracker = (0x800 - (this.frequency | 0)) << 4;
if ((data & 0x80) != 0) {
//Reload nr10:
this.timeSweep = this.lastTimeSweep | 0;
this.Swept = false;
//Reload nr12:
this.envelopeVolume = this.nr12 >> 4;
this.envelopeSweepsLast = ((this.nr12 & 0x7) - 1) | 0;
if ((this.totalLength | 0) == 0) {
this.totalLength = 0x40;
}
if ((this.lastTimeSweep | 0) > 0 || (this.frequencySweepDivider | 0) > 0) {
this.sound.setNR52(0x1);
}
else {
this.sound.unsetNR52(0xFE);
}
if ((data & 0x40) != 0) {
this.sound.setNR52(0x1);
}
this.ShadowFrequency = this.frequency | 0;
//Reset frequency overflow check + frequency sweep type check:
this.SweepFault = false;
//Supposed to run immediately:
this.audioSweepPerformDummy();
}
this.enableCheck();
this.nr14 = data & 0xFF;
}

View file

@ -0,0 +1,186 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceChannel2Synth(sound) {
this.sound = sound;
this.currentSampleLeft = 0;
this.currentSampleRight = 0;
this.CachedDuty = 0xF0000000;
this.totalLength = 0x40;
this.envelopeVolume = 0;
this.frequency = 0;
this.FrequencyTracker = 0x8000;
this.consecutive = true;
this.ShadowFrequency = 0x8000;
this.canPlay = false;
this.Enabled = 0;
this.envelopeSweeps = 0;
this.envelopeSweepsLast = -1;
this.FrequencyCounter = 0;
this.DutyTracker = 0;
this.leftEnable = 0;
this.rightEnable = 0;
this.nr21 = 0;
this.nr22 = 0;
this.nr23 = 0;
this.nr24 = 0;
}
GameBoyAdvanceChannel2Synth.prototype.disabled = function () {
//Clear NR21:
this.nr21 = 0;
this.CachedDuty = 0xF0000000;
this.totalLength = 0x40;
//Clear NR22:
this.nr22 = 0;
this.envelopeVolume = 0;
//Clear NR23:
this.nr23 = 0;
this.frequency = 0;
this.FrequencyTracker = 0x8000;
//Clear NR24:
this.nr24 = 0;
this.consecutive = true;
this.canPlay = false;
this.Enabled = 0;
this.envelopeSweeps = 0;
this.envelopeSweepsLast = -1;
this.FrequencyCounter = 0;
this.DutyTracker = 0;
}
GameBoyAdvanceChannel2Synth.prototype.clockAudioLength = function () {
if ((this.totalLength | 0) > 1) {
this.totalLength = ((this.totalLength | 0) - 1) | 0;
}
else if ((this.totalLength | 0) == 1) {
this.totalLength = 0;
this.enableCheck();
this.sound.unsetNR52(0xFD); //Channel #2 On Flag Off
}
}
GameBoyAdvanceChannel2Synth.prototype.clockAudioEnvelope = function () {
if ((this.envelopeSweepsLast | 0) > -1) {
if ((this.envelopeSweeps | 0) > 0) {
this.envelopeSweeps = ((this.envelopeSweeps | 0) - 1) | 0;
}
else {
if (!this.envelopeType) {
if ((this.envelopeVolume | 0) > 0) {
this.envelopeVolume = ((this.envelopeVolume | 0) - 1) | 0;
this.envelopeSweeps = this.envelopeSweepsLast | 0;
}
else {
this.envelopeSweepsLast = -1;
}
}
else if ((this.envelopeVolume | 0) < 0xF) {
this.envelopeVolume = ((this.envelopeVolume | 0) + 1) | 0;
this.envelopeSweeps = this.envelopeSweepsLast | 0;
}
else {
this.envelopeSweepsLast = -1;
}
}
}
}
GameBoyAdvanceChannel2Synth.prototype.computeAudioChannel = function () {
if ((this.FrequencyCounter | 0) == 0) {
this.FrequencyCounter = this.FrequencyTracker | 0;
this.DutyTracker = ((this.DutyTracker | 0) + 4) & 0x1C;
}
}
GameBoyAdvanceChannel2Synth.prototype.enableCheck = function () {
if ((this.consecutive || (this.totalLength | 0) > 0) && this.canPlay) {
this.Enabled = 0xF;
}
else {
this.Enabled = 0;
}
}
GameBoyAdvanceChannel2Synth.prototype.volumeEnableCheck = function () {
this.canPlay = ((this.nr22 | 0) > 7);
this.enableCheck();
}
GameBoyAdvanceChannel2Synth.prototype.outputLevelCache = function () {
var duty = this.CachedDuty >> (this.DutyTracker | 0);
var envelopeVolume = this.envelopeVolume & this.Enabled & duty;
this.currentSampleLeft = this.leftEnable & envelopeVolume;
this.currentSampleRight = this.rightEnable & envelopeVolume;
}
GameBoyAdvanceChannel2Synth.prototype.setChannelOutputEnable = function (data) {
data = data | 0;
//Set by NR51 handler:
this.rightEnable = (data << 30) >> 31;
this.leftEnable = (data << 26) >> 31;
}
GameBoyAdvanceChannel2Synth.prototype.readSOUND2CNT_L0 = function () {
//NR21:
return this.nr21 | 0;
}
GameBoyAdvanceChannel2Synth.prototype.writeSOUND2CNT_L0 = function (data) {
data = data | 0;
//NR21:
switch ((data >> 6) & 0x3) {
case 0:
this.CachedDuty = 0xF0000000;
break;
case 1:
this.CachedDuty = 0xF000000F;
break;
case 2:
this.CachedDuty = 0xFFF0000F;
break;
default:
this.CachedDuty = 0x0FFFFFF0;
}
this.totalLength = (0x40 - (data & 0x3F)) | 0;
this.nr21 = data & 0xFF;
this.enableCheck();
}
GameBoyAdvanceChannel2Synth.prototype.readSOUND2CNT_L1 = function () {
//NR22:
return this.nr22 | 0;
}
GameBoyAdvanceChannel2Synth.prototype.writeSOUND2CNT_L1 = function (data) {
data = data | 0;
//NR22:
this.envelopeType = ((data & 0x08) != 0);
this.nr22 = data & 0xFF;
this.volumeEnableCheck();
}
GameBoyAdvanceChannel2Synth.prototype.writeSOUND2CNT_H0 = function (data) {
data = data | 0;
//NR23:
this.frequency = (this.frequency & 0x700) | (data & 0xFF);
this.FrequencyTracker = (0x800 - (this.frequency | 0)) << 4;
}
GameBoyAdvanceChannel2Synth.prototype.readSOUND2CNT_H = function () {
//NR24:
return this.nr24 | 0;
}
GameBoyAdvanceChannel2Synth.prototype.writeSOUND2CNT_H1 = function (data) {
data = data | 0;
//NR24:
if ((data & 0x80) != 0) {
//Reload nr22:
this.envelopeVolume = this.nr22 >> 4;
this.envelopeSweepsLast = ((this.nr22 & 0x7) - 1) | 0;
if ((this.totalLength | 0) == 0) {
this.totalLength = 0x40;
}
if ((data & 0x40) != 0) {
this.sound.setNR52(0x2); //Channel #1 On Flag Off
}
}
this.consecutive = ((data & 0x40) == 0x0);
this.frequency = ((data & 0x7) << 8) | (this.frequency & 0xFF);
this.FrequencyTracker = (0x800 - (this.frequency | 0)) << 4;
this.nr24 = data & 0xFF;
this.enableCheck();
}

View file

@ -0,0 +1,301 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceChannel3Synth(sound) {
this.sound = sound;
this.currentSampleLeft = 0;
this.currentSampleRight = 0;
this.lastSampleLookup = 0;
this.canPlay = false;
this.WAVERAMBankSpecified = 0;
this.WAVERAMBankAccessed = 0x20;
this.WaveRAMBankSize = 0x1F;
this.totalLength = 0x100;
this.patternType = 4;
this.frequency = 0;
this.FrequencyPeriod = 0x4000;
this.consecutive = true;
this.Enabled = 0;
this.leftEnable = 0;
this.rightEnable = 0;
this.nr30 = 0;
this.nr31 = 0;
this.nr32 = 0;
this.nr33 = 0;
this.nr34 = 0;
this.cachedSample = 0;
this.PCM = getInt8Array(0x40);
this.PCM16 = getUint16View(this.PCM);
this.PCM32 = getInt32View(this.PCM);
this.WAVERAM8 = getUint8Array(0x20);
this.WAVERAM16 = getUint16View(this.WAVERAM8);
this.WAVERAM32 = getInt32View(this.WAVERAM8);
}
GameBoyAdvanceChannel3Synth.prototype.disabled = function () {
//Clear NR30:
this.nr30 = 0;
this.lastSampleLookup = 0;
this.canPlay = false;
this.WAVERAMBankSpecified = 0;
this.WAVERAMBankAccessed = 0x20;
this.WaveRAMBankSize = 0x1F;
//Clear NR31:
this.totalLength = 0x100;
//Clear NR32:
this.nr32 = 0;
this.patternType = 4;
//Clear NR33:
this.nr33 = 0;
this.frequency = 0;
this.FrequencyPeriod = 0x4000;
//Clear NR34:
this.nr34 = 0;
this.consecutive = true;
this.Enabled = 0;
this.counter = 0;
}
if (typeof Math.imul == "function") {
//Math.imul found, insert the optimized path in:
GameBoyAdvanceChannel3Synth.prototype.updateCache = function () {
if ((this.patternType | 0) != 3) {
this.cachedSample = this.PCM[this.lastSampleLookup | 0] >> (this.patternType | 0);
}
else {
this.cachedSample = Math.imul(this.PCM[this.lastSampleLookup | 0] | 0, 3) >> 2;
}
this.outputLevelCache();
}
}
else {
//Math.imul not found, use the compatibility method:
GameBoyAdvanceChannel3Synth.prototype.updateCache = function () {
if ((this.patternType | 0) != 3) {
this.cachedSample = this.PCM[this.lastSampleLookup | 0] >> (this.patternType | 0);
}
else {
this.cachedSample = (this.PCM[this.lastSampleLookup | 0] * 0.75) | 0;
}
this.outputLevelCache();
}
}
GameBoyAdvanceChannel3Synth.prototype.outputLevelCache = function () {
var cachedSample = this.cachedSample & this.Enabled;
this.currentSampleLeft = this.leftEnable & cachedSample;
this.currentSampleRight = this.rightEnable & cachedSample;
}
GameBoyAdvanceChannel3Synth.prototype.setChannelOutputEnable = function (data) {
data = data | 0;
//Set by NR51 handler:
this.rightEnable = (data << 29) >> 31;
this.leftEnable = (data << 25) >> 31;
}
GameBoyAdvanceChannel3Synth.prototype.readWAVE8 = function (address) {
address = ((address | 0) + (this.WAVERAMBankAccessed >> 1)) | 0;
return this.WAVERAM8[address | 0] | 0;
}
if (__LITTLE_ENDIAN__) {
GameBoyAdvanceChannel3Synth.prototype.writeWAVE8 = function (address, data) {
address = address | 0;
data = data | 0;
address = ((address | 0) + (this.WAVERAMBankAccessed >> 1)) | 0;
this.WAVERAM8[address | 0] = data & 0xFF;
var temp = ((data >> 4) & 0xF);
temp = temp | ((data & 0xF) << 8);
this.PCM16[address | 0] = temp | 0;
}
GameBoyAdvanceChannel3Synth.prototype.writeWAVE16 = function (address, data) {
address = address | 0;
data = data | 0;
address = ((address | 0) + (this.WAVERAMBankAccessed >> 2)) | 0;
this.WAVERAM16[address | 0] = data & 0xFFFF;
var temp = ((data >> 4) & 0xF);
temp = temp | ((data & 0xF) << 8);
temp = temp | ((data & 0xF000) << 4);
temp = temp | ((data & 0xF00) << 16);
this.PCM32[address | 0] = temp | 0;
}
GameBoyAdvanceChannel3Synth.prototype.writeWAVE32 = function (address, data) {
address = address | 0;
data = data | 0;
address = ((address | 0) + (this.WAVERAMBankAccessed >> 3)) | 0;
this.WAVERAM32[address | 0] = data | 0;
var temp = (data >> 4) & 0xF;
temp = temp | ((data & 0xF) << 8);
temp = temp | ((data & 0xF000) << 4);
temp = temp | ((data & 0xF00) << 16);
address = address << 1;
this.PCM32[address | 0] = temp | 0;
temp = (data >> 20) & 0xF;
temp = temp | ((data >> 8) & 0xF00);
temp = temp | ((data >> 12) & 0xF0000);
temp = temp | (data & 0xF000000);
this.PCM32[address | 1] = temp | 0;
}
GameBoyAdvanceChannel3Synth.prototype.readWAVE16 = function (address) {
address = ((address | 0) + (this.WAVERAMBankAccessed >> 2)) | 0;
return this.WAVERAM16[address | 0] | 0;
}
GameBoyAdvanceChannel3Synth.prototype.readWAVE32 = function (address) {
address = ((address | 0) + (this.WAVERAMBankAccessed >> 3)) | 0;
return this.WAVERAM32[address | 0] | 0;
}
}
else {
GameBoyAdvanceChannel3Synth.prototype.writeWAVE8 = function (address, data) {
address += this.WAVERAMBankAccessed >> 1;
this.WAVERAM8[address] = data & 0xFF;
address <<= 1;
this.PCM[address] = (data >> 4) & 0xF;
this.PCM[address | 1] = data & 0xF;
}
GameBoyAdvanceChannel3Synth.prototype.writeWAVE16 = function (address, data) {
address += this.WAVERAMBankAccessed >> 2;
address <<= 1;
this.WAVERAM8[address] = data & 0xFF;
this.WAVERAM8[address | 1] = (data >> 8) & 0xFF;
address <<= 1;
this.PCM[address] = (data >> 4) & 0xF;
this.PCM[address | 1] = data & 0xF;
this.PCM[address | 2] = (data >> 12) & 0xF;
this.PCM[address | 3] = (data >> 8) & 0xF;
}
GameBoyAdvanceChannel3Synth.prototype.writeWAVE32 = function (address, data) {
address += this.WAVERAMBankAccessed >> 3;
address <<= 2;
this.WAVERAM8[address] = data & 0xFF;
this.WAVERAM8[address | 1] = (data >> 8) & 0xFF;
this.WAVERAM8[address | 2] = (data >> 16) & 0xFF;
this.WAVERAM8[address | 3] = data >>> 24;
address <<= 1;
this.PCM[address] = (data >> 4) & 0xF;
this.PCM[address | 1] = data & 0xF;
this.PCM[address | 2] = (data >> 12) & 0xF;
this.PCM[address | 3] = (data >> 8) & 0xF;
this.PCM[address | 4] = (data >> 20) & 0xF;
this.PCM[address | 5] = (data >> 16) & 0xF;
this.PCM[address | 6] = data >>> 28;
this.PCM[address | 7] = (data >> 24) & 0xF;
}
GameBoyAdvanceChannel3Synth.prototype.readWAVE16 = function (address) {
address += this.WAVERAMBankAccessed >> 1;
return (this.WAVERAM8[address] | (this.WAVERAM8[address | 1] << 8));
}
GameBoyAdvanceChannel3Synth.prototype.readWAVE32 = function (address) {
address += this.WAVERAMBankAccessed >> 1;
return (this.WAVERAM8[address] | (this.WAVERAM8[address | 1] << 8) |
(this.WAVERAM8[address | 2] << 16) | (this.WAVERAM8[address | 3] << 24));
}
}
GameBoyAdvanceChannel3Synth.prototype.enableCheck = function () {
if (/*this.canPlay && */(this.consecutive || (this.totalLength | 0) > 0)) {
this.Enabled = 0xF;
}
else {
this.Enabled = 0;
}
}
GameBoyAdvanceChannel3Synth.prototype.clockAudioLength = function () {
if ((this.totalLength | 0) > 1) {
this.totalLength = ((this.totalLength | 0) - 1) | 0;
}
else if ((this.totalLength | 0) == 1) {
this.totalLength = 0;
this.enableCheck();
this.sound.unsetNR52(0xFB); //Channel #3 On Flag Off
}
}
GameBoyAdvanceChannel3Synth.prototype.computeAudioChannel = function () {
if ((this.counter | 0) == 0) {
if (this.canPlay) {
this.lastSampleLookup = (((this.lastSampleLookup | 0) + 1) & this.WaveRAMBankSize) | this.WAVERAMBankSpecified;
}
this.counter = this.FrequencyPeriod | 0;
}
}
GameBoyAdvanceChannel3Synth.prototype.readSOUND3CNT_L = function () {
//NR30:
return this.nr30 | 0;
}
GameBoyAdvanceChannel3Synth.prototype.writeSOUND3CNT_L = function (data) {
data = data | 0;
//NR30:
if (!this.canPlay && (data & 0x80) != 0) {
this.lastSampleLookup = 0;
}
this.canPlay = ((data & 0x80) != 0);
this.WaveRAMBankSize = (data & 0x20) | 0x1F;
this.WAVERAMBankSpecified = ((data & 0x40) >> 1) ^ (data & 0x20);
this.WAVERAMBankAccessed = ((data & 0x40) >> 1) ^ 0x20;
if (this.canPlay && (this.nr30 & 0x80) != 0 && !this.consecutive) {
this.sound.setNR52(0x4);
}
this.nr30 = data & 0xFF;
}
GameBoyAdvanceChannel3Synth.prototype.writeSOUND3CNT_H0 = function (data) {
data = data | 0;
//NR31:
this.totalLength = (0x100 - (data & 0xFF)) | 0;
this.enableCheck();
}
GameBoyAdvanceChannel3Synth.prototype.readSOUND3CNT_H = function () {
//NR32:
return this.nr32 | 0;
}
GameBoyAdvanceChannel3Synth.prototype.writeSOUND3CNT_H1 = function (data) {
data = data | 0;
//NR32:
data = data & 0xFF;
switch (data >> 5) {
case 0:
this.patternType = 4;
break;
case 1:
this.patternType = 0;
break;
case 2:
this.patternType = 1;
break;
case 3:
this.patternType = 2;
break;
default:
this.patternType = 3;
}
this.nr32 = data | 0;
}
GameBoyAdvanceChannel3Synth.prototype.writeSOUND3CNT_X0 = function (data) {
data = data | 0;
//NR33:
this.frequency = (this.frequency & 0x700) | (data & 0xFF);
this.FrequencyPeriod = (0x800 - (this.frequency | 0)) << 3;
}
GameBoyAdvanceChannel3Synth.prototype.readSOUND3CNT_X = function () {
//NR34:
return this.nr34 | 0;
}
GameBoyAdvanceChannel3Synth.prototype.writeSOUND3CNT_X1 = function (data) {
data = data | 0;
//NR34:
if ((data & 0x80) != 0) {
if ((this.totalLength | 0) == 0) {
this.totalLength = 0x100;
}
this.lastSampleLookup = 0;
if ((data & 0x40) != 0) {
this.sound.setNR52(0x4);
}
}
this.consecutive = ((data & 0x40) == 0x0);
this.frequency = ((data & 0x7) << 8) | (this.frequency & 0xFF);
this.FrequencyPeriod = (0x800 - (this.frequency | 0)) << 3;
this.enableCheck();
this.nr34 = data & 0xFF;
}

View file

@ -0,0 +1,243 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceChannel4Synth(sound) {
this.sound = sound;
this.currentSampleLeft = 0;
this.currentSampleRight = 0;
this.totalLength = 0x40;
this.envelopeVolume = 0;
this.FrequencyPeriod = 32;
this.lastSampleLookup = 0;
this.BitRange = 0x7FFF;
this.VolumeShifter = 15;
this.currentVolume = 0;
this.consecutive = true;
this.envelopeSweeps = 0;
this.envelopeSweepsLast = -1;
this.canPlay = false;
this.Enabled = 0;
this.counter = 0;
this.leftEnable = 0;
this.rightEnable = 0;
this.nr42 = 0;
this.nr43 = 0;
this.nr44 = 0;
this.cachedSample = 0;
this.intializeWhiteNoise();
this.noiseSampleTable = this.LSFR15Table;
}
GameBoyAdvanceChannel4Synth.prototype.intializeWhiteNoise = function () {
//Noise Sample Tables:
var randomFactor = 1;
//15-bit LSFR Cache Generation:
this.LSFR15Table = getInt8Array(0x80000);
var LSFR = 0x7FFF; //Seed value has all its bits set.
var LSFRShifted = 0x3FFF;
for (var index = 0; index < 0x8000; ++index) {
//Normalize the last LSFR value for usage:
randomFactor = 1 - (LSFR & 1); //Docs say it's the inverse.
//Cache the different volume level results:
this.LSFR15Table[0x08000 | index] = randomFactor;
this.LSFR15Table[0x10000 | index] = randomFactor * 0x2;
this.LSFR15Table[0x18000 | index] = randomFactor * 0x3;
this.LSFR15Table[0x20000 | index] = randomFactor * 0x4;
this.LSFR15Table[0x28000 | index] = randomFactor * 0x5;
this.LSFR15Table[0x30000 | index] = randomFactor * 0x6;
this.LSFR15Table[0x38000 | index] = randomFactor * 0x7;
this.LSFR15Table[0x40000 | index] = randomFactor * 0x8;
this.LSFR15Table[0x48000 | index] = randomFactor * 0x9;
this.LSFR15Table[0x50000 | index] = randomFactor * 0xA;
this.LSFR15Table[0x58000 | index] = randomFactor * 0xB;
this.LSFR15Table[0x60000 | index] = randomFactor * 0xC;
this.LSFR15Table[0x68000 | index] = randomFactor * 0xD;
this.LSFR15Table[0x70000 | index] = randomFactor * 0xE;
this.LSFR15Table[0x78000 | index] = randomFactor * 0xF;
//Recompute the LSFR algorithm:
LSFRShifted = LSFR >> 1;
LSFR = LSFRShifted | (((LSFRShifted ^ LSFR) & 0x1) << 14);
}
//7-bit LSFR Cache Generation:
this.LSFR7Table = getInt8Array(0x800);
LSFR = 0x7F; //Seed value has all its bits set.
for (index = 0; index < 0x80; ++index) {
//Normalize the last LSFR value for usage:
randomFactor = 1 - (LSFR & 1); //Docs say it's the inverse.
//Cache the different volume level results:
this.LSFR7Table[0x080 | index] = randomFactor;
this.LSFR7Table[0x100 | index] = randomFactor * 0x2;
this.LSFR7Table[0x180 | index] = randomFactor * 0x3;
this.LSFR7Table[0x200 | index] = randomFactor * 0x4;
this.LSFR7Table[0x280 | index] = randomFactor * 0x5;
this.LSFR7Table[0x300 | index] = randomFactor * 0x6;
this.LSFR7Table[0x380 | index] = randomFactor * 0x7;
this.LSFR7Table[0x400 | index] = randomFactor * 0x8;
this.LSFR7Table[0x480 | index] = randomFactor * 0x9;
this.LSFR7Table[0x500 | index] = randomFactor * 0xA;
this.LSFR7Table[0x580 | index] = randomFactor * 0xB;
this.LSFR7Table[0x600 | index] = randomFactor * 0xC;
this.LSFR7Table[0x680 | index] = randomFactor * 0xD;
this.LSFR7Table[0x700 | index] = randomFactor * 0xE;
this.LSFR7Table[0x780 | index] = randomFactor * 0xF;
//Recompute the LSFR algorithm:
LSFRShifted = LSFR >> 1;
LSFR = LSFRShifted | (((LSFRShifted ^ LSFR) & 0x1) << 6);
}
}
GameBoyAdvanceChannel4Synth.prototype.disabled = function () {
//Clear NR41:
this.totalLength = 0x40;
//Clear NR42:
this.nr42 = 0;
this.envelopeVolume = 0;
//Clear NR43:
this.nr43 = 0;
this.FrequencyPeriod = 32;
this.lastSampleLookup = 0;
this.BitRange = 0x7FFF;
this.VolumeShifter = 15;
this.currentVolume = 0;
this.noiseSampleTable = this.LSFR15Table;
//Clear NR44:
this.nr44 = 0;
this.consecutive = true;
this.envelopeSweeps = 0;
this.envelopeSweepsLast = -1;
this.canPlay = false;
this.Enabled = 0;
this.counter = 0;
}
GameBoyAdvanceChannel4Synth.prototype.clockAudioLength = function () {
if ((this.totalLength | 0) > 1) {
this.totalLength = ((this.totalLength | 0) - 1) | 0;
}
else if ((this.totalLength | 0) == 1) {
this.totalLength = 0;
this.enableCheck();
this.sound.unsetNR52(0xF7); //Channel #4 On Flag Off
}
}
GameBoyAdvanceChannel4Synth.prototype.clockAudioEnvelope = function () {
if ((this.envelopeSweepsLast | 0) > -1) {
if ((this.envelopeSweeps | 0) > 0) {
this.envelopeSweeps = ((this.envelopeSweeps | 0) - 1) | 0;
}
else {
if (!this.envelopeType) {
if ((this.envelopeVolume | 0) > 0) {
this.envelopeVolume = ((this.envelopeVolume | 0) - 1) | 0;
this.currentVolume = (this.envelopeVolume | 0) << (this.VolumeShifter | 0);
this.envelopeSweeps = this.envelopeSweepsLast | 0;
}
else {
this.envelopeSweepsLast = -1;
}
}
else if ((this.envelopeVolume | 0) < 0xF) {
this.envelopeVolume = ((this.envelopeVolume | 0) + 1) | 0;
this.currentVolume = (this.envelopeVolume | 0) << (this.VolumeShifter | 0);
this.envelopeSweeps = this.envelopeSweepsLast | 0;
}
else {
this.envelopeSweepsLast = -1;
}
}
}
}
GameBoyAdvanceChannel4Synth.prototype.computeAudioChannel = function () {
if ((this.counter | 0) == 0) {
this.lastSampleLookup = ((this.lastSampleLookup | 0) + 1) & this.BitRange;
this.counter = this.FrequencyPeriod | 0;
}
}
GameBoyAdvanceChannel4Synth.prototype.enableCheck = function () {
if ((this.consecutive || (this.totalLength | 0) > 0) && this.canPlay) {
this.Enabled = 0xF;
}
else {
this.Enabled = 0;
}
}
GameBoyAdvanceChannel4Synth.prototype.volumeEnableCheck = function () {
this.canPlay = ((this.nr42 | 0) > 7);
this.enableCheck();
}
GameBoyAdvanceChannel4Synth.prototype.outputLevelCache = function () {
var cachedSample = this.cachedSample & this.Enabled;
this.currentSampleLeft = this.leftEnable & cachedSample;
this.currentSampleRight = this.rightEnable & cachedSample;
}
GameBoyAdvanceChannel4Synth.prototype.setChannelOutputEnable = function (data) {
data = data | 0;
//Set by NR51 handler:
this.rightEnable = (data << 28) >> 31;
this.leftEnable = (data << 24) >> 31;
}
GameBoyAdvanceChannel4Synth.prototype.updateCache = function () {
this.cachedSample = this.noiseSampleTable[this.currentVolume | this.lastSampleLookup] | 0;
this.outputLevelCache();
}
GameBoyAdvanceChannel4Synth.prototype.writeSOUND4CNT_L0 = function (data) {
data = data | 0;
//NR41:
this.totalLength = (0x40 - (data & 0x3F)) | 0;
this.enableCheck();
}
GameBoyAdvanceChannel4Synth.prototype.writeSOUND4CNT_L1 = function (data) {
data = data | 0;
//NR42:
this.envelopeType = ((data & 0x08) != 0);
this.nr42 = data & 0xFF;
this.volumeEnableCheck();
}
GameBoyAdvanceChannel4Synth.prototype.readSOUND4CNT_L = function () {
//NR42:
return this.nr42 | 0;
}
GameBoyAdvanceChannel4Synth.prototype.writeSOUND4CNT_H0 = function (data) {
data = data | 0;
//NR43:
this.FrequencyPeriod = Math.max((data & 0x7) << 4, 8) << ((((data >> 4) & 0xF) + 2) | 0);
var bitWidth = data & 0x8;
if (((bitWidth | 0) == 0x8 && (this.BitRange | 0) == 0x7FFF) || ((bitWidth | 0) == 0 && (this.BitRange | 0) == 0x7F)) {
this.lastSampleLookup = 0;
this.BitRange = ((bitWidth | 0) == 0x8) ? 0x7F : 0x7FFF;
this.VolumeShifter = ((bitWidth | 0) == 0x8) ? 7 : 15;
this.currentVolume = this.envelopeVolume << (this.VolumeShifter | 0);
this.noiseSampleTable = ((bitWidth | 0) == 0x8) ? this.LSFR7Table : this.LSFR15Table;
}
this.nr43 = data & 0xFF;
}
GameBoyAdvanceChannel4Synth.prototype.readSOUND4CNT_H0 = function () {
//NR43:
return this.nr43 | 0;
}
GameBoyAdvanceChannel4Synth.prototype.writeSOUND4CNT_H1 = function (data) {
data = data | 0;
//NR44:
this.nr44 = data & 0xFF;
this.consecutive = ((data & 0x40) == 0x0);
if ((data & 0x80) != 0) {
this.envelopeVolume = this.nr42 >> 4;
this.currentVolume = this.envelopeVolume << (this.VolumeShifter | 0);
this.envelopeSweepsLast = ((this.nr42 & 0x7) - 1) | 0;
if ((this.totalLength | 0) == 0) {
this.totalLength = 0x40;
}
if ((data & 0x40) != 0) {
this.sound.setNR52(0x8);
}
}
this.enableCheck();
}
GameBoyAdvanceChannel4Synth.prototype.readSOUND4CNT_H1 = function () {
//NR44:
return this.nr44 | 0;
}

View file

@ -0,0 +1,63 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GameBoyAdvanceFIFO() {
this.count = 0;
this.position = 0;
this.buffer = getInt8Array(0x20);
}
GameBoyAdvanceFIFO.prototype.push = function (sample) {
sample = sample | 0;
var writePosition = ((this.position | 0) + (this.count | 0)) | 0;
this.buffer[writePosition & 0x1F] = (sample << 24) >> 24;
if ((this.count | 0) < 0x20) {
//Should we cap at 0x20 or overflow back to 0 and reset queue?
this.count = ((this.count | 0) + 1) | 0;
}
}
GameBoyAdvanceFIFO.prototype.push8 = function (sample) {
sample = sample | 0;
this.push(sample | 0);
this.push(sample | 0);
this.push(sample | 0);
this.push(sample | 0);
}
GameBoyAdvanceFIFO.prototype.push16 = function (sample) {
sample = sample | 0;
this.push(sample | 0);
this.push(sample >> 8);
this.push(sample | 0);
this.push(sample >> 8);
}
GameBoyAdvanceFIFO.prototype.push32 = function (sample) {
sample = sample | 0;
this.push(sample | 0);
this.push(sample >> 8);
this.push(sample >> 16);
this.push(sample >> 24);
}
GameBoyAdvanceFIFO.prototype.shift = function () {
var output = 0;
if ((this.count | 0) > 0) {
this.count = ((this.count | 0) - 1) | 0;
output = this.buffer[this.position & 0x1F] << 3;
this.position = ((this.position | 0) + 1) & 0x1F;
}
return output | 0;
}
GameBoyAdvanceFIFO.prototype.requestingDMA = function () {
return ((this.count | 0) <= 0x10);
}
GameBoyAdvanceFIFO.prototype.samplesUntilDMATrigger = function () {
return ((this.count | 0) - 0x10) | 0;
}
GameBoyAdvanceFIFO.prototype.clear = function () {
this.count = 0;
}

View file

@ -0,0 +1,186 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function getInt8Array(size_t) {
try {
return new Int8Array(size_t);
}
catch (error) {
return getArray(size_t);
}
}
function getUint8Array(size_t) {
try {
return new Uint8Array(size_t);
}
catch (error) {
return getArray(size_t);
}
}
function getUint8View(typed_array) {
try {
return new Uint8Array(typed_array.buffer);
}
catch (error) {
return null;
}
}
function getSharedUint8Array(size_t) {
try {
//Compatibility for older Firefox Nightlies:
return new SharedUint8Array(size_t);
}
catch (error) {
return new Uint8Array(new SharedArrayBuffer(size_t));
}
}
function getInt16Array(size_t) {
try {
return new Int16Array(size_t);
}
catch (error) {
return getArray(size_t);
}
}
function getUint16Array(size_t) {
try {
return new Uint16Array(size_t);
}
catch (error) {
return getArray(size_t);
}
}
function getUint16View(typed_array) {
try {
return new Uint16Array(typed_array.buffer);
}
catch (error) {
return null;
}
}
function getInt32Array(size_t) {
try {
return new Int32Array(size_t);
}
catch (error) {
return getArray(size_t);
}
}
function getInt32View(typed_array) {
try {
return new Int32Array(typed_array.buffer);
}
catch (error) {
return null;
}
}
function getInt32ViewCustom(typed_array, start, end) {
try {
typed_array = getInt32View(typed_array);
return typed_array.subarray(start, end);
}
catch (error) {
try {
//Nightly Firefox 4 used to have the subarray function named as slice:
return typed_array.slice(start, end);
}
catch (error) {
return null;
}
}
}
function getSharedInt32Array(size_t) {
try {
//Compatibility for older Firefox Nightlies:
return new SharedInt32Array(size_t);
}
catch (error) {
return new Int32Array(new SharedArrayBuffer(size_t << 2));
}
}
function getUint8ViewCustom(typed_array, start, end) {
try {
typed_array = getUint8View(typed_array);
return typed_array.subarray(start, end);
}
catch (error) {
try {
//Nightly Firefox 4 used to have the subarray function named as slice:
return typed_array.slice(start, end);
}
catch (error) {
return null;
}
}
}
function getUint32Array(size_t) {
try {
return new Uint32Array(size_t);
}
catch (error) {
return getArray(size_t);
}
}
function getSharedUint32Array(size_t) {
try {
//Compatibility for older Firefox Nightlies:
return new SharedUint32Array(size_t);
}
catch (error) {
return new Uint32Array(new SharedArrayBuffer(size_t << 2));
}
}
function getFloat32Array(size_t) {
try {
return new Float32Array(size_t);
}
catch (error) {
return getArray(size_t);
}
}
function getSharedFloat32Array(size_t) {
try {
//Compatibility for older Firefox Nightlies:
return new SharedFloat32Array(size_t);
}
catch (error) {
return new Float32Array(new SharedArrayBuffer(size_t << 2));
}
}
function getArray(size_t) {
var genericArray = [];
for (var size_index = 0; size_index < size_t; ++size_index) {
genericArray[size_index] = 0;
}
return genericArray;
}
var __VIEWS_SUPPORTED__ = getUint16View(getInt32Array(1)) !== null;
var __LITTLE_ENDIAN__ = (function () {
if (__VIEWS_SUPPORTED__) {
var test = getInt32Array(1);
test[0] = 1;
var test2 = getUint16View(test);
if (test2[0] == 1) {
return true;
}
}
return false;
})();
if (typeof Atomics == "object") {
if (typeof Atomics.futexWait == "function" && typeof Atomics.wait == "undefined") {
//Polyfill in deprecated call names:
Atomics.wait = Atomics.futexWait;
Atomics.notify = Atomics.futexWake;
}
else if (typeof Atomics.wake == "function" && typeof Atomics.notify == "undefined") {
//Polyfill in deprecated call names:
Atomics.notify = Atomics.wake;
}
}

BIN
public/gfiles/gba/bios.bin Normal file

Binary file not shown.

View file

@ -0,0 +1,128 @@
<!DOCTYPE html>
<html>
<head>
<title>GBA</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=160">
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
<!--Required Scripts-->
<script src="IodineGBA/includes/TypedArrayShim.js"></script>
<script src="IodineGBA/core/Cartridge.js"></script>
<script src="IodineGBA/core/DMA.js"></script>
<script src="IodineGBA/core/Emulator.js"></script>
<script src="IodineGBA/core/Graphics.js"></script>
<script src="IodineGBA/core/RunLoop.js"></script>
<script src="IodineGBA/core/Memory.js"></script>
<script src="IodineGBA/core/IRQ.js"></script>
<script src="IodineGBA/core/JoyPad.js"></script>
<script src="IodineGBA/core/Serial.js"></script>
<script src="IodineGBA/core/Sound.js"></script>
<script src="IodineGBA/core/Timer.js"></script>
<script src="IodineGBA/core/Wait.js"></script>
<script src="IodineGBA/core/CPU.js"></script>
<script src="IodineGBA/core/Saves.js"></script>
<script src="IodineGBA/core/sound/FIFO.js"></script>
<script src="IodineGBA/core/sound/Channel1.js"></script>
<script src="IodineGBA/core/sound/Channel2.js"></script>
<script src="IodineGBA/core/sound/Channel3.js"></script>
<script src="IodineGBA/core/sound/Channel4.js"></script>
<script src="IodineGBA/core/CPU/ARM.js"></script>
<script src="IodineGBA/core/CPU/THUMB.js"></script>
<script src="IodineGBA/core/CPU/CPSR.js"></script>
<script src="IodineGBA/core/graphics/Renderer.js"></script>
<script src="IodineGBA/core/graphics/RendererShim.js"></script>
<script src="IodineGBA/core/graphics/RendererProxy.js"></script>
<script src="IodineGBA/core/graphics/BGTEXT.js"></script>
<script src="IodineGBA/core/graphics/BG2FrameBuffer.js"></script>
<script src="IodineGBA/core/graphics/BGMatrix.js"></script>
<script src="IodineGBA/core/graphics/AffineBG.js"></script>
<script src="IodineGBA/core/graphics/ColorEffects.js"></script>
<script src="IodineGBA/core/graphics/Mosaic.js"></script>
<script src="IodineGBA/core/graphics/OBJ.js"></script>
<script src="IodineGBA/core/graphics/OBJWindow.js"></script>
<script src="IodineGBA/core/graphics/Window.js"></script>
<script src="IodineGBA/core/graphics/Compositor.js"></script>
<script src="IodineGBA/core/memory/DMA0.js"></script>
<script src="IodineGBA/core/memory/DMA1.js"></script>
<script src="IodineGBA/core/memory/DMA2.js"></script>
<script src="IodineGBA/core/memory/DMA3.js"></script>
<script src="IodineGBA/core/cartridge/SaveDeterminer.js"></script>
<script src="IodineGBA/core/cartridge/SRAM.js"></script>
<script src="IodineGBA/core/cartridge/FLASH.js"></script>
<script src="IodineGBA/core/cartridge/EEPROM.js"></script>
<script src="IodineGBA/core/cartridge/GPIO.js"></script>
<!--Add your webpage scripts below-->
<script src="user_scripts/AudioGlueCode.js"></script>
<script src="user_scripts/base64.js"></script>
<script src="user_scripts/CoreGlueCode.js"></script>
<script src="user_scripts/GfxGlueCode.js"></script>
<script src="user_scripts/GUIGlueCode.js"></script>
<script src="user_scripts/JoyPadGlueCode.js"></script>
<script src="user_scripts/ROMLoadGlueCode.js"></script>
<script src="user_scripts/SavesGlueCode.js"></script>
<script src="user_scripts/WorkerGfxGlueCode.js"></script>
<script src="user_scripts/WorkerGlueCode.js"></script>
<script src="user_scripts/XAudioJS/swfobject.js"></script>
<script src="user_scripts/XAudioJS/resampler.js"></script>
<script src="user_scripts/XAudioJS/XAudioServer.js"></script>
<link rel="stylesheet" href="user_css/main.css">
</head>
<body>
<div id="container">
<!-- Minified Useless -->
<div id="menu" class="paused"> <ul id="menu_top"> <li> File <ul> <li><span>BIOS: </span> <input type="file" id="bios_load" class="files"></li><li><span>Game: </span> <input type="file" id="rom_load" class="files"></li></ul> </li><li id="play" class="show">Play</li><li id="pause" class="hide">Pause</li><li id="restart">Restart</li><li> Settings <ul> <li> <input type="checkbox" id="skip_boot" checked="checked"> Skip Boot Intro </li><li> <input type="checkbox" id="toggleSmoothScaling"> Smooth Scaling </li><li> <input type="checkbox" id="toggleDynamicSpeed"> Dynamic Speed </li><li> <input type="checkbox" id="offthread-cpu" checked="checked"> CPU off-thread </li><li> <input type="checkbox" id="offthread-gpu" checked="checked"> GPU off-thread </li><li> <input type="checkbox" id="sound"> Sound </li><li> GBA Bindings <ul> <li id="key_a"> <span>A</span> </li><li id="key_b"> <span>B</span> </li><li id="key_l"> <span>L</span> </li><li id="key_r"> <span>R</span> </li><li id="key_start"> <span>Start</span> </li><li id="key_select"> <span>Select</span> </li><li id="key_up"> <span></span> </li><li id="key_down"> <span></span> </li><li id="key_left"> <span></span> </li><li id="key_right"> <span></span> </li></ul> </li><li> Emulator Bindings <ul> <li id="key_volumeup"> <span>Volume Up</span> </li><li id="key_volumedown"> <span>Volume Down</span> </li><li id="key_speedup"> <span>Speed Up</span> </li><li id="key_slowdown"> <span>Slow Down</span> </li><li id="key_speedreset"> <span>Speed Reset</span> </li><li id="key_fullscreen"> <span>Fullscreen</span> </li><li id="key_playpause"> <span>Play/Pause</span> </li><li id="key_restart"> <span>Restart</span> </li></ul> </li></ul> </li><li> Volume <ul> <li> <input type="range" id="volume"> </li></ul> </li><li id="fullscreen">Fullscreen</li><li> <span id="speed">Speed</span> <ul> <li> <input type="range" id="speedset"> </li></ul> </li></ul> </div>
<!-- End useless -->
<ul class="menu">
<li>Controls<ul>
<li>DPad: Arrow keys</li>
<li>Start: Enter</li>
<li>Select: Space</li>
<li>A Button: D</li>
<li>B Button: C</li>
<li>L Bumper: S</li>
<li>R Bumper: X</li>
</ul>
</li>
<li>
<input type="checkbox" id="smooth" onclick="setSmooth()">Smooth
</li>
<li id="saves_menu">Saves<ul id="saves_menu_container">
<li>Import: <input type="file" id="import" class="files" accept=".export">
<label for="import">Upload Save</label>
</li>
<li>
<a href="./" id="export" target="_new">Export All Saves</a>
</li>
</ul>
</li>
</ul>
<div id="ffd" style="display: none;">
<h2>Upload GBA ROM</h2>
<input type="file" id="upload" accept=".gba" onchange="gba_upload(this)" />
<label for="upload">Choose File</label>
</div>
<div id="main">
<canvas class="canvas" id="emulator_target" width="240" height="160" oncontextmenu="event.preventDefault()"></canvas>
</div>
<div class="touch-controls">
<div class="touch-dpad">
<button id="touch-up"></button><br>
<button id="touch-left"></button>
<button id="touch-right"></button><br>
<button id="touch-down"></button>
</div>
<div class="touch-buttons">
<button id="touch-select">SELECT</button>
<button id="touch-start">START</button>
</div>
<div class="touch-buttons">
<button id="touch-a">A</button>
<button id="touch-b">B</button><br>
<button id="touch-l">L</button>
<button id="touch-r">R</button>
</div>
</div>
<span class="message" id="tempMessage"></span>
</div>
</body>
</html>

View file

@ -0,0 +1,281 @@
/*
|-----------------------------------------
| Core CSS
|-----------------------------------------
*/
html, body {
font-family: Open Sans, Arial, sans-serif;
height: 100%;
width:100%;
margin: 0;
padding: 0;
background: #000;
overflow: hidden;
box-sizing: border-box;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* reset all list items */
ul{
list-style: none;
padding: 0;
margin: 0;
}
#container {
width: 100%;
height: 100%;
}
#main {
position:absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
background-color: black;
}
/*
|-----------------------------------------
| Canvas
|-----------------------------------------
|
| Classes are automatically overwritten
| by the filter styles, therefore only
| attributes and id's can be used.
|
*/
canvas {
margin: auto;
display: block;
padding: 0px;
background-color: black;
}
canvas.textureSmooth {
image-rendering: auto;
image-rendering: optimizeQuality;
-ms-interpolation-mode: bicubic;
}
canvas.texturePixelated {
image-rendering: -webkit-optimize-contrast;
image-rendering: -o-crisp-edges;
image-rendering: -moz-crisp-edges;
image-rendering: crisp-edges;
image-rendering: pixelated;
-ms-interpolation-mode: nearest-neighbor;
}
/*
|-----------------------------------------
| Messages
|-----------------------------------------
*/
.message {
background: #6cc27d;
padding: 1em 2em;
text-align: center;
color: #fff;
bottom: 0px;
position: fixed;
left: 0px;
display: none;
font-weight: bold;
vertical-align: bottom;
font-family: monospace;
z-index: 1;
}
/*
|-----------------------------------------
| Main Menu
|-----------------------------------------
*/
/* top level menu */
div#menu {
display: none !important;
width: 0px;
height: 0px;
}
ul{
list-style: none;
padding: 0;
margin: 0;
}
.menu {
position: fixed;
display: flex;
flex-wrap: wrap;
background: #fff;
transition: .3s ease;
cursor: pointer;
font-family: sans-serif;
z-index: 1;
opacity: 0;
height: 42px;
}
.menu:hover{opacity: 0.9;}
.menu ul{
visibility: hidden;
opacity: 0;
position: absolute;
top:100%;
left:0;
background-color: rgb(245, 245, 245);
box-shadow: 0 5px 10px 0 rgba(0,0,0,.1);
transition: .3s ease;
z-index: 1;
}
.menu ul li {
padding: .3em 1em !important;
}
.menu li{
white-space: nowrap;
position: relative;
cursor:pointer;
padding: .7em 1em;
}
.menu li:hover {
background: rgba(0,0,0,.1);
}
.menu li:hover > ul{
visibility: visible;
opacity: 1;
}
li #smooth {
transform: scale(1.5);
margin: 0px 13px 0px 0px;
}
#import {
display: none;
}
#import + label {
border: 1px solid rgb(118, 118, 118);
border-radius: 2px;
padding: 4px;
margin: 2px;
font-size: 13px;
}
#export {
color: black;
text-decoration: none;
}
#ffd {
z-index: 5;
height: 200px;
width: 400px;
position: absolute;
background: #ddd;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
font-family: sans-serif;
border-radius: 2px;
}
#upload {
display: none;
}
#upload + label {
border: 1px solid rgb(118, 118, 118);
border-radius: 2px;
padding: 4px;
margin: 10px;
font-size: 13px;
background-color: white;
display: inline-block;
transition: ease .1s;
}
#upload + label:hover {
background-color: #eee;
}
/*
|-----------------------------------------
| Touch Controls
|-----------------------------------------
*/
.touch-controls{
display: flex;
padding: 1em;
position: absolute;
bottom: 0px;
left: 0px;
right: 0px;
}
.touch-controls button{
display: inline-block;
-webkit-appearance: none;
border:0;
outline: 0;
background: #fff;
opacity: 0.7;
width: 3em;
height: 3em;
line-height: 3em;
text-align: center;
cursor: pointer;
transition: .1s ease;
margin:.5em;
}
.touch-controls button:active{
transform: scale(0.95);
box-shadow: 0 0 10px 0 rgba(0,0,0,0.4) inset;
}
.touch-dpad, .touch-buttons{
flex-grow: 1;
text-align: center;
}
.touch-buttons{
align-self: flex-end;
}
.touch-dpad--up{
flex-grow: 1;
width: 100%;
}
/* Only show controls on portrait mode screens */
@media screen and (min-aspect-ratio: 1/1) {
.touch-controls{
display: none;
}
#main {
display: flex;
justify-content: center;
flex-grow: 1;
align-items: center;
}
}

View file

@ -0,0 +1,283 @@
"use strict";
/*
Copyright (C) 2012-2015 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GlueCodeMixer(playButton) {
var parentObj = this;
this.audio = new XAudioServer(2, this.sampleRate, 0, this.bufferAmount, null, function () {
parentObj.checkHeartbeats();
}, function () {
parentObj.checkPostHeartbeats();
}, 1, function () {
//Disable audio in the callback here:
parentObj.disableAudio();
}, playButton);
this.outputUnits = [];
this.outputUnitsValid = [];
this.initializeBuffer();
}
GlueCodeMixer.prototype.sampleRate = 44100;
GlueCodeMixer.prototype.bufferAmount = 44100;
GlueCodeMixer.prototype.channelCount = 2;
GlueCodeMixer.prototype.initializeBuffer = function () {
this.buffer = new AudioSimpleBuffer(this.channelCount,
this.bufferAmount);
}
GlueCodeMixer.prototype.appendInput = function (inUnit) {
if (this.audio) {
for (var index = 0; index < this.outputUnits.length; index++) {
if (!this.outputUnits[index]) {
break;
}
}
this.outputUnits[index] = inUnit;
this.outputUnitsValid.push(inUnit);
inUnit.registerStackPosition(index);
}
else if (typeof inUnit.errorCallback == "function") {
inUnit.errorCallback();
}
}
GlueCodeMixer.prototype.unregister = function (stackPosition) {
this.outputUnits[stackPosition] = null;
this.outputUnitsValid = [];
for (var index = 0, length = this.outputUnits.length; index < length; ++index) {
if (this.outputUnits[index]) {
this.outputUnitsValid.push(this.outputUnits);
}
}
}
GlueCodeMixer.prototype.checkHeartbeats = function () {
var inputCount = this.outputUnitsValid.length;
for (var inputIndex = 0, output = 0; inputIndex < inputCount; ++inputIndex) {
this.outputUnitsValid[inputIndex].heartBeatCallback();
}
}
GlueCodeMixer.prototype.checkPostHeartbeats = function () {
var inputCount = this.outputUnitsValid.length;
for (var inputIndex = 0, output = 0; inputIndex < inputCount; ++inputIndex) {
this.outputUnitsValid[inputIndex].postHeartBeatCallback();
}
}
GlueCodeMixer.prototype.checkAudio = function () {
if (this.audio) {
var inputCount = this.outputUnitsValid.length;
for (var inputIndex = 0, output = 0; inputIndex < inputCount; ++inputIndex) {
this.outputUnitsValid[inputIndex].prepareShift();
}
for (var count = 0, requested = this.findLowestBufferCount(); count < requested; ++count) {
for (var inputIndex = 0, output = 0; inputIndex < inputCount; ++inputIndex) {
output += this.outputUnitsValid[inputIndex].shift();
}
this.buffer.push(output);
}
var bufferLength = this.buffer.count();
this.audio.writeAudioNoCallback(this.buffer.buffer, bufferLength);
this.buffer.reset();
}
}
GlueCodeMixer.prototype.findLowestBufferCount = function () {
var count = 0;
for (var inputIndex = 0, inputCount = this.outputUnitsValid.length; inputIndex < inputCount; ++inputIndex) {
var tempCount = this.outputUnitsValid[inputIndex].buffer.resampledSamplesLeft();
if (tempCount > 0) {
if (count > 0) {
count = Math.min(count, tempCount);
}
else {
count = tempCount;
}
}
}
return Math.min(count, this.channelCount * this.bufferAmount);
}
GlueCodeMixer.prototype.disableAudio = function () {
this.audio = null;
}
function GlueCodeMixerInput(mixer) {
this.mixer = mixer;
this.volume = 1;
}
GlueCodeMixerInput.prototype.initialize = function (channelCount, sampleRate, bufferAmount, heartBeatCallback, postHeartBeatCallback, errorCallback) {
this.channelCount = channelCount;
this.sampleRate = sampleRate;
this.bufferAmount = bufferAmount;
this.heartBeatCallback = heartBeatCallback;
this.postHeartBeatCallback = postHeartBeatCallback;
this.errorCallback = errorCallback;
var oldBuffer = this.buffer;
this.buffer = new AudioBufferWrapper(this.channelCount,
this.mixer.channelCount,
this.bufferAmount,
this.sampleRate,
this.mixer.sampleRate);
if (oldBuffer) {
//If re-using same mixer input node, copy old buffer contents into the new buffer:
this.buffer.copyOld(oldBuffer);
}
}
GlueCodeMixerInput.prototype.register = function () {
this.mixer.appendInput(this);
}
GlueCodeMixerInput.prototype.setVolume = function (volume) {
this.volume = Math.min(Math.max(volume, 0), 1);
}
GlueCodeMixerInput.prototype.prepareShift = function () {
this.buffer.resampleRefill();
}
GlueCodeMixerInput.prototype.shift = function () {
return this.buffer.shift() * this.volume;
}
GlueCodeMixerInput.prototype.push = function (buffer, start, end) {
this.buffer.push(buffer, start, end);
this.mixer.checkAudio();
}
GlueCodeMixerInput.prototype.pushDeferred = function (buffer, start, end) {
this.buffer.push(buffer, start, end);
}
GlueCodeMixerInput.prototype.flush = function () {
this.mixer.checkAudio();
}
GlueCodeMixerInput.prototype.remainingBuffer = function () {
return this.buffer.remainingBuffer() + (Math.floor((this.mixer.audio.remainingBuffer() * this.sampleRate / this.mixer.sampleRate) / this.mixer.channelCount) * this.mixer.channelCount);
}
GlueCodeMixerInput.prototype.registerStackPosition = function (stackPosition) {
this.stackPosition = stackPosition;
}
GlueCodeMixerInput.prototype.unregister = function () {
this.mixer.unregister(this.stackPosition);
}
GlueCodeMixerInput.prototype.setBufferSpace = function (bufferAmount) {
this.buffer.setBufferSpace(bufferAmount);
}
function AudioBufferWrapper(channelCount,
mixerChannelCount,
bufferAmount,
sampleRate,
mixerSampleRate) {
this.channelCount = channelCount;
this.mixerChannelCount = mixerChannelCount;
this.bufferAmount = bufferAmount;
this.sampleRate = sampleRate;
this.mixerSampleRate = mixerSampleRate;
this.initialize();
}
AudioBufferWrapper.prototype.initialize = function () {
this.inBufferSize = this.bufferAmount * this.mixerChannelCount;
this.inBuffer = getFloat32Array(this.inBufferSize);
this.resampler = new Resampler(this.sampleRate, this.mixerSampleRate, this.mixerChannelCount, this.inBuffer);
this.outBufferSize = this.resampler.outputBuffer.length;
this.outBuffer = getFloat32Array(this.outBufferSize);
this.inputOffset = 0;
this.resampleBufferStart = 0;
this.resampleBufferEnd = 0;
}
AudioBufferWrapper.prototype.copyOld = function (oldBuffer) {
this.resampleRefill();
while (oldBuffer.resampleBufferStart != oldBuffer.resampleBufferEnd) {
this.outBuffer[this.resampleBufferEnd++] = oldBuffer.outBuffer[oldBuffer.resampleBufferStart++];
if (this.resampleBufferEnd == this.outBufferSize) {
this.resampleBufferEnd = 0;
}
if (this.resampleBufferStart == this.resampleBufferEnd) {
this.resampleBufferStart += this.mixerChannelCount;
if (this.resampleBufferStart == this.outBufferSize) {
this.resampleBufferStart = 0;
}
}
if (oldBuffer.resampleBufferStart == oldBuffer.outBufferSize) {
oldBuffer.resampleBufferStart = 0;
}
}
}
AudioBufferWrapper.prototype.push = function (buffer, start, end) {
var length = Math.min(buffer.length, end);
if (this.channelCount < this.mixerChannelCount) {
for (; start < length && this.inputOffset < this.inBufferSize;) {
for (var index = this.channelCount; index < this.mixerChannelCount; ++index) {
this.inBuffer[this.inputOffset++] = buffer[start];
}
for (index = 0; index < this.channelCount && start < length; ++index) {
this.inBuffer[this.inputOffset++] = buffer[start++];
}
}
}
else if (this.channelCount == this.mixerChannelCount) {
for (; start < length && this.inputOffset < this.inBufferSize;) {
this.inBuffer[this.inputOffset++] = buffer[start++];
}
}
else {
for (; start < length && this.inputOffset < this.inBufferSize;) {
for (index = 0; index < this.mixerChannelCount && start < length; ++index) {
this.inBuffer[this.inputOffset++] = buffer[start++];
}
start += this.channelCount - this.mixerChannelCount;
}
}
}
AudioBufferWrapper.prototype.shift = function () {
var output = 0;
if (this.resampleBufferStart != this.resampleBufferEnd) {
output = this.outBuffer[this.resampleBufferStart++];
if (this.resampleBufferStart == this.outBufferSize) {
this.resampleBufferStart = 0;
}
}
return output;
}
AudioBufferWrapper.prototype.resampleRefill = function () {
if (this.inputOffset > 0) {
//Resample a chunk of audio:
var resampleLength = this.resampler.resampler(this.inputOffset);
var resampledResult = this.resampler.outputBuffer;
for (var index2 = 0; index2 < resampleLength;) {
this.outBuffer[this.resampleBufferEnd++] = resampledResult[index2++];
if (this.resampleBufferEnd == this.outBufferSize) {
this.resampleBufferEnd = 0;
}
if (this.resampleBufferStart == this.resampleBufferEnd) {
this.resampleBufferStart += this.mixerChannelCount;
if (this.resampleBufferStart == this.outBufferSize) {
this.resampleBufferStart = 0;
}
}
}
this.inputOffset = 0;
}
}
AudioBufferWrapper.prototype.setBufferSpace = function (bufferAmount) {
while (this.inputOffset < bufferAmount && this.inputOffset < this.inBufferSize) {
this.inBuffer[this.inputOffset++] = 0;
}
}
AudioBufferWrapper.prototype.remainingBuffer = function () {
return (Math.floor((this.resampledSamplesLeft() * this.resampler.ratioWeight) / this.mixerChannelCount) * this.mixerChannelCount) + this.inputOffset;
}
AudioBufferWrapper.prototype.resampledSamplesLeft = function () {
return ((this.resampleBufferStart <= this.resampleBufferEnd) ? 0 : this.outBufferSize) + this.resampleBufferEnd - this.resampleBufferStart;
}
function AudioSimpleBuffer(channelCount, bufferAmount) {
this.channelCount = channelCount;
this.bufferAmount = bufferAmount;
this.outBufferSize = this.channelCount * this.bufferAmount;
this.stackLength = 0;
this.buffer = getFloat32Array(this.outBufferSize);
}
AudioSimpleBuffer.prototype.push = function (data) {
if (this.stackLength < this.outBufferSize) {
this.buffer[this.stackLength++] = data;
}
}
AudioSimpleBuffer.prototype.count = function () {
return this.stackLength;
}
AudioSimpleBuffer.prototype.reset = function () {
this.stackLength = 0;
}

View file

@ -0,0 +1,178 @@
"use strict";
/*
Copyright (C) 2012-2019 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
var IodineGUI = {
Iodine:null,
Blitter:null,
coreTimerID:null,
GUITimerID: null,
toMap:null,
toMapIndice:0,
suspended:false,
isPlaying:false,
startTime:(+(new Date()).getTime()),
mixerInput:null,
currentSpeed:[false,0],
defaults:{
timerRate:8,
sound:true,
volume:1,
skipBoot:false,
toggleSmoothScaling:true,
toggleDynamicSpeed:false,
toggleOffthreadGraphics:true,
toggleOffthreadCPU:(navigator.userAgent.indexOf('AppleWebKit') == -1 || (navigator.userAgent.indexOf('Windows NT 10.0') != -1 && navigator.userAgent.indexOf('Trident') == -1)),
keyZonesGBA:[
//Use this to control the GBA key mapping:
//A:
68,
//B:
67,
//Select:
32,
//Start:
13,
//Right:
39,
//Left:
37,
//Up:
38,
//Down:
40,
//R:
88,
//L:
83
//Why are these switched lol
],
keyZonesControl:[
//Use this to control the emulator function key mapping:
//Volume Down:
55,
//Volume Up:
56,
//Speed Up:
52,
//Slow Down:
51,
//Reset Speed:
53,
//Toggle Fullscreen:
54,
//Play/Pause:
80,
//Restart:
82
]
}
};
window.onload = function () {
//Populate settings:
registerDefaultSettings();
//Initialize Iodine:
registerIodineHandler();
//Initialize the timer:
calculateTiming();
//Initialize the graphics:
registerBlitterHandler();
//Initialize the audio:
registerAudioHandler();
//Register the save handler callbacks:
registerSaveHandlers();
//Register the GUI controls.
registerGUIEvents();
//Register GUI settings.
registerGUISettings();
//Additional things.
IodineGUI.Iodine.toggleSkipBootROM(true);
IodineGUI.Blitter.setSmoothScaling(false);
//File things.
fileThings();
}
function registerIodineHandler() {
try {
/*
We utilize SharedArrayBuffer and Atomics API,
which browsers prior to 2016 do not support:
*/
if (typeof SharedArrayBuffer != "function" || typeof Atomics != "object") {
throw null;
}
else if (!IodineGUI.defaults.toggleOffthreadCPU && IodineGUI.defaults.toggleOffthreadGraphics) {
//Try starting Iodine normally, but initialize offthread gfx:
IodineGUI.Iodine = new IodineGBAWorkerGfxShim();
}
else if (IodineGUI.defaults.toggleOffthreadGraphics) {
//Try starting Iodine in a webworker:
IodineGUI.Iodine = new IodineGBAWorkerShim();
//In order for save on page unload, this needs to be done:
addEvent("beforeunload", window, registerBeforeUnloadHandler);
}
else {
throw null;
}
}
catch (e) {
//Otherwise just run on-thread:
IodineGUI.Iodine = new GameBoyAdvanceEmulator();
}
}
function registerBeforeUnloadHandler(e) {
IodineGUI.Iodine.pause();
if (e.preventDefault) {
e.preventDefault();
}
return "IodineGBA needs to process your save data, leaving now may result in not saving current data.";
}
function initTimer() {
IodineGUI.Iodine.setIntervalRate(+IodineGUI.defaults.timerRate);
IodineGUI.coreTimerID = setInterval(function () {
IodineGUI.Iodine.timerCallback(((+(new Date()).getTime()) - (+IodineGUI.startTime)) >>> 0);
}, IodineGUI.defaults.timerRate | 0);
}
function calculateTiming() {
IodineGUI.Iodine.setIntervalRate(+IodineGUI.defaults.timerRate);
}
function startTimer() {
IodineGUI.coreTimerID = setInterval(function () {
IodineGUI.Iodine.timerCallback(((+(new Date()).getTime()) - (+IodineGUI.startTime)) >>> 0);
}, IodineGUI.defaults.timerRate | 0);
}
function updateTimer(newRate) {
newRate = newRate | 0;
if ((newRate | 0) != (IodineGUI.defaults.timerRate | 0)) {
IodineGUI.defaults.timerRate = newRate | 0;
IodineGUI.Iodine.setIntervalRate(+IodineGUI.defaults.timerRate);
if (IodineGUI.isPlaying) {
if (IodineGUI.coreTimerID) {
clearInterval(IodineGUI.coreTimerID);
}
initTimer();
}
}
}
function registerBlitterHandler() {
IodineGUI.Blitter = new GfxGlueCode(240, 160);
IodineGUI.Blitter.attachCanvas(document.getElementById("emulator_target"));
IodineGUI.Iodine.attachGraphicsFrameHandler(IodineGUI.Blitter);
IodineGUI.Blitter.attachGfxPostCallback(function () {
if (IodineGUI.currentSpeed[0]) {
var speedDOM = document.getElementById("speed");
speedDOM.textContent = "Speed: " + IodineGUI.currentSpeed[1] + "%";
}
});
}
function registerAudioHandler() {
var Mixer = new GlueCodeMixer(document.getElementById("play"));
IodineGUI.mixerInput = new GlueCodeMixerInput(Mixer);
IodineGUI.Iodine.attachAudioHandler(IodineGUI.mixerInput);
}

View file

@ -0,0 +1,667 @@
"use strict";
/*
Copyright (C) 2012-2019 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function registerGUIEvents() {
//Catch any play status changes:
IodineGUI.Iodine.attachPlayStatusHandler(updatePlayButton);
//Add DOM events:
addEvent("keydown", document, keyDown);
addEvent("keyup", document, keyUpPreprocess);
addEvent("change", document.getElementById("rom_load"), fileLoadROM);
addEvent("change", document.getElementById("bios_load"), fileLoadBIOS);
addEvent("click", document.getElementById("play"), function (e) {
IodineGUI.Iodine.play();
});
addEvent("click", document.getElementById("pause"), function (e) {
IodineGUI.Iodine.pause();
});
addEvent("click", document.getElementById("restart"), function (e) {
IodineGUI.Iodine.restart();
});
addEvent("click", document.getElementById("sound"), function () {
setValue("sound", !!this.checked);
if (this.checked) {
IodineGUI.Iodine.enableAudio();
}
else {
IodineGUI.Iodine.disableAudio();
}
});
addEvent("click", document.getElementById("skip_boot"), function () {
setValue("skipBoot", !!this.checked);
IodineGUI.Iodine.toggleSkipBootROM(this.checked);
});
addEvent("click", document.getElementById("toggleDynamicSpeed"), function () {
setValue("toggleDynamicSpeed", !!this.checked);
IodineGUI.Iodine.toggleDynamicSpeed(this.checked);
});
addEvent("click", document.getElementById("offthread-gpu"), function () {
setValue("toggleOffthreadGraphics", !!this.checked);
IodineGUI.Iodine.toggleOffthreadGraphics(this.checked);
});
addEvent("click", document.getElementById("offthread-cpu"), function () {
setValue("toggleOffthreadCPU", !!this.checked);
//Can't do anything until reload of page.
});
addEvent("change", document.getElementById("speedset"), speedChangeFunc);
addEvent("input", document.getElementById("speedset"), speedChangeFunc);
addEvent("click", document.getElementById("fullscreen"), toggleFullScreen);
addEvent("click", document.getElementById("key_a"), function () {
IodineGUI.toMap = IodineGUI.defaults.keyZonesGBA;
IodineGUI.toMapIndice = 0;
});
addEvent("mousedown", document.getElementById("touch-a"), function () {
IodineGUI.Iodine.keyDown(0);
});
addEvent("mouseup", document.getElementById("touch-a"), function () {
IodineGUI.Iodine.keyUp(0);
});
addEvent("click", document.getElementById("key_b"), function () {
IodineGUI.toMap = IodineGUI.defaults.keyZonesGBA;
IodineGUI.toMapIndice = 1;
});
addEvent("mousedown", document.getElementById("touch-b"), function () {
IodineGUI.Iodine.keyDown(1);
});
addEvent("mouseup", document.getElementById("touch-b"), function () {
IodineGUI.Iodine.keyUp(1);
});
addEvent("click", document.getElementById("key_select"), function () {
IodineGUI.toMap = IodineGUI.defaults.keyZonesGBA;
IodineGUI.toMapIndice = 2;
});
addEvent("mousedown", document.getElementById("touch-select"), function () {
IodineGUI.Iodine.keyDown(2);
});
addEvent("mouseup", document.getElementById("touch-select"), function () {
IodineGUI.Iodine.keyUp(2);
});
addEvent("click", document.getElementById("key_start"), function () {
IodineGUI.toMap = IodineGUI.defaults.keyZonesGBA;
IodineGUI.toMapIndice = 3;
});
addEvent("mousedown", document.getElementById("touch-start"), function () {
IodineGUI.Iodine.keyDown(3);
});
addEvent("mouseup", document.getElementById("touch-start"), function () {
IodineGUI.Iodine.keyUp(3);
});
addEvent("click", document.getElementById("key_right"), function () {
IodineGUI.toMap = IodineGUI.defaults.keyZonesGBA;
IodineGUI.toMapIndice = 4;
});
addEvent("mousedown", document.getElementById("touch-right"), function () {
IodineGUI.Iodine.keyDown(4);
});
addEvent("mouseup", document.getElementById("touch-right"), function () {
IodineGUI.Iodine.keyUp(4);
});
addEvent("click", document.getElementById("key_left"), function () {
IodineGUI.toMap = IodineGUI.defaults.keyZonesGBA;
IodineGUI.toMapIndice = 5;
});
addEvent("mousedown", document.getElementById("touch-left"), function () {
IodineGUI.Iodine.keyDown(5);
});
addEvent("mouseup", document.getElementById("touch-left"), function () {
IodineGUI.Iodine.keyUp(5);
});
addEvent("click", document.getElementById("key_up"), function () {
IodineGUI.toMap = IodineGUI.defaults.keyZonesGBA;
IodineGUI.toMapIndice = 6;
});
addEvent("mousedown", document.getElementById("touch-up"), function () {
IodineGUI.Iodine.keyDown(6);
});
addEvent("mouseup", document.getElementById("touch-up"), function () {
IodineGUI.Iodine.keyUp(6);
});
addEvent("click", document.getElementById("key_down"), function () {
IodineGUI.toMap = IodineGUI.defaults.keyZonesGBA;
IodineGUI.toMapIndice = 7;
});
addEvent("mousedown", document.getElementById("touch-down"), function () {
IodineGUI.Iodine.keyDown(7);
});
addEvent("mouseup", document.getElementById("touch-down"), function () {
IodineGUI.Iodine.keyUp(7);
});
addEvent("click", document.getElementById("key_r"), function () {
IodineGUI.toMap = IodineGUI.defaults.keyZonesGBA;
IodineGUI.toMapIndice = 8;
});
addEvent("mousedown", document.getElementById("touch-r"), function () {
IodineGUI.Iodine.keyDown(8);
});
addEvent("mouseup", document.getElementById("touch-r"), function () {
IodineGUI.Iodine.keyUp(8);
});
addEvent("click", document.getElementById("key_l"), function () {
IodineGUI.toMap = IodineGUI.defaults.keyZonesGBA;
IodineGUI.toMapIndice = 9;
});
addEvent("mousedown", document.getElementById("touch-l"), function () {
IodineGUI.Iodine.keyDown(9);
});
addEvent("mouseup", document.getElementById("touch-l"), function () {
IodineGUI.Iodine.keyUp(9);
});
addEvent("click", document.getElementById("key_volumedown"), function () {
IodineGUI.toMap = IodineGUI.defaults.keyZonesControl;
IodineGUI.toMapIndice = 0;
});
addEvent("click", document.getElementById("key_volumeup"), function () {
IodineGUI.toMap = IodineGUI.defaults.keyZonesControl;
IodineGUI.toMapIndice = 1;
});
addEvent("click", document.getElementById("key_speedup"), function () {
IodineGUI.toMap = IodineGUI.defaults.keyZonesControl;
IodineGUI.toMapIndice = 2;
});
addEvent("click", document.getElementById("key_slowdown"), function () {
IodineGUI.toMap = IodineGUI.defaults.keyZonesControl;
IodineGUI.toMapIndice = 3;
});
addEvent("click", document.getElementById("key_speedreset"), function () {
IodineGUI.toMap = IodineGUI.defaults.keyZonesControl;
IodineGUI.toMapIndice = 4;
});
addEvent("click", document.getElementById("key_fullscreen"), function () {
IodineGUI.toMap = IodineGUI.defaults.keyZonesControl;
IodineGUI.toMapIndice = 5;
});
addEvent("click", document.getElementById("key_playpause"), function () {
IodineGUI.toMap = IodineGUI.defaults.keyZonesControl;
IodineGUI.toMapIndice = 6;
});
addEvent("click", document.getElementById("key_restart"), function () {
IodineGUI.toMap = IodineGUI.defaults.keyZonesControl;
IodineGUI.toMapIndice = 7;
});
addEvent("change", document.getElementById("import"), function (e) {
if (typeof this.files != "undefined") {
try {
if (this.files.length >= 1) {
writeRedTemporaryText("Reading the local file \"" + this.files[0].name + "\" for importing.");
try {
//Gecko 1.9.2+ (Standard Method)
var binaryHandle = new FileReader();
binaryHandle.onload = function () {
if (this.readyState == 2) {
writeRedTemporaryText("file imported.");
try {
import_save(this.result);
}
catch (error) {
writeRedTemporaryText(error.message + " file: " + error.fileName + " line: " + error.lineNumber);
}
}
else {
writeRedTemporaryText("importing file, please wait...");
}
}
binaryHandle.readAsBinaryString(this.files[this.files.length - 1]);
}
catch (error) {
//Gecko 1.9.0, 1.9.1 (Non-Standard Method)
var romImageString = this.files[this.files.length - 1].getAsBinary();
try {
import_save(romImageString);
}
catch (error) {
writeRedTemporaryText(error.message + " file: " + error.fileName + " line: " + error.lineNumber);
}
}
}
else {
writeRedTemporaryText("Incorrect number of files selected for local loading.");
}
}
catch (error) {
writeRedTemporaryText("Could not load in a locally stored ROM file.");
}
}
else {
writeRedTemporaryText("could not find the handle on the file to open.");
}
if (e.preventDefault) {
e.preventDefault();
}
});
addEvent("click", document.getElementById("export"), refreshStorageListing);
addEvent("unload", window, ExportSave);
IodineGUI.Iodine.attachSpeedHandler(function (speed) {
speed = speed.toFixed(2);
if (speed != IodineGUI.currentSpeed[1]) {
IodineGUI.currentSpeed[1] = speed;
IodineGUI.currentSpeed[0] = true;
}
});
addEvent("change", document.getElementById("volume"), volChangeFunc);
addEvent("input", document.getElementById("volume"), volChangeFunc);
addEvent("resize", window, resizeCanvasFunc);
addEvent("mouseover", document.getElementById("saves_menu"), rebuildSavesMenu);
if (typeof document.hidden !== "undefined") {
addEvent("visibilitychange", document, visibilityChangeHandle);
}
else if (typeof document.mozHidden !== "undefined") {
addEvent("mozvisibilitychange", document, mozVisibilityChangeHandle);
}
else if (typeof document.msHidden !== "undefined") {
addEvent("msvisibilitychange", document, msVisibilityChangeHandle);
}
else if (typeof document.webkitHidden !== "undefined") {
addEvent("webkitvisibilitychange", document, webkitVisibilityChangeHandle);
}
//Run on init as well:
resizeCanvasFunc();
}
function registerDefaultSettings() {
if (findValue("sound") === null) {
setValue("sound", !!IodineGUI.defaults.sound);
}
else {
IodineGUI.defaults.sound = !!findValue("sound");
}
if (findValue("volume") === null) {
setValue("volume", +IodineGUI.defaults.volume);
}
else {
IodineGUI.defaults.volume = +findValue("volume");
}
document.getElementById("volume").value = Math.round(IodineGUI.defaults.volume * 100);
document.getElementById("speedset").value = 50;
if (findValue("skipBoot") === null) {
setValue("skipBoot", !!IodineGUI.defaults.skipBoot);
}
else {
IodineGUI.defaults.skipBoot = !!findValue("skipBoot");
}
if (findValue("toggleSmoothScaling") === null) {
setValue("toggleSmoothScaling", !!IodineGUI.defaults.toggleSmoothScaling);
}
else {
IodineGUI.defaults.toggleSmoothScaling = !!findValue("toggleSmoothScaling");
}
if (findValue("toggleDynamicSpeed") === null) {
setValue("toggleDynamicSpeed", !!IodineGUI.defaults.toggleDynamicSpeed);
}
else {
IodineGUI.defaults.toggleDynamicSpeed = !!findValue("toggleDynamicSpeed");
}
if (findValue("toggleOffthreadGraphics") === null) {
setValue("toggleOffthreadGraphics", !!IodineGUI.defaults.toggleOffthreadGraphics);
}
else {
IodineGUI.defaults.toggleOffthreadGraphics = !!findValue("toggleOffthreadGraphics");
}
if (findValue("toggleOffthreadCPU") === null) {
setValue("toggleOffthreadCPU", !!IodineGUI.defaults.toggleOffthreadCPU);
}
else {
IodineGUI.defaults.toggleOffthreadCPU = !!findValue("toggleOffthreadCPU");
}
// Why is this even a thing???
/*if (findValue("key_a") === null) {
setValue("key_a", IodineGUI.defaults.keyZonesGBA[0] | 0);
}
else {
IodineGUI.defaults.keyZonesGBA[0] = findValue("key_a");
}
if (findValue("key_b") === null) {
setValue("key_b", IodineGUI.defaults.keyZonesGBA[1] | 0);
}
else {
IodineGUI.defaults.keyZonesGBA[1] = findValue("key_b");
}
if (findValue("key_select") === null) {
setValue("key_select", IodineGUI.defaults.keyZonesGBA[2] | 0);
}
else {
IodineGUI.defaults.keyZonesGBA[2] = findValue("key_select");
}
if (findValue("key_start") === null) {
setValue("key_start", IodineGUI.defaults.keyZonesGBA[3] | 0);
}
else {
IodineGUI.defaults.keyZonesGBA[3] = findValue("key_start");
}
if (findValue("key_right") === null) {
setValue("key_right", IodineGUI.defaults.keyZonesGBA[4] | 0);
}
else {
IodineGUI.defaults.keyZonesGBA[4] = findValue("key_right");
}
if (findValue("key_left") === null) {
setValue("key_left", IodineGUI.defaults.keyZonesGBA[5] | 0);
}
else {
IodineGUI.defaults.keyZonesGBA[5] = findValue("key_left");
}
if (findValue("key_up") === null) {
setValue("key_up", IodineGUI.defaults.keyZonesGBA[6] | 0);
}
else {
IodineGUI.defaults.keyZonesGBA[6] = findValue("key_up");
}
if (findValue("key_down") === null) {
setValue("key_down", IodineGUI.defaults.keyZonesGBA[7] | 0);
}
else {
IodineGUI.defaults.keyZonesGBA[7] = findValue("key_down");
}
if (findValue("key_r") === null) {
setValue("key_r", IodineGUI.defaults.keyZonesGBA[8] | 0);
}
else {
IodineGUI.defaults.keyZonesGBA[8] = findValue("key_r");
}
if (findValue("key_l") === null) {
setValue("key_l", IodineGUI.defaults.keyZonesGBA[9] | 0);
}
else {
IodineGUI.defaults.keyZonesGBA[9] = findValue("key_l");
}*/
if (findValue("key_volumedown") === null) {
setValue("key_volumedown", IodineGUI.defaults.keyZonesControl[0] | 0);
}
else {
IodineGUI.defaults.keyZonesControl[0] = findValue("key_volumedown");
}
if (findValue("key_volumeup") === null) {
setValue("key_volumeup", IodineGUI.defaults.keyZonesControl[1] | 0);
}
else {
IodineGUI.defaults.keyZonesControl[1] = findValue("key_volumeup");
}
if (findValue("key_speedup") === null) {
setValue("key_speedup", IodineGUI.defaults.keyZonesControl[2] | 0);
}
else {
IodineGUI.defaults.keyZonesControl[2] = findValue("key_speedup");
}
if (findValue("key_slowdown") === null) {
setValue("key_slowdown", IodineGUI.defaults.keyZonesControl[3] | 0);
}
else {
IodineGUI.defaults.keyZonesControl[3] = findValue("key_slowdown");
}
if (findValue("key_speedreset") === null) {
setValue("key_speedreset", IodineGUI.defaults.keyZonesControl[4] | 0);
}
else {
IodineGUI.defaults.keyZonesControl[4] = findValue("key_speedreset");
}
if (findValue("key_fullscreen") === null) {
setValue("key_fullscreen", IodineGUI.defaults.keyZonesControl[5] | 0);
}
else {
IodineGUI.defaults.keyZonesControl[5] = findValue("key_fullscreen");
}
if (findValue("key_playpause") === null) {
setValue("key_playpause", IodineGUI.defaults.keyZonesControl[6] | 0);
}
else {
IodineGUI.defaults.keyZonesControl[6] = findValue("key_playpause");
}
if (findValue("key_restart") === null) {
setValue("key_restart", IodineGUI.defaults.keyZonesControl[7] | 0);
}
else {
IodineGUI.defaults.keyZonesControl[7] = findValue("key_restart");
}
}
function saveKeyBindings() {
setValue("key_a", IodineGUI.defaults.keyZonesGBA[0] | 0);
setValue("key_b", IodineGUI.defaults.keyZonesGBA[1] | 0);
setValue("key_select", IodineGUI.defaults.keyZonesGBA[2] | 0);
setValue("key_start", IodineGUI.defaults.keyZonesGBA[3] | 0);
setValue("key_right", IodineGUI.defaults.keyZonesGBA[4] | 0);
setValue("key_left", IodineGUI.defaults.keyZonesGBA[5] | 0);
setValue("key_up", IodineGUI.defaults.keyZonesGBA[6] | 0);
setValue("key_down", IodineGUI.defaults.keyZonesGBA[7] | 0);
setValue("key_r", IodineGUI.defaults.keyZonesGBA[8] | 0);
setValue("key_l", IodineGUI.defaults.keyZonesGBA[9] | 0);
setValue("key_volumedown", IodineGUI.defaults.keyZonesControl[0] | 0);
setValue("key_volumeup", IodineGUI.defaults.keyZonesControl[1] | 0);
setValue("key_speedup", IodineGUI.defaults.keyZonesControl[2] | 0);
setValue("key_slowdown", IodineGUI.defaults.keyZonesControl[3] | 0);
setValue("key_speedreset", IodineGUI.defaults.keyZonesControl[4] | 0);
setValue("key_fullscreen", IodineGUI.defaults.keyZonesControl[5] | 0);
setValue("key_playpause", IodineGUI.defaults.keyZonesControl[6] | 0);
setValue("key_restart", IodineGUI.defaults.keyZonesControl[7] | 0);
}
function registerGUISettings() {
document.getElementById("sound").checked = IodineGUI.defaults.sound;
if (IodineGUI.defaults.sound) {
IodineGUI.Iodine.enableAudio();
}
try {
var volControl = document.getElementById("volume");
volControl.min = 0;
volControl.max = 100;
volControl.step = 1;
volControl.value = IodineGUI.defaults.volume * 100;
}
catch (e) {}
IodineGUI.mixerInput.setVolume(IodineGUI.defaults.volume);
document.getElementById("skip_boot").checked = IodineGUI.defaults.skipBoot;
IodineGUI.Iodine.toggleSkipBootROM(IodineGUI.defaults.skipBoot);
document.getElementById("toggleSmoothScaling").checked = IodineGUI.defaults.toggleSmoothScaling;
IodineGUI.Blitter.setSmoothScaling(IodineGUI.defaults.toggleSmoothScaling);
document.getElementById("toggleDynamicSpeed").checked = IodineGUI.defaults.toggleDynamicSpeed;
IodineGUI.Iodine.toggleDynamicSpeed(IodineGUI.defaults.toggleDynamicSpeed);
document.getElementById("offthread-gpu").checked = IodineGUI.defaults.toggleOffthreadGraphics;
IodineGUI.Iodine.toggleOffthreadGraphics(IodineGUI.defaults.toggleOffthreadGraphics);
document.getElementById("offthread-cpu").checked = IodineGUI.defaults.toggleOffthreadCPU;
if (typeof SharedArrayBuffer != "function" || typeof Atomics != "object") {
document.getElementById("offthread-gpu").disabled = true;
document.getElementById("offthread-cpu").disabled = true;
}
}
function updatePlayButton(isPlaying) {
isPlaying = isPlaying | 0;
if ((isPlaying | 0) == 1) {
document.getElementById("play").className = "hide";
document.getElementById("pause").className = "show";
document.getElementById("menu").className = "playing";
if (!IodineGUI.coreTimerID) {
startTimer();
}
IodineGUI.isPlaying = true;
}
else {
document.getElementById("pause").className = "hide";
document.getElementById("play").className = "show";
document.getElementById("menu").className = "paused";
if (IodineGUI.coreTimerID) {
clearInterval(IodineGUI.coreTimerID);
IodineGUI.coreTimerID = null;
}
IodineGUI.isPlaying = false;
}
}
function visibilityChangeHandle() {
processVisibilityChange(document.hidden);
}
function mozVisibilityChangeHandle() {
processVisibilityChange(document.mozHidden);
}
function msVisibilityChangeHandle() {
processVisibilityChange(document.msHidden);
}
function webkitVisibilityChangeHandle() {
processVisibilityChange(document.webkitHidden);
}
function processVisibilityChange(isHidden) {
if (!isHidden) {
if (IodineGUI.suspended) {
IodineGUI.suspended = false;
IodineGUI.Iodine.play();
}
}
else {
if (document.getElementById("play").className == "hide") {
IodineGUI.Iodine.pause();
IodineGUI.suspended = true;
}
}
}
function stepVolume(delta) {
var volume = document.getElementById("volume").value / 100;
volume = Math.min(Math.max(volume + delta, 0), 1);
IodineGUI.mixerInput.setVolume(volume);
document.getElementById("volume").value = Math.round(volume * 100);
}
function volChangeFunc() {
var volume = Math.min(Math.max(parseInt(this.value), 0), 100) * 0.01;
setValue("volume", +volume);
IodineGUI.mixerInput.setVolume(+volume);
};
function speedChangeFunc() {
var speed = Math.min(Math.max(parseInt(this.value), 0), 100) / 50;
speed = speed * speed;
IodineGUI.Iodine.setSpeed(+speed);
}
function writeRedTemporaryText(textString) {
//lol
/*if (IodineGUI.GUITimerID) {
clearTimeout(IodineGUI.GUITimerID);
}
document.getElementById("tempMessage").style.display = "block";
document.getElementById("tempMessage").textContent = textString;
IodineGUI.GUITimerID = setTimeout(clearTempString, 5000);*/
}
function clearTempString() {
document.getElementById("tempMessage").style.display = "none";
}
function resizeCanvasFunc() {
var container = document.getElementById("main");
var containerHeight = container.clientHeight || container.offsetHeight || 0;
var containerWidth = container.clientWidth || container.offsetWidth || 0;
if (containerHeight > 0 && containerWidth > 0) {
var canvas = document.getElementById("emulator_target");
var maxWidth = Math.floor(containerHeight * 1.5);
var maxHeight = Math.floor(containerWidth / 1.5);
var height = Math.min(maxHeight, containerHeight);
var width = Math.min(maxWidth, containerWidth);
canvas.style.width = width + "px";
canvas.style.height = height + "px";
}
}
function rebuildSavesMenu(e) {
if (didNotEnter(document.getElementById("saves_menu_container"), e)) {
ExportSave();
rebuildExistingSaves();
if (e.preventDefault) {
e.preventDefault();
}
}
}
function rebuildExistingSaves() {
var menu = document.getElementById("existing_saves_list");
ExportSave();
removeChildNodes(menu);
var keys = getSavesKeys();
while (keys.length > 0) {
addExistingSaveItem(menu, keys.shift());
}
}
function addExistingSaveItem(menu, key) {
var listItem = document.createElement("li");
listItem.className = "nowrap";
var spanItem = document.createElement("span");
spanItem.textContent = decodeKeyType(key);
listItem.appendChild(spanItem);
var submenu = document.createElement("ul");
var submenuItem = document.createElement("li");
submenuItem.className = "nowrap";
addEvent("click", submenuItem, function () {
deleteValue(key);
rebuildExistingSaves();
});
var submenuSpan = document.createElement("span");
submenuSpan.textContent = "Delete";
submenuItem.appendChild(submenuSpan);
submenu.appendChild(submenuItem);
var submenuItem2 = document.createElement("li");
submenuItem2.className = "nowrap";
var link1 = document.createElement("a");
link1.href = "data:application/octet-stream;base64," + base64(generateBlob(key, findValue(key)));
link1.download = key + "_" + ((new Date()).getTime()) + ".export";
link1.textContent = "Download as import compatible";
submenuItem2.appendChild(link1);
submenu.appendChild(submenuItem2);
var submenuItem3 = document.createElement("li");
submenuItem3.className = "nowrap";
var link2 = document.createElement("a");
//Saves are already encoded in base64:
link2.href = "data:application/octet-stream;base64," + findValue(key);
link2.download = key + "_" + ((new Date()).getTime()) + ".sav";
link2.textContent = "Download as raw binary";
submenuItem3.appendChild(link2);
submenu.appendChild(submenuItem3);
listItem.appendChild(submenu);
menu.appendChild(listItem);
}
function decodeKeyType(key) {
if (key.substring(0, 15) == "SAVE_TYPE_GUID_") {
return "Game \"" + key.substring(15) + "\" Type Code";
}
else if (key.substring(0, 10) == "SAVE_GUID_") {
return "Game \"" + key.substring(10) + "\" Cartridge Data";
}
else if (key.substring(0, 15) == "SAVE_RTC_GUID_") {
return "Game \"" + key.substring(15) + "\" RTC Data";
}
return key;
}
//Some wrappers and extensions for non-DOM3 browsers:
function removeChildNodes(node) {
while (node.firstChild) {
node.removeChild(node.firstChild);
}
}
function didNotEnter(oElement, event) {
var target = (typeof event.target != "undefined") ? event.target : event.srcElement;
while (target) {
if (isSameNode(target, oElement)) {
return false;
}
target = target.parentElement;
}
return true;
}
function isSameNode(oCheck1, oCheck2) {
return (typeof oCheck1.isSameNode == "function") ? oCheck1.isSameNode(oCheck2) : (oCheck1 === oCheck2);
}
function addEvent(sEvent, oElement, fListener) {
try {
oElement.addEventListener(sEvent, fListener, false);
}
catch (error) {
oElement.attachEvent("on" + sEvent, fListener); //Pity for IE.
}
}
function removeEvent(sEvent, oElement, fListener) {
try {
oElement.removeEventListener(sEvent, fListener, false);
}
catch (error) {
oElement.detachEvent("on" + sEvent, fListener); //Pity for IE.
}
}
function setSmooth(){
var chekd = document.getElementById("smooth").checked;setValue("toggleSmoothScaling", !!chekd);
if (IodineGUI.Blitter) {
IodineGUI.Blitter.setSmoothScaling(chekd);
}
}

View file

@ -0,0 +1,209 @@
"use strict";
/*
Copyright (C) 2010-2016 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function GfxGlueCode(width, height) {
this.graphicsFound = false; //Do we have graphics output sink found yet?
this.gfxCallback = null; //Optional callback user-supplied for vsync eventing.
this.doSmoothing = true; //Texture filter the framebuffer?
this.offscreenWidth = width; //Width of the screen.
this.offscreenHeight = height; //Height of the screen.
this.offscreenRGBCount = this.offscreenWidth * this.offscreenHeight * 3;
this.offscreenRGBACount = this.offscreenWidth * this.offscreenHeight * 4;
this.initializeVSync(); //Setup the vsync event.
this.initializeBuffers(); //Initialize the buffer storage.
}
GfxGlueCode.prototype.initializeVSync = function () {
window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
var parentObj = this;
if (!window.requestAnimationFrame) {
//Fallback timer eventing:
setInterval(function () {
parentObj.vsync();
}, 16);
}
else {
//Initialize the rAF eventing:
window.requestAnimationFrame(
function () {
parentObj.vsync();
parentObj.rAFKeepAlive();
}
)
}
}
GfxGlueCode.prototype.rAFKeepAlive = function () {
//Keep the vsync event requested:
var parentObj = this;
window.requestAnimationFrame(function () {
parentObj.vsync();
parentObj.rAFKeepAlive();
});
}
GfxGlueCode.prototype.attachCanvas = function (canvas) {
this.canvas = canvas;
this.graphicsFound = this.initializeCanvasTarget();
}
GfxGlueCode.prototype.detachCanvas = function () {
this.canvas = null;
}
GfxGlueCode.prototype.attachGfxCallback = function (gfxCallback) {
if (typeof gfxCallback == "function") {
this.gfxCallback = gfxCallback;
}
}
GfxGlueCode.prototype.attachGfxPostCallback = function (gfxPostCallback) {
if (typeof gfxPostCallback == "function") {
this.gfxPostCallback = gfxPostCallback;
}
}
GfxGlueCode.prototype.vsync = function () {
if (this.graphicsFound) {
if (typeof this.gfxCallback == "function") {
//Let the user supplied code prepare a frame or two:
this.gfxCallback();
}
//Draw a frame, if ready:
this.requestDraw();
}
}
GfxGlueCode.prototype.initializeBuffers = function () {
this.swizzledFrameFree = [getUint8Array(this.offscreenRGBCount), getUint8Array(this.offscreenRGBCount)];
this.swizzledFrameReady = [];
}
GfxGlueCode.prototype.recomputeDimension = function () {
//Cache some dimension info:
this.canvasLastWidth = this.canvas.clientWidth;
this.canvasLastHeight = this.canvas.clientHeight;
if ((navigator.userAgent.toLowerCase().indexOf("gecko") != -1 && navigator.userAgent.toLowerCase().indexOf("like gecko") == -1)) { //Sniff out firefox for selecting this path.
//Set target as unscaled:
this.onscreenWidth = this.canvas.width = this.offscreenWidth;
this.onscreenHeight = this.canvas.height = this.offscreenHeight;
}
else {
//Set target canvas as scaled:
this.onscreenWidth = this.canvas.width = this.canvas.clientWidth;
this.onscreenHeight = this.canvas.height = this.canvas.clientHeight;
}
}
GfxGlueCode.prototype.initializeCanvasTarget = function () {
try {
//Obtain dimensional information:
this.recomputeDimension();
//Get handles on the canvases:
this.canvasOffscreen = document.createElement("canvas");
this.canvasOffscreen.width = this.offscreenWidth;
this.canvasOffscreen.height = this.offscreenHeight;
this.drawContextOffscreen = this.canvasOffscreen.getContext("2d");
this.drawContextOnscreen = this.canvas.getContext("2d");
//Initialize the canvas backing buffer:
this.initializeCanvasBuffer();
//Success:
return true;
}
catch (error) {
//Failure:
return false;
}
}
GfxGlueCode.prototype.initializeCanvasBuffer = function () {
//Get a CanvasPixelArray buffer:
this.canvasBuffer = this.getBuffer(this.drawContextOffscreen, this.offscreenWidth, this.offscreenHeight);
//Initialize Alpha Channel:
this.initializeAlpha(this.canvasBuffer.data);
}
GfxGlueCode.prototype.initializeAlpha = function (canvasData) {
var length = canvasData.length;
for (var indexGFXIterate = 3; indexGFXIterate < length; indexGFXIterate += 4) {
canvasData[indexGFXIterate] = 0xFF;
}
}
GfxGlueCode.prototype.getBuffer = function (canvasContext, width, height) {
//Get a CanvasPixelArray buffer:
var buffer = null;
try {
buffer = this.drawContextOffscreen.createImageData(width, height);
}
catch (error) {
buffer = this.drawContextOffscreen.getImageData(0, 0, width, height);
}
return buffer;
}
if (__VIEWS_SUPPORTED__) {
GfxGlueCode.prototype.copyBuffer = function (buffer) {
if (this.graphicsFound) {
if (this.swizzledFrameFree.length == 0) {
this.swizzledFrameFree.push(this.swizzledFrameReady.shift());
}
var swizzledFrame = this.swizzledFrameFree.shift();
swizzledFrame.set(buffer);
this.swizzledFrameReady.push(swizzledFrame);
}
}
}
else {
GfxGlueCode.prototype.copyBuffer = function (buffer) {
if (this.graphicsFound) {
if (this.swizzledFrameFree.length == 0) {
this.swizzledFrameFree.push(this.swizzledFrameReady.shift());
}
var swizzledFrame = this.swizzledFrameFree.shift();
for (var bufferIndex = 0; bufferIndex < this.offscreenRGBCount; bufferIndex++) {
swizzledFrame[bufferIndex] = buffer[bufferIndex];
}
this.swizzledFrameReady.push(swizzledFrame);
}
}
}
GfxGlueCode.prototype.requestDraw = function () {
if (this.swizzledFrameReady.length > 0) {
var canvasData = this.canvasBuffer.data;
var swizzledFrame = this.swizzledFrameReady.shift();
for (var canvasIndex = 0, bufferIndex = 0; canvasIndex < this.offscreenRGBACount; ++canvasIndex) {
canvasData[canvasIndex++] = swizzledFrame[bufferIndex++];
canvasData[canvasIndex++] = swizzledFrame[bufferIndex++];
canvasData[canvasIndex++] = swizzledFrame[bufferIndex++];
}
this.swizzledFrameFree.push(swizzledFrame);
this.graphicsBlit();
if (typeof this.gfxPostCallback == "function") {
//Some UI element redraw:
this.gfxPostCallback();
}
}
}
GfxGlueCode.prototype.graphicsBlit = function () {
if (this.canvasLastWidth != this.canvas.clientWidth || this.canvasLastHeight != this.canvas.clientHeight) {
this.recomputeDimension();
this.processSmoothing();
}
if (this.offscreenWidth == this.onscreenWidth && this.offscreenHeight == this.onscreenHeight) {
//Canvas does not need to scale, draw directly to final:
this.drawContextOnscreen.putImageData(this.canvasBuffer, 0, 0);
}
else {
//Canvas needs to scale, draw to offscreen first:
this.drawContextOffscreen.putImageData(this.canvasBuffer, 0, 0);
//Scale offscreen canvas image onto the final:
this.drawContextOnscreen.drawImage(this.canvasOffscreen, 0, 0, this.onscreenWidth, this.onscreenHeight);
}
}
GfxGlueCode.prototype.setSmoothScaling = function (doSmoothing) {
this.doSmoothing = !!doSmoothing;
this.processSmoothing();
}
GfxGlueCode.prototype.processSmoothing = function () {
if (this.graphicsFound) {
this.canvas.className = (this.doSmoothing) ? "textureSmooth" : "texturePixelated";
this.drawContextOnscreen.mozImageSmoothingEnabled = this.doSmoothing;
this.drawContextOnscreen.webkitImageSmoothingEnabled = this.doSmoothing;
this.drawContextOnscreen.imageSmoothingEnabled = this.doSmoothing;
}
}

View file

@ -0,0 +1,124 @@
"use strict";
/*
Copyright (C) 2012-2016 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function keyDown(e) {
var keyCode = e.keyCode | 0;
for (var keyMapIndex = 0; (keyMapIndex | 0) < 10; keyMapIndex = ((keyMapIndex | 0) + 1) | 0) {
if ((IodineGUI.defaults.keyZonesGBA[keyMapIndex | 0] | 0) == (keyCode | 0)) {
IodineGUI.Iodine.keyDown(keyMapIndex | 0);
if (e.preventDefault) {
e.preventDefault();
}
return;
}
}
}
function keyUpGBA(keyCode) {
keyCode = keyCode | 0;
for (var keyMapIndex = 0; (keyMapIndex | 0) < 10; keyMapIndex = ((keyMapIndex | 0) + 1) | 0) {
if ((IodineGUI.defaults.keyZonesGBA[keyMapIndex | 0] | 0) == (keyCode | 0)) {
IodineGUI.Iodine.keyUp(keyMapIndex | 0);
return;
}
}
}
function keyUp(keyCode) {
keyCode = keyCode | 0;
for (var keyMapIndex = 0; (keyMapIndex | 0) < 8; keyMapIndex = ((keyMapIndex | 0) + 1) | 0) {
if ((IodineGUI.defaults.keyZonesControl[keyMapIndex | 0] | 0) == (keyCode | 0)) {
keyboardEmulatorControl(keyMapIndex | 0);
return true;
}
}
return false;
}
function keyUpPreprocess(e) {
var keyCode = e.keyCode | 0;
//If we're not mapping a key:
if (!IodineGUI.toMap) {
//Check for emulator binding:
if (!keyUp(keyCode | 0)) {
//Check for GBA binding:
keyUpGBA(keyCode);
}
}
else {
//Map a key binding:
IodineGUI.toMap[IodineGUI.toMapIndice | 0] = keyCode | 0;
IodineGUI.toMap = null;
saveKeyBindings();
}
}
function keyboardEmulatorControl(keyCode) {
keyCode = keyCode | 0;
switch (keyCode | 0) {
case 0:
stepVolume(-0.04);
break;
case 1:
stepVolume(0.04);
break;
case 2:
IodineGUI.Iodine.incrementSpeed(0.05);
break;
case 3:
IodineGUI.Iodine.incrementSpeed(-0.05);
break;
case 4:
IodineGUI.Iodine.setSpeed(1);
break;
case 5:
toggleFullScreen();
break;
case 6:
togglePlayState();
break;
case 7:
IodineGUI.Iodine.restart();
}
}
function toggleFullScreen() {
if (!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement ) {
if (document.documentElement.requestFullscreen) {
document.documentElement.requestFullscreen();
}
else if (document.documentElement.msRequestFullscreen) {
document.documentElement.msRequestFullscreen();
}
else if (document.documentElement.mozRequestFullScreen) {
document.documentElement.mozRequestFullScreen();
}
else if (document.documentElement.webkitRequestFullscreen) {
document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
}
}
else {
if (document.exitFullscreen) {
document.exitFullscreen();
}
else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
}
else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
}
}
function togglePlayState() {
if (IodineGUI.isPlaying) {
IodineGUI.Iodine.pause();
}
else {
IodineGUI.Iodine.play();
}
}

View file

@ -0,0 +1,115 @@
"use strict";
/*
Copyright (C) 2012-2016 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function fileThings() {
if(window.location.hash){
downloadBIOS();
downloadROM();
} else {
document.getElementById('ffd').style.display = "block";
downloadBIOS();
}
}
function yayPlay() {
document.getElementById("play").click();
}
function gba_upload(upload) {
document.getElementById('ffd').style.display = "none";
fileLoadShimCode(upload.files, attachROM);
}
function downloadBIOS() {
downloadFile("bios.bin", "BIOS");
}
function downloadROM() {
var romloc = "./roms/" + window.location.hash.substring(1) + ".gba";
downloadFile(romloc, "ROM");
}
function attachBIOS(BIOS) {
try {
IodineGUI.Iodine.attachBIOS(new Uint8Array(BIOS));
}
catch (error) {
IodineGUI.Iodine.attachBIOS(BIOS);
}
}
function attachROM(ROM) {
try {
IodineGUI.Iodine.attachROM(new Uint8Array(ROM));
}
catch (error) {
IodineGUI.Iodine.attachROM(ROM);
}
}
function fileLoadShimCode(files, ROMHandler) {
if (typeof files != "undefined") {
if (files.length >= 1) {
//Gecko 1.9.2+ (Standard Method)
try {
var binaryHandle = new FileReader();
binaryHandle.onloadend = function () {
ROMHandler(this.result);
yayPlay();
}
binaryHandle.readAsArrayBuffer(files[files.length - 1]);
}
catch (error) {
try {
var result = files[files.length - 1].getAsBinary();
var resultConverted = [];
for (var index = 0; index < result.length; ++index) {
resultConverted[index] = result.charCodeAt(index) & 0xFF;
}
ROMHandler(resultConverted);
yayPlay();
}
catch (error) {
alert("Could not load the processed ROM file!");
}
}
}
}
}
function fileLoadBIOS() {
fileLoadShimCode(this.files, attachBIOS);
}
function fileLoadROM() {
fileLoadShimCode(this.files, attachROM);
}
function downloadFile(fileName, reg) {
var ajax = new XMLHttpRequest();
ajax.onload = function(){
if(ajax.status=="404"){
document.getElementById('ffd').style.display = "block";
alert("Could not find " + fileName.substring(2));
} else {
if(reg=="BIOS"){
processDownload(this, attachBIOS);
} else if(reg=="ROM"){
processDownload(this, attachROM);
yayPlay();
}
}
}
ajax.open("GET", "./" + fileName, true);
ajax.responseType = "arraybuffer";
ajax.overrideMimeType("text/plain; charset=x-user-defined");
ajax.send(null);
}
function processDownload(parentObj, attachHandler) {
try {
attachHandler(new Uint8Array(parentObj.response));
}
catch (error) {
var data = parentObj.responseText;
var length = data.length;
var dataArray = [];
for (var index = 0; index < length; index++) {
dataArray[index] = data.charCodeAt(index) & 0xFF;
}
attachHandler(dataArray);
}
}

View file

@ -0,0 +1,289 @@
"use strict";
/*
Copyright (C) 2010-2017 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function ImportSaveCallback(name, callbackFunc, callbackFuncNoSave) {
try {
var save = findValue("SAVE_" + name);
if (save != null) {
writeRedTemporaryText("Loaded save.");
callbackFunc(base64ToArray(save));
return;
}
}
catch (error) {
writeRedTemporaryText("Could not read save: " + error.message);
}
callbackFuncNoSave();
}
function ExportSave() {
IodineGUI.Iodine.exportSave();
}
function ExportSaveCallback(name, save) {
if (name != "") {
try {
setValue("SAVE_" + name, arrayToBase64(save));
}
catch (error) {
writeRedTemporaryText("Could not store save: " + error.message);
}
}
}
function registerSaveHandlers() {
IodineGUI.Iodine.attachSaveExportHandler(ExportSaveCallback);
IodineGUI.Iodine.attachSaveImportHandler(ImportSaveCallback);
}
function import_save(blobData) {
blobData = decodeBlob(blobData);
if (blobData && blobData.blobs) {
if (blobData.blobs.length > 0) {
for (var index = 0; index < blobData.blobs.length; ++index) {
var blobID = blobData.blobs[index].blobID;
var blobContent = blobData.blobs[index].blobContent;
writeRedTemporaryText("Importing blob \"" + blobID + "\"");
if (blobContent) {
setValue(blobID, blobContent);
}
else if (blobID) {
writeRedTemporaryText("Save file imported had blob \"" + blobID + "\" with no blob data interpretable.");
}
else {
writeRedTemporaryText("Blob chunk information missing completely.");
}
}
}
else {
writeRedTemporaryText("Could not decode the imported file.");
}
}
else {
writeRedTemporaryText("Could not decode the imported file.");
}
}
function generateBlob(keyName, encodedData) {
//Append the file format prefix:
var saveString = "EMULATOR_DATA";
var consoleID = "GameBoyAdvance";
//Figure out the length:
var totalLength = (saveString.length + 4 + (1 + consoleID.length)) + ((1 + keyName.length) + (4 + encodedData.length));
//Append the total length in bytes:
saveString += to_little_endian_word(totalLength);
//Append the console ID text's length:
saveString += to_byte(consoleID.length);
//Append the console ID text:
saveString += consoleID;
//Append the blob ID:
saveString += to_byte(keyName.length);
saveString += keyName;
//Now append the save data:
saveString += to_little_endian_word(encodedData.length);
saveString += encodedData;
return saveString;
}
function generateMultiBlob(blobPairs) {
var consoleID = "GameBoyAdvance";
//Figure out the initial length:
var totalLength = 13 + 4 + 1 + consoleID.length;
//Append the console ID text's length:
var saveString = to_byte(consoleID.length);
//Append the console ID text:
saveString += consoleID;
var keyName = "";
var encodedData = "";
//Now append all the blobs:
for (var index = 0; index < blobPairs.length; ++index) {
keyName = blobPairs[index][0];
encodedData = blobPairs[index][1];
//Append the blob ID:
saveString += to_byte(keyName.length);
saveString += keyName;
//Now append the save data:
saveString += to_little_endian_word(encodedData.length);
saveString += encodedData;
//Update the total length:
totalLength += 1 + keyName.length + 4 + encodedData.length;
}
//Now add the prefix:
saveString = "EMULATOR_DATA" + to_little_endian_word(totalLength) + saveString;
return saveString;
}
function decodeBlob(blobData) {
/*Format is as follows:
- 13 byte string "EMULATOR_DATA"
- 4 byte total size (including these 4 bytes).
- 1 byte Console type ID length
- Console type ID text of 8 bit size
blobs {
- 1 byte blob ID length
- blob ID text (Used to say what the data is (SRAM/freeze state/etc...))
- 4 byte blob length
- blob of 32 bit length size
- Blob itself is encoded in base64.
}
*/
var length = blobData.length;
var blobProperties = {};
blobProperties.consoleID = null;
var blobsCount = -1;
blobProperties.blobs = [];
if (length > 17) {
if (blobData.substring(0, 13) == "EMULATOR_DATA") {
var length = Math.min(((blobData.charCodeAt(16) & 0xFF) << 24) | ((blobData.charCodeAt(15) & 0xFF) << 16) | ((blobData.charCodeAt(14) & 0xFF) << 8) | (blobData.charCodeAt(13) & 0xFF), length);
var consoleIDLength = blobData.charCodeAt(17) & 0xFF;
if (length > 17 + consoleIDLength) {
blobProperties.consoleID = blobData.substring(18, 18 + consoleIDLength);
var blobIDLength = 0;
var blobLength = 0;
for (var index = 18 + consoleIDLength; index < length;) {
blobIDLength = blobData.charCodeAt(index++) & 0xFF;
if (index + blobIDLength < length) {
blobProperties.blobs[++blobsCount] = {};
blobProperties.blobs[blobsCount].blobID = blobData.substring(index, index + blobIDLength);
index += blobIDLength;
if (index + 4 < length) {
blobLength = ((blobData.charCodeAt(index + 3) & 0xFF) << 24) | ((blobData.charCodeAt(index + 2) & 0xFF) << 16) | ((blobData.charCodeAt(index + 1) & 0xFF) << 8) | (blobData.charCodeAt(index) & 0xFF);
index += 4;
if (index + blobLength <= length) {
blobProperties.blobs[blobsCount].blobContent = blobData.substring(index, index + blobLength);
index += blobLength;
}
else {
writeRedTemporaryText("Blob length check failed, blob determined to be incomplete.");
break;
}
}
else {
writeRedTemporaryText("Blob was incomplete, bailing out.");
break;
}
}
else {
writeRedTemporaryText("Blob was incomplete, bailing out.");
break;
}
}
}
}
}
return blobProperties;
}
function refreshStorageListing() {
var keys = getSavesKeys();
var blobPairs = [];
for (var index = 0; index < keys.length; ++index) {
blobPairs[index] = [keys[index], findValue(keys[index])];
}
this.href = "data:application/octet-stream;base64," + base64(generateMultiBlob(blobPairs));
this.download = "gameboy_advance_saves_" + ((new Date()).getTime()) + ".export";
}
function checkStorageLength() {
try {
if (window.localStorage) {
return window.localStorage.length;
}
}
catch (error) {
//An older Gecko 1.8.1/1.9.0 method of storage (Deprecated due to the obvious security hole):
if (window.globalStorage && location.hostname) {
return window.globalStorage[location.hostname].length;
}
}
return 0;
}
function getSavesKeys() {
var storageLength = checkStorageLength();
var keysFound = [];
var index = 0;
var nextKey = null;
while (index < storageLength) {
nextKey = findKey(index++);
if (nextKey !== null && nextKey.length > 0) {
if (nextKey.substring(0, 15) == "IodineGBA_SAVE_") {
keysFound.push(nextKey.substring(10));
}
}
else {
break;
}
}
return keysFound;
}
function findKey(keyNum) {
try {
if (window.localStorage) {
return window.localStorage.key(keyNum);
}
}
catch (error) {
//An older Gecko 1.8.1/1.9.0 method of storage (Deprecated due to the obvious security hole):
if (window.globalStorage && location.hostname) {
return window.globalStorage[location.hostname].key(keyNum);
}
}
return null;
}
function to_little_endian_word(str) {
return to_little_endian_hword(str) + to_little_endian_hword(str >> 16);
}
function to_little_endian_hword(str) {
return to_byte(str) + to_byte(str >> 8);
}
function to_byte(str) {
return String.fromCharCode(str & 0xFF);
}
//Wrapper for localStorage getItem, so that data can be retrieved in various types.
function findValue(key) {
key = "IodineGBA_" + key;
try {
if (window.localStorage) {
if (window.localStorage.getItem(key) != null) {
return JSON.parse(window.localStorage.getItem(key));
}
}
}
catch (error) {
//An older Gecko 1.8.1/1.9.0 method of storage (Deprecated due to the obvious security hole):
if (window.globalStorage && location.hostname) {
if (window.globalStorage[location.hostname].getItem(key) != null) {
return JSON.parse(window.globalStorage[location.hostname].getItem(key));
}
}
}
return null;
}
//Wrapper for localStorage setItem, so that data can be set in various types.
function setValue(key, value) {
key = "IodineGBA_" + key;
try {
if (window.localStorage) {
window.localStorage.setItem(key, JSON.stringify(value));
}
}
catch (error) {
//An older Gecko 1.8.1/1.9.0 method of storage (Deprecated due to the obvious security hole):
if (window.globalStorage && location.hostname) {
window.globalStorage[location.hostname].setItem(key, JSON.stringify(value));
}
}
}
//Wrapper for localStorage removeItem, so that data can be set in various types.
function deleteValue(key) {
key = "IodineGBA_" + key;
try {
if (window.localStorage) {
window.localStorage.removeItem(key);
}
}
catch (error) {
//An older Gecko 1.8.1/1.9.0 method of storage (Deprecated due to the obvious security hole):
if (window.globalStorage && location.hostname) {
window.globalStorage[location.hostname].removeItem(key);
}
}
}

View file

@ -0,0 +1,130 @@
"use strict";
/*
Copyright (C) 2012-2019 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
var gfxBuffers = null;
var gfxCounters = null;
function IodineGBAWorkerGfxShim() {
this.gfx = null;
gfxBuffers = [getSharedUint8Array(160 * 240 * 3),
getSharedUint8Array(160 * 240 * 3)];
gfxCounters = getSharedInt32Array(3);
this.Iodine = new GameBoyAdvanceEmulator();
}
IodineGBAWorkerGfxShim.prototype.play = function () {
this.Iodine.play();
}
IodineGBAWorkerGfxShim.prototype.pause = function () {
this.Iodine.pause();
}
IodineGBAWorkerGfxShim.prototype.restart = function () {
this.Iodine.restart();
}
IodineGBAWorkerGfxShim.prototype.setIntervalRate = function (rate) {
rate = +rate;
this.Iodine.setIntervalRate(+rate);
}
IodineGBAWorkerGfxShim.prototype.timerCallback = function (timestamp) {
timestamp = timestamp >>> 0;
this.Iodine.timerCallback(timestamp);
}
IodineGBAWorkerGfxShim.prototype.attachGraphicsFrameHandler = function (gfx) {
this.gfx = gfx;
var parentObj = this;
this.gfx.attachGfxCallback(function () {
parentObj.graphicsHeartBeat();
});
this.Iodine.attachGraphicsFrameHandler(gfx);
}
IodineGBAWorkerGfxShim.prototype.attachAudioHandler = function (audio) {
this.Iodine.attachAudioHandler(audio);
}
IodineGBAWorkerGfxShim.prototype.enableAudio = function () {
this.Iodine.enableAudio();
}
IodineGBAWorkerGfxShim.prototype.disableAudio = function () {
this.Iodine.disableAudio();
}
IodineGBAWorkerGfxShim.prototype.toggleSkipBootROM = function (doEnable) {
doEnable = doEnable | 0;
this.Iodine.toggleSkipBootROM(doEnable | 0);
}
IodineGBAWorkerGfxShim.prototype.toggleDynamicSpeed = function (doEnable) {
doEnable = doEnable | 0;
this.Iodine.toggleDynamicSpeed(doEnable | 0);
}
IodineGBAWorkerGfxShim.prototype.toggleOffthreadGraphics = function (doEnable) {
doEnable = doEnable | 0;
this.Iodine.toggleOffthreadGraphics(doEnable | 0);
}
IodineGBAWorkerGfxShim.prototype.attachSpeedHandler = function (speed) {
this.Iodine.attachSpeedHandler(speed);
}
IodineGBAWorkerGfxShim.prototype.attachPlayStatusHandler = function (playStatus) {
this.Iodine.attachPlayStatusHandler(playStatus);
}
IodineGBAWorkerGfxShim.prototype.keyDown = function (keyCode) {
keyCode = keyCode | 0;
this.Iodine.keyDown(keyCode | 0);
}
IodineGBAWorkerGfxShim.prototype.keyUp = function (keyCode) {
keyCode = keyCode | 0;
this.Iodine.keyUp(keyCode | 0);
}
IodineGBAWorkerGfxShim.prototype.incrementSpeed = function (newSpeed) {
newSpeed = +newSpeed;
this.Iodine.incrementSpeed(+newSpeed);
}
IodineGBAWorkerGfxShim.prototype.setSpeed = function (newSpeed) {
newSpeed = +newSpeed;
this.Iodine.setSpeed(+newSpeed);
}
IodineGBAWorkerGfxShim.prototype.attachBIOS = function (BIOS) {
this.Iodine.attachBIOS(BIOS);
}
IodineGBAWorkerGfxShim.prototype.attachROM = function (ROM) {
this.Iodine.attachROM(ROM);
}
IodineGBAWorkerGfxShim.prototype.exportSave = function () {
this.Iodine.exportSave();
}
IodineGBAWorkerGfxShim.prototype.attachSaveExportHandler = function (saveExport) {
this.Iodine.attachSaveExportHandler(saveExport);
}
IodineGBAWorkerGfxShim.prototype.attachSaveImportHandler = function (saveImport) {
this.Iodine.attachSaveImportHandler(saveImport);
}
IodineGBAWorkerGfxShim.prototype.graphicsHeartBeat = function () {
//If graphics callback handle provided and we got a buffer reference:
if (this.gfx && gfxCounters) {
//Copy the buffer out to local:
this.consumeGraphicsBuffer();
//Wake up the producer thread:
Atomics.notify(gfxCounters, 2, 1);
}
}
IodineGBAWorkerGfxShim.prototype.consumeGraphicsBuffer = function () {
//Load the counter values:
var start = gfxCounters[0] | 0; //Written by this thread.
var end = Atomics.load(gfxCounters, 1) | 0; //Written by the other thread.
//Don't process if nothing to process:
if ((end | 0) == (start | 0)) {
//Buffer is empty:
return;
}
//Copy samples out from the ring buffer:
do {
//Hardcoded for 2 buffers for a triple buffer effect:
this.gfx.copyBuffer(gfxBuffers[start & 0x1]);
start = ((start | 0) + 1) | 0;
} while ((start | 0) != (end | 0));
//Update the starting position counter to match the end position:
//Let the other Atomic loads/stores naturally flush this value:
gfxCounters[0] = end | 0;
}

View file

@ -0,0 +1,328 @@
"use strict";
/*
Copyright (C) 2012-2019 Grant Galitz
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
function IodineGBAWorkerShim() {
this.playStatus = null;
this.gfx = null;
this.audio = null;
this.speed = null;
this.saveExport = null;
this.saveImport = null;
this.worker = null;
this.gfxBuffers = null;
this.gfxCounters = null;
this.audioBuffer = null;
this.audioCounters = null;
this.audioSamplesRemaining = null;
this.audioBufferSize = 0;
this.audioBufferSizeMask = 0;
this.audioInitialized = false;
this.timestamp = null;
this.initialize();
}
var tempvar = document.getElementsByTagName("script");
IodineGBAWorkerShim.prototype.filepath = tempvar[tempvar.length-1].src;
IodineGBAWorkerShim.prototype.initialize = function () {
var parentObj = this;
var loc = this.filepath.split("/");
loc = loc.slice(0, loc.length - 2).join("/");
loc += "/IodineGBA/core/Worker.js";
this.worker = new Worker(loc);
this.worker.onmessage = function (event) {
parentObj.decodeMessage(event.data);
}
}
IodineGBAWorkerShim.prototype.sendMessageSingle = function (eventCode) {
eventCode = eventCode | 0;
this.worker.postMessage({messageID:eventCode});
}
IodineGBAWorkerShim.prototype.sendMessageDouble = function (eventCode, eventData) {
eventCode = eventCode | 0;
this.worker.postMessage({messageID:eventCode, payload:eventData});
}
IodineGBAWorkerShim.prototype.play = function () {
this.sendMessageSingle(0);
}
IodineGBAWorkerShim.prototype.pause = function () {
this.sendMessageSingle(1);
}
IodineGBAWorkerShim.prototype.restart = function () {
this.sendMessageSingle(2);
}
IodineGBAWorkerShim.prototype.setIntervalRate = function (rate) {
rate = +rate;
this.sendMessageDouble(3, +rate);
}
IodineGBAWorkerShim.prototype.timerCallback = function (timestamp) {
timestamp = timestamp >>> 0;
//If memory location provided for timestamp buffering:
if (this.timestamp) {
//Forward latest timestamp to worker:
Atomics.store(this.timestamp, 0, timestamp >>> 0);
}
}
IodineGBAWorkerShim.prototype.attachPlayStatusHandler = function (playStatus) {
this.playStatus = playStatus;
this.sendMessageSingle(23);
}
IodineGBAWorkerShim.prototype.issuePlayStatus = function (isPlaying) {
isPlaying = isPlaying | 0;
if (this.playStatus) {
this.playStatus(isPlaying | 0);
}
}
IodineGBAWorkerShim.prototype.attachGraphicsFrameHandler = function (gfx) {
this.gfx = gfx;
var parentObj = this;
this.gfx.attachGfxCallback(function () {
parentObj.graphicsHeartBeat();
});
this.sendMessageSingle(4);
}
IodineGBAWorkerShim.prototype.attachAudioHandler = function (audio) {
this.audio = audio;
this.sendMessageSingle(5);
}
IodineGBAWorkerShim.prototype.enableAudio = function () {
if (this.audio) {
this.sendMessageSingle(6);
}
}
IodineGBAWorkerShim.prototype.disableAudio = function () {
if (this.audio) {
this.sendMessageSingle(7);
}
}
IodineGBAWorkerShim.prototype.toggleSkipBootROM = function (doEnable) {
doEnable = doEnable | 0;
this.sendMessageDouble(8, doEnable | 0);
}
IodineGBAWorkerShim.prototype.toggleDynamicSpeed = function (doEnable) {
doEnable = doEnable | 0;
this.sendMessageDouble(9, doEnable | 0);
}
IodineGBAWorkerShim.prototype.toggleOffthreadGraphics = function (doEnable) {
doEnable = doEnable | 0;
this.sendMessageDouble(22, doEnable | 0);
}
IodineGBAWorkerShim.prototype.attachSpeedHandler = function (speed) {
this.speed = speed;
this.sendMessageSingle(10);
}
IodineGBAWorkerShim.prototype.keyDown = function (keyCode) {
keyCode = keyCode | 0;
this.sendMessageDouble(11, keyCode | 0);
}
IodineGBAWorkerShim.prototype.keyUp = function (keyCode) {
keyCode = keyCode | 0;
this.sendMessageDouble(12, keyCode | 0);
}
IodineGBAWorkerShim.prototype.incrementSpeed = function (newSpeed) {
newSpeed = +newSpeed;
this.sendMessageDouble(13, +newSpeed);
}
IodineGBAWorkerShim.prototype.setSpeed = function (newSpeed) {
newSpeed = +newSpeed;
this.sendMessageDouble(14, +newSpeed);
}
IodineGBAWorkerShim.prototype.attachBIOS = function (BIOS) {
this.sendMessageDouble(15, BIOS);
}
IodineGBAWorkerShim.prototype.attachROM = function (ROM) {
this.sendMessageDouble(16, ROM);
}
IodineGBAWorkerShim.prototype.exportSave = function () {
this.sendMessageSingle(17);
}
IodineGBAWorkerShim.prototype.attachSaveExportHandler = function (saveExport) {
this.saveExport = saveExport;
this.sendMessageSingle(18);
}
IodineGBAWorkerShim.prototype.attachSaveImportHandler = function (saveImport) {
this.saveImport = saveImport;
this.sendMessageSingle(19);
}
IodineGBAWorkerShim.prototype.decodeMessage = function (data) {
switch (data.messageID | 0) {
case 0:
this.buffersInitialize(data.gfxBuffer1, data.gfxBuffer2, data.gfxCounters, data.audioSamplesRemaining, data.timestamp);
break;
case 1:
this.audioInitialize(data.channels | 0, +data.sampleRate, data.bufferLimit | 0, data.audioBuffer, data.audioCounters);
break;
case 2:
this.audioRegister();
break;
case 3:
this.audioUnregister();
break;
case 4:
this.audioSetBufferSpace(data.audioBufferContainAmount | 0);
break;
case 5:
this.saveImportRequest(data.saveID);
break;
case 6:
this.saveExportRequest(data.saveID, data.saveData);
break;
case 7:
this.speedPush(+data.speed);
break;
default:
this.issuePlayStatus(data.playing | 0);
}
}
IodineGBAWorkerShim.prototype.audioInitialize = function (channels, sampleRate, bufferLimit, audioBuffer, audioCounters) {
channels = channels | 0;
sampleRate = +sampleRate;
bufferLimit = bufferLimit | 0;
var parentObj = this;
if (this.audio) {
//(Re-)Initialize:
this.audio.initialize(channels | 0, +sampleRate, bufferLimit | 0, function () {
//Empty buffers inside the provided audio event callback:
parentObj.audioHeartBeat();
}, function () {
//Get the remaining sample count:
parentObj.audioPostHeartBeat();
},function () {
//Disable audio in the callback here:
parentObj.disableAudio();
});
this.audioInitialized = true;
}
//Grab the new buffer:
this.audioBuffer = audioBuffer;
this.audioCounters = audioCounters;
this.audioBufferSize = audioBuffer.length | 0;
this.audioBufferSizeMask = ((this.audioBufferSize | 0) - 1) | 0;
}
IodineGBAWorkerShim.prototype.audioHeartBeat = function () {
//If audio API handle provided and we got a buffer reference:
if (this.audioInitialized) {
//Empty the buffer out:
this.consumeAudioBuffer();
}
}
IodineGBAWorkerShim.prototype.consumeAudioBuffer = function () {
//Load the counter values:
var start = this.audioCounters[0] | 0; //Written by this thread.
var end = Atomics.load(this.audioCounters, 1) | 0; //Written to by the other thread.
//Don't process if nothing to process:
if ((end | 0) == (start | 0)) {
//Buffer is empty:
return;
}
//Copy samples out from the ring buffer:
this.copyAudioBuffer(start | 0, end | 0);
//Update the sample count reported by the audio mixer:
//Done before updating ring buffer counter, so we don't over-produce:
this.audioPostHeartBeat();
//Update the starting position counter to match the end position:
//Atomic store, because the sample count by the audio system needs to be reported prior to this:
Atomics.store(this.audioCounters, 0, end | 0);
//Tell audio mixer input to flush to audio mixer:
this.audio.flush();
}
IodineGBAWorkerShim.prototype.copyAudioBuffer = function (start, end) {
start = start | 0;
end = end | 0;
//Compute the positions in the ring buffer:
var startCorrected = ((start | 0) & (this.audioBufferSizeMask | 0)) | 0;
var endCorrected = ((end | 0) & (this.audioBufferSizeMask | 0)) | 0;
//Copy samples out to audio mixer input (but don't process them yet):
if ((startCorrected | 0) >= (endCorrected | 0)) {
//Handle looping to start of buffer:
this.audio.pushDeferred(this.audioBuffer, startCorrected | 0, this.audioBufferSize | 0);
this.audio.pushDeferred(this.audioBuffer, 0, endCorrected | 0);
}
else {
this.audio.pushDeferred(this.audioBuffer, startCorrected | 0, endCorrected | 0);
}
}
IodineGBAWorkerShim.prototype.audioPostHeartBeat = function () {
//Push latest audio metrics with no buffering:
this.audioSamplesRemaining[0] = this.audio.remainingBuffer() | 0;
}
IodineGBAWorkerShim.prototype.graphicsHeartBeat = function () {
//If graphics callback handle provided and we got a buffer reference:
if (this.gfx && this.gfxCounters) {
//Copy the buffer out to local:
this.consumeGraphicsBuffer();
//Wake up the producer thread:
Atomics.notify(this.gfxCounters, 2, 1);
}
}
IodineGBAWorkerShim.prototype.consumeGraphicsBuffer = function () {
//Load the counter values:
var start = this.gfxCounters[0] | 0; //Written by this thread.
var end = Atomics.load(this.gfxCounters, 1) | 0; //Written by the other thread.
//Don't process if nothing to process:
if ((end | 0) == (start | 0)) {
//Buffer is empty:
return;
}
//Copy samples out from the ring buffer:
do {
//Hardcoded for 2 buffers for a triple buffer effect:
this.gfx.copyBuffer(this.gfxBuffers[start & 0x1]);
start = ((start | 0) + 1) | 0;
} while ((start | 0) != (end | 0));
//Update the starting position counter to match the end position:
//Let the other Atomic loads/stores naturally flush this value:
this.gfxCounters[0] = end | 0;
}
IodineGBAWorkerShim.prototype.audioRegister = function () {
if (this.audio) {
this.audio.register();
}
}
IodineGBAWorkerShim.prototype.audioUnregister = function () {
if (this.audio) {
//Empty the existing buffer:
this.audioHeartBeat();
//Unregister from mixer:
this.audio.unregister();
}
}
IodineGBAWorkerShim.prototype.audioSetBufferSpace = function (bufferSpace) {
bufferSpace = bufferSpace | 0;
if (this.audio) {
this.audio.setBufferSpace(bufferSpace | 0);
}
}
IodineGBAWorkerShim.prototype.buffersInitialize = function (gfxBuffer1, gfxBuffer2, gfxCounters, audioSamplesRemaining, timestamp) {
this.gfxBuffers = [gfxBuffer1, gfxBuffer2];
this.gfxCounters = gfxCounters;
this.audioSamplesRemaining = audioSamplesRemaining;
this.timestamp = timestamp;
}
IodineGBAWorkerShim.prototype.speedPush = function (speed) {
speed = +speed;
if (this.speed) {
this.speed(+speed);
}
}
IodineGBAWorkerShim.prototype.saveImportRequest = function (saveID) {
if (this.saveImport) {
var parentObj = this;
this.saveImport(saveID, function (saveData) {
parentObj.sendMessageDouble(20, saveData);
},
function () {
parentObj.sendMessageSingle(21);
});
}
}
IodineGBAWorkerShim.prototype.saveExportRequest = function (saveID, saveData) {
if (this.saveExport) {
this.saveExport(saveID, saveData);
}
}

View file

@ -0,0 +1,71 @@
<h1>XAudioJS</h1>
<h3>A minimal cross-browser API for writing PCM audio samples:</h3>
<p>This simple JavaScript library abstracts the push-for-audio API of Mozilla Audio, and the passive callback API of Web Audio.
This library introduces an abstraction layer that provides a push-for-audio and a callback API in one. We even provide a flash fallback to bring us to a total of 3 APIs supported.</p>
<br>
<b>This software is hereby placed in the public domain for anyone to use.</b>
<br>
<h3>How To Initialize:</h3>
<dl>
<dt>new XAudioServer(int channels, double sampleRate, int bufferLow, int bufferHigh, function underRunCallback, function heartbeatCallback, function postheartbeatCallback, double volume, function failureCallback, object userEventLatch);</dt>
<dd>Make sure only one instance of XAudioServer is running at any time.</dd>
<dd>bufferLow MUST be less than bufferHigh.</dd>
<dd>bufferHigh sets the internal FIFO buffer length for all APIs except the Mozilla Audio Data API. Overfill on FIFO causes the oldest samples to be dropped first.</dd>
<dd>
<h4>Array underRunCallback (int samplesRequested)</h4>
<blockquote>
Arguments: Passed the number of samples that are needed to replenish the internal audio buffer back to bufferLow.<br><br>
Functionality: JS developer set callback that can pass back any number of samples to replenish the audio buffer with.<br><br>
Return: Array of samples to be passed into the underlying audio buffer. MUST be divisible by number of channels used (Whole frames required.). The return array length DOES NOT NEED to be of length samplesRequested.
</blockquote>
</dd>
<dd>
<h4>void heartbeatCallback (void)</h4>
<blockquote>
Functionality: JS developers set this callback as a way to program against an audio clock, firing inside an audio event.
</blockquote>
</dd>
<dd>
<h4>void postheartbeatCallback (void)</h4>
<blockquote>
Functionality: JS developers set this callback as a way to program against an audio clock, firing immediately after an audio event.
</blockquote>
</dd>
<dd>volume is the output volume.</dd>
<dd>
<h4>void failureCallback (void)</h4>
<blockquote>
Functionality: JS developers set this callback to handle no audio support being available from the browser.
</blockquote>
</dd>
<dd>
<h4>object userEventLatch</h4>
<blockquote>
Functionality: JS developers set this DOM object for the Web Audio API to enable audio from.
</blockquote>
</dd>
</dl>
<h3>Function Reference:</h3>
<dl>
<dt>void writeAudio (Array buffer, Integer upTo)</dt>
<dd>Arguments: Pass an array of audio samples that is divisible by the number of audio channels utilized (buffer % channels == 0), and an integer length delimiter that follows the same restriction.</dd>
<dd>Functionality: Passes the audio samples directly into the underlying audio subsystem, <b>and can call the specified sample buffer under-run callback as needed (Does the equivalent of executeCallback in addition to the forced sample input.)<b>.</dd>
<dd>Return: void (None).</dd>
<dt>void writeAudioNoCallback (Array buffer, Integer upTo)</dt>
<dd>Arguments: Pass an array of audio samples that is divisible by the number of audio channels utilized (buffer % channels == 0), and an integer length delimiter that follows the same restriction.</dd>
<dd>Functionality: Passes the audio samples directly into the underlying audio subsystem.</dd>
<dd>Return: void (None).</dd>
<dt>int remainingBuffer (void)</dt>
<dd>Arguments: void (None).</dd>
<dd>Functionality: Returns the number of samples left in the audio system before running out of playable samples.</dd>
<dd>Return (On valid): int samples_remaining (<b>CAN BE NEGATIVE<b>)</dd>
<dd>Return (On invalid): null</dd>
<dt>void executeCallback (void)</dt>
<dd>Arguments: void (None).</dd>
<dd>Functionality: Executes the audio sample under-run callback if the samples remaining is below the set buffer low limit.</dd>
<dd>Return: void (None).</dd>
<dt>void changeVolume (double volume)</dt>
<dd>Arguments: double float between 0 and 1 specifying the volume.</dd>
<dd>Functionality: Changes the volume. Will affect samples in buffer, so has a low-latency effect (Use this to do a fast-mute).</dd>
<dd>Return: void (None).</dd>
</dl>

View file

@ -0,0 +1,86 @@
package {
import flash.media.Sound;
import flash.events.SampleDataEvent;
import flash.display.Sprite;
import flash.external.ExternalInterface;
public class XAudioJS extends Sprite {
public var sound:Sound = null;
public var channelBuffer:Vector.<Number> = new Vector.<Number>(8192, true);
public var channels:int = 0;
public var volume:Number = 0;
public var samplesFound:int = 0;
public function XAudioJS() {
ExternalInterface.addCallback('initialize', initialize);
ExternalInterface.addCallback('changeVolume', changeVolume);
}
//Initialization function for the flash backend of XAudioJS:
public function initialize(channels:Number, newVolume:Number):void {
//Initialize the new settings:
this.channels = (int(channels) == 2) ? 2 : 1;
this.changeVolume(newVolume);
this.checkForSound();
}
//Volume changing function for the flash backend of XAudioJS:
public function changeVolume(newVolume:Number):void {
//Set the new volume:
this.volume = Math.min(Math.max(newVolume, 0), 1);
}
//Calls the JavaScript function responsible for the polyfill:
public function requestSamples():Boolean {
//Call the javascript callback function:
var buffer:String = ExternalInterface.call("XAudioJSFlashAudioEvent");
//If we received an appropriate response:
if (buffer !== null) {
if ((buffer.length % this.channels) == 0) { //Outsmart bad programmers from messing us up. :/
var channelSample:Number = 0;
this.samplesFound = Math.min(buffer.length, 4096 * this.channels);
for (var index:int = 0; index < this.samplesFound; ++index) {
//Get the unsigned 15-bit encoded sample value at +0x3000 offset:
channelSample = buffer.charCodeAt(index);
//Range-check the sample frame value and convert it:
this.channelBuffer[index] = (channelSample >= 0x3000 && channelSample < 0xAFFF) ? (this.volume * (((channelSample - 0x3000) / 0x3FFF) - 1)) : 0;
}
return true;
}
}
return false;
}
//Check to make sure the audio stream is enabled:
public function checkForSound():void {
if (this.sound == null) {
this.sound = new Sound();
this.sound.addEventListener(
SampleDataEvent.SAMPLE_DATA,
soundCallback
);
this.sound.play();
}
}
//Flash Audio Refill Callback
public function soundCallback(e:SampleDataEvent):void {
var index:int = 0;
if (this.requestSamples()) {
if (this.channels == 2) {
//Stereo:
while (index < this.samplesFound) {
e.data.writeFloat(this.channelBuffer[index++]);
e.data.writeFloat(this.channelBuffer[index++]);
}
index >>= 1;
}
else {
//Mono:
while (index < this.samplesFound) {
e.data.writeFloat(this.channelBuffer[index]);
e.data.writeFloat(this.channelBuffer[index++]);
}
}
}
//Write some silence if not enough samples are found:
while (++index <= 2048) {
e.data.writeFloat(0);
e.data.writeFloat(0);
}
}
}
}

Binary file not shown.

View file

@ -0,0 +1,530 @@
//XAudioJS realtime audio output compatibility library
//Copyright (C) 2010-2015 Grant Galitz
//Released to Public Domain
var XAudioJSscriptsHandle = document.getElementsByTagName("script");
var XAudioJSsourceHandle = XAudioJSscriptsHandle[XAudioJSscriptsHandle.length-1].src;
function XAudioServer(channels, sampleRate, minBufferSize, maxBufferSize, underRunCallback, heartbeatCallback, postheartbeatCallback, volume, failureCallback, userEventLatch) {
XAudioJSChannelsAllocated = Math.max(channels, 1);
this.XAudioJSSampleRate = Math.abs(sampleRate);
XAudioJSMinBufferSize = (minBufferSize >= (XAudioJSSamplesPerCallback * XAudioJSChannelsAllocated) && minBufferSize < maxBufferSize) ? (minBufferSize & (-XAudioJSChannelsAllocated)) : (XAudioJSSamplesPerCallback * XAudioJSChannelsAllocated);
XAudioJSMaxBufferSize = (Math.floor(maxBufferSize) > XAudioJSMinBufferSize + XAudioJSChannelsAllocated) ? (maxBufferSize & (-XAudioJSChannelsAllocated)) : (XAudioJSMinBufferSize * XAudioJSChannelsAllocated);
this.underRunCallback = (typeof underRunCallback == "function") ? underRunCallback : function () {};
XAudioJSCallbackAPIEventNotificationCallback = (typeof heartbeatCallback == "function") ? heartbeatCallback : null;
XAudioJSCallbackAPIEventNotificationCallback2 = (typeof postheartbeatCallback == "function") ? postheartbeatCallback : null;
XAudioJSVolume = (volume >= 0 && volume <= 1) ? volume : 1;
this.failureCallback = (typeof failureCallback == "function") ? failureCallback : function () { throw(new Error("XAudioJS has encountered a fatal error.")); };
this.userEventLatch = (typeof userEventLatch == "object") ? userEventLatch : null;
this.initializeAudio();
}
XAudioServer.prototype.MOZWriteAudioNoCallback = function (buffer, upTo) {
//Resample before passing to the moz audio api:
var bufferLength = Math.min(buffer.length, upTo);
for (var bufferIndex = 0; bufferIndex < bufferLength;) {
var sliceLength = Math.min(bufferLength - bufferIndex, XAudioJSMaxBufferSize);
for (var sliceIndex = 0; sliceIndex < sliceLength; ++sliceIndex) {
XAudioJSAudioContextSampleBuffer[sliceIndex] = buffer[bufferIndex++];
}
var resampleLength = XAudioJSResampleControl.resampler(sliceIndex);
if (resampleLength > 0) {
var resampledResult = XAudioJSResampleControl.outputBuffer;
var resampledBuffer = XAudioJSGetArraySlice(resampledResult, resampleLength);
this.samplesAlreadyWritten += this.audioHandleMoz.mozWriteAudio(resampledBuffer);
}
}
}
XAudioServer.prototype.callbackBasedWriteAudioNoCallback = function (buffer, upTo) {
//Callback-centered audio APIs:
var bufferLength = Math.min(buffer.length, upTo);
for (var bufferCounter = 0; bufferCounter < bufferLength && XAudioJSAudioBufferSize < XAudioJSMaxBufferSize;) {
XAudioJSAudioContextSampleBuffer[XAudioJSAudioBufferSize++] = buffer[bufferCounter++];
}
}
/*Pass your samples into here!
Pack your samples as a one-dimenional array
With the channel samples packed uniformly.
examples:
mono - [left, left, left, left]
stereo - [left, right, left, right, left, right, left, right]
*/
XAudioServer.prototype.writeAudio = function (buffer, upTo) {
switch (this.audioType) {
case 0:
this.MOZWriteAudioNoCallback(buffer, upTo);
this.MOZExecuteCallback();
break;
case 2:
this.checkFlashInit();
case 1:
this.callbackBasedWriteAudioNoCallback(buffer, upTo);
this.callbackBasedExecuteCallback();
break;
default:
this.failureCallback();
}
}
/*Pass your samples into here if you don't want automatic callback calling:
Pack your samples as a one-dimenional array
With the channel samples packed uniformly.
examples:
mono - [left, left, left, left]
stereo - [left, right, left, right, left, right, left, right]
Useful in preventing infinite recursion issues with calling writeAudio inside your callback.
*/
XAudioServer.prototype.writeAudioNoCallback = function (buffer, upTo) {
switch (this.audioType) {
case 0:
this.MOZWriteAudioNoCallback(buffer, upTo);
break;
case 2:
this.checkFlashInit();
case 1:
this.callbackBasedWriteAudioNoCallback(buffer, upTo);
break;
default:
this.failureCallback();
}
}
//Developer can use this to see how many samples to write (example: minimum buffer allotment minus remaining samples left returned from this function to make sure maximum buffering is done...)
//If null is returned, then that means metric could not be done.
XAudioServer.prototype.remainingBuffer = function () {
switch (this.audioType) {
case 0:
return Math.floor((this.samplesAlreadyWritten - this.audioHandleMoz.mozCurrentSampleOffset()) * XAudioJSResampleControl.ratioWeight / XAudioJSChannelsAllocated) * XAudioJSChannelsAllocated;
case 2:
this.checkFlashInit();
case 1:
return (Math.floor((XAudioJSResampledSamplesLeft() * XAudioJSResampleControl.ratioWeight) / XAudioJSChannelsAllocated) * XAudioJSChannelsAllocated) + XAudioJSAudioBufferSize;
default:
this.failureCallback();
return null;
}
}
XAudioServer.prototype.MOZExecuteCallback = function () {
//mozAudio:
var samplesRequested = XAudioJSMinBufferSize - this.remainingBuffer();
if (samplesRequested > 0) {
var buffer = this.underRunCallback(samplesRequested);
this.MOZWriteAudioNoCallback(buffer, buffer.length);
}
}
XAudioServer.prototype.callbackBasedExecuteCallback = function () {
//WebKit /Flash Audio:
var samplesRequested = XAudioJSMinBufferSize - this.remainingBuffer();
if (samplesRequested > 0) {
var buffer = this.underRunCallback(samplesRequested);
this.callbackBasedWriteAudioNoCallback(buffer, buffer.length);
}
}
//If you just want your callback called for any possible refill (Execution of callback is still conditional):
XAudioServer.prototype.executeCallback = function () {
switch (this.audioType) {
case 0:
this.MOZExecuteCallback();
break;
case 2:
this.checkFlashInit();
case 1:
this.callbackBasedExecuteCallback();
break;
default:
this.failureCallback();
}
}
//DO NOT CALL THIS, the lib calls this internally!
XAudioServer.prototype.initializeAudio = function () {
try {
this.initializeMozAudio();
}
catch (error) {
try {
this.initializeWebAudio();
}
catch (error) {
try {
this.initializeFlashAudio();
}
catch (error) {
this.audioType = -1;
this.failureCallback();
}
}
}
}
XAudioServer.prototype.initializeMozAudio = function () {
this.audioHandleMoz = new Audio();
this.audioHandleMoz.mozSetup(XAudioJSChannelsAllocated, XAudioJSMozAudioSampleRate);
this.audioHandleMoz.volume = XAudioJSVolume;
this.samplesAlreadyWritten = 0;
this.audioType = 0;
//if (navigator.platform != "MacIntel" && navigator.platform != "MacPPC") {
//Add some additional buffering space to workaround a moz audio api issue:
var bufferAmount = (this.XAudioJSSampleRate * XAudioJSChannelsAllocated / 10) | 0;
bufferAmount -= bufferAmount % XAudioJSChannelsAllocated;
this.samplesAlreadyWritten -= bufferAmount;
//}
this.initializeResampler(XAudioJSMozAudioSampleRate);
}
XAudioServer.prototype.initializeWebAudio = function () {
if (typeof AudioContext == "undefined" || typeof XAudioJSWebAudioContextHandle == "undefined") {
throw null;
}
else {
if (!this.userEventLatch) {
this.setupWebAudio();
}
else {
var parentObj = this;
var XAudioJSWebAudioDelayedEvent = 0;
this.userEventLatch.addEventListener("click", function () {
if (XAudioJSWebAudioDelayedEvent == 0) {
parentObj.setupWebAudio();
XAudioJSWebAudioDelayedEvent |= 1;
}
}, false);
this.userEventLatch.addEventListener("touchstart", function () {
if (XAudioJSWebAudioDelayedEvent < 2) {
parentObj.setupWebAudio();
XAudioJSWebAudioDelayedEvent |= 2;
}
}, false);
this.userEventLatch.addEventListener("touchend", function () {
if (XAudioJSWebAudioDelayedEvent < 4) {
parentObj.setupWebAudio();
XAudioJSWebAudioDelayedEvent |= 4;
}
}, false);
//TODO: Restructure API to not have to potentially lie to end client about
//the samples in buffer before user driven event callback that actually starts WA.
this.resetCallbackAPIAudioBuffer(44100);
}
this.audioType = 1;
}
}
XAudioServer.prototype.setupWebAudio = function () {
if (XAudioJSWebAudioLaunchedContext) {
XAudioJSWebAudioContextHandle.close();
}
try {
XAudioJSWebAudioContextHandle = new AudioContext(); //Create a system audio context.
}
catch (error) {
XAudioJSWebAudioContextHandle = new webkitAudioContext(); //Create a system audio context.
}
XAudioJSWebAudioLaunchedContext = true;
if (XAudioJSWebAudioAudioNode) {
XAudioJSWebAudioAudioNode.disconnect();
XAudioJSWebAudioAudioNode.onaudioprocess = null;
XAudioJSWebAudioAudioNode = null;
}
try {
XAudioJSWebAudioAudioNode = XAudioJSWebAudioContextHandle.createScriptProcessor(XAudioJSSamplesPerCallback, 0, XAudioJSChannelsAllocated); //Create the js event node.
}
catch (error) {
XAudioJSWebAudioAudioNode = XAudioJSWebAudioContextHandle.createJavaScriptNode(XAudioJSSamplesPerCallback, 0, XAudioJSChannelsAllocated); //Create the js event node.
}
XAudioJSWebAudioAudioNode.onaudioprocess = XAudioJSWebAudioEvent; //Connect the audio processing event to a handling function so we can manipulate output
XAudioJSWebAudioAudioNode.connect(XAudioJSWebAudioContextHandle.destination); //Send and chain the output of the audio manipulation to the system audio output.
this.resetCallbackAPIAudioBuffer(XAudioJSWebAudioContextHandle.sampleRate);
/*
Firefox has a bug in its web audio implementation...
The node may randomly stop playing on Mac OS X for no
good reason. Keep a watchdog timer to restart the failed
node if it glitches. Google Chrome never had this issue.
*/
XAudioJSWebAudioWatchDogLast = (new Date()).getTime();
if (!XAudioJSWebAudioWatchDogTimer && navigator.userAgent.indexOf('Gecko/') > -1) {
if (XAudioJSWebAudioWatchDogTimer) {
clearInterval(XAudioJSWebAudioWatchDogTimer);
}
var parentObj = this;
XAudioJSWebAudioWatchDogTimer = setInterval(function () {
if(typeof XAudioJSWebAudioContextHandle.state != "undefined") {
if (XAudioJSWebAudioContextHandle.state === 'suspended') {
XAudioJSWebAudioWatchDogLast = (new Date()).getTime();
try {
XAudioJSWebAudioContextHandle.resume();
}
catch (e) {}
}
else {
var timeDiff = (new Date()).getTime() - XAudioJSWebAudioWatchDogLast;
if (timeDiff > 500) {
parentObj.setupWebAudio();
}
}
}
}, 500);
}
}
XAudioServer.prototype.initializeFlashAudio = function () {
var existingFlashload = document.getElementById("XAudioJS");
this.flashInitialized = false;
this.resetCallbackAPIAudioBuffer(44100);
switch (XAudioJSChannelsAllocated) {
case 1:
XAudioJSFlashTransportEncoder = XAudioJSGenerateFlashMonoString;
break;
case 2:
XAudioJSFlashTransportEncoder = XAudioJSGenerateFlashStereoString;
break;
default:
XAudioJSFlashTransportEncoder = XAudioJSGenerateFlashSurroundString;
}
if (existingFlashload == null) {
this.audioHandleFlash = null;
var thisObj = this;
var mainContainerNode = document.createElement("div");
mainContainerNode.setAttribute("style", "position: fixed; bottom: 0px; right: 0px; margin: 0px; padding: 0px; border: none; width: 8px; height: 8px; overflow: hidden; z-index: -1000; ");
var containerNode = document.createElement("div");
containerNode.setAttribute("style", "position: static; border: none; width: 0px; height: 0px; visibility: hidden; margin: 8px; padding: 0px;");
containerNode.setAttribute("id", "XAudioJS");
mainContainerNode.appendChild(containerNode);
document.getElementsByTagName("body")[0].appendChild(mainContainerNode);
swfobject.embedSWF(
XAudioJSsourceHandle.substring(0, XAudioJSsourceHandle.length - 9) + "JS.swf",
"XAudioJS",
"8",
"8",
"9.0.0",
"",
{},
{"allowscriptaccess":"always"},
{"style":"position: static; visibility: hidden; margin: 8px; padding: 0px; border: none"},
function (event) {
if (event.success) {
thisObj.audioHandleFlash = event.ref;
thisObj.checkFlashInit();
}
else {
thisObj.failureCallback();
thisObj.audioType = -1;
}
}
);
}
else {
this.audioHandleFlash = existingFlashload;
this.checkFlashInit();
}
this.audioType = 2;
}
XAudioServer.prototype.changeVolume = function (newVolume) {
if (newVolume >= 0 && newVolume <= 1) {
XAudioJSVolume = newVolume;
switch (this.audioType) {
case 0:
this.audioHandleMoz.volume = XAudioJSVolume;
case 1:
break;
case 2:
if (this.flashInitialized) {
this.audioHandleFlash.changeVolume(XAudioJSVolume);
}
else {
this.checkFlashInit();
}
break;
default:
this.failureCallback();
}
}
}
//Checks to see if the NPAPI Adobe Flash bridge is ready yet:
XAudioServer.prototype.checkFlashInit = function () {
if (!this.flashInitialized) {
try {
if (this.audioHandleFlash && this.audioHandleFlash.initialize) {
this.flashInitialized = true;
this.audioHandleFlash.initialize(XAudioJSChannelsAllocated, XAudioJSVolume);
}
}
catch (error) {
this.flashInitialized = false;
}
}
}
//Set up the resampling:
XAudioServer.prototype.resetCallbackAPIAudioBuffer = function (APISampleRate) {
XAudioJSAudioBufferSize = XAudioJSResampleBufferEnd = XAudioJSResampleBufferStart = 0;
this.initializeResampler(APISampleRate);
XAudioJSResampledBuffer = this.getFloat32(XAudioJSResampleBufferSize);
}
XAudioServer.prototype.initializeResampler = function (sampleRate) {
XAudioJSAudioContextSampleBuffer = this.getFloat32(XAudioJSMaxBufferSize);
XAudioJSResampleControl = new Resampler(this.XAudioJSSampleRate, sampleRate, XAudioJSChannelsAllocated, XAudioJSAudioContextSampleBuffer);
XAudioJSResampleBufferSize = XAudioJSResampleControl.outputBuffer.length;
}
XAudioServer.prototype.getFloat32 = function (size) {
try {
return new Float32Array(size);
}
catch (error) {
return [];
}
}
function XAudioJSFlashAudioEvent() { //The callback that flash calls...
XAudioJSCallbackAPIEventNotificationCallbackCompatTimerClear();
XAudioJSCallbackAPIEventNotification();
XAudioJSResampleRefill();
var outputStr = XAudioJSFlashTransportEncoder();
XAudioJSCallbackAPIEventNotification2();
return outputStr;
}
function XAudioJSGenerateFlashSurroundString() { //Convert the arrays to one long string for speed.
var XAudioJSTotalSamples = XAudioJSSamplesPerCallback << 1;
if (XAudioJSBinaryString.length > XAudioJSTotalSamples) {
XAudioJSBinaryString = [];
}
XAudioJSTotalSamples = 0;
for (var index = 0; index < XAudioJSSamplesPerCallback && XAudioJSResampleBufferStart != XAudioJSResampleBufferEnd; ++index) {
//Sanitize the buffer:
XAudioJSBinaryString[XAudioJSTotalSamples++] = String.fromCharCode(((Math.min(Math.max(XAudioJSResampledBuffer[XAudioJSResampleBufferStart++] + 1, 0), 2) * 0x3FFF) | 0) + 0x3000);
XAudioJSBinaryString[XAudioJSTotalSamples++] = String.fromCharCode(((Math.min(Math.max(XAudioJSResampledBuffer[XAudioJSResampleBufferStart++] + 1, 0), 2) * 0x3FFF) | 0) + 0x3000);
XAudioJSResampleBufferStart += XAudioJSChannelsAllocated - 2;
if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) {
XAudioJSResampleBufferStart = 0;
}
}
return XAudioJSBinaryString.join("");
}
function XAudioJSGenerateFlashStereoString() { //Convert the arrays to one long string for speed.
var XAudioJSTotalSamples = XAudioJSSamplesPerCallback << 1;
if (XAudioJSBinaryString.length > XAudioJSTotalSamples) {
XAudioJSBinaryString = [];
}
for (var index = 0; index < XAudioJSTotalSamples && XAudioJSResampleBufferStart != XAudioJSResampleBufferEnd;) {
//Sanitize the buffer:
XAudioJSBinaryString[index++] = String.fromCharCode(((Math.min(Math.max(XAudioJSResampledBuffer[XAudioJSResampleBufferStart++] + 1, 0), 2) * 0x3FFF) | 0) + 0x3000);
XAudioJSBinaryString[index++] = String.fromCharCode(((Math.min(Math.max(XAudioJSResampledBuffer[XAudioJSResampleBufferStart++] + 1, 0), 2) * 0x3FFF) | 0) + 0x3000);
if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) {
XAudioJSResampleBufferStart = 0;
}
}
return XAudioJSBinaryString.join("");
}
function XAudioJSGenerateFlashMonoString() { //Convert the array to one long string for speed.
if (XAudioJSBinaryString.length > XAudioJSSamplesPerCallback) {
XAudioJSBinaryString = [];
}
for (var index = 0; index < XAudioJSSamplesPerCallback && XAudioJSResampleBufferStart != XAudioJSResampleBufferEnd;) {
//Sanitize the buffer:
XAudioJSBinaryString[index++] = String.fromCharCode(((Math.min(Math.max(XAudioJSResampledBuffer[XAudioJSResampleBufferStart++] + 1, 0), 2) * 0x3FFF) | 0) + 0x3000);
if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) {
XAudioJSResampleBufferStart = 0;
}
}
return XAudioJSBinaryString.join("");
}
//Some Required Globals:
var XAudioJSWebAudioContextHandle = null;
var XAudioJSWebAudioAudioNode = null;
var XAudioJSWebAudioWatchDogTimer = null;
var XAudioJSCallbackAPIEventNotificationCallback = null;
var XAudioJSCallbackAPIEventNotificationCallback2 = null;
var XAudioJSCallbackAPIEventNotificationCallbackCompatTimer = setInterval(XAudioJSCallbackAPIEventNotificationDual, 16);
var XAudioJSWebAudioWatchDogLast = false;
var XAudioJSWebAudioLaunchedContext = false;
var XAudioJSAudioContextSampleBuffer = [];
var XAudioJSResampledBuffer = [];
var XAudioJSMinBufferSize = 15000;
var XAudioJSMaxBufferSize = 25000;
var XAudioJSChannelsAllocated = 1;
var XAudioJSVolume = 1;
var XAudioJSResampleControl = null;
var XAudioJSAudioBufferSize = 0;
var XAudioJSResampleBufferStart = 0;
var XAudioJSResampleBufferEnd = 0;
var XAudioJSResampleBufferSize = 0;
var XAudioJSMozAudioSampleRate = 44100;
var XAudioJSSamplesPerCallback = 2048; //Has to be between 2048 and 4096 (If over, then samples are ignored, if under then silence is added).
var XAudioJSFlashTransportEncoder = null;
var XAudioJSBinaryString = [];
function XAudioJSWebAudioEvent(event) { //Web Audio API callback...
if (XAudioJSWebAudioWatchDogTimer) {
XAudioJSWebAudioWatchDogLast = (new Date()).getTime();
}
//Find all output channels:
for (var bufferCount = 0, buffers = []; bufferCount < XAudioJSChannelsAllocated; ++bufferCount) {
buffers[bufferCount] = event.outputBuffer.getChannelData(bufferCount);
}
XAudioJSCallbackAPIEventNotificationCallbackCompatTimerClear();
XAudioJSCallbackAPIEventNotification();
//Make sure we have resampled samples ready:
XAudioJSResampleRefill();
//Copy samples from XAudioJS to the Web Audio API:
for (var index = 0; index < XAudioJSSamplesPerCallback && XAudioJSResampleBufferStart != XAudioJSResampleBufferEnd; ++index) {
for (bufferCount = 0; bufferCount < XAudioJSChannelsAllocated; ++bufferCount) {
buffers[bufferCount][index] = XAudioJSResampledBuffer[XAudioJSResampleBufferStart++] * XAudioJSVolume;
}
if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) {
XAudioJSResampleBufferStart = 0;
}
}
//Pad with silence if we're underrunning:
while (index < XAudioJSSamplesPerCallback) {
for (bufferCount = 0; bufferCount < XAudioJSChannelsAllocated; ++bufferCount) {
buffers[bufferCount][index] = 0;
}
++index;
}
XAudioJSCallbackAPIEventNotification2();
}
function XAudioJSResampleRefill() {
if (XAudioJSAudioBufferSize > 0) {
//Resample a chunk of audio:
var resampleLength = XAudioJSResampleControl.resampler(XAudioJSAudioBufferSize);
var resampledResult = XAudioJSResampleControl.outputBuffer;
for (var index2 = 0; index2 < resampleLength;) {
XAudioJSResampledBuffer[XAudioJSResampleBufferEnd++] = resampledResult[index2++];
if (XAudioJSResampleBufferEnd == XAudioJSResampleBufferSize) {
XAudioJSResampleBufferEnd = 0;
}
if (XAudioJSResampleBufferStart == XAudioJSResampleBufferEnd) {
XAudioJSResampleBufferStart += XAudioJSChannelsAllocated;
if (XAudioJSResampleBufferStart == XAudioJSResampleBufferSize) {
XAudioJSResampleBufferStart = 0;
}
}
}
XAudioJSAudioBufferSize = 0;
}
}
function XAudioJSCallbackAPIEventNotificationCallbackCompatTimerClear() {
if (XAudioJSCallbackAPIEventNotificationCallbackCompatTimer) {
clearInterval(XAudioJSCallbackAPIEventNotificationCallbackCompatTimer);
}
}
function XAudioJSCallbackAPIEventNotification() {
if (typeof XAudioJSCallbackAPIEventNotificationCallback == "function") {
XAudioJSCallbackAPIEventNotificationCallback();
}
}
function XAudioJSCallbackAPIEventNotification2() {
if (typeof XAudioJSCallbackAPIEventNotificationCallback2 == "function") {
XAudioJSCallbackAPIEventNotificationCallback2();
}
}
function XAudioJSCallbackAPIEventNotificationDual() {
XAudioJSCallbackAPIEventNotification();
XAudioJSCallbackAPIEventNotification2();
}
function XAudioJSResampledSamplesLeft() {
return ((XAudioJSResampleBufferStart <= XAudioJSResampleBufferEnd) ? 0 : XAudioJSResampleBufferSize) + XAudioJSResampleBufferEnd - XAudioJSResampleBufferStart;
}
function XAudioJSGetArraySlice(buffer, lengthOf) {
//Typed array and normal array buffer section referencing:
try {
return buffer.subarray(0, lengthOf);
}
catch (error) {
try {
//Regular array pass:
buffer.length = lengthOf;
return buffer;
}
catch (error) {
//Nightly Firefox 4 used to have the subarray function named as slice:
return buffer.slice(0, lengthOf);
}
}
}

View file

@ -0,0 +1,173 @@
"use strict";
//JavaScript Audio Resampler
//Copyright (C) 2011-2015 Grant Galitz
//Released to Public Domain
function Resampler(fromSampleRate, toSampleRate, channels, inputBuffer) {
//Input Sample Rate:
this.fromSampleRate = +fromSampleRate;
//Output Sample Rate:
this.toSampleRate = +toSampleRate;
//Number of channels:
this.channels = channels | 0;
//Type checking the input buffer:
if (typeof inputBuffer != "object") {
throw(new Error("inputBuffer is not an object."));
}
if (!(inputBuffer instanceof Array) && !(inputBuffer instanceof Float32Array) && !(inputBuffer instanceof Float64Array)) {
throw(new Error("inputBuffer is not an array or a float32 or a float64 array."));
}
this.inputBuffer = inputBuffer;
//Initialize the resampler:
this.initialize();
}
Resampler.prototype.initialize = function () {
//Perform some checks:
if (this.fromSampleRate > 0 && this.toSampleRate > 0 && this.channels > 0) {
if (this.fromSampleRate == this.toSampleRate) {
//Setup a resampler bypass:
this.resampler = this.bypassResampler; //Resampler just returns what was passed through.
this.ratioWeight = 1;
this.outputBuffer = this.inputBuffer;
}
else {
this.ratioWeight = this.fromSampleRate / this.toSampleRate;
if (this.fromSampleRate < this.toSampleRate) {
/*
Use generic linear interpolation if upsampling,
as linear interpolation produces a gradient that we want
and works fine with two input sample points per output in this case.
*/
this.compileLinearInterpolationFunction();
this.lastWeight = 1;
}
else {
/*
Custom resampler I wrote that doesn't skip samples
like standard linear interpolation in high downsampling.
This is more accurate than linear interpolation on downsampling.
*/
this.compileMultiTapFunction();
this.tailExists = false;
this.lastWeight = 0;
}
this.initializeBuffers();
}
}
else {
throw(new Error("Invalid settings specified for the resampler."));
}
}
Resampler.prototype.compileLinearInterpolationFunction = function () {
var toCompile = "var outputOffset = 0;\
if (bufferLength > 0) {\
var buffer = this.inputBuffer;\
var weight = this.lastWeight;\
var firstWeight = 0;\
var secondWeight = 0;\
var sourceOffset = 0;\
var outputOffset = 0;\
var outputBuffer = this.outputBuffer;\
for (; weight < 1; weight += " + this.ratioWeight + ") {\
secondWeight = weight % 1;\
firstWeight = 1 - secondWeight;";
for (var channel = 0; channel < this.channels; ++channel) {
toCompile += "outputBuffer[outputOffset++] = (this.lastOutput[" + channel + "] * firstWeight) + (buffer[" + channel + "] * secondWeight);";
}
toCompile += "}\
weight -= 1;\
for (bufferLength -= " + this.channels + ", sourceOffset = Math.floor(weight) * " + this.channels + "; sourceOffset < bufferLength;) {\
secondWeight = weight % 1;\
firstWeight = 1 - secondWeight;";
for (var channel = 0; channel < this.channels; ++channel) {
toCompile += "outputBuffer[outputOffset++] = (buffer[sourceOffset" + ((channel > 0) ? (" + " + channel) : "") + "] * firstWeight) + (buffer[sourceOffset + " + (this.channels + channel) + "] * secondWeight);";
}
toCompile += "weight += " + this.ratioWeight + ";\
sourceOffset = Math.floor(weight) * " + this.channels + ";\
}";
for (var channel = 0; channel < this.channels; ++channel) {
toCompile += "this.lastOutput[" + channel + "] = buffer[sourceOffset++];";
}
toCompile += "this.lastWeight = weight % 1;\
}\
return outputOffset;";
this.resampler = Function("bufferLength", toCompile);
}
Resampler.prototype.compileMultiTapFunction = function () {
var toCompile = "var outputOffset = 0;\
if (bufferLength > 0) {\
var buffer = this.inputBuffer;\
var weight = 0;";
for (var channel = 0; channel < this.channels; ++channel) {
toCompile += "var output" + channel + " = 0;"
}
toCompile += "var actualPosition = 0;\
var amountToNext = 0;\
var alreadyProcessedTail = !this.tailExists;\
this.tailExists = false;\
var outputBuffer = this.outputBuffer;\
var currentPosition = 0;\
do {\
if (alreadyProcessedTail) {\
weight = " + this.ratioWeight + ";";
for (channel = 0; channel < this.channels; ++channel) {
toCompile += "output" + channel + " = 0;"
}
toCompile += "}\
else {\
weight = this.lastWeight;";
for (channel = 0; channel < this.channels; ++channel) {
toCompile += "output" + channel + " = this.lastOutput[" + channel + "];"
}
toCompile += "alreadyProcessedTail = true;\
}\
while (weight > 0 && actualPosition < bufferLength) {\
amountToNext = 1 + actualPosition - currentPosition;\
if (weight >= amountToNext) {";
for (channel = 0; channel < this.channels; ++channel) {
toCompile += "output" + channel + " += buffer[actualPosition++] * amountToNext;"
}
toCompile += "currentPosition = actualPosition;\
weight -= amountToNext;\
}\
else {";
for (channel = 0; channel < this.channels; ++channel) {
toCompile += "output" + channel + " += buffer[actualPosition" + ((channel > 0) ? (" + " + channel) : "") + "] * weight;"
}
toCompile += "currentPosition += weight;\
weight = 0;\
break;\
}\
}\
if (weight <= 0) {";
for (channel = 0; channel < this.channels; ++channel) {
toCompile += "outputBuffer[outputOffset++] = output" + channel + " / " + this.ratioWeight + ";"
}
toCompile += "}\
else {\
this.lastWeight = weight;";
for (channel = 0; channel < this.channels; ++channel) {
toCompile += "this.lastOutput[" + channel + "] = output" + channel + ";"
}
toCompile += "this.tailExists = true;\
break;\
}\
} while (actualPosition < bufferLength);\
}\
return outputOffset;";
this.resampler = Function("bufferLength", toCompile);
}
Resampler.prototype.bypassResampler = function (upTo) {
return upTo;
}
Resampler.prototype.initializeBuffers = function () {
//Initialize the internal buffer:
var outputBufferSize = (Math.ceil(this.inputBuffer.length * this.toSampleRate / this.fromSampleRate / this.channels * 1.000000476837158203125) * this.channels) + this.channels;
try {
this.outputBuffer = new Float32Array(outputBufferSize);
this.lastOutput = new Float32Array(this.channels);
}
catch (error) {
this.outputBuffer = [];
this.lastOutput = [];
}
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,87 @@
"use strict";
var toBase64 = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+" , "/", "="];
var fromBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
function base64(data) {
try {
var base64 = window.btoa(data); //Use this native function when it's available, as it's a magnitude faster than the non-native code below.
}
catch (error) {
//Defaulting to non-native base64 encoding...
var base64 = "";
var dataLength = data.length;
if (dataLength > 0) {
var bytes = [0, 0, 0];
var index = 0;
var remainder = dataLength % 3;
while (data.length % 3 > 0) {
//Make sure we don't do fuzzy math in the next loop...
data[data.length] = " ";
}
while (index < dataLength) {
//Keep this loop small for speed.
bytes = [data.charCodeAt(index++) & 0xFF, data.charCodeAt(index++) & 0xFF, data.charCodeAt(index++) & 0xFF];
base64 += toBase64[bytes[0] >> 2] + toBase64[((bytes[0] & 0x3) << 4) | (bytes[1] >> 4)] + toBase64[((bytes[1] & 0xF) << 2) | (bytes[2] >> 6)] + toBase64[bytes[2] & 0x3F];
}
if (remainder > 0) {
//Fill in the padding and recalulate the trailing six-bit group...
base64[base64.length - 1] = "=";
if (remainder == 2) {
base64[base64.length - 2] = "=";
base64[base64.length - 3] = toBase64[(bytes[0] & 0x3) << 4];
}
else {
base64[base64.length - 2] = toBase64[(bytes[1] & 0xF) << 2];
}
}
}
}
return base64;
}
function base64_decode(data) {
try {
var decode64 = window.atob(data); //Use this native function when it's available, as it's a magnitude faster than the non-native code below.
}
catch (error) {
//Defaulting to non-native base64 decoding...
var decode64 = "";
var dataLength = data.length;
if (dataLength > 3 && dataLength % 4 == 0) {
var sixbits = [0, 0, 0, 0]; //Declare this out of the loop, to speed up the ops.
var index = 0;
while (index < dataLength) {
//Keep this loop small for speed.
sixbits = [fromBase64.indexOf(data.charAt(index++)), fromBase64.indexOf(data.charAt(index++)), fromBase64.indexOf(data.charAt(index++)), fromBase64.indexOf(data.charAt(index++))];
decode64 += String.fromCharCode((sixbits[0] << 2) | (sixbits[1] >> 4)) + String.fromCharCode(((sixbits[1] & 0x0F) << 4) | (sixbits[2] >> 2)) + String.fromCharCode(((sixbits[2] & 0x03) << 6) | sixbits[3]);
}
//Check for the '=' character after the loop, so we don't hose it up.
if (sixbits[3] >= 0x40) {
decode64.length -= 1;
if (sixbits[2] >= 0x40) {
decode64.length -= 1;
}
}
}
}
return decode64;
}
function arrayToBase64(arrayIn) {
var binString = "";
var length = arrayIn.length;
for (var index = 0; index < length; ++index) {
if (typeof arrayIn[index] == "number") {
binString += String.fromCharCode(arrayIn[index]);
}
}
return base64(binString);
}
function base64ToArray(b64String) {
var binString = base64_decode(b64String);
var outArray = [];
var length = binString.length;
for (var index = 0; index < length;) {
outArray.push(binString.charCodeAt(index++) & 0xFF);
}
return outArray;
}

View file

@ -0,0 +1,93 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>2048</title>
<link href="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/2048/style/main.css" rel="stylesheet" type="text/css">
<link rel="shortcut icon" href="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/2048/favicon.ico">
<link rel="apple-touch-icon" href="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/2048/meta/apple-touch-icon.png">
<link rel="apple-touch-startup-image" href="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/2048/meta/apple-touch-startup-image-640x1096.png" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)"> <!-- iPhone 5+ -->
<link rel="apple-touch-startup-image" href="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/2048/meta/apple-touch-startup-image-640x920.png" media="(device-width: 320px) and (device-height: 480px) and (-webkit-device-pixel-ratio: 2)"> <!-- iPhone, retina -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0, maximum-scale=1, user-scalable=no, minimal-ui">
</head>
<body>
<div class="container">
<div class="heading">
<h1 class="title">2048</h1>
<div class="scores-container">
<div class="score-container">0</div>
<div class="best-container">0</div>
</div>
</div>
<div class="above-game">
<p class="game-intro">Join the numbers and get to the <strong>2048 tile!</strong></p>
<a class="restart-button">New Game</a>
</div>
<div class="game-container">
<div class="game-message">
<p></p>
<div class="lower">
<a class="keep-playing-button">Keep going</a>
<a class="retry-button">Try again</a>
</div>
</div>
<div class="grid-container">
<div class="grid-row">
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
<div class="grid-row">
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
<div class="grid-row">
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
<div class="grid-row">
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
</div>
<div class="tile-container">
</div>
</div>
<p class="game-explanation">
<strong class="important">How to play:</strong> Use your <strong>arrow keys</strong> to move the tiles. When two tiles with the same number touch, they <strong>merge into one!</strong>
</p>
</div>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/2048/js/bind_polyfill.js"></script>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/2048/js/classlist_polyfill.js"></script>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/2048/js/animframe_polyfill.js"></script>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/2048/js/keyboard_input_manager.js"></script>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/2048/js/html_actuator.js"></script>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/2048/js/grid.js"></script>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/2048/js/tile.js"></script>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/2048/js/local_storage_manager.js"></script>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/2048/js/game_manager.js"></script>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/2048/js/application.js"></script>
</body>
</html>

View file

@ -0,0 +1,32 @@
<html>
<head>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/asteroids/jquery-1.4.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/asteroids/vector_battle_regular.typeface.js"></script>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/asteroids/ipad.js"></script>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/asteroids/game.js"></script>
<style>
body {background-color: #000;}
#canvas { border:1px solid black; top:0px; left:0px; background-color: #fff;}
.button { position:absolute; border:1px solid black; }
#left-controls { position:absolute; left:1px; bottom:0px; display:none; }
#right-controls { position:absolute; right:1px; bottom:0px; display:none; }
#up { width:200px; height:100px; bottom:100px;}
#left { width:100px; height:100px; bottom:0px;}
#right { width:100px; height:100px; bottom:0px; left:100px; }
#space { width:200px; height:200px; bottom:0px; right:0px; }
</style>
</head>
<body>
<div id="game-container" align="center">
<canvas id="canvas" width="780" height="540"></canvas>
<div id="left-controls">
<div id="up" class='button'>THRUST</div>
<div id="left" class='button'>LEFT</div>
<div id="right" class='button'>RIGHT</div>
</div>
<div id="right-controls">
<div id="space" class='button'>FIRE</div>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

View file

@ -0,0 +1,372 @@
<html>
<head>
<script src='https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/astray/Box2dWeb.min.js'></script>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/astray/Three.js"></script>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/astray/keyboard.js"></script>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/astray/jquery.js"></script>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/astray/maze.js"></script>
<script>
var camera = undefined,
scene = undefined,
renderer = undefined,
light = undefined,
mouseX = undefined,
mouseY = undefined,
maze = undefined,
mazeMesh = undefined,
mazeDimension = 11,
planeMesh = undefined,
ballMesh = undefined,
ballRadius = 0.25,
keyAxis = [0, 0],
ironTexture = THREE.ImageUtils.loadTexture('ball.png'),
planeTexture = THREE.ImageUtils.loadTexture('concrete.png'),
brickTexture = THREE.ImageUtils.loadTexture('brick.png'),
gameState = undefined,
// Box2D shortcuts
b2World = Box2D.Dynamics.b2World,
b2FixtureDef = Box2D.Dynamics.b2FixtureDef,
b2BodyDef = Box2D.Dynamics.b2BodyDef,
b2Body = Box2D.Dynamics.b2Body,
b2CircleShape = Box2D.Collision.Shapes.b2CircleShape,
b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape,
b2Settings = Box2D.Common.b2Settings,
b2Vec2 = Box2D.Common.Math.b2Vec2,
// Box2D world variables
wWorld = undefined,
wBall = undefined;
function createPhysicsWorld() {
// Create the world object.
wWorld = new b2World(new b2Vec2(0, 0), true);
// Create the ball.
var bodyDef = new b2BodyDef();
bodyDef.type = b2Body.b2_dynamicBody;
bodyDef.position.Set(1, 1);
wBall = wWorld.CreateBody(bodyDef);
var fixDef = new b2FixtureDef();
fixDef.density = 1.0;
fixDef.friction = 0.0;
fixDef.restitution = 0.25;
fixDef.shape = new b2CircleShape(ballRadius);
wBall.CreateFixture(fixDef);
// Create the maze.
bodyDef.type = b2Body.b2_staticBody;
fixDef.shape = new b2PolygonShape();
fixDef.shape.SetAsBox(0.5, 0.5);
for (var i = 0; i < maze.dimension; i++) {
for (var j = 0; j < maze.dimension; j++) {
if (maze[i][j]) {
bodyDef.position.x = i;
bodyDef.position.y = j;
wWorld.CreateBody(bodyDef).CreateFixture(fixDef);
}
}
}
}
function generate_maze_mesh(field) {
var dummy = new THREE.Geometry();
for (var i = 0; i < field.dimension; i++) {
for (var j = 0; j < field.dimension; j++) {
if (field[i][j]) {
var geometry = new THREE.CubeGeometry(1,1,1,1,1,1);
var mesh_ij = new THREE.Mesh(geometry);
mesh_ij.position.x = i;
mesh_ij.position.y = j;
mesh_ij.position.z = 0.5;
THREE.GeometryUtils.merge(dummy, mesh_ij);
}
}
}
var material = new THREE.MeshPhongMaterial({map: brickTexture});
var mesh = new THREE.Mesh(dummy, material)
return mesh;
}
function createRenderWorld() {
// Create the scene object.
scene = new THREE.Scene();
// Add the light.
light= new THREE.PointLight(0xffffff, 1);
light.position.set(1, 1, 1.3);
scene.add(light);
// Add the ball.
g = new THREE.SphereGeometry(ballRadius, 32, 16);
m = new THREE.MeshPhongMaterial({map:ironTexture});
ballMesh = new THREE.Mesh(g, m);
ballMesh.position.set(1, 1, ballRadius);
scene.add(ballMesh);
// Add the camera.
var aspect = window.innerWidth/window.innerHeight;
camera = new THREE.PerspectiveCamera(60, aspect, 1, 1000);
camera.position.set(1, 1, 5);
scene.add(camera);
// Add the maze.
mazeMesh = generate_maze_mesh(maze);
scene.add(mazeMesh);
// Add the ground.
g = new THREE.PlaneGeometry(mazeDimension*10, mazeDimension*10, mazeDimension, mazeDimension);
planeTexture.wrapS = planeTexture.wrapT = THREE.RepeatWrapping;
planeTexture.repeat.set(mazeDimension*5, mazeDimension*5);
m = new THREE.MeshPhongMaterial({map:planeTexture});
planeMesh = new THREE.Mesh(g, m);
planeMesh.position.set((mazeDimension-1)/2, (mazeDimension-1)/2, 0);
planeMesh.rotation.set(Math.PI/2, 0, 0);
scene.add(planeMesh);
}
function updatePhysicsWorld() {
// Apply "friction".
var lv = wBall.GetLinearVelocity();
lv.Multiply(0.95);
wBall.SetLinearVelocity(lv);
// Apply user-directed force.
var f = new b2Vec2(keyAxis[0]*wBall.GetMass()*0.25, keyAxis[1]*wBall.GetMass()*0.25);
wBall.ApplyImpulse(f, wBall.GetPosition());
keyAxis = [0,0];
// Take a time step.
wWorld.Step(1/60, 8, 3);
}
function updateRenderWorld() {
// Update ball position.
var stepX = wBall.GetPosition().x - ballMesh.position.x;
var stepY = wBall.GetPosition().y - ballMesh.position.y;
ballMesh.position.x += stepX;
ballMesh.position.y += stepY;
// Update ball rotation.
var tempMat = new THREE.Matrix4();
tempMat.makeRotationAxis(new THREE.Vector3(0,1,0), stepX/ballRadius);
tempMat.multiplySelf(ballMesh.matrix);
ballMesh.matrix = tempMat;
tempMat = new THREE.Matrix4();
tempMat.makeRotationAxis(new THREE.Vector3(1,0,0), -stepY/ballRadius);
tempMat.multiplySelf(ballMesh.matrix);
ballMesh.matrix = tempMat;
ballMesh.rotation.getRotationFromMatrix(ballMesh.matrix);
// Update camera and light positions.
camera.position.x += (ballMesh.position.x - camera.position.x) * 0.1;
camera.position.y += (ballMesh.position.y - camera.position.y) * 0.1;
camera.position.z += (5 - camera.position.z) * 0.1;
light.position.x = camera.position.x;
light.position.y = camera.position.y;
light.position.z = camera.position.z - 3.7;
}
function gameLoop() {
switch(gameState) {
case 'initialize':
maze = generateSquareMaze(mazeDimension);
maze[mazeDimension-1][mazeDimension-2] = false;
createPhysicsWorld();
createRenderWorld();
camera.position.set(1, 1, 5);
light.position.set(1, 1, 1.3);
light.intensity = 0;
var level = Math.floor((mazeDimension-1)/2 - 4);
$('#level').html('Level ' + level);
gameState = 'fade in';
break;
case 'fade in':
light.intensity += 0.1 * (1.0 - light.intensity);
renderer.render(scene, camera);
if (Math.abs(light.intensity - 1.0) < 0.05) {
light.intensity = 1.0;
gameState = 'play'
}
break;
case 'play':
updatePhysicsWorld();
updateRenderWorld();
renderer.render(scene, camera);
// Check for victory.
var mazeX = Math.floor(ballMesh.position.x + 0.5);
var mazeY = Math.floor(ballMesh.position.y + 0.5);
if (mazeX == mazeDimension && mazeY == mazeDimension - 2) {
mazeDimension += 2;
gameState = 'fade out';
}
break;
case 'fade out':
updatePhysicsWorld();
updateRenderWorld();
light.intensity += 0.1 * (0.0 - light.intensity);
renderer.render(scene, camera);
if (Math.abs(light.intensity - 0.0) < 0.1) {
light.intensity = 0.0;
renderer.render(scene, camera);
gameState = 'initialize'
}
break;
}
requestAnimationFrame(gameLoop);
}
function onResize() {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth/window.innerHeight;
camera.updateProjectionMatrix();
}
function onMoveKey(axis) {
keyAxis = axis.slice(0);
}
jQuery.fn.centerv = function () {
wh = window.innerHeight;
h = this.outerHeight();
this.css("position", "absolute");
this.css("top", Math.max(0, (wh - h)/2) + "px");
return this;
}
jQuery.fn.centerh = function () {
ww = window.innerWidth;
w = this.outerWidth();
this.css("position", "absolute");
this.css("left", Math.max(0, (ww - w)/2) + "px");
return this;
}
jQuery.fn.center = function () {
this.centerv();
this.centerh();
return this;
}
$(document).ready(function() {
// Prepare the instructions.
$('#instructions').center();
$('#instructions').hide();
KeyboardJS.bind.key('i', function(){$('#instructions').show()},
function(){$('#instructions').hide()});
// Create the renderer.
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Bind keyboard and resize events.
KeyboardJS.bind.axis('left', 'right', 'down', 'up', onMoveKey);
KeyboardJS.bind.axis('h', 'l', 'j', 'k', onMoveKey);
$(window).resize(onResize);
// Set the initial game state.
gameState = 'initialize';
// Start the game loop.
requestAnimationFrame(gameLoop);
})
</script>
<style>
body {
background: black;
margin: 0;
padding: 0;
font-family: 'Helvetica';
}
#instructions {
background-color: rgba(0,0,0,0.75);
color: white;
text-align: center;
padding: 32px;
margin: 0px;
display: inline;
border: 2px solid white;
}
#help {
position: absolute;
left: 0px;
bottom: 0px;
padding: 4px;
color: white;
}
#level {
position: absolute;
left: 0px;
top: 0px;
padding: 4px;
color: yellow;
font-weight: bold;
}
</style>
</head>
<body>
<div id='instructions'>
How to play Astray:
<br><br>
Use the arrow keys to move the ball and find the exit to the maze.
<br><br>
Vim trainees: h, j, k, l
</div>
<div id='help'>
Hold down the 'I' key for instructions.
</div>
<div id='level'>
Level 1
</div>
</body>
</html>

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -0,0 +1,66 @@
<!doctype html>
<!--
Bounce Back ~ A boomerang roguelike for JS13k
Copyright (C) 2019 Frank Force
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
-->
<!--
When life gets you down,
it's never too late to...
B O U N C E B A C K
A JS13k 2019 Game
By Frank Force
WASD = Move
Mouse = Aim
Click = Throw
Space = Dash
Hints
- You will keep money after you die!
- You won't take damage while dashing.
- There are 10 levels.
- Sand slows down you and enemies.
- Yellow boormang can grab pickups.
- Blue boomerang does double damage.
- Beat the game to unlock speed run mode.
-->
<html>
<head>
<title>Bounce Back</title>
<style>
body
{
background:#000;
overflow:hidden;
margin:0;
}
canvas
{
width:100%;
height:100%;
image-rendering:-moz-crisp-edges;
image-rendering:pixelated;
}
</style>
</head>
<body>
<canvas id=c1></canvas>
<canvas id=c2 style=visibility:hidden></canvas>
<script src='https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/bounceback/gameEngine.js'></script>
<script src='https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/bounceback/gameEngineDebug.js'></script>
<script src='https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/bounceback/game.js'></script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,122 @@
<!doctype html>
<html>
<!-- Mirrored from www.phoenix-le.cf/gfiles/html5games/breaklock/ by HTTrack Website Copier/3.x [XR&CO'2014], Wed, 29 Jan 2020 01:05:01 GMT -->
<head>
<title>BreakLock</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="shortcut icon" href="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/breaklock/assets/favicon.ico">
<meta name="theme-color" content="#14171b">
<link rel="manifest" href="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/breaklock/manifest.json">
<!-- Add to home screen for Safari on iOS -->
<link rel="apple-touch-icon" href="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/breaklock/assets/icons/ios-180x180.png">
<link rel="apple-touch-startup-image" href="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/breaklock/assets/ios-startup/startup-640x1136.png" media="device-width: 375px">
<link rel="apple-touch-startup-image" href="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/breaklock/assets/ios-startup/startup-1080x1920.png" media="device-width: 414px">
<link rel="apple-touch-startup-image" href="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/breaklock/assets/ios-startup/startup-640x1136.png" media="(device-width: 320px) and (device-height: 568px)">
<link rel="apple-touch-startup-image" href="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/breaklock/assets/ios-startup/startup-640x960.png" media="(device-width: 320px) and (device-height: 480px)">
<meta name="apple-mobile-web-app-title" content="BreakLock">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no, minimal-ui">
<meta name="msapplication-TileImage" content="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/breaklock/assets/icons/icon-144x144.png">
<meta name="msapplication-TileColor" content="#14171b">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/breaklock/app.css">
</head>
<body>
<!-- Intro -->
<p id="app-intro" style="font-family: monospace; color: #fff;">
$ ./breaklock start<br>
Loading...
<style type="text/css">
body {
background: #14171b;
}
</style>
</p>
<!-- Static content -->
<div style="display: none;">
<div id="instructions-template">
<object class="introduction-demo" data="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/breaklock/assets/intro.svg" type="image/svg+xml"></object>
<p>Link the dots to find the lock pattern. After every attempt the game will tell you how many dots you got right.<p>
<table>
<tr>
<td style="width:1.5em;">&#9679;</td>
<td>a dot occurs in the pattern and is in the correct order</td>
</tr>
<tr>
<td>&#9675;</td>
<td>a dot occurs in the pattern but in the wrong order</td>
</tr>
</table>
<p>The difficulty setting changes the amount of dots to connect. Easy is 4 dots, medium is 5 dots and hard is 6 dots.</p>
<p>Good luck!_</p>
<p class="small">by <a href="https://twitter.com/mxwllt"><svg class="icon"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#icon-maxwellito"></use></svg></a> / on <a href="https://github.com/maxwellito/breaklock"><svg class="icon"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#icon-github"></use></svg></a></p>
</div>
</div>
<!-- SVG Icon definitions -->
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol viewBox="0 0 128 128" id="icon-new_game">
<path d="M111.5,120.5h-96c-4.418,0-8-3.582-8-8V16c0-3.236,1.949-6.153,4.938-7.391c2.991-1.237,6.432-0.554,8.718,1.734l48.5,48.5 c2.288,2.288,2.972,5.729,1.733,8.719S67.235,72.5,64,72.5H23.5v32h80v-80h-40c-4.418,0-8-3.582-8-8s3.582-8,8-8h48 c4.418,0,8,3.582,8,8v96C119.5,116.918,115.918,120.5,111.5,120.5z M23.5,56.5h21.187L23.5,35.313V56.5z"/>
</symbol>
<symbol viewBox="0 0 128 128" id="icon-back_home">
<path d="M111.5,120.5h-96c-4.418,0-8-3.582-8-8V64c0-2.129,0.849-4.17,2.358-5.672l48.25-48C59.613,8.831,61.681,8.015,63.776,8 c2.123,0.007,4.157,0.858,5.652,2.365l47.875,48.25c1.907,1.922,2.639,4.569,2.196,7.047V112.5 C119.5,116.918,115.918,120.5,111.5,120.5z M23.5,104.5h80v-32h-40c-4.418,0-8-3.582-8-8s3.582-8,8-8h29.165L63.713,27.321 L23.5,67.326V104.5z"/>
</symbol>
<symbol viewBox="0 0 128 128" id="icon-continue">
<path d="M15.5,120c-1.463,0-2.921-0.4-4.207-1.194C8.936,117.348,7.5,114.772,7.5,112V16c0-2.772,1.436-5.348,3.794-6.805 c2.359-1.458,5.304-1.591,7.784-0.35l96,48C117.788,58.2,119.5,60.97,119.5,64s-1.712,5.8-4.422,7.155l-96,48 C17.949,119.72,16.723,120,15.5,120z M23.5,28.944v70.111L93.611,64L23.5,28.944z"/>
</symbol>
<symbol viewBox="0 0 120 122" id="icon-maxwellito">
<polyline points="30,0 60,17.33 40,28.88 60,40.43 80,28.88 60,17.33 90,0 120,17.33 120,51.98 90,69.29 90,46.20 70,57.75 70,80.84 90,69.29 90,103.93 60,121.26 30,103.93 30,69.29 50,80.84 50,57.75 30,46.2 30,69.29 0,51.98 0,17.33" />
</symbol>
<symbol viewBox="0 0 128 128" id="icon-github">
<path d="M115.9,35.4c-5.3-9.2-12.6-16.5-21.8-21.8c-9.2-5.4-19.2-8-30.1-8c-10.9,0-20.9,2.7-30.1,8C24.7,18.9,17.4,26.2,12,35.4 c-5.3,9.2-8,19.3-8,30.1c0,13.1,3.8,24.9,11.4,35.3c7.7,10.4,17.5,17.7,29.6,21.7c1.4,0.3,2.5,0.1,3.1-0.5c0.7-0.6,1-1.4,1-2.4 v-4.2l-0.1-7l-1.8,0.3c-1.1,0.3-2.6,0.3-4.4,0.3c-1.7,0-3.6-0.2-5.4-0.5c-1.9-0.3-3.6-1.1-5.3-2.3c-1.6-1.2-2.7-2.8-3.4-4.8 L28,99.5c-0.5-1.2-1.3-2.5-2.5-4c-1.1-1.5-2.2-2.5-3.4-3l-0.5-0.4c-0.4-0.3-0.7-0.5-1-0.9c-0.3-0.4-0.5-0.7-0.7-1.1 c-0.1-0.4,0-0.7,0.4-0.9c0.4-0.3,1.2-0.4,2.2-0.4l1.6,0.3c1,0.2,2.3,0.8,3.8,1.8c1.6,1,2.8,2.4,3.8,4.1c1.2,2.1,2.6,3.7,4.3,4.8 c1.7,1.1,3.4,1.6,5.1,1.6c1.7,0,3.2-0.1,4.5-0.4c1.2-0.2,2.4-0.6,3.5-1.1c0.5-3.5,1.8-6.2,3.8-8c-3-0.3-5.6-0.8-8-1.4 c-2.4-0.7-4.8-1.6-7.3-3.1c-2.5-1.4-4.7-3.1-6.3-5.2c-1.6-2.1-3-4.8-4.1-8.2c-1.1-3.4-1.6-7.3-1.6-11.7c0-6.3,2.1-11.7,6.2-16.1 c-1.9-4.7-1.8-10,0.5-16c1.5-0.4,3.7-0.1,6.7,1.1c3,1.2,5.1,2.2,6.5,3c1.4,0.8,2.5,1.5,3.3,2.1c4.8-1.4,9.9-2,15-2 c5.1,0,10.1,0.7,15,2l3-1.9c2-1.3,4.4-2.5,7.2-3.5c2.7-1,4.9-1.4,6.3-0.8c2.4,5.9,2.6,11.2,0.7,15.9c4.1,4.5,6.2,9.9,6.2,16.1 c0,4.4-0.5,8.3-1.6,11.8c-1,3.4-2.4,6.1-4.1,8.2c-1.7,2-3.8,3.8-6.3,5.1c-2.6,1.5-5,2.5-7.4,3.1c-2.4,0.6-5,1.1-8,1.4 c2.7,2.4,4.1,6.1,4.1,11.1v16.5c0,0.9,0.3,1.7,1,2.4c0.7,0.6,1.6,0.8,3.1,0.5c12-4,21.9-11.2,29.6-21.7 c7.7-10.4,11.4-22.2,11.4-35.3C124,54.7,121.3,44.6,115.9,35.4L115.9,35.4z"/>
</symbol>
<symbol viewBox="0 0 128 128" id="icon-facebook">
<path d="M16,16v96h51.164V74.451h-12.08v-13.99h12.08V50.144c0-11.973,7.312-18.493,17.994-18.493c5.115,0,9.514,0.381,10.795,0.552 v12.514l-7.408,0.003c-5.809,0-6.934,2.76-6.934,6.811v8.932h13.854l-1.803,13.99H81.611V112H112V16H16z"/>
</symbol>
<symbol viewBox="0 0 128 128" id="icon-twitter">
<path d="M128,24.3c-4.7,2.1-9.8,3.5-15.1,4.1c5.4-3.2,9.6-8.4,11.5-14.5c-5.1,3-10.7,5.2-16.7,6.4 C103,15.2,96.2,12,88.6,12c-14.5,0-26.3,11.8-26.3,26.3c0,2.1,0.2,4.1,0.7,6C41.2,43.2,21.9,32.7,8.9,16.8 C6.7,20.7,5.4,25.2,5.4,30c0,9.1,4.6,17.1,11.7,21.9c-4.3-0.1-8.4-1.3-11.9-3.3v0.3c0,12.7,9.1,23.3,21.1,25.7 c-2.2,0.6-4.5,0.9-6.9,0.9c-1.7,0-3.3-0.2-4.9-0.5c3.3,10.4,13,18,24.5,18.2c-9,7-20.3,11.2-32.6,11.2c-2.1,0-4.2-0.1-6.3-0.4 c11.6,7.5,25.4,11.8,40.2,11.8c48.3,0,74.7-40,74.7-74.7l-0.1-3.4C120,34.2,124.5,29.6,128,24.3z"/>
</symbol>
</svg>
<noscript>
<!--[if lte IE 6]>
<p>
If you read this it's probably because the world has rebooted. I don't know how but you arrived here. I'm sorry you cannot enjoy this little game but you will be able to in the not too distant future. Browsers will become incredibly better. Just before the world turns into chaos due to Nutella production shortages. Scientists will realise too late that Nutella was to humans what pollen is to bees. Don't believe the hype, the end didn't (or won't) happen because the 'left-pad' package got removed from NPM. Good luck.
</p>
<p>
PS: If the world has ended, please tell thejameskyle that I loved him. Only his cat can save the world. (Shut the door)
</p>
<![endif]-->
</noscript>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/breaklock/app.js"></script>
<script type="text/javascript">
// Set up service worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('service-worker.js', {scope: '.'});
}
// Easter Egg to get it full black
// Awesome for OLED screens
if (localStorage.getItem('isDeepBlack')) {
document.body.classList.add('deepblack')
}
</script>
</body>
<!-- Mirrored from www.phoenix-le.cf/gfiles/html5games/breaklock/ by HTTrack Website Copier/3.x [XR&CO'2014], Wed, 29 Jan 2020 01:05:03 GMT -->
</html>

View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="">
<link href="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/breakout/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/breakout/css/styles.css" rel="stylesheet">
<!-- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"> -->
<link href='https://fonts.googleapis.com/css?family=Titillium+Web' rel='stylesheet' type='text/css'>
</head>
<body>
<canvas id="myCanvas"></canvas>
<div class="container playbtn">
<div class="row text-center">
<button type="button" class="btn btn-lg btn-primary" id="start" autofocus>Start / Resume</button>
<button type="button" class="btn btn-lg btn-danger hidden" id="pause">Pause</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/breakout/js/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/BinBashBanana/gstore/html5games/breakout/js/script.js"></script>
</body>
</html>

View file

@ -0,0 +1,293 @@
/*
* Javascript/Canvas Textured 3D Renderer v0.3.1
* Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com
* This software is free to use for non-commercial purposes. For anything else, please contact the author.
* This is a version modified by Stefano Gioffre'.
*/
Canvas3D.Camera = function() {
this._oPosition = new Canvas3D.Vec3(0,0,0);
this._oSideVec = new Canvas3D.Vec3(1,0,0);
this._oUpVec = new Canvas3D.Vec3(0,1,0);
this._oOutVec = new Canvas3D.Vec3(0,0,1);
this._oRotMat = new Canvas3D.Matrix3();
this._bDirty = false;
this._fFocal = 500;
this._fFocalDistance = this._fFocal;
this._bReverseX = false;
this._bReverseY = false;
this._bTarget = true;
this._iClipNear = 1;
this._iClipFar = 10000000;
this._fScale = 1;
this._oLookAt = new Canvas3D.Vec3(0,0,0);
};
var proto = Canvas3D.Camera.prototype;
proto.getDirty = function() {
return this._bDirty;
}
proto.setDirty = function(bDirty) {
this._bDirty = bDirty;
}
proto.setPosition = function(oPos) {
this._oPosition.set(oPos.x, oPos.y, oPos.z);
this._bDirty = true;
}
proto.getPosition = function() {
return this._oPosition;
}
proto.setScale = function(fScale) {
this._fScale = fScale;
this._bDirty = true;
}
proto.getScale = function() {
return this._fScale;
}
proto.getSide = function() {
return this._oSideVec;
}
proto.getUp = function() {
return this._oUpVec;
}
proto.setUp = function(oVec) {
this._oUpVec = oVec;
}
proto.getOut = function() {
return this._oOutVec;
}
proto.moveSideways = function(d) {
this._oPosition.x += this._oSideVec.x * d;
this._oPosition.y += this._oSideVec.y * d;
this._oPosition.z += this._oSideVec.z * d;
this.setDirty(true);
}
proto.moveUpwards = function(d) {
this._oPosition.x += this._oUpVec.x * d;
this._oPosition.y += this._oUpVec.y * d;
this._oPosition.z += this._oUpVec.z * d;
this.setDirty(true);
}
proto.moveForward = function(d) {
this._oPosition.x += this._oOutVec.x * d;
this._oPosition.y += this._oOutVec.y * d;
this._oPosition.z += this._oOutVec.z * d;
this.setDirty(true);
}
// rotate around the camera's side axis with a target center point (uses camera target if oTarget is null)
proto.pitchAroundTarget = function(fTheta, oTarget) {
var M = new Canvas3D.Matrix3();
var oPos = this.getPosition();
oTarget = oTarget || this.getLookAt();
// translate position to target space
oPos.subVector(oTarget);
// rotate around side axis
M.loadRotationAxis(this._oSideVec, Math.sin(fTheta * Math.PI / 180.0), Math.cos(fTheta * Math.PI / 180.0));
oPos = M.multiplyVector(oPos);
// translate position out of target space
oPos.addVector(oTarget);
this.setPosition(oPos);
this.setDirty(true);
}
proto.yaw = function(fTheta) {
var M = new Canvas3D.Matrix3();
M.loadRotationAxis(this._oUpVec, Math.sin(fTheta), Math.cos(fTheta));
this._oSideVec = M.multiplyVector(this._oSideVec);
this._oOutVec = M.multiplyVector(this._oOutVec);
this.setDirty(true);
}
// rotate around the camera's up axis with a target center point (uses camera target if oTarget is null)
proto.yawAroundTarget = function(fTheta, oTarget) {
var M = new Canvas3D.Matrix3();
var oPos = this.getPosition();
oTarget = oTarget || this.getLookAt();
// translate position to target space
oPos.subVector(oTarget);
// rotate around up axis
M.loadRotationAxis(this._oUpVec, Math.sin(fTheta * Math.PI / 180.0), Math.cos(fTheta * Math.PI / 180.0));
oPos = M.multiplyVector(oPos);
// translate position out of target space
oPos.addVector(oTarget);
this.setPosition(oPos);
this.setDirty(true);
}
// rotate around the camera's out axis with a target center point (uses camera target if oTarget is null)
proto.rollAroundTarget = function(fTheta, oTarget) {
var M = new Canvas3D.Matrix3();
var oPos = this.getPosition();
oTarget = oTarget || this.getLookAt();
// translate position to target space
oPos.subVector(oTarget);
// rotate around out axis
M.loadRotationAxis(this._oOutVec, Math.sin(fTheta * Math.PI / 180.0), Math.cos(fTheta * Math.PI / 180.0));
oPos = M.multiplyVector(oPos);
// translate position out of target space
oPos.addVector(oTarget);
this.setPosition(oPos);
this.setDirty(true);
}
proto.rotateY = function(sine, cosine) {
var M = new Canvas3D.Matrix3();
M.loadRotationY(sine, cosine);
this._oSideVec = M.multiplyVector(this._oSideVec);
this._oUpVec = M.multiplyVector(this._oUpVec);
this._oOutVec = M.multiplyVector(this._oOutVec);
this.setDirty(true);
}
proto.lookAt = function(P, Up) {
Up = Up || this._oUpVec;
this._oOutVec = P.returnSub(this._oPosition).unit();
//this._oSideVec = this._oOutVec.cross(new Canvas3D.Vec3 (0.0, 1.0, 0.0)).unit();
//this._oSideVec = this._oOutVec.cross(this._oUpVec).unit();
this._oSideVec = this._oOutVec.cross(Up).unit();
this._oUpVec = this._oSideVec.cross(this._oOutVec).unit();
this._vecLookAt = P.clone();
this.setDirty(true);
}
proto.getLookAt = function() {
return this._vecLookAt;
}
proto.updateRotationMatrix = function() {
var e0 = this._oRotMat.e[0];
var e1 = this._oRotMat.e[1];
var e2 = this._oRotMat.e[2];
e0[0] = this._oSideVec.x;
e0[1] = this._oSideVec.y;
e0[2] = this._oSideVec.z;
e1[0] = this._oUpVec.x;
e1[1] = this._oUpVec.y;
e1[2] = this._oUpVec.z;
e2[0] = this._oOutVec.x;
e2[1] = this._oOutVec.y;
e2[2] = this._oOutVec.z;
}
proto.transformPoint = function(P) {
var e = this._oRotMat.e;
var oPos = this._oPosition;
var e0 = e[0];
var e1 = e[1];
var e2 = e[2];
var vx = P.x - oPos.x;
var vy = P.y - oPos.y;
var vz = P.z - oPos.z;
return new Canvas3D.Vec3(
(vx * e0[0] + vy * e0[1] + vz * e0[2]),
(vx * e1[0] + vy * e1[1] + vz * e1[2]),
(vx * e2[0] + vy * e2[1] + vz * e2[2])
);
}
proto.project = function(P) {
var fFocal = this._fFocal;
return {
x: P.x * fFocal / (P.z + this._fFocalDistance) * this._fScale * (this._bReverseX?-1:1),
y: -P.y * fFocal / (P.z + this._fFocalDistance) * this._fScale * (this._bReverseY?-1:1)
};
}
proto.clip = function(P) {
if (P.z < 0) {
return true;
}
return false;
}
proto.isBehind = function(P) {
if (P.z > 0) return false;
return false;
}
proto.getClipNear = function() {
return this._iClipNear;
}
proto.getClipFar = function() {
return this._iClipFar;
}
proto.clip = function(P) {
if (P.z > this._iClipNear && P.z < this._iClipFar) {
return false;
} else {
return true;
}
}
proto.setFOV = function(fFOV) {
this._fFOV = fFOV;
var fFocal = 1 / Math.tan(105 * Math.PI*Math.PI / (180*180 * 2));
this._fFocal = fFocal;
this._fFocalDistance = fFocal;
}
proto.getFOV = function() {
return this._fFOV;
}
proto.getFocal = function() {
return this._fFocal;
}
proto.setFocalDistance = function(fValue) {
this._fFocalDistance = fValue;
}
proto.setReverseX = function(bEnable) {
this._bReverseX = bEnable;
}
proto.setReverseY = function(bEnable) {
this._bReverseY = bEnable;
}

View file

@ -0,0 +1,75 @@
/*
* Javascript/Canvas Textured 3D Renderer v0.3
* Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com
* This software is free to use for non-commercial purposes. For anything else, please contact the author.
* This is a version modified by Stefano Gioffre'.
*/
var __iLightIDCounter = 0;
Canvas3D.Light = function() {
this._oPosition = new Canvas3D.Vec3(0,0,0);
this._oColor = {r:255,g:255,b:255};
this._fIntensity = 1.0;
this._iLightID = ++__iLightIDCounter;
};
var proto = Canvas3D.Light.prototype;
proto.setPosition = function(oPos) {
this._oPosition = oPos;
this._bDirty = true;
}
proto.getPosition = function() {
return this._oPosition;
}
proto.setColor = function(oColor) {
this._oColor = oColor;
this._bDirty = true;
}
proto.getColor = function() {
return this._oColor;
}
proto.setIntensity = function(fIntensity) {
this._fIntensity = fIntensity;
this._bDirty = true;
}
proto.getIntensity = function(fIntensity) {
return this._fIntensity;
}
proto.setScene = function(oScene) {
if (this._oScene != oScene) {
this._oScene = oScene;
this._bDirty = true;
}
}
proto.draw = function(oContext, iOffsetX, iOffsetY) {
var oScene = this._oScene;
var oCam = oScene.getActiveCamera();
var oPos2D = oCam.transform2D(oCam.transformPoint(this._oPosition));
var iRadius = 3;
oContext.beginPath();
oContext.moveTo(oPos2D.x + iOffsetX + iRadius, oPos2D.y + iOffsetY);
oContext.arc(oPos2D.x + iOffsetX, oPos2D.y + iOffsetY, iRadius, 0, 360, false);
oContext.fillStyle = "rgb(255,255,0)";
oContext.fill();
}
proto.getDirty = function() {
return this._bDirty;
}
proto.setDirty = function(bDirty) {
this._bDirty = bDirty;
}

View file

@ -0,0 +1,122 @@
/*
* Javascript/Canvas Textured 3D Renderer v0.3
* Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com
* This software is free to use for non-commercial purposes. For anything else, please contact the author.
* This is a version modified by Stefano Gioffre'.
*/
Canvas3D.Matrix3 = function() {
this.e = [
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0]
];
}
Canvas3D.Matrix3.prototype.multiplyVector = function(v) {
var p = new Canvas3D.Vec3;
var e0 = this.e[0], e1 = this.e[1], e2 = this.e[2];
var vx = v.x, vy = v.y, vz = v.z;
p.x = vx * e0[0] + vy * e0[1] + vz * e0[2];
p.y = vx * e1[0] + vy * e1[1] + vz * e1[2];
p.z = vx * e2[0] + vy * e2[1] + vz * e2[2];
return p;
}
Canvas3D.Matrix3.prototype.multiplyMatrix = function(M) {
var p = new Canvas3D.Matrix3();
var e0 = this.e[0], e1 = this.e[1], e2 = this.e[2];
var m0 = M.e[0], m1 = M.e[1], m2 = M.e[2];
var e00 = e0[0], e01 = e0[1], e02 = e0[2];
var e10 = e1[0], e11 = e1[1], e12 = e1[2];
var e20 = e2[0], e21 = e2[1], e22 = e2[2];
var m00 = m0[0], m01 = m0[1], m02 = m0[2];
var m10 = m1[0], m11 = m1[1], m12 = m1[2];
var m20 = m2[0], m21 = m2[1], m22 = m2[2];
p.e[0][0] = m00 * e00 + m10 * e01 + m20 * e02;
p.e[0][1] = m01 * e00 + m11 * e01 + m21 * e02;
p.e[0][2] = m02 * e00 + m12 * e01 + m22 * e02;
p.e[1][0] = m00 * e10 + m10 * e11 + m20 * e12;
p.e[1][1] = m01 * e10 + m11 * e11 + m21 * e12;
p.e[1][2] = m02 * e10 + m12 * e11 + m22 * e12;
p.e[2][0] = m00 * e20 + m10 * e21 + m20 * e22;
p.e[2][1] = m01 * e20 + m11 * e21 + m21 * e22;
p.e[2][2] = m02 * e20 + m12 * e21 + m22 * e22;
return p;
}
Canvas3D.Matrix3.prototype.transpose = function() {
var t = new Canvas3D.Matrix3();
t.e[0][0] = this.e[0][0];
t.e[0][1] = this.e[1][0];
t.e[0][2] = this.e[2][0];
t.e[1][0] = this.e[0][1];
t.e[1][1] = this.e[1][1];
t.e[1][2] = this.e[2][1];
t.e[2][0] = this.e[0][2];
t.e[2][1] = this.e[1][2];
t.e[2][2] = this.e[2][2];
return t;
}
Canvas3D.Matrix3.prototype.loadIdentity = function() {
var e0 = this.e[0], e1 = this.e[1], e2 = this.e[2];
e0[0] = 1; e0[1] = 0; e0[2] = 0;
e1[0] = 0; e1[1] = 1; e1[2] = 0;
e2[0] = 0; e2[1] = 0; e2[2] = 1;
}
Canvas3D.Matrix3.prototype.loadRotationX = function(s, c) {
var e0 = this.e[0], e1 = this.e[1], e2 = this.e[2];
e0[0] = 1; e0[1] = 0; e0[2] = 0;
e1[0] = 0; e1[1] = c; e1[2] = -s;
e2[0] = 0; e2[1] = s; e2[2] = c;
}
Canvas3D.Matrix3.prototype.loadRotationY = function(s, c) {
var e0 = this.e[0], e1 = this.e[1], e2 = this.e[2];
e0[0] = c; e0[1] = 0; e0[2] = s;
e1[0] = 0; e1[1] = 1; e1[2] = 0;
e2[0] = -s; e2[1] = 0; e2[2] = c;
}
Canvas3D.Matrix3.prototype.loadRotationZ = function(s, c) {
var e0 = this.e[0], e1 = this.e[1], e2 = this.e[2];
e0[0] = c; e0[1] = -s; e0[2] = 0;
e1[0] = s; e1[1] = c; e1[2] = 0;
e2[0] = 0; e2[1] = 0; e2[2] = 1;
}
Canvas3D.Matrix3.prototype.loadRotationAxis = function(A, s, c) {
var t = 1 - c;
var tx = t * A.x;
var ty = t * A.y;
var txx = tx * A.x;
var txy = tx * A.y;
var txz = tx * A.z;
var tyy = ty * A.y;
var tyz = ty * A.z;
var tzz = t * A.z * A.z;
var sx = s * A.x;
var sy = s * A.y;
var sz = s * A.z;
this.e[0][0] = txx + c;
this.e[0][1] = txy - sz;
this.e[0][2] = txz + sy;
this.e[1][0] = txy + sz;
this.e[1][1] = tyy + c;
this.e[1][2] = tyz - sx;
this.e[2][0] = txz - sy;
this.e[2][1] = tyz + sx;
this.e[2][2] = tzz + c;
}

View file

@ -0,0 +1,906 @@
/*
* Javascript/Canvas Textured 3D Renderer v0.3.1
* Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com
* This software is free to use for non-commercial purposes. For anything else, please contact the author.
* This is a version modified by Stefano Gioffre'.
*/
Canvas3D.Mesh = function() {
this._oPosition = new Canvas3D.Vec3(0,0,0);
this._oRotation = new Canvas3D.Vec3(0,0,0);
this._aVertices = []; // vertex positions in object space
this._aGlobalVertices = []; // vertices translated to global space
this._aFaces = [];
this._aNormals = [];
this._aMaterials = [];
this._bDirty = true;
this._bVisible = true;
this._iForcedZ = -1;
this._bHideWhenRotating = false;
this._oDefaultColor = {r:155,g:155,b:155};
this._oDefaultMaterial = {};
this._iSize = 1;
this._fScale = 1;
this._bFill = true; // render filled triangles
this._bWire = false; // render wireframe
this._bShading = true; // shade/light filled triangles
this._bBackfaceCull = true; // only draw triangles facing the camera
this._bZSort = true; // sort triangles back-to-front
this._bExpandClipPath = true; // expand clip path by 1px, to minimize gaps
this._bTexture = false; // render textured triangles (must enable bFill as well)
this._bTextureShading = false; // render shading on textured triangles (must enable bShading as well)
// sometimes the exported normals from Max are messed up, or they're imported wrong or whatever, I don't know.
// We can recalculate them after loading.
this._bCalcNormals = true;
// only allow textures if canvas is available
var oCanvas = document.createElement("canvas");
this._bCanTexture = false;
this._bCanTextureUV = false;
if (oCanvas.getContext && oCanvas.getContext("2d")) {
this._bCanTexture = true;
if (oCanvas.getContext("2d").getImageData) {
this._bCanTextureUV = true;
}
}
};
// parse the mesh data
// the mesh data (vertices, faces, texture coordinates, materials) are read from a JSON object structure
// and copied into local arrays
// normals are recalculated, if enabled.
Canvas3D.Mesh.prototype.setMeshData = function(oMeshData, oScene) {
this._oMeshData = oMeshData;
this._aVertices = [];
this._aFaces = [];
this._aNormals = [];
this._aMaterials = [];
var oPos = this._oPosition;
var me = this;
if (this._oMeshData.mat) {
for (var m=0;m<this._oMeshData.mat.length;m++) {
var oMat = this._oMeshData.mat[m];
oMat.idx = m;
if (oMat.t) {
oMat.image = new Image();
if (oMat.uv && this._bCanTextureUV) {
oMat.image.mat = oMat;
oMat.image.onload = function() {
me._bakeTexture(this.mat);
if (oScene) {
oScene.setDirty(true);
}
this.onload = null;
}
}
oMat.image.src = "textures/" + oMat.t;
}
this._aMaterials.push(oMat);
}
}
for (var o=0;o<this._oMeshData.obj.length;o++) {
var oObject = this._oMeshData.obj[o];
var aVertices = oObject.vrt;
var aTexCoords = oObject.tex;
this._aTexCoords = aTexCoords;
var iVertOffset = this._aVertices.length;
var fTotalX = 0;
var fTotalY = 0;
var fTotalZ = 0;
var iNumVertices = aVertices.length;
for (var v=0;v<iNumVertices;v++) {
var oVertex = new Canvas3D.Vec3(
aVertices[v][0],
aVertices[v][1],
aVertices[v][2]
);
this._aVertices.push(oVertex);
this._aGlobalVertices.push(
new Canvas3D.Vec3(
oVertex.x + oPos.x,
oVertex.y + oPos.y,
oVertex.z + oPos.z
)
);
fTotalX += oVertex.x;
fTotalY += oVertex.y;
fTotalZ += oVertex.z;
}
var fAvgX = fTotalX / iNumVertices;
var fAvgY = fTotalY / iNumVertices;
var fAvgZ = fTotalZ / iNumVertices;
var oLocalCenter = new Canvas3D.Vec3(fAvgX, fAvgY, fAvgZ);
var aFaces = oObject.fac;
for (var f=0;f<aFaces.length;f++) {
var oFace = aFaces[f];
var oPoint1 = this._aGlobalVertices[oFace[0] + iVertOffset];
var oPoint2 = this._aGlobalVertices[oFace[1] + iVertOffset];
var oPoint3 = this._aGlobalVertices[oFace[2] + iVertOffset];
var oCenter = new Canvas3D.Vec3(
(oPoint1.x + oPoint2.x + oPoint3.x) / 3,
(oPoint1.y + oPoint2.y + oPoint3.y) / 3,
(oPoint1.z + oPoint2.z + oPoint3.z) / 3
);
var oNormal = new Canvas3D.Vec3(
oObject.nrm[f][0],
oObject.nrm[f][1],
oObject.nrm[f][2]
);
var oFace = {
a : oFace[0] + iVertOffset,
b : oFace[1] + iVertOffset,
c : oFace[2] + iVertOffset,
normal : oNormal,
center : oCenter,
mat : oFace[3],
idx : f,
lights : []
}
this._aFaces.push(oFace);
}
if (this._bCalcNormals) {
this._recalcNormals();
}
}
}
// here we prepare the texture for easy rendering.
// For each face, a triangular region is drawn onto a canvas containing the image data for that face
// We draw a triangular section on the destination canvas containing the corresponding pixel data from the source texture,
// using the UV coords to retrieve the correct pixels
// that way, we have all texture parts rotated and scaled into a common form, so they can easily be drawn later
// at the moment, the data is read with getImageData and is rather slow, but it should be possible do to sort of the reverse process
// of the drawTextureTriangle transform to draw these texture parts.
Canvas3D.Mesh.prototype._bakeTexture = function(oMat) {
var f = this._aFaces.length;
var aTexFaces = [];
// find all face that need this texture
do {
var oFace = this._aFaces[f-1];
if (oFace.mat == oMat.idx) {
aTexFaces.push(oFace);
oFace.texidx = aTexFaces.length-1;
}
} while (--f)
f = aTexFaces.length;
if (f < 1) {
return;
}
var fc = aTexFaces.length;
var iTexRes = oMat.res;
var iTexWidth = iTexRes * f + f*2;
var iTexHeight = iTexRes + 2;
var iSrcWidth = oMat.w;
var iSrcHeight = oMat.h;
// create canvas for source texture image and paint the texture on it
var oSource = document.createElement("canvas");
oSource.width = iSrcWidth;
oSource.height = iSrcHeight;
oSource.style.width = iSrcWidth+"px";
oSource.style.height = iSrcHeight+"px";
var oSrcCtx = oSource.getContext("2d");
oSrcCtx.drawImage(oMat.image, 0, 0, iSrcWidth, iSrcHeight);
var oSrcDataObj = oSrcCtx.getImageData(0, 0, iSrcWidth, iSrcHeight);
var aSrcData = oSrcDataObj.data;
// create canvas for finished face textures.
var oTexCanvas = document.createElement("canvas");
oTexCanvas.width = iTexWidth;
oTexCanvas.height = iTexHeight;
oTexCanvas.style.width = iTexWidth+"px";
oTexCanvas.style.height = (iTexHeight)+"px";
oTexCanvas.style.backgroundColor = "rgb(255,0,255)";
oTexCanvas.resolution = iTexRes;
var oDstCtx = oTexCanvas.getContext("2d");
oDstCtx.fillStyle = "rgb(255,0,255)";
oDstCtx.fillRect(0,0,iTexWidth,iTexHeight);
var oDstDataObj = oDstCtx.getImageData(0, 0, iTexWidth, iTexHeight);
var aDstData = oDstDataObj.data;
var oContext = oTexCanvas.getContext("2d");
//uncomment to see how the texture is prepared
//document.body.appendChild(oSource);
//document.body.appendChild(oTexCanvas);
var iTexOffsetX = iTexRes;
do {
var oFace = aTexFaces[f-1];
var oCoords = this._aTexCoords[oFace.idx];
var oTexPoint1 = oCoords[1];
var oTexPoint2 = oCoords[2];
var oTexPoint3 = oCoords[0];
var x1 = oTexPoint1[0] * iSrcWidth;
var y1 = (1 - oTexPoint1[1]) * iSrcHeight;
var x3 = oTexPoint2[0] * iSrcWidth;
var y3 = (1 - oTexPoint2[1]) * iSrcHeight;
var x2 = oTexPoint3[0] * iSrcWidth;
var y2 = (1 - oTexPoint3[1]) * iSrcHeight;
var fUnitAX = (x2 - x1);
var fUnitAY = (y2 - y1);
var fUnitBX = (x3 - x2);
var fUnitBY = (y3 - y2);
var iOffsetX = 0;
var iDstXOffset = (iTexWidth - iTexOffsetX - (fc-f)*2-2);
// we paint the triangular texture with a 1px margin on each side and let the texture spill over into this margin.
// this is to prevent small transparent gaps to appear between the triangles when they are rotated and scaled into place during rendering.
var y = iTexRes+2;
do {
var iDstY = iTexRes+2-y;
var fStepY = (iTexRes+1-y) / iTexRes;
var fStepYUnitBX = fStepY*fUnitBX;
var fStepYUnitBY = fStepY*fUnitBY;
var iDstYOffset = iDstY*iTexWidth*4;
var x = iTexRes+2 - iOffsetX;
do {
var iDstX = x + iDstXOffset - 1;
var fStepX = (x-1 + iOffsetX) / iTexRes;
var iSrcX = Math.floor(x1 + fStepX*fUnitAX + fStepYUnitBX);
var iSrcY = Math.floor(y1 + fStepX*fUnitAY + fStepYUnitBY);
if (iSrcX < 0) iSrcX = 0;
if (iSrcY < 0) iSrcY = 0;
if (iSrcX >= iSrcWidth) iSrcX = iSrcWidth-1;
if (iSrcY >= iSrcHeight) iSrcY = iSrcHeight-1;
var iDstPixOffset = iDstYOffset + iDstX*4;
var iSrcPixOffset = (iSrcY*iSrcWidth + iSrcX)*4;
aDstData[iDstPixOffset] = aSrcData[iSrcPixOffset];
aDstData[iDstPixOffset+1] = aSrcData[iSrcPixOffset+1];
aDstData[iDstPixOffset+2] = aSrcData[iSrcPixOffset+2];
aDstData[iDstPixOffset+3] = oMat.texalpha ? aSrcData[iSrcPixOffset+3] : 255;
} while (--x);
iOffsetX++;
iDstXOffset++;
} while (--y);
iTexOffsetX += iTexRes;
} while (--f);
oDstCtx.putImageData(oDstDataObj, 0, 0);
oDstCtx.fillRect(0,0,0,0); // Opera doesn't update until we draw something?
oMat.facecanvas = oTexCanvas;
}
var fncZSort = function(a, b) {
return a.transcenter.z - b.transcenter.z;
}
// math and misc shortcuts
var sin = Math.sin;
var cos = Math.cos;
var asin = Math.asin;
var acos = Math.acos;
var pow = Math.pow;
var sqrt = Math.sqrt;
var fRadDeg = 180 / Math.PI;
var fDegRad = Math.PI / 180;
var fDegRad45 = fDegRad*45;
var fDegRad90 = fDegRad*90;
var fDegRad180 = fDegRad*180;
var fSqrt2Div2 = sqrt(2) / 2;
// this functions draws a textured (and shaded) triangle on the canvas
// this is done by rotating/scaling a triangular section in place, setting up a clipping path and drawing the image.
// if UV coords are enabled, only the correct part of the triangle-strip texture is drawn
// if not, the entire texture is drawn for each face
// some of the code used for skewing the image was inspired by the AS function found here:
// http://www.senocular.com/flash/actionscript.php?file=ActionScript_1.0/Prototypes/MovieClip/skew.as
Canvas3D.Mesh.prototype._drawTextureTriangle = function(oContext, oMat, oPoint1, oPoint2, oPoint3, iOffsetX, iOffsetY, fShade, oNormal, iIdx) {
if (!oMat.image) {
return;
}
if (!oMat.image.complete) {
return;
}
var oMatImage = oMat.image;
if (!oMatImage.canvas) {
// first time around, we paint the texture image to a canvas
// drawing the triangle later on is slightly faster using another canvas object rather than an image object
// this should be moved to someplace else
var iTexWidth = 50;
var iTexHeight = 50;
var oTextureCanvas = document.createElement("canvas");
oTextureCanvas.width = iTexWidth;
oTextureCanvas.height = iTexHeight;
oTextureCanvas.style.width = iTexWidth + "px";
oTextureCanvas.style.height = iTexHeight + "px";
var oTexCtx = oTextureCanvas.getContext("2d");
oTexCtx.drawImage(oMatImage, 0, 0, iTexWidth, iTexHeight);
oMatImage.canvas = oTextureCanvas;
}
var x1 = oPoint1.x;
var y1 = oPoint1.y;
var x2 = oPoint2.x;
var y2 = oPoint2.y;
var x3 = oPoint3.x;
var y3 = oPoint3.y;
// trig to calc the angle we need to rotate in order get our texturetriangle in place
var dx = x3 - x2;
var dy = y3 - y2;
var a = sqrt((dx*dx + dy*dy));
dx = x3 - x1;
dy = y3 - y1;
var b = sqrt((dx*dx + dy*dy));
dx = x2 - x1;
dy = y2 - y1;
var c = sqrt((dx*dx + dy*dy));
var aa = a*a, bb = b*b, cc = c*c;
var fCosB = (aa + cc - bb) / (2*a*c);
var fAngleB = acos(fCosB);
if (isNaN(fAngleB)) return;
var fCosC = (aa + bb - cc) / (2*a*b);
var fAngleC = acos(fCosC);
if (isNaN(fAngleC)) return;
if ((fAngleB + fAngleC) == 0) return;
var fSkewX = -(fDegRad90 - (fAngleB + fAngleC));
var fTriRotation = -(asin((y2 - y1) / c));
if (x2 > x1) { // rotate the other way around if triangle is flipped
fTriRotation = fDegRad180 - fTriRotation;
}
if (fSkewX == fDegRad90) fSkewX = fDegRad*89.99;
if (fSkewX == -fDegRad90) fSkewX = -fDegRad*89.99;
var fCosX = cos(fSkewX);
var fRotation = fDegRad45 + fSkewX * 0.5;
var fDiv = 1 / (sin(fRotation) * fSqrt2Div2);
var fScaleX = fCosX * fDiv;
var fScaleY = (sin(fSkewX) + 1) * fDiv;
// setup the clipping path, so only texture within the triangle is drawn.
var iClipX1 = x1 + iOffsetX;
var iClipY1 = y1 + iOffsetY;
var iClipX2 = x2 + iOffsetX;
var iClipY2 = y2 + iOffsetY;
var iClipX3 = x3 + iOffsetX;
var iClipY3 = y3 + iOffsetY;
// here we try to expand the clip path by 1 pixel to get rid of (some of the) gaps between the triangles
// we do this simply by moving the topmost point 1px up, the leftmost point 1px left, and so on.
// later, we also render the triangle itself 1 px too big
// drawbacks are that the contour of the object will appear a bit "pointy".
if (this._bExpandClipPath && false) {
if (iClipY1 < iClipY2 && iClipY1 < iClipY3)
iClipY1--;
else if (iClipY2 < iClipY1 && iClipY2 < iClipY3)
iClipY2--;
else if (iClipY3 < iClipY1 && iClipY3 < iClipY2)
iClipY3--;
if (iClipY1 > iClipY2 && iClipY1 > iClipY3)
iClipY1++;
else if (iClipY2 > iClipY1 && iClipY2 > iClipY3)
iClipY2++;
else if (iClipY3 > iClipY1 && iClipY3 > iClipY2)
iClipY3++;
if (iClipX1 < iClipX2 && iClipX1 < iClipX3)
iClipX1--;
else if (iClipX2 < iClipX1 && iClipX2 < iClipX3)
iClipX2--;
else if (iClipX3 < iClipX1 && iClipX3 < iClipX2)
iClipX3--;
if (iClipX1 > iClipX2 && iClipX1 > iClipX3)
iClipX1++;
else if (iClipX2 > iClipX1 && iClipX2 > iClipX3)
iClipX2++;
else if (iClipX3 > iClipX1 && iClipX3 > iClipX2)
iClipX3++;
}
oContext.save();
// do the clip path
oContext.beginPath();
oContext.moveTo(iClipX1, iClipY1);
oContext.lineTo(iClipX2, iClipY2);
oContext.lineTo(iClipX3, iClipY3);
oContext.closePath();
oContext.clip();
// setup the skew/rotation transformation
oContext.translate(x2 + iOffsetX, y2 + iOffsetY);
oContext.rotate(fRotation + fTriRotation);
oContext.scale(fScaleX, fScaleY);
oContext.rotate(-fDegRad45);
var fTriScaleX = c / 2; // 100 * 50;
var fTriScaleY = b / 2; // 100 * 50;
if (oMat.uv) {
// we are using UV coordinates for texturing
if (this._bCanTextureUV && oMat.facecanvas) {
var iTexRes = oMat.facecanvas.resolution;
// draw our texture
// there will be a small gap between the triangles. Drawing the texture at offset (-1,-1) gets rid of some of it.
oContext.drawImage(
oMat.facecanvas,
iIdx * iTexRes + iIdx*2+1, 1, iTexRes, iTexRes,
-1, -1,
fTriScaleX + 2,
fTriScaleY + 2
);
}
} else {
// no UV, just draw the same texture for all faces
oContext.drawImage(
oMatImage.canvas,
-1, -1,
fTriScaleX + 2,
fTriScaleY + 2
);
}
oContext.restore();
// if shading is turned on, render a semi-transparent black triangle on top.
// that means that a fully lit triangle will just be the raw texture, and the less lit a triangle is, the darker it gets.
// we could render semi-transparent white on top to make it brighter, but it doesn't look right, so we settle for that.
if (this._bTextureShading && fShade > 0) {
oContext.beginPath();
oContext.moveTo(iClipX1, iClipY1);
oContext.lineTo(iClipX2, iClipY2);
oContext.lineTo(iClipX3, iClipY3);
oContext.closePath();
oContext.fillStyle = "rgba(0,0,0," + (fShade*0.5) + ")";
oContext.fill();
}
}
// draw the mesh on the oContext canvas context, at offset [iOffsetX, iOffsetY]
Canvas3D.Mesh.prototype.draw = function(oContext, iOffsetX, iOffsetY) {
if (!this._bVisible) return;
var oScene = this._oScene;
var oCam = oScene.getActiveCamera();
var oAmbient = oScene.getAmbientLight()
// if shading is enabled, calculate the direction vectors to all light sources
var bLightDirty = false;
if (this._bShading && this._bFill) {
var aLights = oScene.getLights();
var aLightDirections = [];
for (var l=0;l<aLights.length;l++) {
// todo: this should be position relative to mesh
var oLightPos = aLights[l].getPosition();
var oLightDirection = oLightPos.unit();
aLightDirections.push(oLightDirection);
if (aLights[l].getDirty())
bLightDirty = true;
}
}
var aVertices = this._aGlobalVertices;
var aFaces = this._aFaces;
if (aVertices.length < 3 || aFaces.length < 1) {
// nothing to draw
return;
}
// let the camera transform all vertices and project them to 2D.
var aPoints2D = [];
var aTransVertices = [];
var v = aVertices.length;
do {
var oVertex = aVertices[v-1];
var oVec = oCam.transformPoint(oVertex);
aTransVertices[v-1] = oVec;
var oPoint2D = oCam.project(oVec);
aPoints2D[v-1] = oPoint2D;
} while (--v);
var aSortedFaces;
// if the faces are filled, we need to do z-sorting
if (this._bFill && this._bZSort) {
var f = aFaces.length;
do {
var oFace = aFaces[f-1];
oFace.transcenter = oCam.transformPoint(oFace.center);
} while (--f);
aSortedFaces = aFaces.sort(fncZSort);
// if not, just use the raw list
} else {
aSortedFaces = aFaces;
}
f = aSortedFaces.length;
if (f < 1) {
return;
}
// run through all faces
do {
var oFace = aSortedFaces[f-1];
var oPoint1 = aPoints2D[oFace.a];
var oPoint2 = aPoints2D[oFace.b];
var oPoint3 = aPoints2D[oFace.c];
var oNormal = oFace.normal;
var bDraw = false;
// do backface culling in screen space
if (this._bBackfaceCull) {
// screen space backface culling adapted from http://www.kirupa.com/developer/actionscript/backface_culling.htm
if (((oPoint3.y-oPoint1.y)/(oPoint3.x-oPoint1.x) - (oPoint2.y-oPoint1.y)/(oPoint2.x-oPoint1.x) <= 0) ^ (oPoint1.x <= oPoint3.x == oPoint1.x > oPoint2.x)){
bDraw = true;
}
} else {
bDraw = true;
}
if (oCam.clip(aTransVertices[oFace.a]) || oCam.clip(aTransVertices[oFace.b]) || oCam.clip(aTransVertices[oFace.c])) {
bDraw = false;
}
// if triangle is facing camera, draw it
if (bDraw) {
// get the material for this face
var oFaceMat = this._aMaterials[oFace.mat];
if (oFaceMat) {
oFaceColor = oFaceMat;
} else {
oFaceMat = this._oDefaultMaterial;
oFaceColor = this._oDefaultColor;
}
var bFaceTexture = this._bTexture && oFaceMat.t;
// save the original color
var oFaceOrgColor = {r:oFaceColor.r, g:oFaceColor.g, b:oFaceColor.b};
var fLight = 0;
var fShade = 1;
if (this._bFill) {
// setup ambient face color
if (!bFaceTexture) {
if (bLightDirty || this._bDirty) {
var oFaceColorAmb = {
r:(oAmbient.r / 255) * oFaceColor.r,
g:(oAmbient.g / 255) * oFaceColor.g,
b:(oAmbient.b / 255) * oFaceColor.b
};
}
}
// do lighting
if (this._bShading) {
if (bLightDirty || this._bDirty) {
for (var l=0;l<aLights.length;l++) {
var oLightPos = aLights[l].getPosition();
var oLightDir = new Canvas3D.Vec3(
oLightPos.x - oFace.center.x,
oLightPos.y - oFace.center.y,
oLightPos.z - oFace.center.z
).unit();
var fDot = -oLightDir.dot(oNormal);
// is the face facing the light source
if (fDot > 0) {
//fDot = Math.sqrt(fDot);
fLight = fDot * aLights[l].getIntensity();
fShade = fShade - fLight;
// lighten the face by the light intensity
if (!bFaceTexture) {
oFaceColorAmb = {
r: oFaceColorAmb.r + oFaceColor.r * fLight,
g: oFaceColorAmb.g + oFaceColor.g * fLight,
b: oFaceColorAmb.b + oFaceColor.b * fLight
};
}
}
}
oFaceColorAmb.r = Math.floor(oFaceColorAmb.r);
oFaceColorAmb.g = Math.floor(oFaceColorAmb.g);
oFaceColorAmb.b = Math.floor(oFaceColorAmb.b);
if (oFaceColorAmb.r < 0) oFaceColorAmb.r = 0;
if (oFaceColorAmb.g < 0) oFaceColorAmb.g = 0;
if (oFaceColorAmb.b < 0) oFaceColorAmb.b = 0;
if (oFaceColorAmb.r > 255) oFaceColorAmb.r = 255;
if (oFaceColorAmb.g > 255) oFaceColorAmb.g = 255;
if (oFaceColorAmb.b > 255) oFaceColorAmb.b = 255;
oFace.calccolor = oFaceColorAmb;
oFace.shade = fShade;
}
oFaceColorAmb = oFace.calccolor;
fShade = oFace.shade;
}
if (!bFaceTexture) {
oFaceColor = oFaceColorAmb;
}
}
oContext.beginPath();
oContext.moveTo(oPoint1.x + iOffsetX, oPoint1.y + iOffsetY);
oContext.lineTo(oPoint2.x + iOffsetX, oPoint2.y + iOffsetY)
oContext.lineTo(oPoint3.x + iOffsetX, oPoint3.y + iOffsetY)
oContext.closePath();
if (this._bFill) {
if (this._bCanTexture && this._bTexture && oFaceMat.image) {
this._drawTextureTriangle(oContext, oFaceMat, oPoint1, oPoint2, oPoint3, iOffsetX, iOffsetY, fShade, oNormal, oFace.texidx);
} else {
oContext.fillStyle = "rgb(" + oFaceColor.r + "," + oFaceColor.g + "," + oFaceColor.b + ")";
oContext.fill();
if (!this._bWire) {
oContext.lineWidth = 0.7;
oContext.strokeStyle = "rgb(" + oFaceColor.r + "," + oFaceColor.g + "," + oFaceColor.b + ")";
oContext.stroke();
}
}
}
if (this._bWire) {
oFaceOrgColor.r = Math.min(Math.max(Math.round(oFaceOrgColor.r),0),255);
oFaceOrgColor.g = Math.min(Math.max(Math.round(oFaceOrgColor.g),0),255);
oFaceOrgColor.b = Math.min(Math.max(Math.round(oFaceOrgColor.b),0),255);
oContext.lineWidth = 1;
oContext.strokeStyle = "rgb(" + oFaceOrgColor.r + "," + oFaceOrgColor.g + "," + oFaceOrgColor.b + ")";
oContext.stroke();
}
}
} while (--f);
this._bDirty = false;
}
Canvas3D.Mesh.prototype.setScene = function(oScene) {
if (this._oScene != oScene) {
this._oScene = oScene;
}
}
Canvas3D.Mesh.prototype.setLighting = function(bEnable) {
this._bShading = bEnable;
}
Canvas3D.Mesh.prototype.setBackfaceCull = function(bEnable) {
this._bBackfaceCull = bEnable;
}
Canvas3D.Mesh.prototype.setZSort = function(bEnable) {
this._bZSort = bEnable;
}
Canvas3D.Mesh.prototype.setFill = function(bEnable) {
this._bFill = bEnable;
}
Canvas3D.Mesh.prototype.setWire = function(bEnable) {
this._bWire = bEnable;
}
Canvas3D.Mesh.prototype.setTexture = function(bEnable) {
this._bTexture = bEnable;
}
Canvas3D.Mesh.prototype.setTextureShading = function(bEnable) {
this._bTextureShading = bEnable;
}
Canvas3D.Mesh.prototype._updateGlobalVertices = function() {
var oRot = this._oRotation;
var oPos = this._oPosition;
for (var i = 0; i < this._aVertices.length; i++) {
var oRotatedVertex = new Canvas3D.Vec3(
this._aVertices[i].x,
this._aVertices[i].y,
this._aVertices[i].z
);
if (oRot.x)
oRotatedVertex.rotateX(oRot.x);
if (oRot.y)
oRotatedVertex.rotateY(oRot.y);
if (oRot.z)
oRotatedVertex.rotateZ(oRot.z);
this._aGlobalVertices[i].x = oRotatedVertex.x * this._fScale + oPos.x;
this._aGlobalVertices[i].y = oRotatedVertex.y * this._fScale + oPos.y;
this._aGlobalVertices[i].z = oRotatedVertex.z * this._fScale + oPos.z;
}
this._recalcNormals();
}
Canvas3D.Mesh.prototype._recalcNormals = function() {
for (var f = 0; f < this._aFaces.length; f++) {
var oFace = this._aFaces[f];
var oPoint1 = this._aGlobalVertices[oFace.a];
var oPoint2 = this._aGlobalVertices[oFace.b];
var oPoint3 = this._aGlobalVertices[oFace.c];
var oCenter = new Canvas3D.Vec3(
(oPoint1.x + oPoint2.x + oPoint3.x) / 3,
(oPoint1.y + oPoint2.y + oPoint3.y) / 3,
(oPoint1.z + oPoint2.z + oPoint3.z) / 3
);
oFace.center = oCenter;
var oNormal = new Canvas3D.Vec3(
((oPoint1.y - oPoint2.y) * (oPoint1.z - oPoint3.z)) - ((oPoint1.z - oPoint2.z) * (oPoint1.y - oPoint3.y)),
((oPoint1.z - oPoint2.z) * (oPoint1.x - oPoint3.x)) - ((oPoint1.x - oPoint2.x) * (oPoint1.z - oPoint3.z)),
((oPoint1.x - oPoint2.x) * (oPoint1.y - oPoint3.y)) - ((oPoint1.y - oPoint2.y) * (oPoint1.x - oPoint3.x))
).unit();
oFace.normal = oNormal;
}
}
Canvas3D.Mesh.prototype.setPosition = function(oVec) {
if (oVec.x != this._oPosition.x || oVec.y != this._oPosition.y || oVec.z != this._oPosition.z) {
this._oPosition = oVec;
this._updateGlobalVertices();
this._bDirty = true;
}
}
Canvas3D.Mesh.prototype.setRotation = function(oVec) {
this._oRotation = oVec;
this._updateGlobalVertices();
this._bDirty = true;
}
Canvas3D.Mesh.prototype.getPosition = function(oVec) {
return this._oPosition;
}
Canvas3D.Mesh.prototype.setForcedZ = function(iZ) {
this._iForcedZ = iZ;
}
Canvas3D.Mesh.prototype.getForcedZ = function() {
return this._iForcedZ;
}
Canvas3D.Mesh.prototype.getHideWhenRotating = function() {
return this._bHideWhenRotating;
}
Canvas3D.Mesh.prototype.setHideWhenRotating = function(bEnable) {
this._bHideWhenRotating = bEnable;
}
Canvas3D.Mesh.prototype.getDirty = function() {
return this._bDirty;
}
Canvas3D.Mesh.prototype.hide = function() {
this._bVisible = false;
this._bDirty = true;
}
Canvas3D.Mesh.prototype.show = function() {
this._bVisible = true;
this._bDirty = true;
}
Canvas3D.Mesh.prototype.isVisible = function() {
return this._bVisible;
}
Canvas3D.Mesh.prototype.setScale = function(fScale) {
this._fScale = fScale;
this._bDirty = true;
this._updateGlobalVertices();
}
Canvas3D.Mesh.prototype.getScale = function() {
return this._fScale;
}

View file

@ -0,0 +1,290 @@
/*
* Javascript/Canvas Textured 3D Renderer v0.3.1
* Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com
* This software is free to use for non-commercial purposes. For anything else, please contact the author.
* This is a version modified by Stefano Gioffre'.
*/
Canvas3D.Scene = function(oContainer, iWidth, iHeight, bObjectCanvas) {
this._oContainer = oContainer;
this._iWidth = iWidth;
this._iHeight = iHeight;
var oCanvas = this._oDrawCanvas = document.createElement("canvas");
this._oDrawContext = oCanvas.getContext("2d");
Canvas3D.addEvent(oCanvas, "selectstart", function() { return(false); });
oCanvas.style.position = "absolute";
this._oDrawCanvas.width = iWidth;
this._oDrawCanvas.height = iHeight;
this._oDrawCanvas.style.width = iWidth + "px";
this._oDrawCanvas.style.height = iHeight + "px";
oContainer.appendChild(this._oDrawCanvas);
this._oActiveCamera = null;
this._aObjects = [];
this._bRunning = false;
// if true, each object will be rendered on its own canvas
this._bUseObjectCanvas = bObjectCanvas;
this._bMouseRotate = true;
this._bMouseRotateY = true;
this._bMouseRotateX = true;
this._oUpVector = new Canvas3D.Vec3(0,1,0);
this._oAmbientLight = {r:50,g:50,b:50};
this._bDrawLights = false;
this._aLights = [];
this._iMaxZ = 10000000;
var oInputOverlay = this._oInputOverlay = document.createElement("span");
oInputOverlay.style.width = iWidth + "px";
oInputOverlay.style.height = iHeight + "px";
oInputOverlay.style.zIndex = this._iMaxZ + 10000000;
oInputOverlay.style.position = "absolute";
this._oContainer.appendChild(oInputOverlay);
Canvas3D.addEvent(oInputOverlay, "selectstart", function() { return(false); });
};
Canvas3D.Scene.prototype.getInputLayer = function() { return(this._oInputOverlay); };
Canvas3D.Scene.prototype.setUpVector = function(oVec) { this._oUpVector = oVec; };
Canvas3D.Scene.prototype.getUpVector = function() { return(this._oUpVector); };
Canvas3D.Scene.prototype.getAmbientLight = function() { return(this._oAmbientLight); };
Canvas3D.Scene.prototype.zoomCamera = function(fZoom) { this.getActiveCamera().moveForward(fZoom); };
Canvas3D.Scene.prototype.getObjects = function() { return(this._aObjects); };
Canvas3D.Scene.prototype.addObject = function(obj) {
if (this._bUseObjectCanvas) {
var oObjectCanvas = document.createElement("canvas");
if (!oObjectCanvas.getContext) return;
var oObjectContext = oObjectCanvas.getContext("2d");
Canvas3D.addEvent(oObjectCanvas, "selectstart", function() { return(false); });
oObjectCanvas.width = this._iWidth;
oObjectCanvas.height = this._iHeight;
oObjectCanvas.style.width = this._iWidth + "px";
oObjectCanvas.style.height = this._iHeight + "px";
oObjectCanvas.style.position = "absolute";
this._oContainer.appendChild(oObjectCanvas);
this._aObjects.push({
"canvas": oObjectCanvas,
"context": oObjectContext,
"object": obj
});
} else {
this._aObjects.push({ "object": obj });
}
obj.setScene(this);
this.setDirty(true);
return(obj);
};
/*
Canvas3D.Scene.prototype.setWidth = function(iWidth) {
for (var iCnvsObj = 0; iCnvsObj < this._aObjects.length; iCnvsObj++) {
if (this._aObjects[iCnvsObj].hasOwnProperty("canvas")) {
this._aObjects[iCnvsObj].canvas.width = iWidth;
this._aObjects[iCnvsObj].canvas.style.width = iWidth + "px";
}
}
this._iWidth = iWidth;
this._oDrawCanvas.width = iWidth;
this._oDrawCanvas.style.width = iWidth + "px";
this._oInputOverlay.style.width = iWidth + "px";
};
Canvas3D.Scene.prototype.setHeight = function(iHeight) {
for (var iCnvsObj = 0; iCnvsObj < this._aObjects.length; iCnvsObj++) {
if (this._aObjects[iCnvsObj].hasOwnProperty("canvas")) {
this._aObjects[iCnvsObj].canvas.height = iHeight;
this._aObjects[iCnvsObj].canvas.style.height = iHeight + "px";
}
}
this._iHeight = iHeight;
this._oDrawCanvas.height = iHeight;
this._oDrawCanvas.style.height = iHeight + "px";
this._oInputOverlay.style.height = iHeight + "px";
};
*/
Canvas3D.Scene.prototype.setDimensions = function(iWidth, iHeight) {
for (var iCnvsObj = 0; iCnvsObj < this._aObjects.length; iCnvsObj++) {
if (this._aObjects[iCnvsObj].hasOwnProperty("canvas")) {
this._aObjects[iCnvsObj].canvas.width = iWidth;
this._aObjects[iCnvsObj].canvas.height = iHeight;
this._aObjects[iCnvsObj].canvas.style.width = iWidth + "px";
this._aObjects[iCnvsObj].canvas.style.height = iHeight + "px";
}
}
this._iWidth = iWidth;
this._iHeight = iHeight;
this._oDrawCanvas.height = iHeight;
this._oDrawCanvas.width = iWidth;
this._oDrawCanvas.style.width = iWidth + "px";
this._oDrawCanvas.style.height = iHeight + "px";
this._oInputOverlay.style.width = iWidth + "px";
this._oInputOverlay.style.height = iHeight + "px";
};
Canvas3D.Scene.prototype.removeObject = function(oObject) {
for (var i=0;i<this._aObjects.length;i++) {
if (this._aObjects[i].object === oObject) {
if (this._bUseObjectCanvas) {
this._oContainer.removeChild(this._aObjects[i].canvas);
}
this._aObjects.splice(i, 1);
}
}
};
Canvas3D.Scene.prototype.removeAllObjects = function() {
if (this._bUseObjectCanvas) {
for (var i=0;i<this._aObjects.length;i++) {
this._oContainer.removeChild(this._aObjects[i].canvas);
}
}
this._aObjects.splice(0);
};
Canvas3D.Scene.prototype.addLight = function(oLight) {
oLight.setScene(this);
return(this._aLights.push(oLight));
};
Canvas3D.Scene.prototype.getLights = function() { return(this._aLights); };
Canvas3D.Scene.prototype.clearObjects = function() { this._aObjects.splice(0); };
Canvas3D.Scene.prototype.setActiveCamera = function(oCam) { this._oActiveCamera = oCam; };
Canvas3D.Scene.prototype.getActiveCamera = function() { return(this._oActiveCamera); };
Canvas3D.Scene.prototype.begin = function() {
this._bRunning = true;
this.getActiveCamera().setDirty(true);
this.drawAll();
var me = this;
this._iInterval = setInterval(function() { me.drawAll(); }, 1000 / 30);
};
Canvas3D.Scene.prototype.end = function() {
this._bRunning = false;
clearInterval(this._iInterval);
};
Canvas3D.Scene.prototype.setDirty = function(bDirty) {
this._bDirty = bDirty;
};
Canvas3D.Scene.prototype.getDirty = function() { return(this._bDirty); };
Canvas3D.Scene.prototype.getWidth = function() { return(this._iWidth); };
Canvas3D.Scene.prototype.getHeight = function() { return(this._iHeight); };
Canvas3D.Scene.prototype.drawAll = function() {
if (!this._bRunning) return;
var oCam = this.getActiveCamera();
var iOffsetX = Math.floor(this._iWidth / 2);
var iOffsetY = Math.floor(this._iHeight / 2);
var aObjects = this._aObjects;
var bCamDirty = oCam.getDirty();
var iObjDrawn = 0;
var bObjDirty = false;
for (var c=0;c<aObjects.length;c++) {
if (aObjects[c].object.getDirty()) {
bObjDirty = true;
}
}
if (bCamDirty || this.getDirty() || bObjDirty) {
if (bCamDirty || this.getDirty()) {
this._oDrawContext.clearRect(0,0,this._iWidth,this._iHeight);
}
var aObjPos = [];
for (var c=0;c<aObjects.length;c++) {
if (aObjects[c].object.getSortPosition)
var oPos = aObjects[c].object.getSortPosition();
else
var oPos = aObjects[c].object.getPosition();
var oObjectPos = oCam.transformPoint(oPos);
aObjPos[c] = [aObjects[c], oObjectPos.z];
}
var aSortObj = aObjPos.sort(
function(a,b) {
return(b[1] - a[1]);
}
);
aObjects = aSortObj;
for (var c=0;c<aObjects.length;c++) {
var oObject = aObjects[c][0];
if (oObject.object.isVisible()) {
if (this._bUseObjectCanvas) {
if (bCamDirty || this.getDirty() || oObject.object.getDirty()) {
if (oObject.object.getForcedZ() > -1) {
oObject.canvas.style.zIndex = oObject.object.getForcedZ();
} else {
var oObjectPos = oCam.transformPoint(oObject.object.getPosition());
oObject.canvas.style.zIndex = 10000000 - Math.round(oObjectPos.z*100);
}
oObject.context.clearRect(0,0,this._iWidth,this._iHeight);
if (oObject.object.draw(oObject.context, iOffsetX, iOffsetY)) {
iObjDrawn++;
}
oObject.canvas.style.display = "block";
}
} else {
if (bCamDirty || this.getDirty()) {
if (oObject.object.draw(this._oDrawContext, iOffsetX, iOffsetY)) {
iObjDrawn++;
}
}
}
} else {
if (this._bUseObjectCanvas) {
oObject.canvas.style.display = "none";
}
}
}
}
if (this._bDrawLights) {
for (var c=0;c<this._aLights.length;c++) {
var oLight = this._aLights[c];
oLight.draw(this._oLightContext, iOffsetX, iOffsetY);
}
}
for (var c=0;c<this._aLights.length;c++) {
this._aLights[c].setDirty(false);
}
oCam.setDirty(false);
this.setDirty(false);
};

View file

@ -0,0 +1,105 @@
/*
* Javascript/Canvas Textured 3D Renderer v0.3
* Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com
* This software is free to use for non-commercial purposes. For anything else, please contact the author.
* This is a version modified by Stefano Gioffre'.
*/
Canvas3D.Vec3 = function(vx, vy, vz) {
this.x = vx;
this.y = vy;
this.z = vz;
}
Canvas3D.Vec3.prototype.set = function(vx, vy, vz) {
this.x = vx;
this.y = vy;
this.z = vz;
}
Canvas3D.Vec3.prototype.addVector = function(V) {
this.x += V.x;
this.y += V.y;
this.z += V.z;
return this;
}
Canvas3D.Vec3.prototype.multiply = function(fScalar) {
this.x *= fScalar;
this.y *= fScalar;
this.z *= fScalar;
return this;
}
Canvas3D.Vec3.prototype.subVector = function(V) {
this.x -= V.x;
this.y -= V.y;
this.z -= V.z;
return this;
}
Canvas3D.Vec3.prototype.returnAdd = function(V) {
return new Canvas3D.Vec3(this.x + V.x, this.y + V.y, this.z + V.z);
}
Canvas3D.Vec3.prototype.returnSub = function(V) {
return new Canvas3D.Vec3(this.x - V.x, this.y - V.y, this.z - V.z);
}
Canvas3D.Vec3.prototype.clone = function() {
return new Canvas3D.Vec3(this.x, this.y, this.z);
}
Canvas3D.Vec3.prototype.dot = function(V) {
return ((this.x * V.x) + (this.y * V.y) + (this.z * V.z));
}
Canvas3D.Vec3.prototype.cross = function(V) {
var vx = V.x;
var vy = V.y;
var vz = V.z;
return new Canvas3D.Vec3((this.y * vz) - (this.z * vy), (this.z * vx) - (this.x * vz), (this.x * vy) - (this.y * vx));
}
Canvas3D.Vec3.prototype.length = function() {
return Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z));
}
Canvas3D.Vec3.prototype.unit = function() {
var l = 1/Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z));
return new Canvas3D.Vec3(this.x * l, this.y * l, this.z * l);
}
Canvas3D.Vec3.prototype.rotateX = function(a) {
var ry = this.y;
var rz = this.z;
var c = Math.cos(a);
var s = Math.sin(a);
this.y = c * ry - s * rz;
this.z = s * ry + c * rz;
}
Canvas3D.Vec3.prototype.rotateY = function(a) {
var rx = this.x;
var rz = this.z;
var c = Math.cos(a);
var s = Math.sin(a);
this.x = c * rx - s * rz;
this.z = s * rx + c * rz;
}
Canvas3D.Vec3.prototype.rotateZ = function(a) {
var rx = this.x;
var ry = this.y;
var c = Math.cos(a);
var s = Math.sin(a);
this.x = c * rx - s * ry;
this.y = s * rx + c * ry;
}
Canvas3D.Vec3.prototype.dist = function(oVec) {
var x = oVec.x - this.x;
var y = oVec.y - this.y;
var z = oVec.z - this.z;
return Math.sqrt(x*x + y*y + z*z);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,32 @@
// retrieves a file via XMLHTTPRequest, calls fncCallback when done or fncError on error.
function XHR(strURL, fncCallback /*, argumentToPass1, argumentToPass2, etc. */) {
var oHTTP, argsArr = Array.prototype.slice.call(arguments, 2);
if (window.XMLHttpRequest) { oHTTP = new XMLHttpRequest(); }
else if (window.ActiveXObject) { oHTTP = new ActiveXObject("Microsoft.XMLHTTP"); }
if (oHTTP) {
if (fncCallback) {
if (typeof(oHTTP.onload) !== "undefined")
oHTTP.onload = function() {
fncCallback.apply(oHTTP, argsArr);
oHTTP = null;
};
else {
oHTTP.onreadystatechange = function() {
if (oHTTP.readyState === 4) {
fncCallback.apply(oHTTP, argsArr);
oHTTP = null;
}
};
}
}
oHTTP.open("GET", strURL, true);
oHTTP.setRequestHeader("Content-Type", "text/plain");
oHTTP.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
oHTTP.send(null);
}
}
function setAttribs() { for (var iAttr = 0; iAttr < arguments.length; iAttr++) { this[arguments[iAttr][0]] = arguments[iAttr][1]; } return(this); }
function setStyles() { for (var iPropr = 0; iPropr < arguments.length; iPropr++) { this.style[arguments[iPropr][0]] = arguments[iPropr][1]; } return(this); }

View file

@ -0,0 +1,339 @@
body {
background-color: white;
}
span.intLink, #chessClosePanel {
cursor: pointer;
}
a.chessCtrlBtn:link, span.chessCtrlBtn {
margin: 0 20px;
text-decoration: none;
-moz-user-select: none;
color: #000000;
cursor: default;
height: 21px;
padding: 0 8px;
font: 13px / 20px "Lucida Grande", sans-serif;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px;
background: rgb(235, 235, 235);
-moz-box-shadow:
0 1px 0 rgba(0, 0, 0, 0.15),
inset 0 1px 0 rgba(0, 0, 0, 0.2),
inset 0 0 1px rgba(0, 0, 0, 0.8),
inset 0 0 1px #000,
inset 0 10px 5px -5px rgba(255, 255, 255, 1),
inset 0 -20px 10px -10px rgba(255, 255, 255, 0.8),
inset 0 -13px rgba(0, 0, 0, 0.1);
-webkit-box-shadow:
0 1px 0 rgba(0, 0, 0, 0.15),
inset 0 1px 0 rgba(0, 0, 0, 0.2),
inset 0 0 1px rgba(0, 0, 0, 0.8),
inset 0 0 1px #000,
inset 0 10px 5px -5px rgba(255, 255, 255, 1),
inset 0 -20px 10px -10px rgba(255, 255, 255, 0.8),
inset 0 -13px rgba(0, 0, 0, 0.1);
box-shadow:
0 1px 0 rgba(0, 0, 0, 0.15),
inset 0 1px 0 rgba(0, 0, 0, 0.2),
inset 0 0 1px rgba(0, 0, 0, 0.8),
inset 0 0 1px #000,
inset 0 10px 5px -5px rgba(255, 255, 255, 1),
inset 0 -20px 10px -10px rgba(255, 255, 255, 0.8),
inset 0 -13px rgba(0, 0, 0, 0.1);
}
a.chessCtrlBtn:active:hover, span.chessCtrlBtn:active:hover {
background-color: #79A7D5;
-moz-box-shadow:
0 1px 0 rgba(0, 0, 0, 0.15),
inset 0 1px 0 rgba(0, 0, 0, 0.2),
inset 0 0 1px rgba(0, 0, 0, 0.8),
inset 0 0 1px #000,
inset 0 10px 5px -5px rgba(255, 255, 255, 0.4),
inset 0 -20px 10px -10px #A4D6F1,
inset 0 -13px #387CC0;
-webkit-box-shadow:
0 1px 0 rgba(0, 0, 0, 0.15),
inset 0 1px 0 rgba(0, 0, 0, 0.2),
inset 0 0 1px rgba(0, 0, 0, 0.8),
inset 0 0 1px #000,
inset 0 10px 5px -5px rgba(255, 255, 255, 0.4),
inset 0 -20px 10px -10px #A4D6F1,
inset 0 -13px #387CC0;
box-shadow:
0 1px 0 rgba(0, 0, 0, 0.15),
inset 0 1px 0 rgba(0, 0, 0, 0.2),
inset 0 0 1px rgba(0, 0, 0, 0.8),
inset 0 0 1px #000,
inset 0 10px 5px -5px rgba(255, 255, 255, 0.4),
inset 0 -20px 10px -10px #A4D6F1,
inset 0 -13px #387CC0;
}
div.chessFilmBox {
position: absolute;
left: 0;
top: 0;
border: 1px #000000 solid;
background-color: #ffffaa;
opacity: 0.5;
filter: alpha(opacity=50);
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
z-index: 10000000;
}
div.gnotify {
padding: 10px;
z-index: 10000001;
}
/** Normal Style Positions **/
div.gnotify { position: fixed; }
div.gnotify.top-left {
left: 0;
top: 0;
}
div.gnotify.top-right {
right: 0;
top: 0;
}
div.gnotify.bottom-left {
left: 0;
bottom: 0;
}
div.gnotify.bottom-right {
right: 0;
bottom: 0;
}
div.gnotify.center {
top: 0;
width: 50%;
left: 25%;
}
div.gnotify-message span.intLink, div.gnotify-message a, div.gnotify-message a:link, div.gnotify-message a:visited, div.gnotify-message a:hover {
font-weight: bold;
text-decoration: inherit;
color: inherit;
}
/** Cross Browser Styling **/
div.center div.gnotify-notification, div.center div.gnotify-closer {
margin-left: auto;
margin-right: auto;
}
div.gnotify div.gnotify-notification, div.gnotify div.gnotify-closer {
background-color: #000000;
color: #ffffff;
opacity: 0.85;
filter: alpha(opacity=85);
width: 235px;
padding: 10px;
margin-top: 5px;
margin-bottom: 5px;
font-family: Tahoma, Arial, Helvetica, sans-serif;
font-size: 12px;
text-align: left;
display: none;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
}
div.gnotify div.gnotify-notification { min-height: 40px; }
div.gnotify div.gnotify-notification div.header {
font-weight: bold;
font-size: 10px;
}
div.gnotify div.gnotify-notification div.close {
float: right;
font-weight: bold;
font-size: 12px;
cursor: pointer;
}
div.gnotify div.gnotify-closer {
height: 15px;
padding-top: 4px;
padding-bottom: 4px;
cursor: pointer;
font-size: 11px;
font-weight: bold;
text-align: center;
}
#chessboardsBox {
position: relative;
margin-left: auto;
margin-right: auto;
left: 0;
top: 0;
clear: both;
overflow: visible;
background-color: #87a1c0;
border: 1px #76808C solid;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
/*
-moz-box-shadow:
0 0 20px black,
20px 15px 30px yellow,
-20px 15px 30px lime,
-20px -15px 30px blue,
20px -15px 30px red;
-webkit-box-shadow:
0 0 20px black,
20px 15px 30px yellow,
-20px 15px 30px lime,
-20px -15px 30px blue,
20px -15px 30px red;
box-shadow:
0 0 20px black,
20px 15px 30px yellow,
-20px 15px 30px lime,
-20px -15px 30px blue,
20px -15px 30px red;
*/
}
#chessSizeHandle {
background-color: transparent;
color: #333333;
font-size: 24px;
line-height: 24px;
width: auto;
height: auto;
position: absolute;
right: -12px;
bottom: -12px;
float: right;
margin-top: auto;
cursor: se-resize;
}
#chess3DBox {
/**
* width: [DYNAMIC VALUE];
* height: [DYNAMIC VALUE];
*/
float: left;
}
#chess2DBox {
/**
* width: [DYNAMIC VALUE];
* height: [DYNAMIC VALUE];
*/
float: right;
}
#chessCtrlPanel, #chessCtrlPanel select, #chessCtrlPanel input[type=text] {
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
}
#chessCtrlPanel {
position: fixed;
bottom: 0;
right: 0;
width: 140px;
height: auto;
overflow: hidden;
background-color: #404d4f;
color: #ffffff;
font-size: 12px;
font-family: Tahoma, Arial, Verdana, Helvetica;
-moz-box-shadow: 3px 3px red, -8px -2px 8px #2d3637;
-webkit-box-shadow: 3px 3px red, -8px -2px 8px #2d3637;
box-shadow: 3px 3px red, -8px -2px 8px #2d3637;
}
#chessCtrlPanel form {
clear: both;
}
#chessClosePanel {
width: auto;
height: auto;
margin: 0 12px 1px auto;
float: right;
}
#chessCtrlPanel p {
margin-top: 6px;
text-align: center;
}
#chessInfo span.intLink {
color: #ffffff;
text-decoration: underline;
}
span.infoKey, span.infoVal {
color: #ffffff;
cursor: pointer;
}
span.infoKey {
font-weight: bold;
}
#chessMoves {
width: 92%;
height: 120px;
padding: 4px;
font-family: Courier New, Courier;
font-size: 12px;
}
#chessMoves, #chessAlgebraic {
border: 1px solid #778284;
background-color: transparent;
color: #ffffff;
-moz-box-shadow: inset 2px 2px 5px black, 2px 2px 5px black;
-webkit-box-shadow: inset 2px 2px 5px black, 2px 2px 5px black;
box-shadow: inset 2px 2px 5px black, 2px 2px 5px black;
}
#chessAlgebraic {
width: 90%;
margin-top: 3px;
font-size: 11px;
}
#chessInfo p {
text-align: center;
margin: 6px 4px 6px 4px;
}
#chessCurtain {
display:table;
width: 100%;
height: 100%;
position: fixed;
left: 0;
top: 0;
background-color: #000000;
opacity: 0.5;
filter: alpha(opacity=50);
z-index: 10000002;
}
#chessLoading {
display: table-cell;
vertical-align: middle;
text-align: center;
border: 1px #000000 solid;
color: #ffff00;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 849 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 843 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 815 B

Some files were not shown because too many files have changed in this diff Show more