summaryrefslogtreecommitdiffstats
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/channels.js685
-rw-r--r--js/cpu.js1203
-rw-r--r--js/cpuinfo.js503
-rw-r--r--js/globals.js15
-rw-r--r--js/jquery-1.2.6.min.js32
-rw-r--r--js/jquery.dimensions.min.js12
-rw-r--r--js/keyboard.js46
-rw-r--r--js/mappers.js1147
-rw-r--r--js/nametable.js43
-rw-r--r--js/nes.js250
-rw-r--r--js/palettetable.js163
-rw-r--r--js/papu.js789
-rw-r--r--js/ppu.js1503
-rw-r--r--js/rom.js227
-rw-r--r--js/roms.js777
-rw-r--r--js/tile.js157
-rw-r--r--js/utils.js26
17 files changed, 7578 insertions, 0 deletions
diff --git a/js/channels.js b/js/channels.js
new file mode 100644
index 0000000..afcb3d7
--- /dev/null
+++ b/js/channels.js
@@ -0,0 +1,685 @@
+function ChannelDM(papu) {
+ this.papu = papu;
+
+ this.MODE_NORMAL = 0;
+ this.MODE_LOOP = 1;
+ this.MODE_IRQ = 2;
+
+ this.isEnabled = null;
+ this.hasSample = null;
+ this.irqGenerated=false;
+
+ this.playMode = null;
+ this.dmaFrequency = null;
+ this.dmaCounter = null;
+ this.deltaCounter = null;
+ this.playStartAddress = null;
+ this.playAddress = null;
+ this.playLength = null;
+ this.playLengthCounter = null;
+ this.shiftCounter = null;
+ this.reg4012 = null;
+ this.reg4013 = null;
+ this.sample = null;
+ this.dacLsb = null;
+ this.data = null;
+
+ this.reset();
+}
+
+ChannelDM.prototype.clockDmc = function(){
+
+ // Only alter DAC value if the sample buffer has data:
+ if(this.hasSample){
+
+ if((this.data&1)===0){
+
+ // Decrement delta:
+ if(this.deltaCounter>0) {
+ this.deltaCounter--;
+ }
+
+ }else{
+
+ // Increment delta:
+ if(this.deltaCounter<63) {
+ this.deltaCounter++;
+ }
+
+ }
+
+ // Update sample value:
+ this.sample = this.isEnabled ? (this.deltaCounter<<1)+this.dacLsb : 0;
+
+ // Update shift register:
+ this.data>>=1;
+
+ }
+
+ this.dmaCounter--;
+ if(this.dmaCounter <= 0){
+
+ // No more sample bits.
+ this.hasSample = false;
+ this.endOfSample();
+ this.dmaCounter = 8;
+
+ }
+
+ if(this.irqGenerated){
+ this.papu.nes.cpu.requestIrq(this.papu.nes.cpu.IRQ_NORMAL);
+ }
+
+};
+
+ChannelDM.prototype.endOfSample = function(){
+
+
+ if(this.playLengthCounter===0 && this.playMode==this.MODE_LOOP){
+
+ // Start from beginning of sample:
+ this.playAddress = this.playStartAddress;
+ this.playLengthCounter = this.playLength;
+
+ }
+
+ if(this.playLengthCounter > 0){
+
+ // Fetch next sample:
+ this.nextSample();
+
+ if(this.playLengthCounter == 0){
+
+ // Last byte of sample fetched, generate IRQ:
+ if(this.playMode == this.MODE_IRQ){
+
+ // Generate IRQ:
+ this.irqGenerated = true;
+
+ }
+
+ }
+
+ }
+
+};
+
+ChannelDM.prototype.nextSample = function(){
+
+ // Fetch byte:
+ this.data = this.papu.nes.memMapper.load(this.playAddress);
+ this.papu.nes.cpu.haltCycles(4);
+
+ this.playLengthCounter--;
+ this.playAddress++;
+ if(this.playAddress>0xFFFF){
+ this.playAddress = 0x8000;
+ }
+
+ this.hasSample = true;
+
+};
+
+ChannelDM.prototype.writeReg = function(address, value){
+
+ if(address == 0x4010){
+
+ // Play mode, DMA Frequency
+ if((value>>6)==0){
+ this.playMode = this.MODE_NORMAL;
+ }else if(((value>>6)&1)==1){
+ this.playMode = this.MODE_LOOP;
+ }else if((value>>6)==2){
+ this.playMode = this.MODE_IRQ;
+ }
+
+ if((value&0x80)==0){
+ this.irqGenerated = false;
+ }
+
+ this.dmaFrequency = this.papu.getDmcFrequency(value&0xF);
+
+ }else if(address == 0x4011){
+
+ // Delta counter load register:
+ this.deltaCounter = (value>>1)&63;
+ this.dacLsb = value&1;
+ this.sample = ((this.deltaCounter<<1)+this.dacLsb); // update sample value
+
+ }else if(address == 0x4012){
+
+ // DMA address load register
+ this.playStartAddress = (value<<6)|0x0C000;
+ this.playAddress = this.playStartAddress;
+ this.reg4012 = value;
+
+ }else if(address == 0x4013){
+
+ // Length of play code
+ this.playLength = (value<<4)+1;
+ this.playLengthCounter = this.playLength;
+ this.reg4013 = value;
+
+ }else if(address == 0x4015){
+
+ // DMC/IRQ Status
+ if(((value>>4)&1)==0){
+ // Disable:
+ this.playLengthCounter = 0;
+ }else{
+ // Restart:
+ this.playAddress = this.playStartAddress;
+ this.playLengthCounter = this.playLength;
+ }
+ this.irqGenerated = false;
+ }
+
+};
+
+ChannelDM.prototype.setEnabled = function(value){
+
+ if((!this.isEnabled) && value){
+ this.playLengthCounter = this.playLength;
+ }
+ this.isEnabled = value;
+
+};
+
+ChannelDM.prototype.getLengthStatus = function(){
+ return ((this.playLengthCounter==0 || !this.isEnabled)?0:1);
+};
+
+ChannelDM.prototype.getIrqStatus = function(){
+ return (this.irqGenerated?1:0);
+};
+
+ChannelDM.prototype.reset = function(){
+
+ this.isEnabled = false;
+ this.irqGenerated = false;
+ this.playMode = this.MODE_NORMAL;
+ this.dmaFrequency = 0;
+ this.dmaCounter = 0;
+ this.deltaCounter = 0;
+ this.playStartAddress = 0;
+ this.playAddress = 0;
+ this.playLength = 0;
+ this.playLengthCounter = 0;
+ this.sample = 0;
+ this.dacLsb = 0;
+ this.shiftCounter = 0;
+ this.reg4012 = 0;
+ this.reg4013 = 0;
+ this.data = 0;
+
+};
+
+
+function ChannelNoise(papu) {
+ this.papu = papu;
+
+ this.isEnabled = null;
+ this.envDecayDisable = null;
+ this.envDecayLoopEnable = null;
+ this.lengthCounterEnable = null;
+ this.envReset = null;
+ this.shiftNow = null;
+
+ this.lengthCounter = null;
+ this.progTimerCount = null;
+ this.progTimerMax = null;
+ this.envDecayRate = null;
+ this.envDecayCounter = null;
+ this.envVolume = null;
+ this.masterVolume = null;
+ this.shiftReg = 1<<14;
+ this.randomBit = null;
+ this.randomMode = null;
+ this.sampleValue = null;
+ this.accValue=0;
+ this.accCount=1;
+ this.tmp = null;
+
+ this.reset();
+}
+
+ChannelNoise.prototype.reset = function(){
+ this.progTimerCount = 0;
+ this.progTimerMax = 0;
+ this.isEnabled = false;
+ this.lengthCounter = 0;
+ this.lengthCounterEnable = false;
+ this.envDecayDisable = false;
+ this.envDecayLoopEnable = false;
+ this.shiftNow = false;
+ this.envDecayRate = 0;
+ this.envDecayCounter = 0;
+ this.envVolume = 0;
+ this.masterVolume = 0;
+ this.shiftReg = 1;
+ this.randomBit = 0;
+ this.randomMode = 0;
+ this.sampleValue = 0;
+ this.tmp = 0;
+};
+
+ChannelNoise.prototype.clockLengthCounter = function(){
+ if(this.lengthCounterEnable && this.lengthCounter>0){
+ this.lengthCounter--;
+ if(this.lengthCounter == 0) this.updateSampleValue();
+ }
+};
+
+ChannelNoise.prototype.clockEnvDecay = function(){
+
+ if(this.envReset){
+
+ // Reset envelope:
+ this.envReset = false;
+ this.envDecayCounter = this.envDecayRate + 1;
+ this.envVolume = 0xF;
+ }else if(--this.envDecayCounter <= 0){
+
+ // Normal handling:
+ this.envDecayCounter = this.envDecayRate + 1;
+ if(this.envVolume>0){
+ this.envVolume--;
+ }else{
+ this.envVolume = this.envDecayLoopEnable ? 0xF : 0;
+ }
+ }
+ this.masterVolume = this.envDecayDisable ? this.envDecayRate : this.envVolume;
+ this.updateSampleValue();
+};
+
+ChannelNoise.prototype.updateSampleValue = function(){
+ if(this.isEnabled && this.lengthCounter>0){
+ this.sampleValue = this.randomBit * this.masterVolume;
+ }
+};
+
+ChannelNoise.prototype.writeReg = function(address, value){
+
+ if(address == 0x400C){
+
+ // Volume/Envelope decay:
+ this.envDecayDisable = ((value&0x10)!=0);
+ this.envDecayRate = value&0xF;
+ this.envDecayLoopEnable = ((value&0x20)!=0);
+ this.lengthCounterEnable = ((value&0x20)==0);
+ this.masterVolume = this.envDecayDisable?this.envDecayRate:this.envVolume;
+
+ }else if(address == 0x400E){
+
+ // Programmable timer:
+ this.progTimerMax = this.papu.getNoiseWaveLength(value&0xF);
+ this.randomMode = value>>7;
+
+ }else if(address == 0x400F){
+
+ // Length counter
+ this.lengthCounter = this.papu.getLengthMax(value&248);
+ this.envReset = true;
+
+ }
+
+ // Update:
+ //updateSampleValue();
+
+};
+
+ChannelNoise.prototype.setEnabled = function(value){
+ this.isEnabled = value;
+ if(!value) this.lengthCounter = 0;
+ this.updateSampleValue();
+};
+
+ChannelNoise.prototype.getLengthStatus = function(){
+ return ((this.lengthCounter==0 || !this.isEnabled)?0:1);
+};
+
+
+function ChannelSquare(papu, square1){
+ this.papu = papu;
+
+ this.dutyLookup = [
+ 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 0, 0, 0,
+ 1, 0, 0, 1, 1, 1, 1, 1
+ ];
+ this.impLookup = [
+ 1,-1, 0, 0, 0, 0, 0, 0,
+ 1, 0,-1, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0,-1, 0, 0, 0,
+ -1, 0, 1, 0, 0, 0, 0, 0
+ ];
+
+ this.sqr1 = square1;
+ this.isEnabled = null;
+ this.lengthCounterEnable = null;
+ this.sweepActive = null;
+ this.envDecayDisable = null;
+ this.envDecayLoopEnable = null;
+ this.envReset = null;
+ this.sweepCarry = null;
+ this.updateSweepPeriod = null;
+
+ this.progTimerCount = null;
+ this.progTimerMax = null;
+ this.lengthCounter = null;
+ this.squareCounter = null;
+ this.sweepCounter = null;
+ this.sweepCounterMax = null;
+ this.sweepMode = null;
+ this.sweepShiftAmount = null;
+ this.envDecayRate = null;
+ this.envDecayCounter = null;
+ this.envVolume = null;
+ this.masterVolume = null;
+ this.dutyMode = null;
+ this.sweepResult = null;
+ this.sampleValue = null;
+ this.vol = null;
+
+ this.reset();
+}
+
+
+ChannelSquare.prototype.reset = function() {
+ this.progTimerCount = 0;
+ this.progTimerMax = 0;
+ this.lengthCounter = 0;
+ this.squareCounter = 0;
+ this.sweepCounter = 0;
+ this.sweepCounterMax = 0;
+ this.sweepMode = 0;
+ this.sweepShiftAmount = 0;
+ this.envDecayRate = 0;
+ this.envDecayCounter = 0;
+ this.envVolume = 0;
+ this.masterVolume = 0;
+ this.dutyMode = 0;
+ this.vol = 0;
+
+ this.isEnabled = false;
+ this.lengthCounterEnable = false;
+ this.sweepActive = false;
+ this.sweepCarry = false;
+ this.envDecayDisable = false;
+ this.envDecayLoopEnable = false;
+};
+
+ChannelSquare.prototype.clockLengthCounter = function(){
+
+ if(this.lengthCounterEnable && this.lengthCounter>0){
+ this.lengthCounter--;
+ if(this.lengthCounter==0) this.updateSampleValue();
+ }
+
+};
+
+ChannelSquare.prototype.clockEnvDecay = function() {
+
+ if(this.envReset){
+
+ // Reset envelope:
+ this.envReset = false;
+ this.envDecayCounter = this.envDecayRate + 1;
+ this.envVolume = 0xF;
+
+ }else if((--this.envDecayCounter) <= 0){
+
+ // Normal handling:
+ this.envDecayCounter = this.envDecayRate + 1;
+ if(this.envVolume>0){
+ this.envVolume--;
+ }else{
+ this.envVolume = this.envDecayLoopEnable ? 0xF : 0;
+ }
+
+ }
+
+ this.masterVolume = this.envDecayDisable ? this.envDecayRate : this.envVolume;
+ this.updateSampleValue();
+
+};
+
+ChannelSquare.prototype.clockSweep = function() {
+
+ if(--this.sweepCounter<=0){
+
+ this.sweepCounter = this.sweepCounterMax + 1;
+ if(this.sweepActive && this.sweepShiftAmount>0 && this.progTimerMax>7){
+
+ // Calculate result from shifter:
+ this.sweepCarry = false;
+ if(this.sweepMode==0){
+ this.progTimerMax += (this.progTimerMax>>this.sweepShiftAmount);
+ if(this.progTimerMax > 4095){
+ this.progTimerMax = 4095;
+ this.sweepCarry = true;
+ }
+ }else{
+ this.progTimerMax = this.progTimerMax - ((this.progTimerMax>>this.sweepShiftAmount)-(this.sqr1?1:0));
+ }
+
+ }
+
+ }
+
+ if(this.updateSweepPeriod){
+ this.updateSweepPeriod = false;
+ this.sweepCounter = this.sweepCounterMax + 1;
+ }
+
+};
+
+ChannelSquare.prototype.updateSampleValue = function() {
+
+ if(this.isEnabled && this.lengthCounter>0 && this.progTimerMax>7){
+
+ if(this.sweepMode==0 && (this.progTimerMax + (this.progTimerMax>>this.sweepShiftAmount)) > 4095){
+ //if(this.sweepCarry){
+ this.sampleValue = 0;
+ }else{
+ this.sampleValue = this.masterVolume*this.dutyLookup[(this.dutyMode<<3)+this.squareCounter];
+ }
+ }else{
+ this.sampleValue = 0;
+ }
+
+};
+
+ChannelSquare.prototype.writeReg = function(address, value){
+
+ var addrAdd = (this.sqr1?0:4);
+ if(address == 0x4000+addrAdd){
+
+ // Volume/Envelope decay:
+ this.envDecayDisable = ((value&0x10)!=0);
+ this.envDecayRate = value & 0xF;
+ this.envDecayLoopEnable = ((value&0x20)!=0);
+ this.dutyMode = (value>>6)&0x3;
+ this.lengthCounterEnable = ((value&0x20)==0);
+ this.masterVolume = this.envDecayDisable?this.envDecayRate:this.envVolume;
+ this.updateSampleValue();
+
+ }else if(address == 0x4001+addrAdd){
+
+ // Sweep:
+ this.sweepActive = ((value&0x80)!=0);
+ this.sweepCounterMax = ((value>>4)&7);
+ this.sweepMode = (value>>3)&1;
+ this.sweepShiftAmount = value&7;
+ this.updateSweepPeriod = true;
+
+ }else if(address == 0x4002+addrAdd){
+
+ // Programmable timer:
+ this.progTimerMax &= 0x700;
+ this.progTimerMax |= value;
+
+ }else if(address == 0x4003+addrAdd){
+
+ // Programmable timer, length counter
+ this.progTimerMax &= 0xFF;
+ this.progTimerMax |= ((value&0x7)<<8);
+
+ if(this.isEnabled){
+ this.lengthCounter = this.papu.getLengthMax(value&0xF8);
+ }
+
+ this.envReset = true;
+
+ }
+
+};
+
+ChannelSquare.prototype.setEnabled = function(value){
+ this.isEnabled = value;
+ if(!value) this.lengthCounter = 0;
+ this.updateSampleValue();
+};
+
+ChannelSquare.prototype.getLengthStatus = function() {
+ return ((this.lengthCounter==0 || !this.isEnabled)?0:1);
+};
+
+
+function ChannelTriangle(papu) {
+ this.papu = papu;
+
+ this.isEnabled = null;
+ this.sampleCondition = null;
+ this.lengthCounterEnable = null;
+ this.lcHalt = null;
+ this.lcControl = null;
+
+ this.progTimerCount = null;
+ this.progTimerMax = null;
+ this.triangleCounter = null;
+ this.lengthCounter = null;
+ this.linearCounter = null;
+ this.lcLoadValue = null;
+ this.sampleValue = null;
+ this.tmp = null;
+
+ this.reset();
+}
+
+ChannelTriangle.prototype.reset = function(){
+ this.progTimerCount = 0;
+ this.progTimerMax = 0;
+ this.triangleCounter = 0;
+ this.isEnabled = false;
+ this.sampleCondition = false;
+ this.lengthCounter = 0;
+ this.lengthCounterEnable = false;
+ this.linearCounter = 0;
+ this.lcLoadValue = 0;
+ this.lcHalt = true;
+ this.lcControl = false;
+ this.tmp = 0;
+ this.sampleValue = 0xF;
+};
+
+ChannelTriangle.prototype.clockLengthCounter = function(){
+ if(this.lengthCounterEnable && this.lengthCounter>0){
+ this.lengthCounter--;
+ if(this.lengthCounter==0){
+ this.updateSampleCondition();
+ }
+ }
+};
+
+ChannelTriangle.prototype.clockLinearCounter = function(){
+ if(this.lcHalt){
+ // Load:
+ this.linearCounter = this.lcLoadValue;
+ this.updateSampleCondition();
+ }else if(this.linearCounter > 0){
+ // Decrement:
+ this.linearCounter--;
+ this.updateSampleCondition();
+ }
+ if(!this.lcControl){
+ // Clear halt flag:
+ this.lcHalt = false;
+ }
+};
+
+ChannelTriangle.prototype.getLengthStatus = function(){
+ return ((this.lengthCounter === 0 || !this.isEnabled)?0:1);
+};
+
+ChannelTriangle.prototype.readReg = function(address){
+ return 0;
+};
+
+ChannelTriangle.prototype.writeReg = function(address, value){
+
+ if(address == 0x4008){
+
+ // New values for linear counter:
+ this.lcControl = (value&0x80)!==0;
+ this.lcLoadValue = value&0x7F;
+
+ // Length counter enable:
+ this.lengthCounterEnable = !this.lcControl;
+
+ }else if(address == 0x400A){
+
+ // Programmable timer:
+ this.progTimerMax &= 0x700;
+ this.progTimerMax |= value;
+
+ }else if(address == 0x400B){
+
+ // Programmable timer, length counter
+ this.progTimerMax &= 0xFF;
+ this.progTimerMax |= ((value&0x07)<<8);
+ this.lengthCounter = this.papu.getLengthMax(value&0xF8);
+ this.lcHalt = true;
+
+ }
+
+ this.updateSampleCondition();
+
+};
+
+ChannelTriangle.prototype.clockProgrammableTimer = function(nCycles){
+
+ if(this.progTimerMax>0){
+ this.progTimerCount += nCycles;
+ while(this.progTimerMax > 0 && this.progTimerCount >= this.progTimerMax){
+ this.progTimerCount-=this.progTimerMax;
+ if(this.isEnabled && this.lengthCounter>0 && this.linearCounter>0){
+ this.clockTriangleGenerator();
+ }
+ }
+ }
+
+};
+
+ChannelTriangle.prototype.clockTriangleGenerator = function(){
+ this.triangleCounter++;
+ this.triangleCounter &= 0x1F;
+};
+
+ChannelTriangle.prototype.setEnabled = function(value){
+ this.isEnabled = value;
+ if(!value) this.lengthCounter = 0;
+ this.updateSampleCondition();
+};
+
+ChannelTriangle.prototype.updateSampleCondition = function(){
+ this.sampleCondition =
+ this.isEnabled &&
+ this.progTimerMax>7 &&
+ this.linearCounter>0 &&
+ this.lengthCounter>0
+ ;
+};
+
+
diff --git a/js/cpu.js b/js/cpu.js
new file mode 100644
index 0000000..df15c5c
--- /dev/null
+++ b/js/cpu.js
@@ -0,0 +1,1203 @@
+function CPU(nes) {
+ this.nes = nes;
+
+ this.mmap = null;
+
+ // CPU Registers:
+ this.REG_ACC = null;
+ this.REG_X = null;
+ this.REG_Y = null;
+ this.REG_STATUS = null;
+ this.REG_PC = null;
+ this.REG_SP = null;
+
+ this.REG_PC_NEW = null;
+
+ // Status flags:
+ this.F_CARRY = null;
+ this.F_ZERO = null;
+ this.F_INTERRUPT = null;
+ this.F_DECIMAL = null;
+ this.F_BRK = null;
+ this.F_NOTUSED = null;
+ this.F_OVERFLOW = null;
+ this.F_SIGN = null;
+
+ this.F_INTERRUPT = null;
+ this.F_BRK_NEW = null;
+
+ // IRQ Types
+ this.IRQ_NORMAL = 0;
+ this.IRQ_NMI = 1;
+ this.IRQ_RESET = 2;
+
+ // Interrupt notification:
+ this.irqRequested = null;
+ this.irqType = null;
+
+ // Op/Inst Data:
+ this.opdata = null;
+
+ // Misc vars:
+ this.cyclesToHalt = null;
+ this.crash = null;
+
+ this.palCnt = null;
+
+ this.init = function() {
+ this.opdata = CpuInfo.getOpData();
+ this.mmap = this.nes.memoryMapper;
+ // Reset crash flag:
+ this.crash = false;
+ // Set flags:
+ this.F_BRK_NEW = 1;
+ this.F_NOTUSED_NEW = 1;
+ this.F_INTERRUPT_NEW = 1;
+ this.irqRequested = false;
+ }
+
+ this.reset = function() {
+ this.REG_ACC = 0;
+ this.REG_X = 0;
+ this.REG_Y = 0;
+ this.irqRequested = false;
+ this.irqType = 0;
+ // Reset Stack pointer:
+ this.REG_SP = 0x01FF;
+ // Reset Program counter:
+ this.REG_PC = 0x8000-1;
+ this.REG_PC_NEW = 0x8000-1;
+ // Reset Status register:
+ this.REG_STATUS = 0x28;
+ this.setStatus(0x28);
+ // Reset crash flag:
+ this.crash = false;
+ // Set flags:
+ this.F_CARRY = 0;
+ this.F_DECIMAL = 0;
+ this.F_INTERRUPT = 1;
+ this.F_INTERRUPT_NEW = 1;
+ this.F_OVERFLOW = 0;
+ this.F_SIGN = 0;
+ this.F_ZERO = 1;
+
+ this.F_NOTUSED = 1;
+ this.F_BRK = 1;
+ this.F_BRK_NEW = 1;
+
+ this.cyclesToHalt = 0;
+
+ this.palCnt = 0;
+ }
+
+
+ // Emulates a single CPU instruction, returns the number of cycles
+ this.emulate = function() {
+ var temp;
+ var add;
+
+ // Check interrupts:
+ if(this.irqRequested){
+ temp =
+ (this.F_CARRY)|
+ ((this.F_ZERO===0?1:0)<<1)|
+ (this.F_INTERRUPT<<2)|
+ (this.F_DECIMAL<<3)|
+ (this.F_BRK<<4)|
+ (this.F_NOTUSED<<5)|
+ (this.F_OVERFLOW<<6)|
+ (this.F_SIGN<<7);
+
+ this.REG_PC_NEW = this.REG_PC;
+ this.F_INTERRUPT_NEW = this.F_INTERRUPT;
+ switch(this.irqType){
+ case 0: {
+ // Normal IRQ:
+ if(this.F_INTERRUPT!=0){
+ ////System.out.println("Interrupt was masked.");
+ break;
+ }
+ doIrq(temp);
+ ////System.out.println("Did normal IRQ. I="+this.F_INTERRUPT);
+ break;
+ }case 1:{
+ // NMI:
+ this.doNonMaskableInterrupt(temp);
+ break;
+
+ }case 2:{
+ // Reset:
+ this.doResetInterrupt();
+ break;
+ }
+ }
+
+ this.REG_PC = this.REG_PC_NEW;
+ this.F_INTERRUPT = this.F_INTERRUPT_NEW;
+ this.F_BRK = this.F_BRK_NEW;
+ this.irqRequested = false;
+ }
+
+ var opinf = this.opdata[this.mmap.load(this.REG_PC+1)];
+ var cycleCount = (opinf>>24);
+ var cycleAdd = 0;
+
+ // Find address mode:
+ var addrMode = (opinf>>8)&0xFF;
+
+ // Increment PC by number of op bytes:
+ var opaddr = this.REG_PC;
+ this.REG_PC+=((opinf>>16)&0xFF);
+
+ var addr=0;
+ switch(addrMode){
+ case 0:{
+ // Zero Page mode. Use the address given after the opcode,
+ // but without high byte.
+ addr = this.load(opaddr+2);
+ break;
+
+ }case 1:{
+ // Relative mode.
+ addr = this.load(opaddr+2);
+ if(addr<0x80){
+ addr += this.REG_PC;
+ }else{
+ addr += this.REG_PC-256;
+ }
+ break;
+ }case 2:{
+ // Ignore. Address is implied in instruction.
+ break;
+ }case 3:{
+ // Absolute mode. Use the two bytes following the opcode as
+ // an address.
+ addr = this.load16bit(opaddr+2);
+ break;
+ }case 4:{
+ // Accumulator mode. The address is in the accumulator
+ // register.
+ addr = this.REG_ACC;
+ break;
+ }case 5:{
+ // Immediate mode. The value is given after the opcode.
+ addr = this.REG_PC;
+ break;
+ }case 6:{
+ // Zero Page Indexed mode, X as index. Use the address given
+ // after the opcode, then add the
+ // X register to it to get the final address.
+ addr = (this.load(opaddr+2)+this.REG_X)&0xFF;
+ break;
+ }case 7:{
+ // Zero Page Indexed mode, Y as index. Use the address given
+ // after the opcode, then add the
+ // Y register to it to get the final address.
+ addr = (this.load(opaddr+2)+this.REG_Y)&0xFF;
+ break;
+ }case 8:{
+ // Absolute Indexed Mode, X as index. Same as zero page
+ // indexed, but with the high byte.
+ addr = this.load16bit(opaddr+2);
+ if((addr&0xFF00)!=((addr+this.REG_X)&0xFF00)){
+ cycleAdd = 1;
+ }
+ addr+=this.REG_X;
+ break;
+ }case 9:{
+ // Absolute Indexed Mode, Y as index. Same as zero page
+ // indexed, but with the high byte.
+ addr = this.load16bit(opaddr+2);
+ if((addr&0xFF00)!=((addr+this.REG_Y)&0xFF00)){
+ cycleAdd = 1;
+ }
+ addr+=this.REG_Y;
+ break;
+ }case 10:{
+ // Pre-indexed Indirect mode. Find the 16-bit address
+ // starting at the given location plus
+ // the current X register. The value is the contents of that
+ // address.
+ addr = this.load(opaddr+2);
+ if((addr&0xFF00)!=((addr+this.REG_X)&0xFF00)){
+ cycleAdd = 1;
+ }
+ addr+=this.REG_X;
+ addr&=0xFF;
+ addr = this.load16bit(addr);
+ break;
+ }case 11:{
+ // Post-indexed Indirect mode. Find the 16-bit address
+ // contained in the given location
+ // (and the one following). Add to that address the contents
+ // of the Y register. Fetch the value
+ // stored at that adress.
+ addr = this.load16bit(this.load(opaddr+2));
+ if((addr&0xFF00)!=((addr+this.REG_Y)&0xFF00)){
+ cycleAdd = 1;
+ }
+ addr+=this.REG_Y;
+ break;
+ }case 12:{
+ // Indirect Absolute mode. Find the 16-bit address contained
+ // at the given location.
+ addr = this.load16bit(opaddr+2);// Find op
+ if(addr < 0x1FFF){
+ addr = this.nes.cpuMem[addr] + (this.nes.cpuMem[(addr&0xFF00)|(((addr&0xFF)+1)&0xFF)]<<8);// Read from address given in op
+ }else{
+ addr = this.mmap.load(addr)+(this.mmap.load((addr&0xFF00)|(((addr&0xFF)+1)&0xFF))<<8);
+ }
+ break;
+
+ }
+
+ }
+ // Wrap around for addresses above 0xFFFF:
+ addr&=0xFFFF;
+
+ // ----------------------------------------------------------------------------------------------------
+ // Decode & execute instruction:
+ // ----------------------------------------------------------------------------------------------------
+
+ // This should be compiled to a jump table.
+ switch(opinf&0xFF){
+ case 0:{
+ // *******
+ // * ADC *
+ // *******
+
+ // Add with carry.
+ temp = this.REG_ACC + this.load(addr) + this.F_CARRY;
+ this.F_OVERFLOW = ((!(((this.REG_ACC ^ this.load(addr)) & 0x80)!=0) && (((this.REG_ACC ^ temp) & 0x80))!=0)?1:0);
+ this.F_CARRY = (temp>255?1:0);
+ this.F_SIGN = (temp>>7)&1;
+ this.F_ZERO = temp&0xFF;
+ this.REG_ACC = (temp&255);
+ cycleCount+=cycleAdd;
+ break;
+
+ }case 1:{
+ // *******
+ // * AND *
+ // *******
+
+ // AND memory with accumulator.
+ this.REG_ACC = this.REG_ACC & this.load(addr);
+ this.F_SIGN = (this.REG_ACC>>7)&1;
+ this.F_ZERO = this.REG_ACC;
+ //this.REG_ACC = temp;
+ if(addrMode!=11)cycleCount+=cycleAdd; // PostIdxInd = 11
+ break;
+ }case 2:{
+ // *******
+ // * ASL *
+ // *******
+
+ // Shift left one bit
+ if(addrMode == 4){ // ADDR_ACC = 4
+
+ this.F_CARRY = (this.REG_ACC>>7)&1;
+ this.REG_ACC = (this.REG_ACC<<1)&255;
+ this.F_SIGN = (this.REG_ACC>>7)&1;
+ this.F_ZERO = this.REG_ACC;
+
+ }else{
+
+ temp = this.load(addr);
+ this.F_CARRY = (temp>>7)&1;
+ temp = (temp<<1)&255;
+ this.F_SIGN = (temp>>7)&1;
+ this.F_ZERO = temp;
+ this.write(addr, temp);
+
+ }
+ break;
+
+ }case 3:{
+
+ // *******
+ // * BCC *
+ // *******
+
+ // Branch on carry clear
+ if(this.F_CARRY == 0){
+ cycleCount += ((opaddr&0xFF00)!=(addr&0xFF00)?2:1);
+ this.REG_PC = addr;
+ }
+ break;
+
+ }case 4:{
+
+ // *******
+ // * BCS *
+ // *******
+
+ // Branch on carry set
+ if(this.F_CARRY == 1){
+ cycleCount += ((opaddr&0xFF00)!=(addr&0xFF00)?2:1);
+ this.REG_PC = addr;
+ }
+ break;
+
+ }case 5:{
+
+ // *******
+ // * BEQ *
+ // *******
+
+ // Branch on zero
+ if(this.F_ZERO == 0){
+ cycleCount += ((opaddr&0xFF00)!=(addr&0xFF00)?2:1);
+ this.REG_PC = addr;
+ }
+ break;
+
+ }case 6:{
+
+ // *******
+ // * BIT *
+ // *******
+
+ temp = this.load(addr);
+ this.F_SIGN = (temp>>7)&1;
+ this.F_OVERFLOW = (temp>>6)&1;
+ temp &= this.REG_ACC;
+ this.F_ZERO = temp;
+ break;
+
+ }case 7:{
+
+ // *******
+ // * BMI *
+ // *******
+
+ // Branch on negative result
+ if(this.F_SIGN == 1){
+ cycleCount++;
+ this.REG_PC = addr;
+ }
+ break;
+
+ }case 8:{
+
+ // *******
+ // * BNE *
+ // *******
+
+ // Branch on not zero
+ if(this.F_ZERO != 0){
+ cycleCount += ((opaddr&0xFF00)!=(addr&0xFF00)?2:1);
+ this.REG_PC = addr;
+ }
+ break;
+
+ }case 9:{
+
+ // *******
+ // * BPL *
+ // *******
+
+ // Branch on positive result
+ if(this.F_SIGN == 0){
+ cycleCount += ((opaddr&0xFF00)!=(addr&0xFF00)?2:1);
+ this.REG_PC = addr;
+ }
+ break;
+
+ }case 10:{
+
+ // *******
+ // * BRK *
+ // *******
+
+ this.REG_PC+=2;
+ this.push((this.REG_PC>>8)&255);
+ this.push(this.REG_PC&255);
+ this.F_BRK = 1;
+
+ this.push(
+ (this.F_CARRY)|
+ ((this.F_ZERO==0?1:0)<<1)|
+ (this.F_INTERRUPT<<2)|
+ (this.F_DECIMAL<<3)|
+ (this.F_BRK<<4)|
+ (this.F_NOTUSED<<5)|
+ (this.F_OVERFLOW<<6)|
+ (this.F_SIGN<<7)
+ );
+
+ this.F_INTERRUPT = 1;
+ //this.REG_PC = load(0xFFFE) | (load(0xFFFF) << 8);
+ this.REG_PC = this.load16bit(0xFFFE);
+ this.REG_PC--;
+ break;
+
+ }case 11:{
+
+ // *******
+ // * BVC *
+ // *******
+
+ // Branch on overflow clear
+ if(this.F_OVERFLOW == 0){
+ cycleCount += ((opaddr&0xFF00)!=(addr&0xFF00)?2:1);
+ this.REG_PC = addr;
+ }
+ break;
+
+ }case 12:{
+
+ // *******
+ // * BVS *
+ // *******
+
+ // Branch on overflow set
+ if(this.F_OVERFLOW == 1){
+ cycleCount += ((opaddr&0xFF00)!=(addr&0xFF00)?2:1);
+ this.REG_PC = addr;
+ }
+ break;
+
+ }case 13:{
+
+ // *******
+ // * CLC *
+ // *******
+
+ // Clear carry flag
+ this.F_CARRY = 0;
+ break;
+
+ }case 14:{
+
+ // *******
+ // * CLD *
+ // *******
+
+ // Clear decimal flag
+ this.F_DECIMAL = 0;
+ break;
+
+ }case 15:{
+
+ // *******
+ // * CLI *
+ // *******
+
+ // Clear interrupt flag
+ this.F_INTERRUPT = 0;
+ break;
+
+ }case 16:{
+
+ // *******
+ // * CLV *
+ // *******
+
+ // Clear overflow flag
+ this.F_OVERFLOW = 0;
+ break;
+
+ }case 17:{
+
+ // *******
+ // * CMP *
+ // *******
+
+ // Compare memory and accumulator:
+ temp = this.REG_ACC - this.load(addr);
+ this.F_CARRY = (temp>=0?1:0);
+ this.F_SIGN = (temp>>7)&1;
+ this.F_ZERO = temp&0xFF;
+ cycleCount+=cycleAdd;
+ break;
+
+ }case 18:{
+
+ // *******
+ // * CPX *
+ // *******
+
+ // Compare memory and index X:
+ temp = this.REG_X - this.load(addr);
+ this.F_CARRY = (temp>=0?1:0);
+ this.F_SIGN = (temp>>7)&1;
+ this.F_ZERO = temp&0xFF;
+ break;
+
+ }case 19:{
+
+ // *******
+ // * CPY *
+ // *******
+
+ // Compare memory and index Y:
+ temp = this.REG_Y - this.load(addr);
+ this.F_CARRY = (temp>=0?1:0);
+ this.F_SIGN = (temp>>7)&1;
+ this.F_ZERO = temp&0xFF;
+ break;
+
+ }case 20:{
+
+ // *******
+ // * DEC *
+ // *******
+
+ // Decrement memory by one:
+ temp = (this.load(addr)-1)&0xFF;
+ this.F_SIGN = (temp>>7)&1;
+ this.F_ZERO = temp;
+ this.write(addr, temp);
+ break;
+
+ }case 21:{
+
+ // *******
+ // * DEX *
+ // *******
+
+ // Decrement index X by one:
+ this.REG_X = (this.REG_X-1)&0xFF;
+ this.F_SIGN = (this.REG_X>>7)&1;
+ this.F_ZERO = this.REG_X;
+ break;
+
+ }case 22:{
+
+ // *******
+ // * DEY *
+ // *******
+
+ // Decrement index Y by one:
+ this.REG_Y = (this.REG_Y-1)&0xFF;
+ this.F_SIGN = (this.REG_Y>>7)&1;
+ this.F_ZERO = this.REG_Y;
+ break;
+
+ }case 23:{
+
+ // *******
+ // * EOR *
+ // *******
+
+ // XOR Memory with accumulator, store in accumulator:
+ this.REG_ACC = (this.load(addr)^this.REG_ACC)&0xFF;
+ this.F_SIGN = (this.REG_ACC>>7)&1;
+ this.F_ZERO = this.REG_ACC;
+ cycleCount+=cycleAdd;
+ break;
+
+ }case 24:{
+
+ // *******
+ // * INC *
+ // *******
+
+ // Increment memory by one:
+ temp = (this.load(addr)+1)&0xFF;
+ this.F_SIGN = (temp>>7)&1;
+ this.F_ZERO = temp;
+ this.write(addr, temp&0xFF);
+ break;
+
+ }case 25:{
+
+ // *******
+ // * INX *
+ // *******
+
+ // Increment index X by one:
+ this.REG_X = (this.REG_X+1)&0xFF;
+ this.F_SIGN = (this.REG_X>>7)&1;
+ this.F_ZERO = this.REG_X;
+ break;
+
+ }case 26:{
+
+ // *******
+ // * INY *
+ // *******
+
+ // Increment index Y by one:
+ this.REG_Y++;
+ this.REG_Y &= 0xFF;
+ this.F_SIGN = (this.REG_Y>>7)&1;
+ this.F_ZERO = this.REG_Y;
+ break;
+
+ }case 27:{
+
+ // *******
+ // * JMP *
+ // *******
+
+ // Jump to new location:
+ this.REG_PC = addr-1;
+ break;
+
+ }case 28:{
+
+ // *******
+ // * JSR *
+ // *******
+
+ // Jump to new location, saving return address.
+ // Push return address on stack:
+ this.push((this.REG_PC>>8)&255);
+ this.push(this.REG_PC&255);
+ this.REG_PC = addr-1;
+ break;
+
+ }case 29:{
+
+ // *******
+ // * LDA *
+ // *******
+
+ // Load accumulator with memory:
+ this.REG_ACC = this.load(addr);
+ this.F_SIGN = (this.REG_ACC>>7)&1;
+ this.F_ZERO = this.REG_ACC;
+ cycleCount+=cycleAdd;
+ break;
+
+ }case 30:{
+
+ // *******
+ // * LDX *
+ // *******
+
+ // Load index X with memory:
+ this.REG_X = this.load(addr);
+ this.F_SIGN = (this.REG_X>>7)&1;
+ this.F_ZERO = this.REG_X;
+ cycleCount+=cycleAdd;
+ break;
+
+ }case 31:{
+
+ // *******
+ // * LDY *
+ // *******
+
+ // Load index Y with memory:
+ this.REG_Y = this.load(addr);
+ this.F_SIGN = (this.REG_Y>>7)&1;
+ this.F_ZERO = this.REG_Y;
+ cycleCount+=cycleAdd;
+ break;
+
+ }case 32:{
+
+ // *******
+ // * LSR *
+ // *******
+
+ // Shift right one bit:
+ if(addrMode == 4){ // ADDR_ACC
+
+ temp = (this.REG_ACC & 0xFF);
+ this.F_CARRY = temp&1;
+ temp >>= 1;
+ this.REG_ACC = temp;
+
+ }else{
+
+ temp = this.load(addr) & 0xFF;
+ this.F_CARRY = temp&1;
+ temp >>= 1;
+ this.write(addr, temp);
+
+ }
+ this.F_SIGN = 0;
+ this.F_ZERO = temp;
+ break;
+
+ }case 33:{
+
+ // *******
+ // * NOP *
+ // *******
+
+ // No OPeration.
+ // Ignore.
+ break;
+
+ }case 34:{
+
+ // *******
+ // * ORA *
+ // *******
+
+ // OR memory with accumulator, store in accumulator.
+ temp = (this.load(addr)|this.REG_ACC)&255;
+ this.F_SIGN = (temp>>7)&1;
+ this.F_ZERO = temp;
+ this.REG_ACC = temp;
+ if(addrMode!=11)cycleCount+=cycleAdd; // PostIdxInd = 11
+ break;
+
+ }case 35:{
+
+ // *******
+ // * PHA *
+ // *******
+
+ // Push accumulator on stack
+ this.push(this.REG_ACC);
+ break;
+
+ }case 36:{
+
+ // *******
+ // * PHP *
+ // *******
+
+ // Push processor status on stack
+ this.F_BRK = 1;
+ this.push(
+ (this.F_CARRY)|
+ ((this.F_ZERO==0?1:0)<<1)|
+ (this.F_INTERRUPT<<2)|
+ (this.F_DECIMAL<<3)|
+ (this.F_BRK<<4)|
+ (this.F_NOTUSED<<5)|
+ (this.F_OVERFLOW<<6)|
+ (this.F_SIGN<<7)
+ );
+ break;
+
+ }case 37:{
+
+ // *******
+ // * PLA *
+ // *******
+
+ // Pull accumulator from stack
+ this.REG_ACC = this.pull();
+ this.F_SIGN = (this.REG_ACC>>7)&1;
+ this.F_ZERO = this.REG_ACC;
+ break;
+
+ }case 38:{
+
+ // *******
+ // * PLP *
+ // *******
+
+ // Pull processor status from stack
+ temp = this.pull();
+ this.F_CARRY = (temp )&1;
+ this.F_ZERO = (((temp>>1)&1)==1)?0:1;
+ this.F_INTERRUPT = (temp>>2)&1;
+ this.F_DECIMAL = (temp>>3)&1;
+ this.F_BRK = (temp>>4)&1;
+ this.F_NOTUSED = (temp>>5)&1;
+ this.F_OVERFLOW = (temp>>6)&1;
+ this.F_SIGN = (temp>>7)&1;
+
+ this.F_NOTUSED = 1;
+ break;
+
+ }case 39:{
+
+ // *******
+ // * ROL *
+ // *******
+
+ // Rotate one bit left
+ if(addrMode == 4){ // ADDR_ACC = 4
+
+ temp = this.REG_ACC;
+ add = this.F_CARRY;
+ this.F_CARRY = (temp>>7)&1;
+ temp = ((temp<<1)&0xFF)+add;
+ this.REG_ACC = temp;
+
+ }else{
+
+ temp = this.load(addr);
+ add = this.F_CARRY;
+ this.F_CARRY = (temp>>7)&1;
+ temp = ((temp<<1)&0xFF)+add;
+ this.write(addr, temp);
+
+ }
+ this.F_SIGN = (temp>>7)&1;
+ this.F_ZERO = temp;
+ break;
+
+ }case 40:{
+
+ // *******
+ // * ROR *
+ // *******
+
+ // Rotate one bit right
+ if(addrMode == 4){ // ADDR_ACC = 4
+
+ add = this.F_CARRY<<7;
+ this.F_CARRY = this.REG_ACC&1;
+ temp = (this.REG_ACC>>1)+add;
+ this.REG_ACC = temp;
+
+ }else{
+
+ temp = this.load(addr);
+ add = this.F_CARRY<<7;
+ this.F_CARRY = temp&1;
+ temp = (temp>>1)+add;
+ this.write(addr, temp);
+
+ }
+ this.F_SIGN = (temp>>7)&1;
+ this.F_ZERO = temp;
+ break;
+
+ }case 41:{
+
+ // *******
+ // * RTI *
+ // *******
+
+ // Return from interrupt. Pull status and PC from stack.
+
+ temp = this.pull();
+ this.F_CARRY = (temp )&1;
+ this.F_ZERO = ((temp>>1)&1)==0?1:0;
+ this.F_INTERRUPT = (temp>>2)&1;
+ this.F_DECIMAL = (temp>>3)&1;
+ this.F_BRK = (temp>>4)&1;
+ this.F_NOTUSED = (temp>>5)&1;
+ this.F_OVERFLOW = (temp>>6)&1;
+ this.F_SIGN = (temp>>7)&1;
+
+ this.REG_PC = this.pull();
+ this.REG_PC += (this.pull()<<8);
+ if(this.REG_PC==0xFFFF){
+ return;
+ }
+ this.REG_PC--;
+ this.F_NOTUSED = 1;
+ break;
+
+ }case 42:{
+
+ // *******
+ // * RTS *
+ // *******
+
+ // Return from subroutine. Pull PC from stack.
+
+ this.REG_PC = this.pull();
+ this.REG_PC += (this.pull()<<8);
+
+ if(this.REG_PC==0xFFFF){
+ return; // return from NSF play routine:
+ }
+ break;
+
+ }case 43:{
+
+ // *******
+ // * SBC *
+ // *******
+
+ temp = this.REG_ACC-this.load(addr)-(1-this.F_CARRY);
+ this.F_SIGN = (temp>>7)&1;
+ this.F_ZERO = temp&0xFF;
+ this.F_OVERFLOW = ((((this.REG_ACC^temp)&0x80)!=0 && ((this.REG_ACC^this.load(addr))&0x80)!=0)?1:0);
+ this.F_CARRY = (temp<0?0:1);
+ this.REG_ACC = (temp&0xFF);
+ if(addrMode!=11)cycleCount+=cycleAdd; // PostIdxInd = 11
+ break;
+
+ }case 44:{
+
+ // *******
+ // * SEC *
+ // *******
+
+ // Set carry flag
+ this.F_CARRY = 1;
+ break;
+
+ }case 45:{
+
+ // *******
+ // * SED *
+ // *******
+
+ // Set decimal mode
+ this.F_DECIMAL = 1;
+ break;
+
+ }case 46:{
+
+ // *******
+ // * SEI *
+ // *******
+
+ // Set interrupt disable status
+ this.F_INTERRUPT = 1;
+ break;
+
+ }case 47:{
+
+ // *******
+ // * STA *
+ // *******
+
+ // Store accumulator in memory
+ this.write(addr, this.REG_ACC);
+ break;
+
+ }case 48:{
+
+ // *******
+ // * STX *
+ // *******
+
+ // Store index X in memory
+ this.write(addr, this.REG_X);
+ break;
+
+ }case 49:{
+
+ // *******
+ // * STY *
+ // *******
+
+ // Store index Y in memory:
+ this.write(addr, this.REG_Y);
+ break;
+
+ }case 50:{
+
+ // *******
+ // * TAX *
+ // *******
+
+ // Transfer accumulator to index X:
+ this.REG_X = this.REG_ACC;
+ this.F_SIGN = (this.REG_ACC>>7)&1;
+ this.F_ZERO = this.REG_ACC;
+ break;
+
+ }case 51:{
+
+ // *******
+ // * TAY *
+ // *******
+
+ // Transfer accumulator to index Y:
+ this.REG_Y = this.REG_ACC;
+ this.F_SIGN = (this.REG_ACC>>7)&1;
+ this.F_ZERO = this.REG_ACC;
+ break;
+
+ }case 52:{
+
+ // *******
+ // * TSX *
+ // *******
+
+ // Transfer stack pointer to index X:
+ this.REG_X = (this.REG_SP-0x0100);
+ this.F_SIGN = (this.REG_SP>>7)&1;
+ this.F_ZERO = this.REG_X;
+ break;
+
+ }case 53:{
+
+ // *******
+ // * TXA *
+ // *******
+
+ // Transfer index X to accumulator:
+ this.REG_ACC = this.REG_X;
+ this.F_SIGN = (this.REG_X>>7)&1;
+ this.F_ZERO = this.REG_X;
+ break;
+
+ }case 54:{
+
+ // *******
+ // * TXS *
+ // *******
+
+ // Transfer index X to stack pointer:
+ this.REG_SP = (this.REG_X+0x0100);
+ this.stackWrap();
+ break;
+
+ }case 55:{
+
+ // *******
+ // * TYA *
+ // *******
+
+ // Transfer index Y to accumulator:
+ this.REG_ACC = this.REG_Y;
+ this.F_SIGN = (this.REG_Y>>7)&1;
+ this.F_ZERO = this.REG_Y;
+ break;
+
+ }default:{
+
+ // *******
+ // * ??? *
+ // *******
+
+ this.nes.stop();
+ this.nes.crashMessage = "Game crashed, invalid opcode at address $"+opaddr.toString(16);
+ break;
+
+ }
+
+ }// end of switch
+
+ // ----------------------------------------------------------------------------------------------------
+ // ----------------------------------------------------------------------------------------------------
+
+ /* This isn't set anywhere
+ if(Globals.palEmulation){
+ this.palCnt++;
+ if(this.palCnt==5){
+ this.palCnt=0;
+ cycleCount++;
+ }
+ }*/
+
+ return cycleCount;
+
+ }
+
+ this.load = function(addr){
+ return addr<0x2000 ? this.nes.cpuMem[addr&0x7FF] : this.mmap.load(addr);
+ }
+
+ this.load16bit = function(addr){
+ return addr<0x1FFF ?
+ this.nes.cpuMem[addr&0x7FF] | (this.nes.cpuMem[(addr+1)&0x7FF]<<8)
+ :
+ this.mmap.load(addr) | (this.mmap.load(addr+1)<<8)
+ ;
+ }
+
+ this.write = function(addr, val){
+ if(addr < 0x2000){
+ this.nes.cpuMem[addr&0x7FF] = val;
+ }else{
+ this.mmap.write(addr,val);
+ }
+ }
+
+ this.requestIrq = function(type){
+ if(this.irqRequested){
+ if(type == this.IRQ_NORMAL){
+ return;
+ }
+ ////System.out.println("too fast irqs. type="+type);
+ }
+ this.irqRequested = true;
+ this.irqType = type;
+ }
+
+ this.push = function(value){
+ this.mmap.write(this.REG_SP, value);
+ this.REG_SP--;
+ this.REG_SP = 0x0100 | (this.REG_SP&0xFF);
+ }
+
+ this.stackWrap = function(){
+ this.REG_SP = 0x0100 | (this.REG_SP&0xFF);
+ }
+
+ this.pull = function(){
+ this.REG_SP++;
+ this.REG_SP = 0x0100 | (this.REG_SP&0xFF);
+ return this.mmap.load(this.REG_SP);
+ }
+
+ this.pageCrossed = function(addr1, addr2){
+ return ((addr1&0xFF00)!=(addr2&0xFF00));
+ }
+
+ this.haltCycles = function(cycles){
+ this.cyclesToHalt += cycles;
+ }
+
+ this.doNonMaskableInterrupt = function(status){
+
+ if((this.mmap.load(0x2000)&128)!=0){ // Check whether VBlank Interrupts are enabled
+
+ this.REG_PC_NEW++;
+ this.push((this.REG_PC_NEW>>8)&0xFF);
+ this.push(this.REG_PC_NEW&0xFF);
+ //this.F_INTERRUPT_NEW = 1;
+ this.push(status);
+
+ this.REG_PC_NEW = this.mmap.load(0xFFFA) | (this.mmap.load(0xFFFB) << 8);
+ this.REG_PC_NEW--;
+
+ }
+
+
+ }
+
+ this.doResetInterrupt = function(){
+ this.REG_PC_NEW = this.mmap.load(0xFFFC) | (this.mmap.load(0xFFFD) << 8);
+ this.REG_PC_NEW--;
+ }
+
+ this.doIrq = function(status){
+ this.REG_PC_NEW++;
+ this.push((this.REG_PC_NEW>>8)&0xFF);
+ this.push(this.REG_PC_NEW&0xFF);
+ this.push(status);
+ this.F_INTERRUPT_NEW = 1;
+ this.F_BRK_NEW = 0;
+
+ this.REG_PC_NEW = this.mmap.load(0xFFFE) | (this.mmap.load(0xFFFF) << 8);
+ this.REG_PC_NEW--;
+ }
+
+ this.getStatus = function(){
+ return (this.F_CARRY)
+ |(this.F_ZERO<<1)
+ |(this.F_INTERRUPT<<2)
+ |(this.F_DECIMAL<<3)
+ |(this.F_BRK<<4)
+ |(this.F_NOTUSED<<5)
+ |(this.F_OVERFLOW<<6)
+ |(this.F_SIGN<<7);
+ }
+
+ this.setStatus = function(st){
+ this.F_CARRY = (st )&1;
+ this.F_ZERO = (st>>1)&1;
+ this.F_INTERRUPT = (st>>2)&1;
+ this.F_DECIMAL = (st>>3)&1;
+ this.F_BRK = (st>>4)&1;
+ this.F_NOTUSED = (st>>5)&1;
+ this.F_OVERFLOW = (st>>6)&1;
+ this.F_SIGN = (st>>7)&1;
+ }
+
+ this.setCrashed = function(value){
+ this.crash = value;
+ }
+
+ this.setMapper = function(mapper){
+ this.mmap = mapper;
+ }
+
+ this.destroy = function(){
+ this.nes = null;
+ this.mmap = null;
+ }
+
+
+} \ No newline at end of file
diff --git a/js/cpuinfo.js b/js/cpuinfo.js
new file mode 100644
index 0000000..f70f4d0
--- /dev/null
+++ b/js/cpuinfo.js
@@ -0,0 +1,503 @@
+// Holds info on the cpu. Mostly constants that are placed here
+// to keep the CPU code clean.
+var CpuInfo = {
+ INS_ADC: 0,
+ INS_AND: 1,
+ INS_ASL: 2,
+
+ INS_BCC: 3,
+ INS_BCS: 4,
+ INS_BEQ: 5,
+ INS_BIT: 6,
+ INS_BMI: 7,
+ INS_BNE: 8,
+ INS_BPL: 9,
+ INS_BRK: 10,
+ INS_BVC: 11,
+ INS_BVS: 12,
+
+ INS_CLC: 13,
+ INS_CLD: 14,
+ INS_CLI: 15,
+ INS_CLV: 16,
+ INS_CMP: 17,
+ INS_CPX: 18,
+ INS_CPY: 19,
+
+ INS_DEC: 20,
+ INS_DEX: 21,
+ INS_DEY: 22,
+
+ INS_EOR: 23,
+
+ INS_INC: 24,
+ INS_INX: 25,
+ INS_INY: 26,
+
+ INS_JMP: 27,
+ INS_JSR: 28,
+
+ INS_LDA: 29,
+ INS_LDX: 30,
+ INS_LDY: 31,
+ INS_LSR: 32,
+
+ INS_NOP: 33,
+
+ INS_ORA: 34,
+
+ INS_PHA: 35,
+ INS_PHP: 36,
+ INS_PLA: 37,
+ INS_PLP: 38,
+
+ INS_ROL: 39,
+ INS_ROR: 40,
+ INS_RTI: 41,
+ INS_RTS: 42,
+
+ INS_SBC: 43,
+ INS_SEC: 44,
+ INS_SED: 45,
+ INS_SEI: 46,
+ INS_STA: 47,
+ INS_STX: 48,
+ INS_STY: 49,
+
+ INS_TAX: 50,
+ INS_TAY: 51,
+ INS_TSX: 52,
+ INS_TXA: 53,
+ INS_TXS: 54,
+ INS_TYA: 55,
+
+ INS_DUMMY: 56, // dummy instruction used for 'halting' the processor some cycles
+
+ // -------------------------------- //
+
+
+ // Addressing modes:
+ ADDR_ZP : 0,
+ ADDR_REL : 1,
+ ADDR_IMP : 2,
+ ADDR_ABS : 3,
+ ADDR_ACC : 4,
+ ADDR_IMM : 5,
+ ADDR_ZPX : 6,
+ ADDR_ZPY : 7,
+ ADDR_ABSX : 8,
+ ADDR_ABSY : 9,
+ ADDR_PREIDXIND : 10,
+ ADDR_POSTIDXIND: 11,
+ ADDR_INDABS : 12,
+
+ getOpData: function() {
+ if (this.opdata==null) this.initOpData();
+ return this.opdata;
+ },
+
+ getInstNames: function() {
+ if (this.instname==null) this.initInstNames();
+ return this.instname;
+ },
+
+ getInstName: function(inst) {
+ if (this.instname==null) this.initInstNames();
+ if (inst < this.instname.length)
+ return this.instname[inst];
+ return "???";
+ },
+
+ getAddressModeNames: function() {
+ if (this.addrDesc==null) this.initAddrDesc();
+ return this.addrDesc
+ },
+
+ getAddressModeName: function(addrMode) {
+ if (this.addrDesc==null) this.initAddrDesc();
+ if (addrMode>=0 && addrMode < this.addrDesc.length)
+ return this.addrDesc[addrMode];
+ return "???";
+ },
+
+ initOpData: function() {
+ this.opdata = new Array(256);
+ // Set all to invalid instruction (to detect crashes):
+ for(var i=0;i<256;i++) this.opdata[i]=0xFF;
+
+ // Now fill in all valid opcodes:
+
+ // ADC:
+ this.setOp(this.INS_ADC,0x69,this.ADDR_IMM,2,2);
+ this.setOp(this.INS_ADC,0x65,this.ADDR_ZP,2,3);
+ this.setOp(this.INS_ADC,0x75,this.ADDR_ZPX,2,4);
+ this.setOp(this.INS_ADC,0x6D,this.ADDR_ABS,3,4);
+ this.setOp(this.INS_ADC,0x7D,this.ADDR_ABSX,3,4);
+ this.setOp(this.INS_ADC,0x79,this.ADDR_ABSY,3,4);
+ this.setOp(this.INS_ADC,0x61,this.ADDR_PREIDXIND,2,6);
+ this.setOp(this.INS_ADC,0x71,this.ADDR_POSTIDXIND,2,5);
+
+ // AND:
+ this.setOp(this.INS_AND,0x29,this.ADDR_IMM,2,2);
+ this.setOp(this.INS_AND,0x25,this.ADDR_ZP,2,3);
+ this.setOp(this.INS_AND,0x35,this.ADDR_ZPX,2,4);
+ this.setOp(this.INS_AND,0x2D,this.ADDR_ABS,3,4);
+ this.setOp(this.INS_AND,0x3D,this.ADDR_ABSX,3,4);
+ this.setOp(this.INS_AND,0x39,this.ADDR_ABSY,3,4);
+ this.setOp(this.INS_AND,0x21,this.ADDR_PREIDXIND,2,6);
+ this.setOp(this.INS_AND,0x31,this.ADDR_POSTIDXIND,2,5);
+
+ // ASL:
+ this.setOp(this.INS_ASL,0x0A,this.ADDR_ACC,1,2);
+ this.setOp(this.INS_ASL,0x06,this.ADDR_ZP,2,5);
+ this.setOp(this.INS_ASL,0x16,this.ADDR_ZPX,2,6);
+ this.setOp(this.INS_ASL,0x0E,this.ADDR_ABS,3,6);
+ this.setOp(this.INS_ASL,0x1E,this.ADDR_ABSX,3,7);
+
+ // BCC:
+ this.setOp(this.INS_BCC,0x90,this.ADDR_REL,2,2);
+
+ // BCS:
+ this.setOp(this.INS_BCS,0xB0,this.ADDR_REL,2,2);
+
+ // BEQ:
+ this.setOp(this.INS_BEQ,0xF0,this.ADDR_REL,2,2);
+
+ // BIT:
+ this.setOp(this.INS_BIT,0x24,this.ADDR_ZP,2,3);
+ this.setOp(this.INS_BIT,0x2C,this.ADDR_ABS,3,4);
+
+ // BMI:
+ this.setOp(this.INS_BMI,0x30,this.ADDR_REL,2,2);
+
+ // BNE:
+ this.setOp(this.INS_BNE,0xD0,this.ADDR_REL,2,2);
+
+ // BPL:
+ this.setOp(this.INS_BPL,0x10,this.ADDR_REL,2,2);
+
+ // BRK:
+ this.setOp(this.INS_BRK,0x00,this.ADDR_IMP,1,7);
+
+ // BVC:
+ this.setOp(this.INS_BVC,0x50,this.ADDR_REL,2,2);
+
+ // BVS:
+ this.setOp(this.INS_BVS,0x70,this.ADDR_REL,2,2);
+
+ // CLC:
+ this.setOp(this.INS_CLC,0x18,this.ADDR_IMP,1,2);
+
+ // CLD:
+ this.setOp(this.INS_CLD,0xD8,this.ADDR_IMP,1,2);
+
+ // CLI:
+ this.setOp(this.INS_CLI,0x58,this.ADDR_IMP,1,2);
+
+ // CLV:
+ this.setOp(this.INS_CLV,0xB8,this.ADDR_IMP,1,2);
+
+ // CMP:
+ this.setOp(this.INS_CMP,0xC9,this.ADDR_IMM,2,2);
+ this.setOp(this.INS_CMP,0xC5,this.ADDR_ZP,2,3);
+ this.setOp(this.INS_CMP,0xD5,this.ADDR_ZPX,2,4);
+ this.setOp(this.INS_CMP,0xCD,this.ADDR_ABS,3,4);
+ this.setOp(this.INS_CMP,0xDD,this.ADDR_ABSX,3,4);
+ this.setOp(this.INS_CMP,0xD9,this.ADDR_ABSY,3,4);
+ this.setOp(this.INS_CMP,0xC1,this.ADDR_PREIDXIND,2,6);
+ this.setOp(this.INS_CMP,0xD1,this.ADDR_POSTIDXIND,2,5);
+
+ // CPX:
+ this.setOp(this.INS_CPX,0xE0,this.ADDR_IMM,2,2);
+ this.setOp(this.INS_CPX,0xE4,this.ADDR_ZP,2,3);
+ this.setOp(this.INS_CPX,0xEC,this.ADDR_ABS,3,4);
+
+ // CPY:
+ this.setOp(this.INS_CPY,0xC0,this.ADDR_IMM,2,2);
+ this.setOp(this.INS_CPY,0xC4,this.ADDR_ZP,2,3);
+ this.setOp(this.INS_CPY,0xCC,this.ADDR_ABS,3,4);
+
+ // DEC:
+ this.setOp(this.INS_DEC,0xC6,this.ADDR_ZP,2,5);
+ this.setOp(this.INS_DEC,0xD6,this.ADDR_ZPX,2,6);
+ this.setOp(this.INS_DEC,0xCE,this.ADDR_ABS,3,6);
+ this.setOp(this.INS_DEC,0xDE,this.ADDR_ABSX,3,7);
+
+ // DEX:
+ this.setOp(this.INS_DEX,0xCA,this.ADDR_IMP,1,2);
+
+ // DEY:
+ this.setOp(this.INS_DEY,0x88,this.ADDR_IMP,1,2);
+
+ // EOR:
+ this.setOp(this.INS_EOR,0x49,this.ADDR_IMM,2,2);
+ this.setOp(this.INS_EOR,0x45,this.ADDR_ZP,2,3);
+ this.setOp(this.INS_EOR,0x55,this.ADDR_ZPX,2,4);
+ this.setOp(this.INS_EOR,0x4D,this.ADDR_ABS,3,4);
+ this.setOp(this.INS_EOR,0x5D,this.ADDR_ABSX,3,4);
+ this.setOp(this.INS_EOR,0x59,this.ADDR_ABSY,3,4);
+ this.setOp(this.INS_EOR,0x41,this.ADDR_PREIDXIND,2,6);
+ this.setOp(this.INS_EOR,0x51,this.ADDR_POSTIDXIND,2,5);
+
+ // INC:
+ this.setOp(this.INS_INC,0xE6,this.ADDR_ZP,2,5);
+ this.setOp(this.INS_INC,0xF6,this.ADDR_ZPX,2,6);
+ this.setOp(this.INS_INC,0xEE,this.ADDR_ABS,3,6);
+ this.setOp(this.INS_INC,0xFE,this.ADDR_ABSX,3,7);
+
+ // INX:
+ this.setOp(this.INS_INX,0xE8,this.ADDR_IMP,1,2);
+
+ // INY:
+ this.setOp(this.INS_INY,0xC8,this.ADDR_IMP,1,2);
+
+ // JMP:
+ this.setOp(this.INS_JMP,0x4C,this.ADDR_ABS,3,3);
+ this.setOp(this.INS_JMP,0x6C,this.ADDR_INDABS,3,5);
+
+ // JSR:
+ this.setOp(this.INS_JSR,0x20,this.ADDR_ABS,3,6);
+
+ // LDA:
+ this.setOp(this.INS_LDA,0xA9,this.ADDR_IMM,2,2);
+ this.setOp(this.INS_LDA,0xA5,this.ADDR_ZP,2,3);
+ this.setOp(this.INS_LDA,0xB5,this.ADDR_ZPX,2,4);
+ this.setOp(this.INS_LDA,0xAD,this.ADDR_ABS,3,4);
+ this.setOp(this.INS_LDA,0xBD,this.ADDR_ABSX,3,4);
+ this.setOp(this.INS_LDA,0xB9,this.ADDR_ABSY,3,4);
+ this.setOp(this.INS_LDA,0xA1,this.ADDR_PREIDXIND,2,6);
+ this.setOp(this.INS_LDA,0xB1,this.ADDR_POSTIDXIND,2,5);
+
+
+ // LDX:
+ this.setOp(this.INS_LDX,0xA2,this.ADDR_IMM,2,2);
+ this.setOp(this.INS_LDX,0xA6,this.ADDR_ZP,2,3);
+ this.setOp(this.INS_LDX,0xB6,this.ADDR_ZPY,2,4);
+ this.setOp(this.INS_LDX,0xAE,this.ADDR_ABS,3,4);
+ this.setOp(this.INS_LDX,0xBE,this.ADDR_ABSY,3,4);
+
+ // LDY:
+ this.setOp(this.INS_LDY,0xA0,this.ADDR_IMM,2,2);
+ this.setOp(this.INS_LDY,0xA4,this.ADDR_ZP,2,3);
+ this.setOp(this.INS_LDY,0xB4,this.ADDR_ZPX,2,4);
+ this.setOp(this.INS_LDY,0xAC,this.ADDR_ABS,3,4);
+ this.setOp(this.INS_LDY,0xBC,this.ADDR_ABSX,3,4);
+
+ // LSR:
+ this.setOp(this.INS_LSR,0x4A,this.ADDR_ACC,1,2);
+ this.setOp(this.INS_LSR,0x46,this.ADDR_ZP,2,5);
+ this.setOp(this.INS_LSR,0x56,this.ADDR_ZPX,2,6);
+ this.setOp(this.INS_LSR,0x4E,this.ADDR_ABS,3,6);
+ this.setOp(this.INS_LSR,0x5E,this.ADDR_ABSX,3,7);
+
+ // NOP:
+ this.setOp(this.INS_NOP,0xEA,this.ADDR_IMP,1,2);
+
+ // ORA:
+ this.setOp(this.INS_ORA,0x09,this.ADDR_IMM,2,2);
+ this.setOp(this.INS_ORA,0x05,this.ADDR_ZP,2,3);
+ this.setOp(this.INS_ORA,0x15,this.ADDR_ZPX,2,4);
+ this.setOp(this.INS_ORA,0x0D,this.ADDR_ABS,3,4);
+ this.setOp(this.INS_ORA,0x1D,this.ADDR_ABSX,3,4);
+ this.setOp(this.INS_ORA,0x19,this.ADDR_ABSY,3,4);
+ this.setOp(this.INS_ORA,0x01,this.ADDR_PREIDXIND,2,6);
+ this.setOp(this.INS_ORA,0x11,this.ADDR_POSTIDXIND,2,5);
+
+ // PHA:
+ this.setOp(this.INS_PHA,0x48,this.ADDR_IMP,1,3);
+
+ // PHP:
+ this.setOp(this.INS_PHP,0x08,this.ADDR_IMP,1,3);
+
+ // PLA:
+ this.setOp(this.INS_PLA,0x68,this.ADDR_IMP,1,4);
+
+ // PLP:
+ this.setOp(this.INS_PLP,0x28,this.ADDR_IMP,1,4);
+
+ // ROL:
+ this.setOp(this.INS_ROL,0x2A,this.ADDR_ACC,1,2);
+ this.setOp(this.INS_ROL,0x26,this.ADDR_ZP,2,5);
+ this.setOp(this.INS_ROL,0x36,this.ADDR_ZPX,2,6);
+ this.setOp(this.INS_ROL,0x2E,this.ADDR_ABS,3,6);
+ this.setOp(this.INS_ROL,0x3E,this.ADDR_ABSX,3,7);
+
+ // ROR:
+ this.setOp(this.INS_ROR,0x6A,this.ADDR_ACC,1,2);
+ this.setOp(this.INS_ROR,0x66,this.ADDR_ZP,2,5);
+ this.setOp(this.INS_ROR,0x76,this.ADDR_ZPX,2,6);
+ this.setOp(this.INS_ROR,0x6E,this.ADDR_ABS,3,6);
+ this.setOp(this.INS_ROR,0x7E,this.ADDR_ABSX,3,7);
+
+ // RTI:
+ this.setOp(this.INS_RTI,0x40,this.ADDR_IMP,1,6);
+
+ // RTS:
+ this.setOp(this.INS_RTS,0x60,this.ADDR_IMP,1,6);
+
+ // SBC:
+ this.setOp(this.INS_SBC,0xE9,this.ADDR_IMM,2,2);
+ this.setOp(this.INS_SBC,0xE5,this.ADDR_ZP,2,3);
+ this.setOp(this.INS_SBC,0xF5,this.ADDR_ZPX,2,4);
+ this.setOp(this.INS_SBC,0xED,this.ADDR_ABS,3,4);
+ this.setOp(this.INS_SBC,0xFD,this.ADDR_ABSX,3,4);
+ this.setOp(this.INS_SBC,0xF9,this.ADDR_ABSY,3,4);
+ this.setOp(this.INS_SBC,0xE1,this.ADDR_PREIDXIND,2,6);
+ this.setOp(this.INS_SBC,0xF1,this.ADDR_POSTIDXIND,2,5);
+
+ // SEC:
+ this.setOp(this.INS_SEC,0x38,this.ADDR_IMP,1,2);
+
+ // SED:
+ this.setOp(this.INS_SED,0xF8,this.ADDR_IMP,1,2);
+
+ // SEI:
+ this.setOp(this.INS_SEI,0x78,this.ADDR_IMP,1,2);
+
+ // STA:
+ this.setOp(this.INS_STA,0x85,this.ADDR_ZP,2,3);
+ this.setOp(this.INS_STA,0x95,this.ADDR_ZPX,2,4);
+ this.setOp(this.INS_STA,0x8D,this.ADDR_ABS,3,4);
+ this.setOp(this.INS_STA,0x9D,this.ADDR_ABSX,3,5);
+ this.setOp(this.INS_STA,0x99,this.ADDR_ABSY,3,5);
+ this.setOp(this.INS_STA,0x81,this.ADDR_PREIDXIND,2,6);
+ this.setOp(this.INS_STA,0x91,this.ADDR_POSTIDXIND,2,6);
+
+ // STX:
+ this.setOp(this.INS_STX,0x86,this.ADDR_ZP,2,3);
+ this.setOp(this.INS_STX,0x96,this.ADDR_ZPY,2,4);
+ this.setOp(this.INS_STX,0x8E,this.ADDR_ABS,3,4);
+
+ // STY:
+ this.setOp(this.INS_STY,0x84,this.ADDR_ZP,2,3);
+ this.setOp(this.INS_STY,0x94,this.ADDR_ZPX,2,4);
+ this.setOp(this.INS_STY,0x8C,this.ADDR_ABS,3,4);
+
+ // TAX:
+ this.setOp(this.INS_TAX,0xAA,this.ADDR_IMP,1,2);
+
+ // TAY:
+ this.setOp(this.INS_TAY,0xA8,this.ADDR_IMP,1,2);
+
+ // TSX:
+ this.setOp(this.INS_TSX,0xBA,this.ADDR_IMP,1,2);
+
+ // TXA:
+ this.setOp(this.INS_TXA,0x8A,this.ADDR_IMP,1,2);
+
+ // TXS:
+ this.setOp(this.INS_TXS,0x9A,this.ADDR_IMP,1,2);
+
+ // TYA:
+ this.setOp(this.INS_TYA,0x98,this.ADDR_IMP,1,2);
+
+ this.cycTable = new Array(
+ /*0x00*/ 7,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6,
+ /*0x10*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,
+ /*0x20*/ 6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6,
+ /*0x30*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,
+ /*0x40*/ 6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6,
+ /*0x50*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,
+ /*0x60*/ 6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6,
+ /*0x70*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,
+ /*0x80*/ 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,
+ /*0x90*/ 2,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5,
+ /*0xA0*/ 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,
+ /*0xB0*/ 2,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4,
+ /*0xC0*/ 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,
+ /*0xD0*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,
+ /*0xE0*/ 2,6,3,8,3,3,5,5,2,2,2,2,4,4,6,6,
+ /*0xF0*/ 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7
+ );
+ },
+
+ setOp: function(inst, op, addr, size, cycles){
+ this.opdata[op] =
+ ((inst &0xFF) )|
+ ((addr &0xFF)<< 8)|
+ ((size &0xFF)<<16)|
+ ((cycles&0xFF)<<24);
+ },
+
+ initInstNames: function() {
+ this.instname = new Array(56);
+
+ // Instruction Names:
+ this.instname[ 0] = "ADC";
+ this.instname[ 1] = "AND";
+ this.instname[ 2] = "ASL";
+ this.instname[ 3] = "BCC";
+ this.instname[ 4] = "BCS";
+ this.instname[ 5] = "BEQ";
+ this.instname[ 6] = "BIT";
+ this.instname[ 7] = "BMI";
+ this.instname[ 8] = "BNE";
+ this.instname[ 9] = "BPL";
+ this.instname[10] = "BRK";
+ this.instname[11] = "BVC";
+ this.instname[12] = "BVS";
+ this.instname[13] = "CLC";
+ this.instname[14] = "CLD";
+ this.instname[15] = "CLI";
+ this.instname[16] = "CLV";
+ this.instname[17] = "CMP";
+ this.instname[18] = "CPX";
+ this.instname[19] = "CPY";
+ this.instname[20] = "DEC";
+ this.instname[21] = "DEX";
+ this.instname[22] = "DEY";
+ this.instname[23] = "EOR";
+ this.instname[24] = "INC";
+ this.instname[25] = "INX";
+ this.instname[26] = "INY";
+ this.instname[27] = "JMP";
+ this.instname[28] = "JSR";
+ this.instname[29] = "LDA";
+ this.instname[30] = "LDX";
+ this.instname[31] = "LDY";
+ this.instname[32] = "LSR";
+ this.instname[33] = "NOP";
+ this.instname[34] = "ORA";
+ this.instname[35] = "PHA";
+ this.instname[36] = "PHP";
+ this.instname[37] = "PLA";
+ this.instname[38] = "PLP";
+ this.instname[39] = "ROL";
+ this.instname[40] = "ROR";
+ this.instname[41] = "RTI";
+ this.instname[42] = "RTS";
+ this.instname[43] = "SBC";
+ this.instname[44] = "SEC";
+ this.instname[45] = "SED";
+ this.instname[46] = "SEI";
+ this.instname[47] = "STA";
+ this.instname[48] = "STX";
+ this.instname[49] = "STY";
+ this.instname[50] = "TAX";
+ this.instname[51] = "TAY";
+ this.instname[52] = "TSX";
+ this.instname[53] = "TXA";
+ this.instname[54] = "TXS";
+ this.instname[55] = "TYA";
+
+ },
+
+ initAddrDesc: function() {
+ this.addrDesc = new Array(
+ "Zero Page ",
+ "Relative ",
+ "Implied ",
+ "Absolute ",
+ "Accumulator ",
+ "Immediate ",
+ "Zero Page,X ",
+ "Zero Page,Y ",
+ "Absolute,X ",
+ "Absolute,Y ",
+ "Preindexed Indirect ",
+ "Postindexed Indirect",
+ "Indirect Absolute "
+ );
+
+ }
+} \ No newline at end of file
diff --git a/js/globals.js b/js/globals.js
new file mode 100644
index 0000000..897e755
--- /dev/null
+++ b/js/globals.js
@@ -0,0 +1,15 @@
+var Globals = {
+ CPU_FREQ_NTSC: 1789772.5,//1789772.72727272d;
+ CPU_FREQ_PAL: 1773447.4,
+
+ preferredFrameRate: 60,
+ frameTime: null, // Microsecs per frame
+ memoryFlushValue: 0xFF, // What value to flush memory with on power-up
+ nes: null,
+ fpsInterval: 500, // Time between updating FPS in ms
+ showDisplay: true,
+
+ emulateSound: false,
+ sampleRate: 44100, // Sound sample rate in hz
+}
+Globals.frameTime = 1000/Globals.preferredFrameRate;
diff --git a/js/jquery-1.2.6.min.js b/js/jquery-1.2.6.min.js
new file mode 100644
index 0000000..82b98e1
--- /dev/null
+++ b/js/jquery-1.2.6.min.js
@@ -0,0 +1,32 @@
+/*
+ * jQuery 1.2.6 - New Wave Javascript
+ *
+ * Copyright (c) 2008 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $
+ * $Rev: 5685 $
+ */
+(function(){var _jQuery=window.jQuery,_$=window.$;var jQuery=window.jQuery=window.$=function(selector,context){return new jQuery.fn.init(selector,context);};var quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,isSimple=/^.[^:#\[\.]*$/,undefined;jQuery.fn=jQuery.prototype={init:function(selector,context){selector=selector||document;if(selector.nodeType){this[0]=selector;this.length=1;return this;}if(typeof selector=="string"){var match=quickExpr.exec(selector);if(match&&(match[1]||!context)){if(match[1])selector=jQuery.clean([match[1]],context);else{var elem=document.getElementById(match[3]);if(elem){if(elem.id!=match[3])return jQuery().find(selector);return jQuery(elem);}selector=[];}}else
+return jQuery(context).find(selector);}else if(jQuery.isFunction(selector))return jQuery(document)[jQuery.fn.ready?"ready":"load"](selector);return this.setArray(jQuery.makeArray(selector));},jquery:"1.2.6",size:function(){return this.length;},length:0,get:function(num){return num==undefined?jQuery.makeArray(this):this[num];},pushStack:function(elems){var ret=jQuery(elems);ret.prevObject=this;return ret;},setArray:function(elems){this.length=0;Array.prototype.push.apply(this,elems);return this;},each:function(callback,args){return jQuery.each(this,callback,args);},index:function(elem){var ret=-1;return jQuery.inArray(elem&&elem.jquery?elem[0]:elem,this);},attr:function(name,value,type){var options=name;if(name.constructor==String)if(value===undefined)return this[0]&&jQuery[type||"attr"](this[0],name);else{options={};options[name]=value;}return this.each(function(i){for(name in options)jQuery.attr(type?this.style:this,name,jQuery.prop(this,options[name],type,i,name));});},css:function(key,value){if((key=='width'||key=='height')&&parseFloat(value)<0)value=undefined;return this.attr(key,value,"curCSS");},text:function(text){if(typeof text!="object"&&text!=null)return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(text));var ret="";jQuery.each(text||this,function(){jQuery.each(this.childNodes,function(){if(this.nodeType!=8)ret+=this.nodeType!=1?this.nodeValue:jQuery.fn.text([this]);});});return ret;},wrapAll:function(html){if(this[0])jQuery(html,this[0].ownerDocument).clone().insertBefore(this[0]).map(function(){var elem=this;while(elem.firstChild)elem=elem.firstChild;return elem;}).append(this);return this;},wrapInner:function(html){return this.each(function(){jQuery(this).contents().wrapAll(html);});},wrap:function(html){return this.each(function(){jQuery(this).wrapAll(html);});},append:function(){return this.domManip(arguments,true,false,function(elem){if(this.nodeType==1)this.appendChild(elem);});},prepend:function(){return this.domManip(arguments,true,true,function(elem){if(this.nodeType==1)this.insertBefore(elem,this.firstChild);});},before:function(){return this.domManip(arguments,false,false,function(elem){this.parentNode.insertBefore(elem,this);});},after:function(){return this.domManip(arguments,false,true,function(elem){this.parentNode.insertBefore(elem,this.nextSibling);});},end:function(){return this.prevObject||jQuery([]);},find:function(selector){var elems=jQuery.map(this,function(elem){return jQuery.find(selector,elem);});return this.pushStack(/[^+>] [^+>]/.test(selector)||selector.indexOf("..")>-1?jQuery.unique(elems):elems);},clone:function(events){var ret=this.map(function(){if(jQuery.browser.msie&&!jQuery.isXMLDoc(this)){var clone=this.cloneNode(true),container=document.createElement("div");container.appendChild(clone);return jQuery.clean([container.innerHTML])[0];}else
+return this.cloneNode(true);});var clone=ret.find("*").andSelf().each(function(){if(this[expando]!=undefined)this[expando]=null;});if(events===true)this.find("*").andSelf().each(function(i){if(this.nodeType==3)return;var events=jQuery.data(this,"events");for(var type in events)for(var handler in events[type])jQuery.event.add(clone[i],type,events[type][handler],events[type][handler].data);});return ret;},filter:function(selector){return this.pushStack(jQuery.isFunction(selector)&&jQuery.grep(this,function(elem,i){return selector.call(elem,i);})||jQuery.multiFilter(selector,this));},not:function(selector){if(selector.constructor==String)if(isSimple.test(selector))return this.pushStack(jQuery.multiFilter(selector,this,true));else
+selector=jQuery.multiFilter(selector,this);var isArrayLike=selector.length&&selector[selector.length-1]!==undefined&&!selector.nodeType;return this.filter(function(){return isArrayLike?jQuery.inArray(this,selector)<0:this!=selector;});},add:function(selector){return this.pushStack(jQuery.unique(jQuery.merge(this.get(),typeof selector=='string'?jQuery(selector):jQuery.makeArray(selector))));},is:function(selector){return!!selector&&jQuery.multiFilter(selector,this).length>0;},hasClass:function(selector){return this.is("."+selector);},val:function(value){if(value==undefined){if(this.length){var elem=this[0];if(jQuery.nodeName(elem,"select")){var index=elem.selectedIndex,values=[],options=elem.options,one=elem.type=="select-one";if(index<0)return null;for(var i=one?index:0,max=one?index+1:options.length;i<max;i++){var option=options[i];if(option.selected){value=jQuery.browser.msie&&!option.attributes.value.specified?option.text:option.value;if(one)return value;values.push(value);}}return values;}else
+return(this[0].value||"").replace(/\r/g,"");}return undefined;}if(value.constructor==Number)value+='';return this.each(function(){if(this.nodeType!=1)return;if(value.constructor==Array&&/radio|checkbox/.test(this.type))this.checked=(jQuery.inArray(this.value,value)>=0||jQuery.inArray(this.name,value)>=0);else if(jQuery.nodeName(this,"select")){var values=jQuery.makeArray(value);jQuery("option",this).each(function(){this.selected=(jQuery.inArray(this.value,values)>=0||jQuery.inArray(this.text,values)>=0);});if(!values.length)this.selectedIndex=-1;}else
+this.value=value;});},html:function(value){return value==undefined?(this[0]?this[0].innerHTML:null):this.empty().append(value);},replaceWith:function(value){return this.after(value).remove();},eq:function(i){return this.slice(i,i+1);},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments));},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem);}));},andSelf:function(){return this.add(this.prevObject);},data:function(key,value){var parts=key.split(".");parts[1]=parts[1]?"."+parts[1]:"";if(value===undefined){var data=this.triggerHandler("getData"+parts[1]+"!",[parts[0]]);if(data===undefined&&this.length)data=jQuery.data(this[0],key);return data===undefined&&parts[1]?this.data(parts[0]):data;}else
+return this.trigger("setData"+parts[1]+"!",[parts[0],value]).each(function(){jQuery.data(this,key,value);});},removeData:function(key){return this.each(function(){jQuery.removeData(this,key);});},domManip:function(args,table,reverse,callback){var clone=this.length>1,elems;return this.each(function(){if(!elems){elems=jQuery.clean(args,this.ownerDocument);if(reverse)elems.reverse();}var obj=this;if(table&&jQuery.nodeName(this,"table")&&jQuery.nodeName(elems[0],"tr"))obj=this.getElementsByTagName("tbody")[0]||this.appendChild(this.ownerDocument.createElement("tbody"));var scripts=jQuery([]);jQuery.each(elems,function(){var elem=clone?jQuery(this).clone(true)[0]:this;if(jQuery.nodeName(elem,"script"))scripts=scripts.add(elem);else{if(elem.nodeType==1)scripts=scripts.add(jQuery("script",elem).remove());callback.call(obj,elem);}});scripts.each(evalScript);});}};jQuery.fn.init.prototype=jQuery.fn;function evalScript(i,elem){if(elem.src)jQuery.ajax({url:elem.src,async:false,dataType:"script"});else
+jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");if(elem.parentNode)elem.parentNode.removeChild(elem);}function now(){return+new Date;}jQuery.extend=jQuery.fn.extend=function(){var target=arguments[0]||{},i=1,length=arguments.length,deep=false,options;if(target.constructor==Boolean){deep=target;target=arguments[1]||{};i=2;}if(typeof target!="object"&&typeof target!="function")target={};if(length==i){target=this;--i;}for(;i<length;i++)if((options=arguments[i])!=null)for(var name in options){var src=target[name],copy=options[name];if(target===copy)continue;if(deep&&copy&&typeof copy=="object"&&!copy.nodeType)target[name]=jQuery.extend(deep,src||(copy.length!=null?[]:{}),copy);else if(copy!==undefined)target[name]=copy;}return target;};var expando="jQuery"+now(),uuid=0,windowData={},exclude=/z-?index|font-?weight|opacity|zoom|line-?height/i,defaultView=document.defaultView||{};jQuery.extend({noConflict:function(deep){window.$=_$;if(deep)window.jQuery=_jQuery;return jQuery;},isFunction:function(fn){return!!fn&&typeof fn!="string"&&!fn.nodeName&&fn.constructor!=Array&&/^[\s[]?function/.test(fn+"");},isXMLDoc:function(elem){return elem.documentElement&&!elem.body||elem.tagName&&elem.ownerDocument&&!elem.ownerDocument.body;},globalEval:function(data){data=jQuery.trim(data);if(data){var head=document.getElementsByTagName("head")[0]||document.documentElement,script=document.createElement("script");script.type="text/javascript";if(jQuery.browser.msie)script.text=data;else
+script.appendChild(document.createTextNode(data));head.insertBefore(script,head.firstChild);head.removeChild(script);}},nodeName:function(elem,name){return elem.nodeName&&elem.nodeName.toUpperCase()==name.toUpperCase();},cache:{},data:function(elem,name,data){elem=elem==window?windowData:elem;var id=elem[expando];if(!id)id=elem[expando]=++uuid;if(name&&!jQuery.cache[id])jQuery.cache[id]={};if(data!==undefined)jQuery.cache[id][name]=data;return name?jQuery.cache[id][name]:id;},removeData:function(elem,name){elem=elem==window?windowData:elem;var id=elem[expando];if(name){if(jQuery.cache[id]){delete jQuery.cache[id][name];name="";for(name in jQuery.cache[id])break;if(!name)jQuery.removeData(elem);}}else{try{delete elem[expando];}catch(e){if(elem.removeAttribute)elem.removeAttribute(expando);}delete jQuery.cache[id];}},each:function(object,callback,args){var name,i=0,length=object.length;if(args){if(length==undefined){for(name in object)if(callback.apply(object[name],args)===false)break;}else
+for(;i<length;)if(callback.apply(object[i++],args)===false)break;}else{if(length==undefined){for(name in object)if(callback.call(object[name],name,object[name])===false)break;}else
+for(var value=object[0];i<length&&callback.call(value,i,value)!==false;value=object[++i]){}}return object;},prop:function(elem,value,type,i,name){if(jQuery.isFunction(value))value=value.call(elem,i);return value&&value.constructor==Number&&type=="curCSS"&&!exclude.test(name)?value+"px":value;},className:{add:function(elem,classNames){jQuery.each((classNames||"").split(/\s+/),function(i,className){if(elem.nodeType==1&&!jQuery.className.has(elem.className,className))elem.className+=(elem.className?" ":"")+className;});},remove:function(elem,classNames){if(elem.nodeType==1)elem.className=classNames!=undefined?jQuery.grep(elem.className.split(/\s+/),function(className){return!jQuery.className.has(classNames,className);}).join(" "):"";},has:function(elem,className){return jQuery.inArray(className,(elem.className||elem).toString().split(/\s+/))>-1;}},swap:function(elem,options,callback){var old={};for(var name in options){old[name]=elem.style[name];elem.style[name]=options[name];}callback.call(elem);for(var name in options)elem.style[name]=old[name];},css:function(elem,name,force){if(name=="width"||name=="height"){var val,props={position:"absolute",visibility:"hidden",display:"block"},which=name=="width"?["Left","Right"]:["Top","Bottom"];function getWH(){val=name=="width"?elem.offsetWidth:elem.offsetHeight;var padding=0,border=0;jQuery.each(which,function(){padding+=parseFloat(jQuery.curCSS(elem,"padding"+this,true))||0;border+=parseFloat(jQuery.curCSS(elem,"border"+this+"Width",true))||0;});val-=Math.round(padding+border);}if(jQuery(elem).is(":visible"))getWH();else
+jQuery.swap(elem,props,getWH);return Math.max(0,val);}return jQuery.curCSS(elem,name,force);},curCSS:function(elem,name,force){var ret,style=elem.style;function color(elem){if(!jQuery.browser.safari)return false;var ret=defaultView.getComputedStyle(elem,null);return!ret||ret.getPropertyValue("color")=="";}if(name=="opacity"&&jQuery.browser.msie){ret=jQuery.attr(style,"opacity");return ret==""?"1":ret;}if(jQuery.browser.opera&&name=="display"){var save=style.outline;style.outline="0 solid black";style.outline=save;}if(name.match(/float/i))name=styleFloat;if(!force&&style&&style[name])ret=style[name];else if(defaultView.getComputedStyle){if(name.match(/float/i))name="float";name=name.replace(/([A-Z])/g,"-$1").toLowerCase();var computedStyle=defaultView.getComputedStyle(elem,null);if(computedStyle&&!color(elem))ret=computedStyle.getPropertyValue(name);else{var swap=[],stack=[],a=elem,i=0;for(;a&&color(a);a=a.parentNode)stack.unshift(a);for(;i<stack.length;i++)if(color(stack[i])){swap[i]=stack[i].style.display;stack[i].style.display="block";}ret=name=="display"&&swap[stack.length-1]!=null?"none":(computedStyle&&computedStyle.getPropertyValue(name))||"";for(i=0;i<swap.length;i++)if(swap[i]!=null)stack[i].style.display=swap[i];}if(name=="opacity"&&ret=="")ret="1";}else if(elem.currentStyle){var camelCase=name.replace(/\-(\w)/g,function(all,letter){return letter.toUpperCase();});ret=elem.currentStyle[name]||elem.currentStyle[camelCase];if(!/^\d+(px)?$/i.test(ret)&&/^\d/.test(ret)){var left=style.left,rsLeft=elem.runtimeStyle.left;elem.runtimeStyle.left=elem.currentStyle.left;style.left=ret||0;ret=style.pixelLeft+"px";style.left=left;elem.runtimeStyle.left=rsLeft;}}return ret;},clean:function(elems,context){var ret=[];context=context||document;if(typeof context.createElement=='undefined')context=context.ownerDocument||context[0]&&context[0].ownerDocument||document;jQuery.each(elems,function(i,elem){if(!elem)return;if(elem.constructor==Number)elem+='';if(typeof elem=="string"){elem=elem.replace(/(<(\w+)[^>]*?)\/>/g,function(all,front,tag){return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?all:front+"></"+tag+">";});var tags=jQuery.trim(elem).toLowerCase(),div=context.createElement("div");var wrap=!tags.indexOf("<opt")&&[1,"<select multiple='multiple'>","</select>"]||!tags.indexOf("<leg")&&[1,"<fieldset>","</fieldset>"]||tags.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"<table>","</table>"]||!tags.indexOf("<tr")&&[2,"<table><tbody>","</tbody></table>"]||(!tags.indexOf("<td")||!tags.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||!tags.indexOf("<col")&&[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"]||jQuery.browser.msie&&[1,"div<div>","</div>"]||[0,"",""];div.innerHTML=wrap[1]+elem+wrap[2];while(wrap[0]--)div=div.lastChild;if(jQuery.browser.msie){var tbody=!tags.indexOf("<table")&&tags.indexOf("<tbody")<0?div.firstChild&&div.firstChild.childNodes:wrap[1]=="<table>"&&tags.indexOf("<tbody")<0?div.childNodes:[];for(var j=tbody.length-1;j>=0;--j)if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length)tbody[j].parentNode.removeChild(tbody[j]);if(/^\s/.test(elem))div.insertBefore(context.createTextNode(elem.match(/^\s*/)[0]),div.firstChild);}elem=jQuery.makeArray(div.childNodes);}if(elem.length===0&&(!jQuery.nodeName(elem,"form")&&!jQuery.nodeName(elem,"select")))return;if(elem[0]==undefined||jQuery.nodeName(elem,"form")||elem.options)ret.push(elem);else
+ret=jQuery.merge(ret,elem);});return ret;},attr:function(elem,name,value){if(!elem||elem.nodeType==3||elem.nodeType==8)return undefined;var notxml=!jQuery.isXMLDoc(elem),set=value!==undefined,msie=jQuery.browser.msie;name=notxml&&jQuery.props[name]||name;if(elem.tagName){var special=/href|src|style/.test(name);if(name=="selected"&&jQuery.browser.safari)elem.parentNode.selectedIndex;if(name in elem&&notxml&&!special){if(set){if(name=="type"&&jQuery.nodeName(elem,"input")&&elem.parentNode)throw"type property can't be changed";elem[name]=value;}if(jQuery.nodeName(elem,"form")&&elem.getAttributeNode(name))return elem.getAttributeNode(name).nodeValue;return elem[name];}if(msie&&notxml&&name=="style")return jQuery.attr(elem.style,"cssText",value);if(set)elem.setAttribute(name,""+value);var attr=msie&&notxml&&special?elem.getAttribute(name,2):elem.getAttribute(name);return attr===null?undefined:attr;}if(msie&&name=="opacity"){if(set){elem.zoom=1;elem.filter=(elem.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(value)+''=="NaN"?"":"alpha(opacity="+value*100+")");}return elem.filter&&elem.filter.indexOf("opacity=")>=0?(parseFloat(elem.filter.match(/opacity=([^)]*)/)[1])/100)+'':"";}name=name.replace(/-([a-z])/ig,function(all,letter){return letter.toUpperCase();});if(set)elem[name]=value;return elem[name];},trim:function(text){return(text||"").replace(/^\s+|\s+$/g,"");},makeArray:function(array){var ret=[];if(array!=null){var i=array.length;if(i==null||array.split||array.setInterval||array.call)ret[0]=array;else
+while(i)ret[--i]=array[i];}return ret;},inArray:function(elem,array){for(var i=0,length=array.length;i<length;i++)if(array[i]===elem)return i;return-1;},merge:function(first,second){var i=0,elem,pos=first.length;if(jQuery.browser.msie){while(elem=second[i++])if(elem.nodeType!=8)first[pos++]=elem;}else
+while(elem=second[i++])first[pos++]=elem;return first;},unique:function(array){var ret=[],done={};try{for(var i=0,length=array.length;i<length;i++){var id=jQuery.data(array[i]);if(!done[id]){done[id]=true;ret.push(array[i]);}}}catch(e){ret=array;}return ret;},grep:function(elems,callback,inv){var ret=[];for(var i=0,length=elems.length;i<length;i++)if(!inv!=!callback(elems[i],i))ret.push(elems[i]);return ret;},map:function(elems,callback){var ret=[];for(var i=0,length=elems.length;i<length;i++){var value=callback(elems[i],i);if(value!=null)ret[ret.length]=value;}return ret.concat.apply([],ret);}});var userAgent=navigator.userAgent.toLowerCase();jQuery.browser={version:(userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[])[1],safari:/webkit/.test(userAgent),opera:/opera/.test(userAgent),msie:/msie/.test(userAgent)&&!/opera/.test(userAgent),mozilla:/mozilla/.test(userAgent)&&!/(compatible|webkit)/.test(userAgent)};var styleFloat=jQuery.browser.msie?"styleFloat":"cssFloat";jQuery.extend({boxModel:!jQuery.browser.msie||document.compatMode=="CSS1Compat",props:{"for":"htmlFor","class":"className","float":styleFloat,cssFloat:styleFloat,styleFloat:styleFloat,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing"}});jQuery.each({parent:function(elem){return elem.parentNode;},parents:function(elem){return jQuery.dir(elem,"parentNode");},next:function(elem){return jQuery.nth(elem,2,"nextSibling");},prev:function(elem){return jQuery.nth(elem,2,"previousSibling");},nextAll:function(elem){return jQuery.dir(elem,"nextSibling");},prevAll:function(elem){return jQuery.dir(elem,"previousSibling");},siblings:function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},children:function(elem){return jQuery.sibling(elem.firstChild);},contents:function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}},function(name,fn){jQuery.fn[name]=function(selector){var ret=jQuery.map(this,fn);if(selector&&typeof selector=="string")ret=jQuery.multiFilter(selector,ret);return this.pushStack(jQuery.unique(ret));};});jQuery.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(name,original){jQuery.fn[name]=function(){var args=arguments;return this.each(function(){for(var i=0,length=args.length;i<length;i++)jQuery(args[i])[original](this);});};});jQuery.each({removeAttr:function(name){jQuery.attr(this,name,"");if(this.nodeType==1)this.removeAttribute(name);},addClass:function(classNames){jQuery.className.add(this,classNames);},removeClass:function(classNames){jQuery.className.remove(this,classNames);},toggleClass:function(classNames){jQuery.className[jQuery.className.has(this,classNames)?"remove":"add"](this,classNames);},remove:function(selector){if(!selector||jQuery.filter(selector,[this]).r.length){jQuery("*",this).add(this).each(function(){jQuery.event.remove(this);jQuery.removeData(this);});if(this.parentNode)this.parentNode.removeChild(this);}},empty:function(){jQuery(">*",this).remove();while(this.firstChild)this.removeChild(this.firstChild);}},function(name,fn){jQuery.fn[name]=function(){return this.each(fn,arguments);};});jQuery.each(["Height","Width"],function(i,name){var type=name.toLowerCase();jQuery.fn[type]=function(size){return this[0]==window?jQuery.browser.opera&&document.body["client"+name]||jQuery.browser.safari&&window["inner"+name]||document.compatMode=="CSS1Compat"&&document.documentElement["client"+name]||document.body["client"+name]:this[0]==document?Math.max(Math.max(document.body["scroll"+name],document.documentElement["scroll"+name]),Math.max(document.body["offset"+name],document.documentElement["offset"+name])):size==undefined?(this.length?jQuery.css(this[0],type):null):this.css(type,size.constructor==String?size:size+"px");};});function num(elem,prop){return elem[0]&&parseInt(jQuery.curCSS(elem[0],prop,true),10)||0;}var chars=jQuery.browser.safari&&parseInt(jQuery.browser.version)<417?"(?:[\\w*_-]|\\\\.)":"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",quickChild=new RegExp("^>\\s*("+chars+"+)"),quickID=new RegExp("^("+chars+"+)(#)("+chars+"+)"),quickClass=new RegExp("^([#.]?)("+chars+"*)");jQuery.extend({expr:{"":function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},"#":function(a,i,m){return a.getAttribute("id")==m[2];},":":{lt:function(a,i,m){return i<m[3]-0;},gt:function(a,i,m){return i>m[3]-0;},nth:function(a,i,m){return m[3]-0==i;},eq:function(a,i,m){return m[3]-0==i;},first:function(a,i){return i==0;},last:function(a,i,m,r){return i==r.length-1;},even:function(a,i){return i%2==0;},odd:function(a,i){return i%2;},"first-child":function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},"last-child":function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},"only-child":function(a){return!jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},parent:function(a){return a.firstChild;},empty:function(a){return!a.firstChild;},contains:function(a,i,m){return(a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},visible:function(a){return"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},hidden:function(a){return"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},enabled:function(a){return!a.disabled;},disabled:function(a){return a.disabled;},checked:function(a){return a.checked;},selected:function(a){return a.selected||jQuery.attr(a,"selected");},text:function(a){return"text"==a.type;},radio:function(a){return"radio"==a.type;},checkbox:function(a){return"checkbox"==a.type;},file:function(a){return"file"==a.type;},password:function(a){return"password"==a.type;},submit:function(a){return"submit"==a.type;},image:function(a){return"image"==a.type;},reset:function(a){return"reset"==a.type;},button:function(a){return"button"==a.type||jQuery.nodeName(a,"button");},input:function(a){return/input|select|textarea|button/i.test(a.nodeName);},has:function(a,i,m){return jQuery.find(m[3],a).length;},header:function(a){return/h\d/i.test(a.nodeName);},animated:function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}}},parse:[/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,new RegExp("^([:.#]*)("+chars+"+)")],multiFilter:function(expr,elems,not){var old,cur=[];while(expr&&expr!=old){old=expr;var f=jQuery.filter(expr,elems,not);expr=f.t.replace(/^\s*,\s*/,"");cur=not?elems=f.r:jQuery.merge(cur,f.r);}return cur;},find:function(t,context){if(typeof t!="string")return[t];if(context&&context.nodeType!=1&&context.nodeType!=9)return[];context=context||document;var ret=[context],done=[],last,nodeName;while(t&&last!=t){var r=[];last=t;t=jQuery.trim(t);var foundToken=false,re=quickChild,m=re.exec(t);if(m){nodeName=m[1].toUpperCase();for(var i=0;ret[i];i++)for(var c=ret[i].firstChild;c;c=c.nextSibling)if(c.nodeType==1&&(nodeName=="*"||c.nodeName.toUpperCase()==nodeName))r.push(c);ret=r;t=t.replace(re,"");if(t.indexOf(" ")==0)continue;foundToken=true;}else{re=/^([>+~])\s*(\w*)/i;if((m=re.exec(t))!=null){r=[];var merge={};nodeName=m[2].toUpperCase();m=m[1];for(var j=0,rl=ret.length;j<rl;j++){var n=m=="~"||m=="+"?ret[j].nextSibling:ret[j].firstChild;for(;n;n=n.nextSibling)if(n.nodeType==1){var id=jQuery.data(n);if(m=="~"&&merge[id])break;if(!nodeName||n.nodeName.toUpperCase()==nodeName){if(m=="~")merge[id]=true;r.push(n);}if(m=="+")break;}}ret=r;t=jQuery.trim(t.replace(re,""));foundToken=true;}}if(t&&!foundToken){if(!t.indexOf(",")){if(context==ret[0])ret.shift();done=jQuery.merge(done,ret);r=ret=[context];t=" "+t.substr(1,t.length);}else{var re2=quickID;var m=re2.exec(t);if(m){m=[0,m[2],m[3],m[1]];}else{re2=quickClass;m=re2.exec(t);}m[2]=m[2].replace(/\\/g,"");var elem=ret[ret.length-1];if(m[1]=="#"&&elem&&elem.getElementById&&!jQuery.isXMLDoc(elem)){var oid=elem.getElementById(m[2]);if((jQuery.browser.msie||jQuery.browser.opera)&&oid&&typeof oid.id=="string"&&oid.id!=m[2])oid=jQuery('[@id="'+m[2]+'"]',elem)[0];ret=r=oid&&(!m[3]||jQuery.nodeName(oid,m[3]))?[oid]:[];}else{for(var i=0;ret[i];i++){var tag=m[1]=="#"&&m[3]?m[3]:m[1]!=""||m[0]==""?"*":m[2];if(tag=="*"&&ret[i].nodeName.toLowerCase()=="object")tag="param";r=jQuery.merge(r,ret[i].getElementsByTagName(tag));}if(m[1]==".")r=jQuery.classFilter(r,m[2]);if(m[1]=="#"){var tmp=[];for(var i=0;r[i];i++)if(r[i].getAttribute("id")==m[2]){tmp=[r[i]];break;}r=tmp;}ret=r;}t=t.replace(re2,"");}}if(t){var val=jQuery.filter(t,r);ret=r=val.r;t=jQuery.trim(val.t);}}if(t)ret=[];if(ret&&context==ret[0])ret.shift();done=jQuery.merge(done,ret);return done;},classFilter:function(r,m,not){m=" "+m+" ";var tmp=[];for(var i=0;r[i];i++){var pass=(" "+r[i].className+" ").indexOf(m)>=0;if(!not&&pass||not&&!pass)tmp.push(r[i]);}return tmp;},filter:function(t,r,not){var last;while(t&&t!=last){last=t;var p=jQuery.parse,m;for(var i=0;p[i];i++){m=p[i].exec(t);if(m){t=t.substring(m[0].length);m[2]=m[2].replace(/\\/g,"");break;}}if(!m)break;if(m[1]==":"&&m[2]=="not")r=isSimple.test(m[3])?jQuery.filter(m[3],r,true).r:jQuery(r).not(m[3]);else if(m[1]==".")r=jQuery.classFilter(r,m[2],not);else if(m[1]=="["){var tmp=[],type=m[3];for(var i=0,rl=r.length;i<rl;i++){var a=r[i],z=a[jQuery.props[m[2]]||m[2]];if(z==null||/href|src|selected/.test(m[2]))z=jQuery.attr(a,m[2])||'';if((type==""&&!!z||type=="="&&z==m[5]||type=="!="&&z!=m[5]||type=="^="&&z&&!z.indexOf(m[5])||type=="$="&&z.substr(z.length-m[5].length)==m[5]||(type=="*="||type=="~=")&&z.indexOf(m[5])>=0)^not)tmp.push(a);}r=tmp;}else if(m[1]==":"&&m[2]=="nth-child"){var merge={},tmp=[],test=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(m[3]=="even"&&"2n"||m[3]=="odd"&&"2n+1"||!/\D/.test(m[3])&&"0n+"+m[3]||m[3]),first=(test[1]+(test[2]||1))-0,last=test[3]-0;for(var i=0,rl=r.length;i<rl;i++){var node=r[i],parentNode=node.parentNode,id=jQuery.data(parentNode);if(!merge[id]){var c=1;for(var n=parentNode.firstChild;n;n=n.nextSibling)if(n.nodeType==1)n.nodeIndex=c++;merge[id]=true;}var add=false;if(first==0){if(node.nodeIndex==last)add=true;}else if((node.nodeIndex-last)%first==0&&(node.nodeIndex-last)/first>=0)add=true;if(add^not)tmp.push(node);}r=tmp;}else{var fn=jQuery.expr[m[1]];if(typeof fn=="object")fn=fn[m[2]];if(typeof fn=="string")fn=eval("false||function(a,i){return "+fn+";}");r=jQuery.grep(r,function(elem,i){return fn(elem,i,m,r);},not);}}return{r:r,t:t};},dir:function(elem,dir){var matched=[],cur=elem[dir];while(cur&&cur!=document){if(cur.nodeType==1)matched.push(cur);cur=cur[dir];}return matched;},nth:function(cur,result,dir,elem){result=result||1;var num=0;for(;cur;cur=cur[dir])if(cur.nodeType==1&&++num==result)break;return cur;},sibling:function(n,elem){var r=[];for(;n;n=n.nextSibling){if(n.nodeType==1&&n!=elem)r.push(n);}return r;}});jQuery.event={add:function(elem,types,handler,data){if(elem.nodeType==3||elem.nodeType==8)return;if(jQuery.browser.msie&&elem.setInterval)elem=window;if(!handler.guid)handler.guid=this.guid++;if(data!=undefined){var fn=handler;handler=this.proxy(fn,function(){return fn.apply(this,arguments);});handler.data=data;}var events=jQuery.data(elem,"events")||jQuery.data(elem,"events",{}),handle=jQuery.data(elem,"handle")||jQuery.data(elem,"handle",function(){if(typeof jQuery!="undefined"&&!jQuery.event.triggered)return jQuery.event.handle.apply(arguments.callee.elem,arguments);});handle.elem=elem;jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];handler.type=parts[1];var handlers=events[type];if(!handlers){handlers=events[type]={};if(!jQuery.event.special[type]||jQuery.event.special[type].setup.call(elem)===false){if(elem.addEventListener)elem.addEventListener(type,handle,false);else if(elem.attachEvent)elem.attachEvent("on"+type,handle);}}handlers[handler.guid]=handler;jQuery.event.global[type]=true;});elem=null;},guid:1,global:{},remove:function(elem,types,handler){if(elem.nodeType==3||elem.nodeType==8)return;var events=jQuery.data(elem,"events"),ret,index;if(events){if(types==undefined||(typeof types=="string"&&types.charAt(0)=="."))for(var type in events)this.remove(elem,type+(types||""));else{if(types.type){handler=types.handler;types=types.type;}jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];if(events[type]){if(handler)delete events[type][handler.guid];else
+for(handler in events[type])if(!parts[1]||events[type][handler].type==parts[1])delete events[type][handler];for(ret in events[type])break;if(!ret){if(!jQuery.event.special[type]||jQuery.event.special[type].teardown.call(elem)===false){if(elem.removeEventListener)elem.removeEventListener(type,jQuery.data(elem,"handle"),false);else if(elem.detachEvent)elem.detachEvent("on"+type,jQuery.data(elem,"handle"));}ret=null;delete events[type];}}});}for(ret in events)break;if(!ret){var handle=jQuery.data(elem,"handle");if(handle)handle.elem=null;jQuery.removeData(elem,"events");jQuery.removeData(elem,"handle");}}},trigger:function(type,data,elem,donative,extra){data=jQuery.makeArray(data);if(type.indexOf("!")>=0){type=type.slice(0,-1);var exclusive=true;}if(!elem){if(this.global[type])jQuery("*").add([window,document]).trigger(type,data);}else{if(elem.nodeType==3||elem.nodeType==8)return undefined;var val,ret,fn=jQuery.isFunction(elem[type]||null),event=!data[0]||!data[0].preventDefault;if(event){data.unshift({type:type,target:elem,preventDefault:function(){},stopPropagation:function(){},timeStamp:now()});data[0][expando]=true;}data[0].type=type;if(exclusive)data[0].exclusive=true;var handle=jQuery.data(elem,"handle");if(handle)val=handle.apply(elem,data);if((!fn||(jQuery.nodeName(elem,'a')&&type=="click"))&&elem["on"+type]&&elem["on"+type].apply(elem,data)===false)val=false;if(event)data.shift();if(extra&&jQuery.isFunction(extra)){ret=extra.apply(elem,val==null?data:data.concat(val));if(ret!==undefined)val=ret;}if(fn&&donative!==false&&val!==false&&!(jQuery.nodeName(elem,'a')&&type=="click")){this.triggered=true;try{elem[type]();}catch(e){}}this.triggered=false;}return val;},handle:function(event){var val,ret,namespace,all,handlers;event=arguments[0]=jQuery.event.fix(event||window.event);namespace=event.type.split(".");event.type=namespace[0];namespace=namespace[1];all=!namespace&&!event.exclusive;handlers=(jQuery.data(this,"events")||{})[event.type];for(var j in handlers){var handler=handlers[j];if(all||handler.type==namespace){event.handler=handler;event.data=handler.data;ret=handler.apply(this,arguments);if(val!==false)val=ret;if(ret===false){event.preventDefault();event.stopPropagation();}}}return val;},fix:function(event){if(event[expando]==true)return event;var originalEvent=event;event={originalEvent:originalEvent};var props="altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which".split(" ");for(var i=props.length;i;i--)event[props[i]]=originalEvent[props[i]];event[expando]=true;event.preventDefault=function(){if(originalEvent.preventDefault)originalEvent.preventDefault();originalEvent.returnValue=false;};event.stopPropagation=function(){if(originalEvent.stopPropagation)originalEvent.stopPropagation();originalEvent.cancelBubble=true;};event.timeStamp=event.timeStamp||now();if(!event.target)event.target=event.srcElement||document;if(event.target.nodeType==3)event.target=event.target.parentNode;if(!event.relatedTarget&&event.fromElement)event.relatedTarget=event.fromElement==event.target?event.toElement:event.fromElement;if(event.pageX==null&&event.clientX!=null){var doc=document.documentElement,body=document.body;event.pageX=event.clientX+(doc&&doc.scrollLeft||body&&body.scrollLeft||0)-(doc.clientLeft||0);event.pageY=event.clientY+(doc&&doc.scrollTop||body&&body.scrollTop||0)-(doc.clientTop||0);}if(!event.which&&((event.charCode||event.charCode===0)?event.charCode:event.keyCode))event.which=event.charCode||event.keyCode;if(!event.metaKey&&event.ctrlKey)event.metaKey=event.ctrlKey;if(!event.which&&event.button)event.which=(event.button&1?1:(event.button&2?3:(event.button&4?2:0)));return event;},proxy:function(fn,proxy){proxy.guid=fn.guid=fn.guid||proxy.guid||this.guid++;return proxy;},special:{ready:{setup:function(){bindReady();return;},teardown:function(){return;}},mouseenter:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseover",jQuery.event.special.mouseenter.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseover",jQuery.event.special.mouseenter.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseenter";return jQuery.event.handle.apply(this,arguments);}},mouseleave:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseout",jQuery.event.special.mouseleave.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseout",jQuery.event.special.mouseleave.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseleave";return jQuery.event.handle.apply(this,arguments);}}}};jQuery.fn.extend({bind:function(type,data,fn){return type=="unload"?this.one(type,data,fn):this.each(function(){jQuery.event.add(this,type,fn||data,fn&&data);});},one:function(type,data,fn){var one=jQuery.event.proxy(fn||data,function(event){jQuery(this).unbind(event,one);return(fn||data).apply(this,arguments);});return this.each(function(){jQuery.event.add(this,type,one,fn&&data);});},unbind:function(type,fn){return this.each(function(){jQuery.event.remove(this,type,fn);});},trigger:function(type,data,fn){return this.each(function(){jQuery.event.trigger(type,data,this,true,fn);});},triggerHandler:function(type,data,fn){return this[0]&&jQuery.event.trigger(type,data,this[0],false,fn);},toggle:function(fn){var args=arguments,i=1;while(i<args.length)jQuery.event.proxy(fn,args[i++]);return this.click(jQuery.event.proxy(fn,function(event){this.lastToggle=(this.lastToggle||0)%i;event.preventDefault();return args[this.lastToggle++].apply(this,arguments)||false;}));},hover:function(fnOver,fnOut){return this.bind('mouseenter',fnOver).bind('mouseleave',fnOut);},ready:function(fn){bindReady();if(jQuery.isReady)fn.call(document,jQuery);else
+jQuery.readyList.push(function(){return fn.call(this,jQuery);});return this;}});jQuery.extend({isReady:false,readyList:[],ready:function(){if(!jQuery.isReady){jQuery.isReady=true;if(jQuery.readyList){jQuery.each(jQuery.readyList,function(){this.call(document);});jQuery.readyList=null;}jQuery(document).triggerHandler("ready");}}});var readyBound=false;function bindReady(){if(readyBound)return;readyBound=true;if(document.addEventListener&&!jQuery.browser.opera)document.addEventListener("DOMContentLoaded",jQuery.ready,false);if(jQuery.browser.msie&&window==top)(function(){if(jQuery.isReady)return;try{document.documentElement.doScroll("left");}catch(error){setTimeout(arguments.callee,0);return;}jQuery.ready();})();if(jQuery.browser.opera)document.addEventListener("DOMContentLoaded",function(){if(jQuery.isReady)return;for(var i=0;i<document.styleSheets.length;i++)if(document.styleSheets[i].disabled){setTimeout(arguments.callee,0);return;}jQuery.ready();},false);if(jQuery.browser.safari){var numStyles;(function(){if(jQuery.isReady)return;if(document.readyState!="loaded"&&document.readyState!="complete"){setTimeout(arguments.callee,0);return;}if(numStyles===undefined)numStyles=jQuery("style, link[rel=stylesheet]").length;if(document.styleSheets.length!=numStyles){setTimeout(arguments.callee,0);return;}jQuery.ready();})();}jQuery.event.add(window,"load",jQuery.ready);}jQuery.each(("blur,focus,load,resize,scroll,unload,click,dblclick,"+"mousedown,mouseup,mousemove,mouseover,mouseout,change,select,"+"submit,keydown,keypress,keyup,error").split(","),function(i,name){jQuery.fn[name]=function(fn){return fn?this.bind(name,fn):this.trigger(name);};});var withinElement=function(event,elem){var parent=event.relatedTarget;while(parent&&parent!=elem)try{parent=parent.parentNode;}catch(error){parent=elem;}return parent==elem;};jQuery(window).bind("unload",function(){jQuery("*").add(document).unbind();});jQuery.fn.extend({_load:jQuery.fn.load,load:function(url,params,callback){if(typeof url!='string')return this._load(url);var off=url.indexOf(" ");if(off>=0){var selector=url.slice(off,url.length);url=url.slice(0,off);}callback=callback||function(){};var type="GET";if(params)if(jQuery.isFunction(params)){callback=params;params=null;}else{params=jQuery.param(params);type="POST";}var self=this;jQuery.ajax({url:url,type:type,dataType:"html",data:params,complete:function(res,status){if(status=="success"||status=="notmodified")self.html(selector?jQuery("<div/>").append(res.responseText.replace(/<script(.|\s)*?\/script>/g,"")).find(selector):res.responseText);self.each(callback,[res.responseText,status,res]);}});return this;},serialize:function(){return jQuery.param(this.serializeArray());},serializeArray:function(){return this.map(function(){return jQuery.nodeName(this,"form")?jQuery.makeArray(this.elements):this;}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type));}).map(function(i,elem){var val=jQuery(this).val();return val==null?null:val.constructor==Array?jQuery.map(val,function(val,i){return{name:elem.name,value:val};}):{name:elem.name,value:val};}).get();}});jQuery.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(i,o){jQuery.fn[o]=function(f){return this.bind(o,f);};});var jsc=now();jQuery.extend({get:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data=null;}return jQuery.ajax({type:"GET",url:url,data:data,success:callback,dataType:type});},getScript:function(url,callback){return jQuery.get(url,null,callback,"script");},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json");},post:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data={};}return jQuery.ajax({type:"POST",url:url,data:data,success:callback,dataType:type});},ajaxSetup:function(settings){jQuery.extend(jQuery.ajaxSettings,settings);},ajaxSettings:{url:location.href,global:true,type:"GET",timeout:0,contentType:"application/x-www-form-urlencoded",processData:true,async:true,data:null,username:null,password:null,accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(s){s=jQuery.extend(true,s,jQuery.extend(true,{},jQuery.ajaxSettings,s));var jsonp,jsre=/=\?(&|$)/g,status,data,type=s.type.toUpperCase();if(s.data&&s.processData&&typeof s.data!="string")s.data=jQuery.param(s.data);if(s.dataType=="jsonp"){if(type=="GET"){if(!s.url.match(jsre))s.url+=(s.url.match(/\?/)?"&":"?")+(s.jsonp||"callback")+"=?";}else if(!s.data||!s.data.match(jsre))s.data=(s.data?s.data+"&":"")+(s.jsonp||"callback")+"=?";s.dataType="json";}if(s.dataType=="json"&&(s.data&&s.data.match(jsre)||s.url.match(jsre))){jsonp="jsonp"+jsc++;if(s.data)s.data=(s.data+"").replace(jsre,"="+jsonp+"$1");s.url=s.url.replace(jsre,"="+jsonp+"$1");s.dataType="script";window[jsonp]=function(tmp){data=tmp;success();complete();window[jsonp]=undefined;try{delete window[jsonp];}catch(e){}if(head)head.removeChild(script);};}if(s.dataType=="script"&&s.cache==null)s.cache=false;if(s.cache===false&&type=="GET"){var ts=now();var ret=s.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+ts+"$2");s.url=ret+((ret==s.url)?(s.url.match(/\?/)?"&":"?")+"_="+ts:"");}if(s.data&&type=="GET"){s.url+=(s.url.match(/\?/)?"&":"?")+s.data;s.data=null;}if(s.global&&!jQuery.active++)jQuery.event.trigger("ajaxStart");var remote=/^(?:\w+:)?\/\/([^\/?#]+)/;if(s.dataType=="script"&&type=="GET"&&remote.test(s.url)&&remote.exec(s.url)[1]!=location.host){var head=document.getElementsByTagName("head")[0];var script=document.createElement("script");script.src=s.url;if(s.scriptCharset)script.charset=s.scriptCharset;if(!jsonp){var done=false;script.onload=script.onreadystatechange=function(){if(!done&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){done=true;success();complete();head.removeChild(script);}};}head.appendChild(script);return undefined;}var requestDone=false;var xhr=window.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest();if(s.username)xhr.open(type,s.url,s.async,s.username,s.password);else
+xhr.open(type,s.url,s.async);try{if(s.data)xhr.setRequestHeader("Content-Type",s.contentType);if(s.ifModified)xhr.setRequestHeader("If-Modified-Since",jQuery.lastModified[s.url]||"Thu, 01 Jan 1970 00:00:00 GMT");xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Accept",s.dataType&&s.accepts[s.dataType]?s.accepts[s.dataType]+", */*":s.accepts._default);}catch(e){}if(s.beforeSend&&s.beforeSend(xhr,s)===false){s.global&&jQuery.active--;xhr.abort();return false;}if(s.global)jQuery.event.trigger("ajaxSend",[xhr,s]);var onreadystatechange=function(isTimeout){if(!requestDone&&xhr&&(xhr.readyState==4||isTimeout=="timeout")){requestDone=true;if(ival){clearInterval(ival);ival=null;}status=isTimeout=="timeout"&&"timeout"||!jQuery.httpSuccess(xhr)&&"error"||s.ifModified&&jQuery.httpNotModified(xhr,s.url)&&"notmodified"||"success";if(status=="success"){try{data=jQuery.httpData(xhr,s.dataType,s.dataFilter);}catch(e){status="parsererror";}}if(status=="success"){var modRes;try{modRes=xhr.getResponseHeader("Last-Modified");}catch(e){}if(s.ifModified&&modRes)jQuery.lastModified[s.url]=modRes;if(!jsonp)success();}else
+jQuery.handleError(s,xhr,status);complete();if(s.async)xhr=null;}};if(s.async){var ival=setInterval(onreadystatechange,13);if(s.timeout>0)setTimeout(function(){if(xhr){xhr.abort();if(!requestDone)onreadystatechange("timeout");}},s.timeout);}try{xhr.send(s.data);}catch(e){jQuery.handleError(s,xhr,null,e);}if(!s.async)onreadystatechange();function success(){if(s.success)s.success(data,status);if(s.global)jQuery.event.trigger("ajaxSuccess",[xhr,s]);}function complete(){if(s.complete)s.complete(xhr,status);if(s.global)jQuery.event.trigger("ajaxComplete",[xhr,s]);if(s.global&&!--jQuery.active)jQuery.event.trigger("ajaxStop");}return xhr;},handleError:function(s,xhr,status,e){if(s.error)s.error(xhr,status,e);if(s.global)jQuery.event.trigger("ajaxError",[xhr,s,e]);},active:0,httpSuccess:function(xhr){try{return!xhr.status&&location.protocol=="file:"||(xhr.status>=200&&xhr.status<300)||xhr.status==304||xhr.status==1223||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpNotModified:function(xhr,url){try{var xhrRes=xhr.getResponseHeader("Last-Modified");return xhr.status==304||xhrRes==jQuery.lastModified[url]||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpData:function(xhr,type,filter){var ct=xhr.getResponseHeader("content-type"),xml=type=="xml"||!type&&ct&&ct.indexOf("xml")>=0,data=xml?xhr.responseXML:xhr.responseText;if(xml&&data.documentElement.tagName=="parsererror")throw"parsererror";if(filter)data=filter(data,type);if(type=="script")jQuery.globalEval(data);if(type=="json")data=eval("("+data+")");return data;},param:function(a){var s=[];if(a.constructor==Array||a.jquery)jQuery.each(a,function(){s.push(encodeURIComponent(this.name)+"="+encodeURIComponent(this.value));});else
+for(var j in a)if(a[j]&&a[j].constructor==Array)jQuery.each(a[j],function(){s.push(encodeURIComponent(j)+"="+encodeURIComponent(this));});else
+s.push(encodeURIComponent(j)+"="+encodeURIComponent(jQuery.isFunction(a[j])?a[j]():a[j]));return s.join("&").replace(/%20/g,"+");}});jQuery.fn.extend({show:function(speed,callback){return speed?this.animate({height:"show",width:"show",opacity:"show"},speed,callback):this.filter(":hidden").each(function(){this.style.display=this.oldblock||"";if(jQuery.css(this,"display")=="none"){var elem=jQuery("<"+this.tagName+" />").appendTo("body");this.style.display=elem.css("display");if(this.style.display=="none")this.style.display="block";elem.remove();}}).end();},hide:function(speed,callback){return speed?this.animate({height:"hide",width:"hide",opacity:"hide"},speed,callback):this.filter(":visible").each(function(){this.oldblock=this.oldblock||jQuery.css(this,"display");this.style.display="none";}).end();},_toggle:jQuery.fn.toggle,toggle:function(fn,fn2){return jQuery.isFunction(fn)&&jQuery.isFunction(fn2)?this._toggle.apply(this,arguments):fn?this.animate({height:"toggle",width:"toggle",opacity:"toggle"},fn,fn2):this.each(function(){jQuery(this)[jQuery(this).is(":hidden")?"show":"hide"]();});},slideDown:function(speed,callback){return this.animate({height:"show"},speed,callback);},slideUp:function(speed,callback){return this.animate({height:"hide"},speed,callback);},slideToggle:function(speed,callback){return this.animate({height:"toggle"},speed,callback);},fadeIn:function(speed,callback){return this.animate({opacity:"show"},speed,callback);},fadeOut:function(speed,callback){return this.animate({opacity:"hide"},speed,callback);},fadeTo:function(speed,to,callback){return this.animate({opacity:to},speed,callback);},animate:function(prop,speed,easing,callback){var optall=jQuery.speed(speed,easing,callback);return this[optall.queue===false?"each":"queue"](function(){if(this.nodeType!=1)return false;var opt=jQuery.extend({},optall),p,hidden=jQuery(this).is(":hidden"),self=this;for(p in prop){if(prop[p]=="hide"&&hidden||prop[p]=="show"&&!hidden)return opt.complete.call(this);if(p=="height"||p=="width"){opt.display=jQuery.css(this,"display");opt.overflow=this.style.overflow;}}if(opt.overflow!=null)this.style.overflow="hidden";opt.curAnim=jQuery.extend({},prop);jQuery.each(prop,function(name,val){var e=new jQuery.fx(self,opt,name);if(/toggle|show|hide/.test(val))e[val=="toggle"?hidden?"show":"hide":val](prop);else{var parts=val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),start=e.cur(true)||0;if(parts){var end=parseFloat(parts[2]),unit=parts[3]||"px";if(unit!="px"){self.style[name]=(end||1)+unit;start=((end||1)/e.cur(true))*start;self.style[name]=start+unit;}if(parts[1])end=((parts[1]=="-="?-1:1)*end)+start;e.custom(start,end,unit);}else
+e.custom(start,val,"");}});return true;});},queue:function(type,fn){if(jQuery.isFunction(type)||(type&&type.constructor==Array)){fn=type;type="fx";}if(!type||(typeof type=="string"&&!fn))return queue(this[0],type);return this.each(function(){if(fn.constructor==Array)queue(this,type,fn);else{queue(this,type).push(fn);if(queue(this,type).length==1)fn.call(this);}});},stop:function(clearQueue,gotoEnd){var timers=jQuery.timers;if(clearQueue)this.queue([]);this.each(function(){for(var i=timers.length-1;i>=0;i--)if(timers[i].elem==this){if(gotoEnd)timers[i](true);timers.splice(i,1);}});if(!gotoEnd)this.dequeue();return this;}});var queue=function(elem,type,array){if(elem){type=type||"fx";var q=jQuery.data(elem,type+"queue");if(!q||array)q=jQuery.data(elem,type+"queue",jQuery.makeArray(array));}return q;};jQuery.fn.dequeue=function(type){type=type||"fx";return this.each(function(){var q=queue(this,type);q.shift();if(q.length)q[0].call(this);});};jQuery.extend({speed:function(speed,easing,fn){var opt=speed&&speed.constructor==Object?speed:{complete:fn||!fn&&easing||jQuery.isFunction(speed)&&speed,duration:speed,easing:fn&&easing||easing&&easing.constructor!=Function&&easing};opt.duration=(opt.duration&&opt.duration.constructor==Number?opt.duration:jQuery.fx.speeds[opt.duration])||jQuery.fx.speeds.def;opt.old=opt.complete;opt.complete=function(){if(opt.queue!==false)jQuery(this).dequeue();if(jQuery.isFunction(opt.old))opt.old.call(this);};return opt;},easing:{linear:function(p,n,firstNum,diff){return firstNum+diff*p;},swing:function(p,n,firstNum,diff){return((-Math.cos(p*Math.PI)/2)+0.5)*diff+firstNum;}},timers:[],timerId:null,fx:function(elem,options,prop){this.options=options;this.elem=elem;this.prop=prop;if(!options.orig)options.orig={};}});jQuery.fx.prototype={update:function(){if(this.options.step)this.options.step.call(this.elem,this.now,this);(jQuery.fx.step[this.prop]||jQuery.fx.step._default)(this);if(this.prop=="height"||this.prop=="width")this.elem.style.display="block";},cur:function(force){if(this.elem[this.prop]!=null&&this.elem.style[this.prop]==null)return this.elem[this.prop];var r=parseFloat(jQuery.css(this.elem,this.prop,force));return r&&r>-10000?r:parseFloat(jQuery.curCSS(this.elem,this.prop))||0;},custom:function(from,to,unit){this.startTime=now();this.start=from;this.end=to;this.unit=unit||this.unit||"px";this.now=this.start;this.pos=this.state=0;this.update();var self=this;function t(gotoEnd){return self.step(gotoEnd);}t.elem=this.elem;jQuery.timers.push(t);if(jQuery.timerId==null){jQuery.timerId=setInterval(function(){var timers=jQuery.timers;for(var i=0;i<timers.length;i++)if(!timers[i]())timers.splice(i--,1);if(!timers.length){clearInterval(jQuery.timerId);jQuery.timerId=null;}},13);}},show:function(){this.options.orig[this.prop]=jQuery.attr(this.elem.style,this.prop);this.options.show=true;this.custom(0,this.cur());if(this.prop=="width"||this.prop=="height")this.elem.style[this.prop]="1px";jQuery(this.elem).show();},hide:function(){this.options.orig[this.prop]=jQuery.attr(this.elem.style,this.prop);this.options.hide=true;this.custom(this.cur(),0);},step:function(gotoEnd){var t=now();if(gotoEnd||t>this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var done=true;for(var i in this.options.curAnim)if(this.options.curAnim[i]!==true)done=false;if(done){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(jQuery.css(this.elem,"display")=="none")this.elem.style.display="block";}if(this.options.hide)this.elem.style.display="none";if(this.options.hide||this.options.show)for(var p in this.options.curAnim)jQuery.attr(this.elem.style,p,this.options.orig[p]);}if(done)this.options.complete.call(this.elem);return false;}else{var n=t-this.startTime;this.state=n/this.options.duration;this.pos=jQuery.easing[this.options.easing||(jQuery.easing.swing?"swing":"linear")](this.state,n,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update();}return true;}};jQuery.extend(jQuery.fx,{speeds:{slow:600,fast:200,def:400},step:{scrollLeft:function(fx){fx.elem.scrollLeft=fx.now;},scrollTop:function(fx){fx.elem.scrollTop=fx.now;},opacity:function(fx){jQuery.attr(fx.elem.style,"opacity",fx.now);},_default:function(fx){fx.elem.style[fx.prop]=fx.now+fx.unit;}}});jQuery.fn.offset=function(){var left=0,top=0,elem=this[0],results;if(elem)with(jQuery.browser){var parent=elem.parentNode,offsetChild=elem,offsetParent=elem.offsetParent,doc=elem.ownerDocument,safari2=safari&&parseInt(version)<522&&!/adobeair/i.test(userAgent),css=jQuery.curCSS,fixed=css(elem,"position")=="fixed";if(elem.getBoundingClientRect){var box=elem.getBoundingClientRect();add(box.left+Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),box.top+Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));add(-doc.documentElement.clientLeft,-doc.documentElement.clientTop);}else{add(elem.offsetLeft,elem.offsetTop);while(offsetParent){add(offsetParent.offsetLeft,offsetParent.offsetTop);if(mozilla&&!/^t(able|d|h)$/i.test(offsetParent.tagName)||safari&&!safari2)border(offsetParent);if(!fixed&&css(offsetParent,"position")=="fixed")fixed=true;offsetChild=/^body$/i.test(offsetParent.tagName)?offsetChild:offsetParent;offsetParent=offsetParent.offsetParent;}while(parent&&parent.tagName&&!/^body|html$/i.test(parent.tagName)){if(!/^inline|table.*$/i.test(css(parent,"display")))add(-parent.scrollLeft,-parent.scrollTop);if(mozilla&&css(parent,"overflow")!="visible")border(parent);parent=parent.parentNode;}if((safari2&&(fixed||css(offsetChild,"position")=="absolute"))||(mozilla&&css(offsetChild,"position")!="absolute"))add(-doc.body.offsetLeft,-doc.body.offsetTop);if(fixed)add(Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));}results={top:top,left:left};}function border(elem){add(jQuery.curCSS(elem,"borderLeftWidth",true),jQuery.curCSS(elem,"borderTopWidth",true));}function add(l,t){left+=parseInt(l,10)||0;top+=parseInt(t,10)||0;}return results;};jQuery.fn.extend({position:function(){var left=0,top=0,results;if(this[0]){var offsetParent=this.offsetParent(),offset=this.offset(),parentOffset=/^body|html$/i.test(offsetParent[0].tagName)?{top:0,left:0}:offsetParent.offset();offset.top-=num(this,'marginTop');offset.left-=num(this,'marginLeft');parentOffset.top+=num(offsetParent,'borderTopWidth');parentOffset.left+=num(offsetParent,'borderLeftWidth');results={top:offset.top-parentOffset.top,left:offset.left-parentOffset.left};}return results;},offsetParent:function(){var offsetParent=this[0].offsetParent;while(offsetParent&&(!/^body|html$/i.test(offsetParent.tagName)&&jQuery.css(offsetParent,'position')=='static'))offsetParent=offsetParent.offsetParent;return jQuery(offsetParent);}});jQuery.each(['Left','Top'],function(i,name){var method='scroll'+name;jQuery.fn[method]=function(val){if(!this[0])return;return val!=undefined?this.each(function(){this==window||this==document?window.scrollTo(!i?val:jQuery(window).scrollLeft(),i?val:jQuery(window).scrollTop()):this[method]=val;}):this[0]==window||this[0]==document?self[i?'pageYOffset':'pageXOffset']||jQuery.boxModel&&document.documentElement[method]||document.body[method]:this[0][method];};});jQuery.each(["Height","Width"],function(i,name){var tl=i?"Left":"Top",br=i?"Right":"Bottom";jQuery.fn["inner"+name]=function(){return this[name.toLowerCase()]()+num(this,"padding"+tl)+num(this,"padding"+br);};jQuery.fn["outer"+name]=function(margin){return this["inner"+name]()+num(this,"border"+tl+"Width")+num(this,"border"+br+"Width")+(margin?num(this,"margin"+tl)+num(this,"margin"+br):0);};});})(); \ No newline at end of file
diff --git a/js/jquery.dimensions.min.js b/js/jquery.dimensions.min.js
new file mode 100644
index 0000000..34c06de
--- /dev/null
+++ b/js/jquery.dimensions.min.js
@@ -0,0 +1,12 @@
+/* Copyright (c) 2007 Paul Bakaus (paul.bakaus@googlemail.com) and Brandon Aaron (brandon.aaron@gmail.com || http://brandonaaron.net)
+ * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
+ * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
+ *
+ * $LastChangedDate: 2007-12-20 08:43:48 -0600 (Thu, 20 Dec 2007) $
+ * $Rev: 4257 $
+ *
+ * Version: 1.2
+ *
+ * Requires: jQuery 1.2+
+ */
+(function($){$.dimensions={version:'1.2'};$.each(['Height','Width'],function(i,name){$.fn['inner'+name]=function(){if(!this[0])return;var torl=name=='Height'?'Top':'Left',borr=name=='Height'?'Bottom':'Right';return this.is(':visible')?this[0]['client'+name]:num(this,name.toLowerCase())+num(this,'padding'+torl)+num(this,'padding'+borr);};$.fn['outer'+name]=function(options){if(!this[0])return;var torl=name=='Height'?'Top':'Left',borr=name=='Height'?'Bottom':'Right';options=$.extend({margin:false},options||{});var val=this.is(':visible')?this[0]['offset'+name]:num(this,name.toLowerCase())+num(this,'border'+torl+'Width')+num(this,'border'+borr+'Width')+num(this,'padding'+torl)+num(this,'padding'+borr);return val+(options.margin?(num(this,'margin'+torl)+num(this,'margin'+borr)):0);};});$.each(['Left','Top'],function(i,name){$.fn['scroll'+name]=function(val){if(!this[0])return;return val!=undefined?this.each(function(){this==window||this==document?window.scrollTo(name=='Left'?val:$(window)['scrollLeft'](),name=='Top'?val:$(window)['scrollTop']()):this['scroll'+name]=val;}):this[0]==window||this[0]==document?self[(name=='Left'?'pageXOffset':'pageYOffset')]||$.boxModel&&document.documentElement['scroll'+name]||document.body['scroll'+name]:this[0]['scroll'+name];};});$.fn.extend({position:function(){var left=0,top=0,elem=this[0],offset,parentOffset,offsetParent,results;if(elem){offsetParent=this.offsetParent();offset=this.offset();parentOffset=offsetParent.offset();offset.top-=num(elem,'marginTop');offset.left-=num(elem,'marginLeft');parentOffset.top+=num(offsetParent,'borderTopWidth');parentOffset.left+=num(offsetParent,'borderLeftWidth');results={top:offset.top-parentOffset.top,left:offset.left-parentOffset.left};}return results;},offsetParent:function(){var offsetParent=this[0].offsetParent;while(offsetParent&&(!/^body|html$/i.test(offsetParent.tagName)&&$.css(offsetParent,'position')=='static'))offsetParent=offsetParent.offsetParent;return $(offsetParent);}});function num(el,prop){return parseInt($.curCSS(el.jquery?el[0]:el,prop,true))||0;};})(jQuery); \ No newline at end of file
diff --git a/js/keyboard.js b/js/keyboard.js
new file mode 100644
index 0000000..73594d5
--- /dev/null
+++ b/js/keyboard.js
@@ -0,0 +1,46 @@
+
+function Keyboard() {
+ this.keys = {KEY_A: 0, KEY_B: 1, KEY_SELECT: 2, KEY_START: 3, KEY_UP: 4, KEY_DOWN: 5, KEY_LEFT: 6, KEY_RIGHT: 7};
+
+ this.state1 = Array(8);
+ for (var i = 0; i < this.state1.length; i++) this.state1[i] = 0x40;
+ this.state2 = Array(8);
+ for (var i = 0; i < this.state2.length; i++) this.state2[i] = 0x40;
+}
+
+Keyboard.prototype.setKey = function(key, value) {
+ switch (key) {
+ case 88: this.state1[this.keys.KEY_A] = value; break; // X
+ case 90: this.state1[this.keys.KEY_B] = value; break; // Z
+ case 17: this.state1[this.keys.KEY_SELECT] = value; break; // Right Ctrl
+ case 13: this.state1[this.keys.KEY_START] = value; break; // Enter
+ case 38: this.state1[this.keys.KEY_UP] = value; break; // Up
+ case 40: this.state1[this.keys.KEY_DOWN] = value; break; // Down
+ case 37: this.state1[this.keys.KEY_LEFT] = value; break; // Left
+ case 39: this.state1[this.keys.KEY_RIGHT] = value; break; // Right
+
+ case 103: this.state2[this.keys.KEY_A] = value; break; // Num-7
+ case 105: this.state2[this.keys.KEY_B] = value; break; // Num-9
+ case 99: this.state2[this.keys.KEY_SELECT] = value; break; // Num-3
+ case 97: this.state2[this.keys.KEY_START] = value; break; // Num-1
+ case 104: this.state2[this.keys.KEY_UP] = value; break; // Num-8
+ case 98: this.state2[this.keys.KEY_DOWN] = value; break; // Num-2
+ case 100: this.state2[this.keys.KEY_LEFT] = value; break; // Num-4
+ case 102: this.state2[this.keys.KEY_RIGHT] = value; break; // Num-6
+ default: return true;
+ }
+ return false; // preventDefault
+}
+
+Keyboard.prototype.keyDown = function(evt) {
+ if (!this.setKey(evt.keyCode, 0x41) && evt.preventDefault)
+ evt.preventDefault();
+}
+Keyboard.prototype.keyUp = function(evt) {
+ if (!this.setKey(evt.keyCode, 0x40) && evt.preventDefault)
+ evt.preventDefault();
+}
+
+var keyboard = new Keyboard();
+document.onkeydown = function(evt) {keyboard.keyDown(evt)}
+document.onkeyup = function(evt) {keyboard.keyUp(evt)}
diff --git a/js/mappers.js b/js/mappers.js
new file mode 100644
index 0000000..a58aa1a
--- /dev/null
+++ b/js/mappers.js
@@ -0,0 +1,1147 @@
+function MapperDefault(nes) {
+ this.nes = nes;
+
+ this.joy1StrobeState = null;
+ this.joy2StrobeState = null;
+ this.joypadLastWrite = -1;
+
+ this.mousePressed = null;
+ this.gameGenieActive = null;
+ this.mouseX = null;
+ this.mouseY = null;
+
+ this.tmp = null;
+
+}
+
+MapperDefault.prototype.write = function(address, value){
+
+ if(address<0x2000){
+
+ // Mirroring of RAM:
+ this.nes.cpuMem[address & 0x7FF] = value;
+
+ }else if(address>0x4017){
+
+ this.nes.cpuMem[address] = value;
+ if(address>=0x6000 && address<0x8000){
+
+ // Write to SaveRAM. Store in file:
+ // TODO: not yet
+ //if(this.nes.rom!=null)
+ // this.nes.rom.writeBatteryRam(address,value);
+
+ }
+
+ }else if(address>0x2007 && address<0x4000){
+
+ this.regWrite(0x2000 + (address & 0x7),value);
+
+ }else{
+
+ this.regWrite(address,value);
+
+ }
+
+}
+
+MapperDefault.prototype.load = function(address){
+
+ // Game Genie codes active?
+ /*if(this.gameGenieActive){
+ if(this.nes.gameGenie.addressMatch[address]){
+
+ tmp = nes.gameGenie.getCodeIndex(address);
+
+ // Check the code type:
+ if(nes.gameGenie.getCodeType(tmp) == GameGenie.TYPE_6CHAR){
+
+ // Return the code value:
+ return (short)nes.gameGenie.getCodeValue(tmp);
+
+ }else{
+
+ // Check whether the actual value equals the compare value:
+ if(nes.cpuMem[address] == nes.gameGenie.getCodeCompare(tmp)){
+
+ // The values match, so use the supplied game genie value:
+ return (short)nes.gameGenie.getCodeValue(tmp);
+
+ }
+
+ }
+ }
+ }*/
+
+ // Wrap around:
+ address &= 0xFFFF;
+
+ // Check address range:
+ if(address > 0x4017){
+
+ // ROM:
+ return this.nes.cpuMem[address];
+
+ }else if(address >= 0x2000){
+
+ // I/O Ports.
+ return this.regLoad(address);
+
+ }else{
+
+ // RAM (mirrored)
+ return this.nes.cpuMem[address&0x7FF];
+
+ }
+
+}
+
+MapperDefault.prototype.regLoad = function(address){
+
+ switch(address>>12){ // use fourth nibble (0xF000)
+
+ case 0:{
+ break;
+ }case 1:{
+ break;
+ }case 2:{
+
+ // Fall through to case 3
+
+ }case 3:{
+
+ // PPU Registers
+ switch(address&0x7){
+ case 0x0:{
+
+ // 0x2000:
+ // PPU Control Register 1.
+ // (the value is stored both
+ // in main memory and in the
+ // PPU as flags):
+ // (not in the real NES)
+ return this.nes.cpuMem[0x2000];
+
+ }case 0x1:{
+
+ // 0x2001:
+ // PPU Control Register 2.
+ // (the value is stored both
+ // in main memory and in the
+ // PPU as flags):
+ // (not in the real NES)
+ return this.nes.cpuMem[0x2001];
+
+ }case 0x2:{
+
+ // 0x2002:
+ // PPU Status Register.
+ // The value is stored in
+ // main memory in addition
+ // to as flags in the PPU.
+ // (not in the real NES)
+ return this.nes.ppu.readStatusRegister();
+
+ }case 0x3:{
+ return 0;
+ }case 0x4:{
+
+ // 0x2004:
+ // Sprite Memory read.
+ return this.nes.ppu.sramLoad();
+
+ }case 0x5:{
+ return 0;
+ }case 0x6:{
+ return 0;
+ }case 0x7:{
+
+ // 0x2007:
+ // VRAM read:
+ return this.nes.ppu.vramLoad();
+
+ }
+ }
+ break;
+
+ }case 4:{
+
+
+ // Sound+Joypad registers
+
+ switch(address-0x4015){
+ case 0:{
+
+ // 0x4015:
+ // Sound channel enable, DMC Status
+ return this.nes.papu.readReg(address);
+
+ }case 1:{
+
+ // 0x4016:
+ // Joystick 1 + Strobe
+ return this.joy1Read();
+
+ }case 2:{
+
+ // 0x4017:
+ // Joystick 2 + Strobe
+ if(this.mousePressed){
+
+ // Check for white pixel nearby:
+
+ var sx = Math.max(0,this.mouseX-4);
+ var ex = Math.min(256,this.mouseX+4);
+ var sy = Math.max(0,this.mouseY-4);
+ var ey = Math.min(240,this.mouseY+4);
+ var w = 0;
+
+ for(var y=sy; y<ey; y++){
+ for(var x=sx; x<ex; x++){
+
+ if(this.nes.ppu.buffer[(y<<8)+x] == 0xFFFFFF) {
+ w |= 0x1<<3;
+ console.debug("Clicked on white!");
+ break;
+ }
+ }
+ }
+
+ w |= (this.mousePressed?(0x1<<4):0);
+ return (this.joy2Read()|w) & 0xFFFF;
+ }
+ else{
+ return this.joy2Read();
+ }
+
+ }
+ }
+
+ break;
+
+ }
+ }
+
+ return 0;
+
+}
+
+MapperDefault.prototype.regWrite = function(address, value){
+
+ switch(address){
+ case 0x2000:{
+
+ // PPU Control register 1
+ this.nes.cpuMem[address] = value;
+ this.nes.ppu.updateControlReg1(value);
+ break;
+
+ }case 0x2001:{
+
+ // PPU Control register 2
+ this.nes.cpuMem[address] = value;
+ this.nes.ppu.updateControlReg2(value);
+ break;
+
+ }case 0x2003:{
+
+ // Set Sprite RAM address:
+ this.nes.ppu.writeSRAMAddress(value);
+ break;
+
+ }case 0x2004:{
+
+ // Write to Sprite RAM:
+ this.nes.ppu.sramWrite(value);
+ break;
+
+ }case 0x2005:{
+
+ // Screen Scroll offsets:
+ this.nes.ppu.scrollWrite(value);
+ break;
+
+ }case 0x2006:{
+
+ // Set VRAM address:
+ this.nes.ppu.writeVRAMAddress(value);
+ break;
+
+ }case 0x2007:{
+
+ // Write to VRAM:
+ this.nes.ppu.vramWrite(value);
+ break;
+
+ }case 0x4014:{
+
+ // Sprite Memory DMA Access
+ this.nes.ppu.sramDMA(value);
+ break;
+
+ }case 0x4015:{
+
+ // Sound Channel Switch, DMC Status
+ this.nes.papu.writeReg(address,value);
+ break;
+
+ }case 0x4016:{
+
+ ////System.out.println("joy strobe write "+value);
+
+ // Joystick 1 + Strobe
+ if(value==0 && this.joypadLastWrite==1){
+ ////System.out.println("Strobes reset.");
+ this.joy1StrobeState = 0;
+ this.joy2StrobeState = 0;
+ }
+ this.joypadLastWrite = value;
+ break;
+
+ }case 0x4017:{
+
+ // Sound channel frame sequencer:
+ this.nes.papu.writeReg(address,value);
+ break;
+
+ }default:{
+
+ // Sound registers
+ ////System.out.println("write to sound reg");
+ if(address >= 0x4000 && address <= 0x4017){
+ this.nes.papu.writeReg(address,value);
+ }
+ break;
+
+ }
+ }
+
+}
+
+MapperDefault.prototype.joy1Read = function(){
+ var ret;
+
+ switch(this.joy1StrobeState){
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ ret = keyboard.state1[this.joy1StrobeState];
+ break;
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ case 16:
+ case 17:
+ case 18:
+ ret = 0;
+ break;
+ case 19:
+ ret = 1;
+ break;
+ default:
+ ret = 0;
+ }
+
+ this.joy1StrobeState++;
+ if(this.joy1StrobeState == 24){
+ this.joy1StrobeState = 0;
+ }
+
+ return ret;
+
+}
+
+MapperDefault.prototype.joy2Read = function(){
+ var ret;
+
+ this.joy2StrobeState++;
+ if(this.joy2StrobeState == 24){
+ this.joy2StrobeState = 0;
+ }
+
+ switch(this.joy2StrobeState){
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ ret = keyboard.state2[this.joy2StrobeState];
+ break;
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ case 16:
+ case 17:
+ ret = 0;
+ break;
+ case 18:
+ ret = 1;
+ break;
+ default:
+ ret = 0;
+ }
+
+ return ret;
+
+}
+
+MapperDefault.prototype.loadROM = function(){
+
+ if(!this.nes.rom.valid || this.nes.rom.romCount<1){
+ alert("NoMapper: Invalid ROM! Unable to load.");
+ return;
+ }
+
+ // Load ROM into memory:
+ this.loadPRGROM();
+
+ // Load CHR-ROM:
+ this.loadCHRROM();
+
+ // Load Battery RAM (if present):
+ this.loadBatteryRam();
+
+ // Reset IRQ:
+ //nes.getCpu().doResetInterrupt();
+ this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);
+
+}
+
+MapperDefault.prototype.loadPRGROM = function(){
+ if(this.nes.rom.romCount>1){
+ // Load the two first banks into memory.
+ this.loadRomBank(0,0x8000);
+ this.loadRomBank(1,0xC000);
+ }else{
+ // Load the one bank into both memory locations:
+ this.loadRomBank(0,0x8000);
+ this.loadRomBank(0,0xC000);
+ }
+}
+
+MapperDefault.prototype.loadCHRROM = function(){
+ ////System.out.println("Loading CHR ROM..");
+ if(this.nes.rom.vromCount > 0){
+ if(this.nes.rom.vromCount == 1){
+ this.loadVromBank(0,0x0000);
+ this.loadVromBank(0,0x1000);
+ }else{
+ this.loadVromBank(0,0x0000);
+ this.loadVromBank(1,0x1000);
+ }
+ }else{
+ //System.out.println("There aren't any CHR-ROM banks..");
+ }
+}
+
+MapperDefault.prototype.loadBatteryRam = function(){
+ if(this.nes.rom.batteryRam){
+ var ram = this.nes.rom.batteryRam;
+ if(ram!=null && ram.length==0x2000){
+ // Load Battery RAM into memory:
+ arraycopy(ram, 0, this.nes.cpuMem, 0x6000, 0x2000);
+ }
+ }
+}
+
+MapperDefault.prototype.loadRomBank = function(bank, address){
+ // Loads a ROM bank into the specified address.
+ bank %= this.nes.rom.romCount;
+ //var data = this.nes.rom.rom[bank];
+ //cpuMem.write(address,data,data.length);
+ arraycopy(this.nes.rom.rom[bank], 0, this.nes.cpuMem, address, 16384);
+
+}
+
+MapperDefault.prototype.loadVromBank = function(bank, address){
+
+ if(this.nes.rom.vromCount == 0) return;
+ this.nes.ppu.triggerRendering();
+
+ arraycopy(this.nes.rom.vrom[bank%this.nes.rom.vromCount],0,
+ this.nes.ppuMem,address,4096);
+
+ var vromTile = this.nes.rom.vromTile[bank%this.nes.rom.vromCount];
+ arraycopy(vromTile,0,this.nes.ppu.ptTile,address>>4,256);
+
+}
+
+MapperDefault.prototype.load32kRomBank = function(bank, address){
+
+ this.loadRomBank((bank*2)%this.nes.rom.romCount,address);
+ this.loadRomBank((bank*2+1)%this.nes.rom.romCount,address+16384);
+
+}
+
+MapperDefault.prototype.load8kVromBank = function(bank4kStart, address){
+ if(this.nes.rom.vromCount == 0) return;
+ this.nes.ppu.triggerRendering();
+
+ this.loadVromBank((bank4kStart)%this.nes.rom.vromCount,address);
+ this.loadVromBank((bank4kStart+1)%this.nes.rom.vromCount,address+4096);
+
+}
+
+MapperDefault.prototype.load1kVromBank = function(bank1k, address){
+
+ if(this.nes.rom.vromCount == 0)return;
+ this.nes.ppu.triggerRendering();
+
+ var bank4k = parseInt(bank1k/4)%this.nes.rom.vromCount;
+ var bankoffset = (bank1k%4)*1024;
+ arraycopy(this.nes.rom.vrom[bank4k],0,this.nes.ppuMem,
+ bankoffset,1024);
+
+ // Update tiles:
+ var vromTile = this.nes.rom.vromTile[bank4k];
+ var baseIndex = address >> 4;
+ for(var i=0;i<64;i++){
+ this.nes.ppu.ptTile[baseIndex+i] = vromTile[((bank1k%4)<<6)+i];
+ }
+
+}
+
+MapperDefault.prototype.load2kVromBank = function(bank2k, address){
+
+ if(this.nes.rom.vromCount == 0)return;
+ this.nes.ppu.triggerRendering();
+
+ var bank4k = parseInt(bank2k/2)%this.nes.rom.vromCount;
+ var bankoffset = (bank2k%2)*2048;
+ arraycopy(this.nes.rom.vrom[bank4k], bankoffset,
+ this.nes.ppuMem,address,2048);
+
+ // Update tiles:
+ var vromTile = this.nes.rom.vromTile[bank4k];
+ var baseIndex = address >> 4;
+ for(var i=0;i<128;i++){
+ this.nes.ppu.ptTile[baseIndex+i] = vromTile[((bank2k%2)<<7)+i];
+ }
+
+}
+
+MapperDefault.prototype.load8kRomBank = function(bank8k, address){
+
+ var bank16k = parseInt(bank8k/2)%this.nes.rom.romCount;
+ var offset = (bank8k%2)*8192;
+
+ //this.nes.cpuMem.write(address,this.nes.rom.rom[bank16k],offset,8192);
+ arraycopy(this.nes.rom.rom[bank16k], offset,
+ this.nes.cpuMem, address, 8192);
+
+}
+
+MapperDefault.prototype.clockIrqCounter = function(){
+
+ // Does nothing. This is used by the MMC3 mapper.
+
+}
+
+MapperDefault.prototype.latchAccess = function(address){
+
+ // Does nothing. This is used by MMC2.
+
+}
+
+MapperDefault.prototype.reset = function(){
+
+ this.joy1StrobeState = 0;
+ this.joy2StrobeState = 0;
+ this.joypadLastWrite = 0;
+ this.mousePressed = false;
+
+}
+
+function Mapper001(nes) {
+ this.nes = nes
+
+ // Register flags:
+
+ // Register 0:
+ this.mirroring = null;
+ this.oneScreenMirroring = null;
+ this.prgSwitchingArea = 1;
+ this.prgSwitchingSize = 1;
+ this.vromSwitchingSize = null;
+
+ // Register 1:
+ this.romSelectionReg0 = null;
+
+ // Register 2:
+ this.romSelectionReg1 = null;
+
+ // Register 3:
+ this.romBankSelect = null;
+
+ // 5-bit buffer:
+ this.regBuffer = null;
+ this.regBufferCounter = null;
+}
+
+copyPrototype(Mapper001, MapperDefault);
+
+Mapper001.prototype.write = function(address, value){
+
+ // Writes to addresses other than MMC registers are handled by NoMapper.
+ if(address < 0x8000){
+ MapperDefault.prototype.write.apply(this, arguments);
+ return;
+ }
+
+ ////System.out.println("MMC Write. Reg="+(getRegNumber(address))+" Value="+value);
+
+ // See what should be done with the written value:
+ if((value&128)!=0){
+
+ // Reset buffering:
+ this.regBufferCounter = 0;
+ this.regBuffer = 0;
+
+ // Reset register:
+ if(this.getRegNumber(address) == 0){
+
+ this.prgSwitchingArea = 1;
+ this.prgSwitchingSize = 1;
+
+ }
+
+ }else{
+
+ // Continue buffering:
+ //regBuffer = (regBuffer & (0xFF-(1<<regBufferCounter))) | ((value & (1<<regBufferCounter))<<regBufferCounter);
+ this.regBuffer = (this.regBuffer & (0xFF-(1<<this.regBufferCounter))) | ((value&1)<<this.regBufferCounter);
+ this.regBufferCounter++;
+ if(this.regBufferCounter == 5){
+
+ // Use the buffered value:
+ this.setReg(this.getRegNumber(address), this.regBuffer);
+
+ // Reset buffer:
+ this.regBuffer = 0;
+ this.regBufferCounter = 0;
+
+ }
+
+ }
+
+}
+
+Mapper001.prototype.setReg = function(reg, value){
+
+ var tmp,tmp2;
+
+ switch (reg) {
+ case 0:
+ // Mirroring:
+ tmp = value&3;
+ if(tmp != this.mirroring){
+ // Set mirroring:
+ this.mirroring = tmp;
+ if((this.mirroring & 2)==0){
+ // SingleScreen mirroring overrides the other setting:
+ this.nes.ppu.setMirroring(
+ this.nes.rom.SINGLESCREEN_MIRRORING);
+ }else{
+ // Not overridden by SingleScreen mirroring.
+ this.nes.ppu.setMirroring(
+ (this.mirroring&1)!=0 ?
+ this.nes.rom.HORIZONTAL_MIRRORING
+ :
+ this.nes.rom.VERTICAL_MIRRORING);
+ }
+ }
+
+ // PRG Switching Area;
+ this.prgSwitchingArea = (value>>2)&1;
+
+ // PRG Switching Size:
+ this.prgSwitchingSize = (value>>3)&1;
+
+ // VROM Switching Size:
+ this.vromSwitchingSize = (value>>4)&1;
+
+ break;
+
+ case 1:
+ // ROM selection:
+ this.romSelectionReg0 = (value>>4)&1;
+
+ // Check whether the cart has VROM:
+ if(this.nes.rom.vromCount > 0){
+
+ // Select VROM bank at 0x0000:
+ if(this.vromSwitchingSize == 0){
+
+ // Swap 8kB VROM:
+ ////System.out.println("Swapping 8k VROM, bank="+(value&0xF)+" romSelReg="+romSelectionReg0);
+ if(this.romSelectionReg0==0){
+ this.load8kVromBank((value&0xF), 0x0000);
+ }else{
+ this.load8kVromBank(
+ parseInt(this.nes.rom.vromCount/2) + (value&0xF),
+ 0x0000);
+ }
+
+ }else{
+
+ // Swap 4kB VROM:
+ ////System.out.println("ROMSELREG0 = "+romSelectionReg0);
+ ////System.out.println("Swapping 4k VROM at 0x0000, bank="+(value&0xF));
+
+ if(this.romSelectionReg0 == 0){
+ this.loadVromBank((value&0xF), 0x0000);
+ }else{
+ this.loadVromBank(
+ parseInt(this.nes.rom.vromCount/2)+(value&0xF),
+ 0x0000);
+ }
+
+ }
+
+ }
+
+ break;
+
+ case 2:
+ // ROM selection:
+ this.romSelectionReg1 = (value>>4)&1;
+
+ // Check whether the cart has VROM:
+ if(this.nes.rom.vromCount > 0){
+
+ // Select VROM bank at 0x1000:
+ if(this.vromSwitchingSize == 1){
+
+ // Swap 4kB of VROM:
+ ////System.out.println("ROMSELREG1 = "+romSelectionReg1);
+ ////System.out.println("Swapping 4k VROM at 0x1000, bank="+(value&0xF));
+ if(this.romSelectionReg1 == 0){
+ this.loadVromBank((value&0xF),0x1000);
+ }else{
+ this.loadVromBank(
+ parseInt(this.nes.rom.vromCount/2)+(value&0xF),
+ 0x1000);
+ }
+
+ }
+
+ }
+
+ default:
+ // Select ROM bank:
+ // -------------------------
+ tmp = value & 0xF;
+ var bank;
+ var baseBank = 0;
+
+ if(this.nes.rom.romCount >= 32){
+
+ // 1024 kB cart
+ if(this.vromSwitchingSize == 0){
+ if(this.romSelectionReg0 == 1){
+ baseBank = 16;
+ }
+ }else{
+ baseBank = (this.romSelectionReg0
+ | (this.romSelectionReg1<<1))<<3;
+ }
+
+ }else if(this.nes.rom.romCount >= 16){
+
+ // 512 kB cart
+ if(this.romSelectionReg0 == 1){
+ baseBank = 8;
+ }
+
+ }
+
+ if(this.prgSwitchingSize == 0){
+
+ // 32kB
+ bank = baseBank+(value&0xF);
+ this.load32kRomBank(bank, 0x8000);
+
+ }else{
+
+ // 16kB
+ bank = baseBank*2+(value&0xF);
+ if(this.prgSwitchingArea == 0){
+ this.loadRomBank(bank, 0xC000);
+ }else{
+ this.loadRomBank(bank, 0x8000);
+ }
+
+ }
+
+ }
+
+}
+
+// Returns the register number from the address written to:
+Mapper001.prototype.getRegNumber = function(address){
+
+ if(address>=0x8000 && address<=0x9FFF){
+ return 0;
+ }else if(address>=0xA000 && address<=0xBFFF){
+ return 1;
+ }else if(address>=0xC000 && address<=0xDFFF){
+ return 2;
+ }else{
+ return 3;
+ }
+
+}
+
+Mapper001.prototype.loadROM = function(rom){
+
+ //System.out.println("Loading ROM.");
+
+ if(!this.nes.rom.valid){
+ alert("MMC1: Invalid ROM! Unable to load.");
+ return;
+ }
+
+ // Load PRG-ROM:
+ this.loadRomBank(0,0x8000); // First ROM bank..
+ this.loadRomBank(this.nes.rom.romCount-1,0xC000); // ..and last ROM bank.
+
+ // Load CHR-ROM:
+ this.loadCHRROM();
+
+ // Load Battery RAM (if present):
+ this.loadBatteryRam();
+
+ // Do Reset-Interrupt:
+ this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);
+
+}
+
+Mapper001.prototype.reset = function(){
+
+ MapperDefault.prototype.reset.apply(this);
+
+ this.regBuffer = 0;
+ this.regBufferCounter = 0;
+
+ // Register 0:
+ this.mirroring = 0;
+ this.oneScreenMirroring = 0;
+ this.prgSwitchingArea = 1;
+ this.prgSwitchingSize = 1;
+ this.vromSwitchingSize = 0;
+
+ // Register 1:
+ this.romSelectionReg0 = 0;
+
+ // Register 2:
+ this.romSelectionReg1 = 0;
+
+ // Register 3:
+ this.romBankSelect = 0;
+
+}
+
+Mapper001.prototype.switchLowHighPrgRom = function(oldSetting){
+
+ // not yet.
+
+}
+
+Mapper001.prototype.switch16to32 = function(){
+
+ // not yet.
+
+}
+
+Mapper001.prototype.switch32to16 = function(){
+
+ // not yet.
+
+}
+
+function Mapper002(nes) {
+ this.nes = nes
+}
+
+copyPrototype(Mapper002, MapperDefault);
+
+Mapper002.prototype.write = function(address, value){
+
+ // Writes to addresses other than MMC registers are handled by NoMapper.
+ if(address < 0x8000){
+ MapperDefault.prototype.write.apply(this, arguments);
+ return;
+ }
+
+ else{
+
+ // This is a ROM bank select command.
+ // Swap in the given ROM bank at 0x8000:
+ this.loadRomBank(value,0x8000);
+
+ }
+}
+
+Mapper002.prototype.loadROM = function(rom){
+
+ if(!this.nes.rom.valid){
+ alert("UNROM: Invalid ROM! Unable to load.");
+ return;
+ }
+
+ // Load PRG-ROM:
+ this.loadRomBank(0,0x8000);
+ this.loadRomBank(this.nes.rom.romCount-1,0xC000);
+
+ // Load CHR-ROM:
+ this.loadCHRROM();
+
+ // Do Reset-Interrupt:
+ this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);
+
+}
+
+function Mapper004(nes) {
+ this.nes = nes
+
+ this.CMD_SEL_2_1K_VROM_0000 = 0;
+ this.CMD_SEL_2_1K_VROM_0800 = 1;
+ this.CMD_SEL_1K_VROM_1000 = 2;
+ this.CMD_SEL_1K_VROM_1400 = 3;
+ this.CMD_SEL_1K_VROM_1800 = 4;
+ this.CMD_SEL_1K_VROM_1C00 = 5;
+ this.CMD_SEL_ROM_PAGE1 = 6;
+ this.CMD_SEL_ROM_PAGE2 = 7;
+
+ this.command = null;
+ this.prgAddressSelect = null;
+ this.chrAddressSelect = null;
+ this.pageNumber = null;
+ this.irqCounter = null;
+ this.irqLatchValue = null;
+ this.irqEnable = null;
+ this.prgAddressChanged = false;
+}
+
+copyPrototype(Mapper004, MapperDefault);
+
+Mapper004.prototype.write = function(address, value){
+
+ // Writes to addresses other than MMC registers are handled by NoMapper.
+ if(address < 0x8000){
+ MapperDefault.prototype.write.apply(this, arguments);
+ return;
+ }
+
+ switch (address) {
+ case 0x8000:
+ // Command/Address Select register
+ this.command = value&7;
+ var tmp = (value>>6)&1;
+ if(tmp != this.prgAddressSelect){
+ this.prgAddressChanged = true;
+ }
+ this.prgAddressSelect = tmp;
+ this.chrAddressSelect = (value>>7)&1;
+ break;
+
+ case 0x8001:
+ // Page number for command
+ this.executeCommand(this.command,value);
+ break;
+
+ case 0xA000:
+ // Mirroring select
+ if((value&1) != 0){
+ this.nes.ppu.setMirroring(this.nes.rom.HORIZONTAL_MIRRORING);
+ }else{
+ this.nes.ppu.setMirroring(this.nes.rom.VERTICAL_MIRRORING);
+ }
+ break;
+
+ case 0xA001:
+ // SaveRAM Toggle
+ // TODO
+ //nes.getRom().setSaveState((value&1)!=0);
+ break;
+
+ case 0xC000:
+ // IRQ Counter register
+ this.irqCounter = value;
+ //nes.ppu.mapperIrqCounter = 0;
+ break;
+
+ case 0xC001:
+ // IRQ Latch register
+ this.irqLatchValue = value;
+ break;
+
+ case 0xE000:
+ // IRQ Control Reg 0 (disable)
+ //irqCounter = irqLatchValue;
+ this.irqEnable = 0;
+ break;
+
+ case 0xE001:
+ // IRQ Control Reg 1 (enable)
+ this.irqEnable = 1;
+ break;
+
+ default:
+ // Not a MMC3 register.
+ // The game has probably crashed,
+ // since it tries to write to ROM..
+ // IGNORE.
+
+ }
+}
+
+Mapper004.prototype.executeCommand = function(cmd, arg){
+ switch (cmd) {
+ case this.CMD_SEL_2_1K_VROM_0000:
+ // Select 2 1KB VROM pages at 0x0000:
+ if(this.chrAddressSelect == 0){
+ this.load1kVromBank(arg,0x0000);
+ this.load1kVromBank(arg+1,0x0400);
+ }else{
+ this.load1kVromBank(arg,0x1000);
+ this.load1kVromBank(arg+1,0x1400);
+ }
+ break;
+
+ case this.CMD_SEL_2_1K_VROM_0800:
+ // Select 2 1KB VROM pages at 0x0800:
+ if(this.chrAddressSelect == 0){
+ this.load1kVromBank(arg,0x0800);
+ this.load1kVromBank(arg+1,0x0C00);
+ }else{
+ this.load1kVromBank(arg,0x1800);
+ this.load1kVromBank(arg+1,0x1C00);
+ }
+ break;
+
+ case this.CMD_SEL_1K_VROM_1000:
+ // Select 1K VROM Page at 0x1000:
+ if(this.chrAddressSelect == 0){
+ this.load1kVromBank(arg,0x1000);
+ }else{
+ this.load1kVromBank(arg,0x0000);
+ }
+ break;
+
+ case this.CMD_SEL_1K_VROM_1400:
+ // Select 1K VROM Page at 0x1400:
+ if(this.chrAddressSelect == 0){
+ this.load1kVromBank(arg,0x1400);
+ }else{
+ this.load1kVromBank(arg,0x0400);
+ }
+ break;
+
+ case this.CMD_SEL_1K_VROM_1800:
+ // Select 1K VROM Page at 0x1800:
+ if(this.chrAddressSelect == 0){
+ this.load1kVromBank(arg,0x1800);
+ }else{
+ this.load1kVromBank(arg,0x0800);
+ }
+ break;
+
+ case this.CMD_SEL_1K_VROM_1C00:
+ // Select 1K VROM Page at 0x1C00:
+ if(this.chrAddressSelect == 0){
+ this.load1kVromBank(arg,0x1C00);
+ }else{
+ this.load1kVromBank(arg,0x0C00);
+ }
+ break;
+
+ case this.CMD_SEL_ROM_PAGE1:
+ if(this.prgAddressChanged){
+ // Load the two hardwired banks:
+ if(this.prgAddressSelect == 0){
+ this.load8kRomBank(
+ ((this.nes.rom.romCount-1)*2),
+ 0xC000);
+ } else {
+ this.load8kRomBank(
+ ((this.nes.rom.romCount-1)*2),
+ 0x8000);
+ }
+ this.prgAddressChanged = false;
+ }
+
+ // Select first switchable ROM page:
+ if(this.prgAddressSelect == 0){
+ this.load8kRomBank(arg,0x8000);
+ }else{
+ this.load8kRomBank(arg,0xC000);
+ }
+ break;
+
+ case this.CMD_SEL_ROM_PAGE2:
+
+ // Select second switchable ROM page:
+ this.load8kRomBank(arg,0xA000);
+
+ // hardwire appropriate bank:
+ if(this.prgAddressChanged){
+ // Load the two hardwired banks:
+ if(this.prgAddressSelect == 0){
+ this.load8kRomBank(
+ ((this.nes.rom.romCount-1)*2),
+ 0xC000);
+ }else{
+ this.load8kRomBank(
+ ((this.nes.rom.romCount-1)*2),
+ 0x8000);
+ }
+ this.prgAddressChanged = false;
+ }
+ }
+
+}
+
+Mapper004.prototype.loadROM = function(rom){
+
+ if(!this.nes.rom.valid){
+ alert("MMC3: Invalid ROM! Unable to load.");
+ return;
+ }
+
+ // Load hardwired PRG banks (0xC000 and 0xE000):
+ this.load8kRomBank(((this.nes.rom.romCount-1)*2) ,0xC000);
+ this.load8kRomBank(((this.nes.rom.romCount-1)*2)+1,0xE000);
+
+ // Load swappable PRG banks (0x8000 and 0xA000):
+ this.load8kRomBank(0,0x8000);
+ this.load8kRomBank(1,0xA000);
+
+ // Load CHR-ROM:
+ this.loadCHRROM();
+
+ // Load Battery RAM (if present):
+ this.loadBatteryRam();
+
+ // Do Reset-Interrupt:
+ this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);
+
+}
diff --git a/js/nametable.js b/js/nametable.js
new file mode 100644
index 0000000..92a09a5
--- /dev/null
+++ b/js/nametable.js
@@ -0,0 +1,43 @@
+function NameTable(width, height, name) {
+ this.name = name;
+
+ this.tile = new Array(width*height);
+ this.attrib = new Array(width*height);
+
+ this.width = width;
+ this.height = height;
+}
+
+NameTable.prototype.getTileIndex = function(x, y){
+ return this.tile[y*this.width+x];
+}
+
+NameTable.prototype.getAttrib = function(x, y){
+ return this.attrib[y*this.width+x];
+}
+
+NameTable.prototype.writeAttrib = function(index, value){
+ var basex,basey;
+ var add;
+ var tx,ty;
+ var attindex;
+ basex = index%8;
+ basey = parseInt(index/8);
+ basex *= 4;
+ basey *= 4;
+
+ for(var sqy=0;sqy<2;sqy++){
+ for(var sqx=0;sqx<2;sqx++){
+ add = (value>>(2*(sqy*2+sqx)))&3;
+ for(var y=0;y<2;y++){
+ for(var x=0;x<2;x++){
+ tx = basex+sqx*2+x;
+ ty = basey+sqy*2+y;
+ attindex = ty*this.width+tx;
+ this.attrib[ty*this.width+tx] = (add<<2)&12;
+ ////System.out.println("x="+tx+" y="+ty+" value="+attrib[ty*width+tx]+" index="+attindex);
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/js/nes.js b/js/nes.js
new file mode 100644
index 0000000..0f77430
--- /dev/null
+++ b/js/nes.js
@@ -0,0 +1,250 @@
+function NES() {
+ Globals.nes = this;
+
+ this.cpuMem = new Array(0x10000); // Main memory (internal to CPU)
+ this.ppuMem = new Array(0x8000); // VRAM memory (internal to PPU)
+ this.sprMem = new Array(0x100); // Sprite RAM (internal to PPU)
+
+ this.cpu = new CPU(this);
+ this.ppu = new PPU(this);
+ this.papu = new PAPU(this);
+ this.memMapper = null;
+ this.palTable = new PaletteTable();
+ this.rom = null;
+
+ this.romFile = null;
+ this.isRunning = false;
+ this.lastFrameTime = null;
+ this.lastFpsTime = null;
+ this.fpsFrameCount = 0;
+ this.crashMessage = null;
+ this.limitFrames = true;
+
+ this.palTable.loadNTSCPalette();
+ //this.palTable.loadDefaultPalette();
+
+ // Graphics
+ this.canvas = document.getElementById('screen');
+ this.ctx = this.canvas.getContext('2d');
+ this.imageData = this.ctx.getImageData(0,0,256,240);
+ this.ctx.fillStyle = 'black';
+ this.ctx.fillRect(0,0,256,240); /* set alpha to opaque */
+ // Set alpha
+ for (var i = 3; i < this.imageData.data.length-3; i+=4) {
+ this.imageData.data[i] = 0xFF;
+ }
+
+ // Init sound registers:
+ for(var i=0;i<0x14;i++){
+ if(i==0x10){
+ this.papu.writeReg(0x4010, 0x10);
+ }else{
+ this.papu.writeReg(0x4000+i, 0);
+ }
+ }
+
+ this.start = function() {
+ if(this.rom != null && this.rom.valid) {
+ if (!this.isRunning) {
+ //$("#status").text("Running "+this.romFile)
+ this.isRunning = true;
+
+ this.frameInterval = setInterval(runFrame, Globals.frameTime/2);
+ this.resetFps();
+ this.printFps();
+ this.fpsInterval = setInterval(runPrintFps, Globals.fpsInterval);
+ }
+ }
+ else {
+ alert("There is no ROM loaded, or it is invalid.");
+ }
+ }
+
+ this.frame = function() {
+ this.ppu.startFrame();
+ var cycles = 0;
+ var emulateSound = Globals.emulateSound;
+ var cpu = this.cpu;
+ var ppu = this.ppu;
+ var papu = this.papu;
+ FRAMELOOP: for (;;) {
+ if (cpu.cyclesToHalt == 0) {
+ // Execute a CPU instruction
+ cycles = cpu.emulate();
+ if (emulateSound) {
+ papu.clockFrameCounter(cycles);
+ }
+ cycles *= 3;
+ }
+ else {
+ if (cpu.cyclesToHalt > 8) {
+ cycles = 24;
+ if (emulateSound) {
+ papu.clockFrameCounter(8);
+ }
+ cpu.cyclesToHalt -= 8;
+ }
+ else {
+ cycles = cpu.cyclesToHalt * 3;
+ if (emulateSound) {
+ papu.clockFrameCounter(cpu.cyclesToHalt);
+ }
+ cpu.cyclesToHalt = 0;
+ }
+ }
+
+ for(;cycles>0;cycles--){
+
+ if(ppu.curX == ppu.spr0HitX
+ && ppu.f_spVisibility==1
+ && ppu.scanline-21 == ppu.spr0HitY){
+ // Set sprite 0 hit flag:
+ ppu.setStatusFlag(ppu.STATUS_SPRITE0HIT,true);
+ }
+
+ if(ppu.requestEndFrame){
+ ppu.nmiCounter--;
+ if(ppu.nmiCounter == 0){
+ ppu.requestEndFrame = false;
+ ppu.startVBlank();
+ break FRAMELOOP;
+ }
+ }
+
+ ppu.curX++;
+ if(ppu.curX==341){
+ ppu.curX = 0;
+ ppu.endScanline();
+ }
+
+ }
+ }
+ if (this.limitFrames) {
+ if (this.lastFrameTime) {
+ while ((new Date()).getTime() - this.lastFrameTime < Globals.frameTime) {
+ // twiddle thumbs
+ }
+ }
+ }
+ this.ctx.putImageData(this.imageData, 0, 0);
+ this.fpsFrameCount++;
+ this.lastFrameTime = (new Date()).getTime();
+ }
+
+ this.printFps = function() {
+ var now = (new Date()).getTime();
+ var s = 'Running';
+ if (this.lastFpsTime) {
+ s += ': '+(this.fpsFrameCount/((now-this.lastFpsTime)/1000)).toFixed(2)+' FPS';
+ }
+ $("#status").text(s);
+ this.fpsFrameCount = 0;
+ this.lastFpsTime = now;
+ }
+
+ this.stop = function() {
+ //$("#status").text("Stopped.");
+ clearInterval(this.frameInterval);
+ clearInterval(this.fpsInterval);
+ this.isRunning = false;
+ }
+
+ this.reloadRom = function() {
+ if(this.romFile != null){
+ this.loadRom(this.romFile);
+ }
+ }
+
+ this.clearCPUMemory = function() {
+ for(var i=0;i<0x2000;i++) {
+ this.cpuMem[i] = Globals.memoryFlushValue;
+ }
+ for(var p=0;p<4;p++){
+ var i = p*0x800;
+ this.cpuMem[i+0x008] = 0xF7;
+ this.cpuMem[i+0x009] = 0xEF;
+ this.cpuMem[i+0x00A] = 0xDF;
+ this.cpuMem[i+0x00F] = 0xBF;
+ }
+ }
+
+ // Loads a ROM file into the CPU and PPU.
+ // The ROM file is validated first.
+ this.loadRom = function(file){
+ // Can't load ROM while still running.
+ if(this.isRunning)
+ this.stop();
+
+ $("#status").text("Loading "+file);
+
+ // Load ROM file:
+ this.rom = new ROM(this);
+ this.rom.load(file);
+ if(this.rom.valid){
+
+ // The CPU will load
+ // the ROM into the CPU
+ // and PPU memory.
+
+ this.reset();
+
+ this.memMapper = this.rom.createMapper();
+ if (!this.memMapper) return;
+ this.cpu.mmap = this.memMapper;
+ this.memMapper.loadROM();
+ this.ppu.setMirroring(this.rom.getMirroringType());
+ this.romFile = file;
+
+ $("#status").text(file+" successfully loaded. Ready to be started.");
+ }
+ else {
+ $("#status").text(file+" is an invalid ROM!");
+ }
+ return this.rom.valid;
+ }
+
+ // Resets the system.
+ this.reset = function(){
+ if(this.rom!=null)
+ this.rom.closeRom();
+ if(this.memMapper != null)
+ this.memMapper.reset();
+ for (var i=0; i<this.cpuMem.length; i++) this.cpuMem[i] = 0;
+ for (var i=0; i<this.ppuMem.length; i++) this.ppuMem[i] = 0;
+ for (var i=0; i<this.sprMem.length; i++) this.sprMem[i] = 0;
+ this.clearCPUMemory();
+ this.cpu.reset();
+ this.cpu.init();
+ this.ppu.reset();
+ this.palTable.reset();
+
+ this.papu.reset();
+ }
+
+ this.resetFps = function() {
+ this.lastFpsTime = null;
+ this.fpsFrameCount = 0;
+ }
+
+ this.setFramerate = function(rate){
+ Globals.preferredFrameRate = rate;
+ Globals.frameTime = 1000/rate;
+ papu.setSampleRate(Globals.sampleRate, false);
+ }
+
+ this.setLimitFrames = function(limit) {
+ this.limitFrames = limit;
+ this.lastFrameTime = null;
+ }
+
+ this.cpu.init();
+ this.ppu.init();
+
+ this.clearCPUMemory();
+
+ $("#status").text("Initialised. Ready to load a ROM.");
+
+}
+
+function runFrame() { Globals.nes.frame(); }
+function runPrintFps() { Globals.nes.printFps(); }
diff --git a/js/palettetable.js b/js/palettetable.js
new file mode 100644
index 0000000..d8d8af3
--- /dev/null
+++ b/js/palettetable.js
@@ -0,0 +1,163 @@
+function PaletteTable() {
+ this.curTable = new Array(64);
+ this.emphTable = new Array(8);
+ this.currentEmph = -1;
+
+ this.loadNTSCPalette = function() {
+ this.curTable = [0x525252, 0xB40000, 0xA00000, 0xB1003D, 0x740069, 0x00005B, 0x00005F, 0x001840, 0x002F10, 0x084A08, 0x006700, 0x124200, 0x6D2800, 0x000000, 0x000000, 0x000000, 0xC4D5E7, 0xFF4000, 0xDC0E22, 0xFF476B, 0xD7009F, 0x680AD7, 0x0019BC, 0x0054B1, 0x006A5B, 0x008C03, 0x00AB00, 0x2C8800, 0xA47200, 0x000000, 0x000000, 0x000000, 0xF8F8F8, 0xFFAB3C, 0xFF7981, 0xFF5BC5, 0xFF48F2, 0xDF49FF, 0x476DFF, 0x00B4F7, 0x00E0FF, 0x00E375, 0x03F42B, 0x78B82E, 0xE5E218, 0x787878, 0x000000, 0x000000, 0xFFFFFF, 0xFFF2BE, 0xF8B8B8, 0xF8B8D8, 0xFFB6FF, 0xFFC3FF, 0xC7D1FF, 0x9ADAFF, 0x88EDF8, 0x83FFDD, 0xB8F8B8, 0xF5F8AC, 0xFFFFB0, 0xF8D8F8, 0x000000, 0x000000];
+ this.makeTables();
+ this.setEmphasis(0);
+ }
+
+ this.loadPALPalette = function() {
+ this.curTable = [0x525252, 0xB40000, 0xA00000, 0xB1003D, 0x740069, 0x00005B, 0x00005F, 0x001840, 0x002F10, 0x084A08, 0x006700, 0x124200, 0x6D2800, 0x000000, 0x000000, 0x000000, 0xC4D5E7, 0xFF4000, 0xDC0E22, 0xFF476B, 0xD7009F, 0x680AD7, 0x0019BC, 0x0054B1, 0x006A5B, 0x008C03, 0x00AB00, 0x2C8800, 0xA47200, 0x000000, 0x000000, 0x000000, 0xF8F8F8, 0xFFAB3C, 0xFF7981, 0xFF5BC5, 0xFF48F2, 0xDF49FF, 0x476DFF, 0x00B4F7, 0x00E0FF, 0x00E375, 0x03F42B, 0x78B82E, 0xE5E218, 0x787878, 0x000000, 0x000000, 0xFFFFFF, 0xFFF2BE, 0xF8B8B8, 0xF8B8D8, 0xFFB6FF, 0xFFC3FF, 0xC7D1FF, 0x9ADAFF, 0x88EDF8, 0x83FFDD, 0xB8F8B8, 0xF5F8AC, 0xFFFFB0, 0xF8D8F8, 0x000000, 0x000000];
+ this.makeTables();
+ this.setEmphasis(0);
+ }
+
+ this.makeTables = function(){
+
+ var r,g,b,col;
+
+ // Calculate a table for each possible emphasis setting:
+ for(var emph=0;emph<8;emph++){
+
+ // Determine color component factors:
+ var rFactor=1.0, gFactor=1.0, bFactor=1.0;
+ if((emph&1)!=0){
+ rFactor = 0.75;
+ bFactor = 0.75;
+ }
+ if((emph&2)!=0){
+ rFactor = 0.75;
+ gFactor = 0.75;
+ }
+ if((emph&4)!=0){
+ gFactor = 0.75;
+ bFactor = 0.75;
+ }
+
+ this.emphTable[emph] = new Array(64);
+
+ // Calculate table:
+ for(var i=0;i<64;i++){
+
+ col = this.curTable[i];
+ r = parseInt(this.getRed(col) * rFactor);
+ g = parseInt(this.getGreen(col) * gFactor);
+ b = parseInt(this.getBlue(col) * bFactor);
+ this.emphTable[emph][i] = this.getRgb(r,g,b);
+
+ }
+
+ }
+
+ }
+
+ this.setEmphasis = function(emph){
+
+ if(emph != this.currentEmph){
+ this.currentEmph = emph;
+ for(var i=0;i<64;i++){
+ this.curTable[i] = this.emphTable[emph][i];
+ }
+ }
+
+ }
+
+ this.getEntry = function(yiq){
+ return this.curTable[yiq];
+ }
+
+ this.getRed = function(rgb){
+ return (rgb>>16)&0xFF;
+ }
+
+ this.getGreen = function(rgb){
+ return (rgb>>8)&0xFF;
+ }
+
+ this.getBlue = function(rgb){
+ return rgb&0xFF;
+ }
+
+ this.getRgb = function(r, g, b){
+ return ((r<<16)|(g<<8)|(b));
+ }
+
+ this.loadDefaultPalette = function(){
+
+ this.curTable[ 0] = this.getRgb(117,117,117);
+ this.curTable[ 1] = this.getRgb( 39, 27,143);
+ this.curTable[ 2] = this.getRgb( 0, 0,171);
+ this.curTable[ 3] = this.getRgb( 71, 0,159);
+ this.curTable[ 4] = this.getRgb(143, 0,119);
+ this.curTable[ 5] = this.getRgb(171, 0, 19);
+ this.curTable[ 6] = this.getRgb(167, 0, 0);
+ this.curTable[ 7] = this.getRgb(127, 11, 0);
+ this.curTable[ 8] = this.getRgb( 67, 47, 0);
+ this.curTable[ 9] = this.getRgb( 0, 71, 0);
+ this.curTable[10] = this.getRgb( 0, 81, 0);
+ this.curTable[11] = this.getRgb( 0, 63, 23);
+ this.curTable[12] = this.getRgb( 27, 63, 95);
+ this.curTable[13] = this.getRgb( 0, 0, 0);
+ this.curTable[14] = this.getRgb( 0, 0, 0);
+ this.curTable[15] = this.getRgb( 0, 0, 0);
+ this.curTable[16] = this.getRgb(188,188,188);
+ this.curTable[17] = this.getRgb( 0,115,239);
+ this.curTable[18] = this.getRgb( 35, 59,239);
+ this.curTable[19] = this.getRgb(131, 0,243);
+ this.curTable[20] = this.getRgb(191, 0,191);
+ this.curTable[21] = this.getRgb(231, 0, 91);
+ this.curTable[22] = this.getRgb(219, 43, 0);
+ this.curTable[23] = this.getRgb(203, 79, 15);
+ this.curTable[24] = this.getRgb(139,115, 0);
+ this.curTable[25] = this.getRgb( 0,151, 0);
+ this.curTable[26] = this.getRgb( 0,171, 0);
+ this.curTable[27] = this.getRgb( 0,147, 59);
+ this.curTable[28] = this.getRgb( 0,131,139);
+ this.curTable[29] = this.getRgb( 0, 0, 0);
+ this.curTable[30] = this.getRgb( 0, 0, 0);
+ this.curTable[31] = this.getRgb( 0, 0, 0);
+ this.curTable[32] = this.getRgb(255,255,255);
+ this.curTable[33] = this.getRgb( 63,191,255);
+ this.curTable[34] = this.getRgb( 95,151,255);
+ this.curTable[35] = this.getRgb(167,139,253);
+ this.curTable[36] = this.getRgb(247,123,255);
+ this.curTable[37] = this.getRgb(255,119,183);
+ this.curTable[38] = this.getRgb(255,119, 99);
+ this.curTable[39] = this.getRgb(255,155, 59);
+ this.curTable[40] = this.getRgb(243,191, 63);
+ this.curTable[41] = this.getRgb(131,211, 19);
+ this.curTable[42] = this.getRgb( 79,223, 75);
+ this.curTable[43] = this.getRgb( 88,248,152);
+ this.curTable[44] = this.getRgb( 0,235,219);
+ this.curTable[45] = this.getRgb( 0, 0, 0);
+ this.curTable[46] = this.getRgb( 0, 0, 0);
+ this.curTable[47] = this.getRgb( 0, 0, 0);
+ this.curTable[48] = this.getRgb(255,255,255);
+ this.curTable[49] = this.getRgb(171,231,255);
+ this.curTable[50] = this.getRgb(199,215,255);
+ this.curTable[51] = this.getRgb(215,203,255);
+ this.curTable[52] = this.getRgb(255,199,255);
+ this.curTable[53] = this.getRgb(255,199,219);
+ this.curTable[54] = this.getRgb(255,191,179);
+ this.curTable[55] = this.getRgb(255,219,171);
+ this.curTable[56] = this.getRgb(255,231,163);
+ this.curTable[57] = this.getRgb(227,255,163);
+ this.curTable[58] = this.getRgb(171,243,191);
+ this.curTable[59] = this.getRgb(179,255,207);
+ this.curTable[60] = this.getRgb(159,255,243);
+ this.curTable[61] = this.getRgb( 0, 0, 0);
+ this.curTable[62] = this.getRgb( 0, 0, 0);
+ this.curTable[63] = this.getRgb( 0, 0, 0);
+
+ this.makeTables();
+ this.setEmphasis(0);
+ }
+
+ this.reset = function(){
+ this.setEmphasis(0);
+ }
+
+} \ No newline at end of file
diff --git a/js/papu.js b/js/papu.js
new file mode 100644
index 0000000..6c1efb8
--- /dev/null
+++ b/js/papu.js
@@ -0,0 +1,789 @@
+
+function PAPU(nes) {
+ this.nes = nes;
+
+ this.square1 = new ChannelSquare(this,true);
+ this.square2 = new ChannelSquare(this,false);
+ this.triangle = new ChannelTriangle(this);
+ this.noise = new ChannelNoise(this);
+ this.dmc = new ChannelDM(this);
+
+ this.frameIrqCounter = null;
+ this.frameIrqCounterMax = 4;
+ this.initCounter = 2048;
+ this.channelEnableValue = null;
+
+ this.bufferSize = 8192;
+ this.bufferIndex = 0;
+ this.sampleRate = 44100;
+
+ this.lengthLookup = null;
+ this.dmcFreqLookup = null;
+ this.noiseWavelengthLookup = null;
+ this.square_table = null;
+ this.tnd_table = null;
+ this.sampleBuffer = new Array(this.bufferSize*2);
+
+ this.frameIrqEnabled = false;
+ this.frameIrqActive;
+ this.frameClockNow;
+ this.startedPlaying=false;
+ this.recordOutput = false;
+ this.initingHardware = false;
+
+ this.masterFrameCounter = null;
+ this.derivedFrameCounter = null;
+ this.countSequence = null;
+ this.sampleTimer = null;
+ this.frameTime = null;
+ this.sampleTimerMax = null;
+ this.sampleCount = null;
+
+ this.smpSquare1 = null;
+ this.smpSquare2 = null;
+ this.smpTriangle = null;
+ this.smpDmc = null;
+ this.accCount = null;
+
+ // DC removal vars:
+ this.prevSampleL = 0;
+ this.prevSampleR = 0;
+ this.smpAccumL = 0
+ this.smpAccumR = 0;
+
+ // DAC range:
+ this.dacRange = 0;
+ this.dcValue = 0;
+
+ // Master volume:
+ this.masterVolume = 256;
+
+
+ // Stereo positioning:
+ this.stereoPosLSquare1 = null;
+ this.stereoPosLSquare2 = null;
+ this.stereoPosLTriangle = null;
+ this.stereoPosLNoise = null;
+ this.stereoPosLDMC = null;
+ this.stereoPosRSquare1 = null;
+ this.stereoPosRSquare2 = null;
+ this.stereoPosRTriangle = null;
+ this.stereoPosRNoise = null;
+ this.stereoPosRDMC = null;
+
+ this.extraCycles = null;
+
+ // Panning:
+ this.panning = new Array(
+ 80,
+ 170,
+ 100,
+ 150,
+ 128
+ );
+ this.setPanning(this.panning);
+
+ // Initialize lookup tables:
+ this.initLengthLookup();
+ this.initDmcFrequencyLookup();
+ this.initNoiseWavelengthLookup();
+ this.initDACtables();
+
+ this.reset();
+}
+
+PAPU.prototype.reset = function(){
+ this.sampleRate = Globals.sampleRate;
+ this.sampleTimerMax = parseInt((1024.0*Globals.CPU_FREQ_NTSC*Globals.preferredFrameRate) /
+ (this.sampleRate*60.0));
+
+ this.frameTime = parseInt((14915.0*Globals.preferredFrameRate)/60.0);
+
+ this.sampleTimer = 0;
+ this.bufferIndex = 0;
+
+ this.updateChannelEnable(0);
+ this.masterFrameCounter = 0;
+ this.derivedFrameCounter = 0;
+ this.countSequence = 0;
+ this.sampleCount = 0;
+ this.initCounter = 2048;
+ this.frameIrqEnabled = false;
+ this.initingHardware = false;
+
+ this.resetCounter();
+
+ this.square1.reset();
+ this.square2.reset();
+ this.triangle.reset();
+ this.noise.reset();
+ this.dmc.reset();
+
+ this.bufferIndex = 0;
+ this.accCount = 0;
+ this.smpSquare1 = 0;
+ this.smpSquare2 = 0;
+ this.smpTriangle = 0;
+ this.smpDmc = 0;
+
+ this.frameIrqEnabled = false;
+ this.frameIrqCounterMax = 4;
+
+ this.channelEnableValue = 0xFF;
+ this.startedPlaying = false;
+ this.prevSampleL = 0;
+ this.prevSampleR = 0;
+ this.smpAccumL = 0;
+ this.smpAccumR = 0;
+
+ this.maxSample = -500000;
+ this.minSample = 500000;
+}
+
+PAPU.prototype.readReg = function(address){
+ // Read 0x4015:
+ var tmp = 0;
+ tmp |= (this.square1.getLengthStatus() );
+ tmp |= (this.square2.getLengthStatus() <<1);
+ tmp |= (this.triangle.getLengthStatus()<<2);
+ tmp |= (this.noise.getLengthStatus() <<3);
+ tmp |= (this.dmc.getLengthStatus() <<4);
+ tmp |= (((this.frameIrqActive && this.frameIrqEnabled)?1:0)<<6);
+ tmp |= (this.dmc.getIrqStatus() <<7);
+
+ this.frameIrqActive = false;
+ this.dmc.irqGenerated = false;
+
+ return tmp&0xFFFF;
+}
+
+PAPU.prototype.writeReg = function(address, value){
+
+ if(address>=0x4000 && address<0x4004){
+
+ // Square Wave 1 Control
+ this.square1.writeReg(address,value);
+ ////System.out.println("Square Write");
+
+ }else if(address>=0x4004 && address<0x4008){
+
+ // Square 2 Control
+ this.square2.writeReg(address,value);
+
+ }else if(address>=0x4008 && address<0x400C){
+
+ // Triangle Control
+ this.triangle.writeReg(address,value);
+
+ }else if(address>=0x400C && address<=0x400F){
+
+ // Noise Control
+ this.noise.writeReg(address,value);
+
+ }else if(address == 0x4010){
+
+ // DMC Play mode & DMA frequency
+ this.dmc.writeReg(address,value);
+
+ }else if(address == 0x4011){
+
+ // DMC Delta Counter
+ this.dmc.writeReg(address,value);
+
+ }else if(address == 0x4012){
+
+ // DMC Play code starting address
+ this.dmc.writeReg(address,value);
+
+ }else if(address == 0x4013){
+
+ // DMC Play code length
+ this.dmc.writeReg(address,value);
+
+ }else if(address == 0x4015){
+
+ // Channel enable
+ this.updateChannelEnable(value);
+
+ if(value!=0 && this.initCounter>0){
+
+ // Start hardware initialization
+ this.initingHardware = true;
+
+ }
+
+ // DMC/IRQ Status
+ this.dmc.writeReg(address,value);
+
+ }else if(address == 0x4017){
+
+
+ // Frame counter control
+ this.countSequence = (value>>7)&1;
+ this.masterFrameCounter = 0;
+ this.frameIrqActive = false;
+
+ if(((value>>6)&0x1)==0){
+ this.frameIrqEnabled = true;
+ }else{
+ this.frameIrqEnabled = false;
+ }
+
+ if(this.countSequence == 0){
+
+ // NTSC:
+ this.frameIrqCounterMax = 4;
+ this.derivedFrameCounter = 4;
+
+ }else{
+
+ // PAL:
+ this.frameIrqCounterMax = 5;
+ this.derivedFrameCounter = 0;
+ this.frameCounterTick();
+
+ }
+
+ }
+}
+
+PAPU.prototype.resetCounter = function(){
+
+ if(this.countSequence==0){
+ this.derivedFrameCounter = 4;
+ }else{
+ this.derivedFrameCounter = 0;
+ }
+
+}
+
+
+// Updates channel enable status.
+// This is done on writes to the
+// channel enable register (0x4015),
+// and when the user enables/disables channels
+// in the GUI.
+PAPU.prototype.updateChannelEnable = function(value){
+
+ this.channelEnableValue = value&0xFFFF;
+ this.square1.setEnabled((value&1)!=0);
+ this.square2.setEnabled((value&2)!=0);
+ this.triangle.setEnabled((value&4)!=0);
+ this.noise.setEnabled((value&8)!=0);
+ this.dmc.setEnabled((value&16)!=0);
+
+}
+
+// Clocks the frame counter. It should be clocked at
+// twice the cpu speed, so the cycles will be
+// divided by 2 for those counters that are
+// clocked at cpu speed.
+PAPU.prototype.clockFrameCounter = function(nCycles){
+
+ if(this.initCounter > 0){
+ if(this.initingHardware){
+ this.initCounter -= nCycles;
+ if(this.initCounter<=0) this.initingHardware = false;
+ return;
+ }
+ }
+
+ // Don't process ticks beyond next sampling:
+ nCycles += this.extraCycles;
+ var maxCycles = this.sampleTimerMax-this.sampleTimer;
+ if((nCycles<<10) > maxCycles){
+
+ this.extraCycles = ((nCycles<<10) - maxCycles)>>10;
+ nCycles -= this.extraCycles;
+
+ }else{
+
+ this.extraCycles = 0;
+
+ }
+
+ var dmc = this.dmc;
+ var triangle = this.triangle;
+ var square1 = this.square1;
+ var square2 = this.square2;
+ var noise = this.noise;
+
+ // Clock DMC:
+ if(dmc.isEnabled){
+
+ dmc.shiftCounter-=(nCycles<<3);
+ while(dmc.shiftCounter<=0 && dmc.dmaFrequency>0){
+ dmc.shiftCounter += dmc.dmaFrequency;
+ dmc.clockDmc();
+ }
+
+ }
+
+ // Clock Triangle channel Prog timer:
+ if(triangle.progTimerMax>0){
+
+ triangle.progTimerCount -= nCycles;
+ while(triangle.progTimerCount <= 0){
+
+ triangle.progTimerCount += triangle.progTimerMax+1;
+ if(triangle.linearCounter>0 && triangle.lengthCounter>0){
+
+ triangle.triangleCounter++;
+ triangle.triangleCounter &= 0x1F;
+
+ if(triangle.isEnabled){
+ if(triangle.triangleCounter>=0x10){
+ // Normal value.
+ triangle.sampleValue = (triangle.triangleCounter&0xF);
+ }else{
+ // Inverted value.
+ triangle.sampleValue = (0xF - (triangle.triangleCounter&0xF));
+ }
+ triangle.sampleValue <<= 4;
+ }
+
+ }
+ }
+
+ }
+
+ // Clock Square channel 1 Prog timer:
+ square1.progTimerCount -= nCycles;
+ if(square1.progTimerCount <= 0){
+
+ square1.progTimerCount += (square1.progTimerMax+1)<<1;
+
+ square1.squareCounter++;
+ square1.squareCounter&=0x7;
+ square1.updateSampleValue();
+
+ }
+
+ // Clock Square channel 2 Prog timer:
+ square2.progTimerCount -= nCycles;
+ if(square2.progTimerCount <= 0){
+
+ square2.progTimerCount += (square2.progTimerMax+1)<<1;
+
+ square2.squareCounter++;
+ square2.squareCounter&=0x7;
+ square2.updateSampleValue();
+
+ }
+
+ // Clock noise channel Prog timer:
+ var acc_c = nCycles;
+ if(noise.progTimerCount-acc_c > 0){
+
+ // Do all cycles at once:
+ noise.progTimerCount -= acc_c;
+ noise.accCount += acc_c;
+ noise.accValue += acc_c * noise.sampleValue;
+
+ }else{
+
+ // Slow-step:
+ while((acc_c--) > 0){
+
+ if(--noise.progTimerCount <= 0 && noise.progTimerMax>0){
+
+ // Update noise shift register:
+ noise.shiftReg <<= 1;
+ noise.tmp = (((noise.shiftReg << (noise.randomMode==0?1:6)) ^ noise.shiftReg) & 0x8000 );
+ if(noise.tmp!=0){
+
+ // Sample value must be 0.
+ noise.shiftReg |= 0x01;
+ noise.randomBit = 0;
+ noise.sampleValue = 0;
+
+ }else{
+
+ // Find sample value:
+ noise.randomBit = 1;
+ if(noise.isEnabled && noise.lengthCounter>0){
+ noise.sampleValue = noise.masterVolume;
+ }else{
+ noise.sampleValue = 0;
+ }
+
+ }
+
+ noise.progTimerCount += noise.progTimerMax;
+
+ }
+
+ noise.accValue += noise.sampleValue;
+ noise.accCount++;
+
+ }
+ }
+
+
+ // Frame IRQ handling:
+ if (this.frameIrqEnabled && this.frameIrqActive){
+ this.nes.cpu.requestIrq(this.nes.cpu.IRQ_NORMAL);
+ }
+
+ // Clock frame counter at double CPU speed:
+ this.masterFrameCounter += (nCycles<<1);
+ if (this.masterFrameCounter >= this.frameTime) {
+ // 240Hz tick:
+ this.masterFrameCounter -= this.frameTime;
+ this.frameCounterTick();
+ }
+
+ // Accumulate sample value:
+ this.accSample(nCycles);
+
+ // Clock sample timer:
+ this.sampleTimer += nCycles<<10;
+ if(this.sampleTimer>=this.sampleTimerMax){
+ // Sample channels:
+ this.sample();
+ this.sampleTimer -= this.sampleTimerMax;
+ }
+}
+
+PAPU.prototype.accSample = function(cycles){
+ var triangle = this.triangle;
+
+ // Special treatment for triangle channel - need to interpolate.
+ if(triangle.sampleCondition){
+
+ var triValue = parseInt((triangle.progTimerCount<<4) / (triangle.progTimerMax+1));
+ if(triValue>16) triValue = 16;
+ if(triangle.triangleCounter >= 16){
+ triValue = 16-triValue;
+ }
+
+ // Add non-interpolated sample value:
+ triValue += triangle.sampleValue;
+
+ }
+
+
+ // Now sample normally:
+ if(cycles == 2){
+
+ this.smpTriangle += triValue << 1;
+ this.smpDmc += this.dmc.sample << 1;
+ this.smpSquare1 += this.square1.sampleValue << 1;
+ this.smpSquare2 += this.square2.sampleValue << 1;
+ this.accCount += 2;
+
+ }else if(cycles == 4){
+
+ this.smpTriangle += triValue << 2;
+ this.smpDmc += this.dmc.sample << 2;
+ this.smpSquare1 += this.square1.sampleValue << 2;
+ this.smpSquare2 += this.square2.sampleValue << 2;
+ this.accCount += 4;
+
+ }else{
+
+ this.smpTriangle += cycles * triValue;
+ this.smpDmc += cycles * this.dmc.sample;
+ this.smpSquare1 += cycles * this.square1.sampleValue;
+ this.smpSquare2 += cycles * this.square2.sampleValue;
+ this.accCount += cycles;
+
+ }
+
+}
+
+PAPU.prototype.frameCounterTick = function(){
+
+ this.derivedFrameCounter++;
+ if(this.derivedFrameCounter >= this.frameIrqCounterMax){
+ this.derivedFrameCounter = 0;
+ }
+
+ if(this.derivedFrameCounter==1 || this.derivedFrameCounter==3){
+
+ // Clock length & sweep:
+ this.triangle.clockLengthCounter();
+ this.square1.clockLengthCounter();
+ this.square2.clockLengthCounter();
+ this.noise.clockLengthCounter();
+ this.square1.clockSweep();
+ this.square2.clockSweep();
+
+ }
+
+ if(this.derivedFrameCounter >= 0 && this.derivedFrameCounter < 4){
+
+ // Clock linear & decay:
+ this.square1.clockEnvDecay();
+ this.square2.clockEnvDecay();
+ this.noise.clockEnvDecay();
+ this.triangle.clockLinearCounter();
+
+ }
+
+ if(this.derivedFrameCounter == 3 && this.countSequence==0){
+
+ // Enable IRQ:
+ this.frameIrqActive = true;
+
+ }
+
+
+ // End of 240Hz tick
+
+}
+
+
+// Samples the channels, mixes the output together,
+// writes to buffer and (if enabled) file.
+PAPU.prototype.sample = function(){
+
+ if(this.accCount>0){
+
+ this.smpSquare1 <<= 4;
+ this.smpSquare1 = parseInt(this.smpSquare1/this.accCount);
+
+ this.smpSquare2 <<= 4;
+ this.smpSquare2 = parseInt(this.smpSquare2/this.accCount);
+
+ this.smpTriangle = parseInt(this.smpTriangle/this.accCount);
+
+ this.smpDmc <<= 4;
+ this.smpDmc = parseInt(this.smpDmc/this.accCount);
+
+ this.accCount = 0;
+
+ }else{
+
+ this.smpSquare1 = this.square1.sampleValue << 4;
+ this.smpSquare2 = this.square2.sampleValue << 4;
+ this.smpTriangle = this.triangle.sampleValue ;
+ this.smpDmc = this.dmc.sample << 4;
+
+ }
+
+ var smpNoise = parseInt((this.noise.accValue<<4)/this.noise.accCount);
+ this.noise.accValue = smpNoise>>4;
+ this.noise.accCount = 1;
+
+ // Stereo sound.
+
+ // Left channel:
+ var sq_index = ( this.smpSquare1 * this.stereoPosLSquare1 + this.smpSquare2 * this.stereoPosLSquare2 )>>8;
+ var tnd_index = (3*this.smpTriangle * this.stereoPosLTriangle + (smpNoise<<1) * this.stereoPosLNoise + this.smpDmc*this.stereoPosLDMC)>>8;
+ if(sq_index >= this.square_table.length)sq_index = this.square_table.length-1;
+ if(tnd_index >= this.tnd_table.length)tnd_index = this.tnd_table.length-1;
+ var sampleValueL = this.square_table[sq_index] + this.tnd_table[tnd_index] - this.dcValue;
+
+ // Right channel:
+ var sq_index = ( this.smpSquare1 * this.stereoPosRSquare1 + this.smpSquare2 * this.stereoPosRSquare2 )>>8;
+ var tnd_index = (3*this.smpTriangle * this.stereoPosRTriangle + (smpNoise<<1)* this.stereoPosRNoise + this.smpDmc*this.stereoPosRDMC)>>8;
+ if(sq_index >= this.square_table.length)sq_index = this.square_table.length-1;
+ if(tnd_index >= this.tnd_table.length)tnd_index = this.tnd_table.length-1;
+ var sampleValueR = this.square_table[sq_index] + this.tnd_table[tnd_index] - this.dcValue;
+
+ // Remove DC from left channel:
+ var smpDiffL = sampleValueL - this.prevSampleL;
+ this.prevSampleL += smpDiffL;
+ this.smpAccumL += smpDiffL - (this.smpAccumL >> 10);
+ sampleValueL = this.smpAccumL;
+
+
+ // Remove DC from right channel:
+ var smpDiffR = sampleValueR - this.prevSampleR;
+ this.prevSampleR += smpDiffR;
+ this.smpAccumR += smpDiffR - (this.smpAccumR >> 10);
+ sampleValueR = this.smpAccumR;
+
+ // Write:
+ if(this.bufferIndex+2 < this.sampleBuffer.length){
+ if (sampleValueL > this.maxSample) this.maxSample = sampleValueL;
+ if (sampleValueL < this.minSample) this.minSample = sampleValueL;
+ this.sampleBuffer[this.bufferIndex++] = (sampleValueL );
+ this.sampleBuffer[this.bufferIndex++] = (sampleValueR );
+ }
+ else {
+ //console.debug('Reached end of buffer');
+ }
+
+ // Reset sampled values:
+ this.smpSquare1 = 0;
+ this.smpSquare2 = 0;
+ this.smpTriangle = 0;
+ this.smpDmc = 0;
+
+}
+
+PAPU.prototype.readBuffer = function() {
+ //console.debug(this.bufferIndex);
+ if (this.bufferIndex >= 2048) {
+ var b = this.sampleBuffer;
+ this.sampleBuffer = new Array(this.bufferSize*2);
+ this.bufferIndex = 0;
+ return b
+ }
+ else {
+ //console.debug("Insufficient buffer: "+this.bufferIndex);
+ }
+}
+
+PAPU.prototype.getLengthMax = function(value){
+ return this.lengthLookup[value>>3];
+}
+
+PAPU.prototype.getDmcFrequency = function(value){
+ if(value>=0 && value<0x10){
+ return this.dmcFreqLookup[value];
+ }
+ return 0;
+}
+
+PAPU.prototype.getNoiseWaveLength = function(value){
+ if(value>=0 && value<0x10){
+ return this.noiseWavelengthLookup[value];
+ }
+ return 0;
+}
+
+PAPU.prototype.setPanning = function(pos){
+ for(var i=0;i<5;i++){
+ this.panning[i] = pos[i];
+ }
+ this.updateStereoPos();
+}
+
+PAPU.prototype.setMasterVolume = function(value){
+ if(value<0)value=0;
+ if(value>256)value=256;
+ this.masterVolume = value;
+ this.updateStereoPos();
+}
+
+PAPU.prototype.updateStereoPos = function(){
+ this.stereoPosLSquare1 = (this.panning[0]*this.masterVolume)>>8;
+ this.stereoPosLSquare2 = (this.panning[1]*this.masterVolume)>>8;
+ this.stereoPosLTriangle = (this.panning[2]*this.masterVolume)>>8;
+ this.stereoPosLNoise = (this.panning[3]*this.masterVolume)>>8;
+ this.stereoPosLDMC = (this.panning[4]*this.masterVolume)>>8;
+
+ this.stereoPosRSquare1 = this.masterVolume - this.stereoPosLSquare1;
+ this.stereoPosRSquare2 = this.masterVolume - this.stereoPosLSquare2;
+ this.stereoPosRTriangle = this.masterVolume - this.stereoPosLTriangle;
+ this.stereoPosRNoise = this.masterVolume - this.stereoPosLNoise;
+ this.stereoPosRDMC = this.masterVolume - this.stereoPosLDMC;
+}
+
+PAPU.prototype.initLengthLookup = function(){
+
+ this.lengthLookup = new Array(
+ 0x0A, 0xFE,
+ 0x14, 0x02,
+ 0x28, 0x04,
+ 0x50, 0x06,
+ 0xA0, 0x08,
+ 0x3C, 0x0A,
+ 0x0E, 0x0C,
+ 0x1A, 0x0E,
+ 0x0C, 0x10,
+ 0x18, 0x12,
+ 0x30, 0x14,
+ 0x60, 0x16,
+ 0xC0, 0x18,
+ 0x48, 0x1A,
+ 0x10, 0x1C,
+ 0x20, 0x1E
+ );
+
+}
+
+PAPU.prototype.initDmcFrequencyLookup = function(){
+
+ this.dmcFreqLookup = new Array(16);
+
+ this.dmcFreqLookup[0x0] = 0xD60;
+ this.dmcFreqLookup[0x1] = 0xBE0;
+ this.dmcFreqLookup[0x2] = 0xAA0;
+ this.dmcFreqLookup[0x3] = 0xA00;
+ this.dmcFreqLookup[0x4] = 0x8F0;
+ this.dmcFreqLookup[0x5] = 0x7F0;
+ this.dmcFreqLookup[0x6] = 0x710;
+ this.dmcFreqLookup[0x7] = 0x6B0;
+ this.dmcFreqLookup[0x8] = 0x5F0;
+ this.dmcFreqLookup[0x9] = 0x500;
+ this.dmcFreqLookup[0xA] = 0x470;
+ this.dmcFreqLookup[0xB] = 0x400;
+ this.dmcFreqLookup[0xC] = 0x350;
+ this.dmcFreqLookup[0xD] = 0x2A0;
+ this.dmcFreqLookup[0xE] = 0x240;
+ this.dmcFreqLookup[0xF] = 0x1B0;
+ //for(int i=0;i<16;i++)dmcFreqLookup[i]/=8;
+
+}
+
+PAPU.prototype.initNoiseWavelengthLookup = function(){
+
+ this.noiseWavelengthLookup = new Array(16);
+
+ this.noiseWavelengthLookup[0x0] = 0x004;
+ this.noiseWavelengthLookup[0x1] = 0x008;
+ this.noiseWavelengthLookup[0x2] = 0x010;
+ this.noiseWavelengthLookup[0x3] = 0x020;
+ this.noiseWavelengthLookup[0x4] = 0x040;
+ this.noiseWavelengthLookup[0x5] = 0x060;
+ this.noiseWavelengthLookup[0x6] = 0x080;
+ this.noiseWavelengthLookup[0x7] = 0x0A0;
+ this.noiseWavelengthLookup[0x8] = 0x0CA;
+ this.noiseWavelengthLookup[0x9] = 0x0FE;
+ this.noiseWavelengthLookup[0xA] = 0x17C;
+ this.noiseWavelengthLookup[0xB] = 0x1FC;
+ this.noiseWavelengthLookup[0xC] = 0x2FA;
+ this.noiseWavelengthLookup[0xD] = 0x3F8;
+ this.noiseWavelengthLookup[0xE] = 0x7F2;
+ this.noiseWavelengthLookup[0xF] = 0xFE4;
+
+}
+
+PAPU.prototype.initDACtables = function(){
+
+ this.square_table = new Array(32*16);
+ this.tnd_table = new Array(204*16);
+ var value;
+
+ var ival;
+ var max_sqr = 0;
+ var max_tnd = 0;
+
+ for(var i=0;i<32*16;i++){
+
+
+ value = 95.52 / (8128.0 / (i/16.0) + 100.0);
+ value *= 0.98411;
+ value *= 50000.0;
+ ival = parseInt(value);
+
+ this.square_table[i] = ival;
+ if(ival > max_sqr){
+ max_sqr = ival;
+ }
+
+ }
+
+ for(var i=0;i<204*16;i++){
+
+ value = 163.67 / (24329.0 / (i/16.0) + 100.0);
+ value *= 0.98411;
+ value *= 50000.0;
+ ival = parseInt(value);
+
+ this.tnd_table[i] = ival;
+ if(ival > max_tnd){
+ max_tnd = ival;
+ }
+
+ }
+
+ this.dacRange = max_sqr+max_tnd;
+ this.dcValue = this.dacRange/2;
+
+}
+
diff --git a/js/ppu.js b/js/ppu.js
new file mode 100644
index 0000000..5df57b9
--- /dev/null
+++ b/js/ppu.js
@@ -0,0 +1,1503 @@
+function PPU(nes) {
+ this.nes = nes;
+ this.timer = null;
+ this.ppuMem = null;
+ this.sprMem = null;
+
+ // Rendering Options:
+ this.showSpr0Hit = false;
+ this.showSoundBuffer = false;
+ this.clipToTvSize = true;
+
+
+ // Control Flags Register 1:
+ this.f_nmiOnVblank = null; // NMI on VBlank. 0=disable, 1=enable
+ this.f_spriteSize = null; // Sprite size. 0=8x8, 1=8x16
+ this.f_bgPatternTable = null; // Background Pattern Table address. 0=0x0000,1=0x1000
+ this.f_spPatternTable = null; // Sprite Pattern Table address. 0=0x0000,1=0x1000
+ this.f_addrInc = null; // PPU Address Increment. 0=1,1=32
+ this.f_nTblAddress = null; // Name Table Address. 0=0x2000,1=0x2400,2=0x2800,3=0x2C00
+
+
+ // Control Flags Register 2:
+ this.f_color = null; // Background color. 0=black, 1=blue, 2=green, 4=red
+ this.f_spVisibility = null; // Sprite visibility. 0=not displayed,1=displayed
+ this.f_bgVisibility = null; // Background visibility. 0=Not Displayed,1=displayed
+ this.f_spClipping = null; // Sprite clipping. 0=Sprites invisible in left 8-pixel column,1=No clipping
+ this.f_bgClipping = null; // Background clipping. 0=BG invisible in left 8-pixel column, 1=No clipping
+ this.f_dispType = null; // Display type. 0=color, 1=monochrome
+
+
+ // Status flags:
+ this.STATUS_VRAMWRITE = 4;
+ this.STATUS_SLSPRITECOUNT = 5;
+ this.STATUS_SPRITE0HIT = 6;
+ this.STATUS_VBLANK = 7;
+
+
+ // VRAM I/O:
+ this.vramAddress = null;
+ this.vramTmpAddress = null;
+ this.vramBufferedReadValue = null;
+ this.firstWrite=true; // VRAM/Scroll Hi/Lo latch
+
+
+ this.vramMirrorTable = null; // Mirroring Lookup Table.
+
+ // SPR-RAM I/O:
+ this.sramAddress = null; // 8-bit only.
+
+ // Counters:
+ this.cntFV = null;
+ this.cntV = null;
+ this.cntH = null;
+ this.cntVT = null;
+ this.cntHT = null;
+
+ // Registers:
+ this.regFV = null;
+ this.regV = null;
+ this.regH = null;
+ this.regVT = null;
+ this.regHT = null;
+ this.regFH = null;
+ this.regS = null;
+
+ this.curX = null;
+ this.scanline = null;
+ this.lastRenderedScanline = null;
+ this.mapperIrqCounter = null;
+
+
+ // Sprite data:
+ this.sprX = null; // X coordinate
+ this.sprY = null; // Y coordinate
+ this.sprTile = null; // Tile Index (into pattern table)
+ this.sprCol = null; // Upper two bits of color
+ this.vertFlip = null; // Vertical Flip
+ this.horiFlip = null; // Horizontal Flip
+ this.bgPriority = null; // Background priority
+ this.spr0HitX = null; // Sprite #0 hit X coordinate
+ this.spr0HitY = null; // Sprite #0 hit Y coordinate
+ this.hitSpr0 = null;
+
+ // Tiles:
+ this.ptTile = null;
+
+
+ // Name table data:
+ this.ntable1 = new Array(4);
+ this.nameTable = null;
+ this.currentMirroring = -1;
+
+ // Palette data:
+ this.sprPalette = new Array(16);
+ this.imgPalette = new Array(16);
+
+ // Misc:
+ this.scanlineAlreadyRendered = null;
+ this.requestEndFrame = null;
+ this.nmiOk = null;
+ this.nmiCounter = null;
+ this.tmp = null;
+ this.dummyCycleToggle = null;
+
+ // Variables used when rendering:
+ this.attrib = new Array(32);
+ this.buffer = new Array(256*240);
+ this.prevBuffer = new Array(256*240);
+ this.bgbuffer = new Array(256*240);
+ this.pixrendered = new Array(256*240);
+ this.spr0dummybuffer = new Array(256*240);
+ this.dummyPixPriTable = new Array(256*240);
+
+ this.validTileData = null;
+
+ this.scantile = new Array(32);
+
+ // These are temporary variables used in rendering and sound procedures.
+ // Their states outside of those procedures can be ignored.
+ // TODO: the use of this is a bit weird, investigate
+ this.curNt = null;
+
+ this.init = function() {
+ // Get the memory:
+ this.ppuMem = this.nes.ppuMem;
+ this.sprMem = this.nes.sprMem;
+
+ this.updateControlReg1(0);
+ this.updateControlReg2(0);
+
+ // Initialize misc vars:
+ this.scanline = 0;
+
+ // Create sprite arrays:
+ this.sprX = new Array(64);
+ this.sprY = new Array(64);
+ this.sprTile = new Array(64);
+ this.sprCol = new Array(64);
+ this.vertFlip = new Array(64);
+ this.horiFlip = new Array(64);
+ this.bgPriority = new Array(64);
+
+ // Create pattern table tile buffers:
+ if(this.ptTile==null){
+ this.ptTile = new Array(512);
+ for(var i=0;i<512;i++){
+ this.ptTile[i] = new Tile();
+ }
+ }
+
+ // Create nametable buffers:
+ this.nameTable = new Array(4);
+ for(var i=0;i<4;i++){
+ this.nameTable[i] = new NameTable(32,32,"Nt"+i);
+ }
+
+ // Initialize mirroring lookup table:
+ this.vramMirrorTable = new Array(0x8000);
+ for(var i=0;i<0x8000;i++){
+ this.vramMirrorTable[i] = i;
+ }
+
+ this.lastRenderedScanline = -1;
+ this.curX = 0;
+
+ }
+
+
+ // Sets Nametable mirroring.
+ this.setMirroring = function(mirroring){
+
+ if(mirroring == this.currentMirroring){
+ return;
+ }
+
+ this.currentMirroring = mirroring;
+ this.triggerRendering();
+
+ // Remove mirroring:
+ if(this.vramMirrorTable==null){
+ this.vramMirrorTable = new Array(0x8000);
+ }
+ for(var i=0;i<0x8000;i++){
+ this.vramMirrorTable[i] = i;
+ }
+
+ // Palette mirroring:
+ this.defineMirrorRegion(0x3f20,0x3f00,0x20);
+ this.defineMirrorRegion(0x3f40,0x3f00,0x20);
+ this.defineMirrorRegion(0x3f80,0x3f00,0x20);
+ this.defineMirrorRegion(0x3fc0,0x3f00,0x20);
+
+ // Additional mirroring:
+ this.defineMirrorRegion(0x3000,0x2000,0xf00);
+ this.defineMirrorRegion(0x4000,0x0000,0x4000);
+
+ if(mirroring == this.nes.rom.HORIZONTAL_MIRRORING){
+
+
+ // Horizontal mirroring.
+
+ this.ntable1[0] = 0;
+ this.ntable1[1] = 0;
+ this.ntable1[2] = 1;
+ this.ntable1[3] = 1;
+
+ this.defineMirrorRegion(0x2400,0x2000,0x400);
+ this.defineMirrorRegion(0x2c00,0x2800,0x400);
+
+ }else if(mirroring == this.nes.rom.VERTICAL_MIRRORING){
+
+ // Vertical mirroring.
+
+ this.ntable1[0] = 0;
+ this.ntable1[1] = 1;
+ this.ntable1[2] = 0;
+ this.ntable1[3] = 1;
+
+ this.defineMirrorRegion(0x2800,0x2000,0x400);
+ this.defineMirrorRegion(0x2c00,0x2400,0x400);
+
+ }else if(mirroring == this.nes.rom.SINGLESCREEN_MIRRORING){
+
+ // Single Screen mirroring
+
+ this.ntable1[0] = 0;
+ this.ntable1[1] = 0;
+ this.ntable1[2] = 0;
+ this.ntable1[3] = 0;
+
+ this.defineMirrorRegion(0x2400,0x2000,0x400);
+ this.defineMirrorRegion(0x2800,0x2000,0x400);
+ this.defineMirrorRegion(0x2c00,0x2000,0x400);
+
+ }else if(mirroring == this.nes.rom.SINGLESCREEN_MIRRORING2){
+
+
+ this.ntable1[0] = 1;
+ this.ntable1[1] = 1;
+ this.ntable1[2] = 1;
+ this.ntable1[3] = 1;
+
+ this.defineMirrorRegion(0x2400,0x2400,0x400);
+ this.defineMirrorRegion(0x2800,0x2400,0x400);
+ this.defineMirrorRegion(0x2c00,0x2400,0x400);
+
+ }else{
+
+ // Assume Four-screen mirroring.
+
+ this.ntable1[0] = 0;
+ this.ntable1[1] = 1;
+ this.ntable1[2] = 2;
+ this.ntable1[3] = 3;
+
+ }
+
+ }
+
+
+ // Define a mirrored area in the address lookup table.
+ // Assumes the regions don't overlap.
+ // The 'to' region is the region that is physically in memory.
+ this.defineMirrorRegion = function(fromStart, toStart, size){
+
+ for(var i=0;i<size;i++){
+ this.vramMirrorTable[fromStart+i] = toStart+i;
+ }
+
+ }
+
+ this.startVBlank = function(){
+
+ // Do NMI:
+ this.nes.cpu.requestIrq(this.nes.cpu.IRQ_NMI);
+
+ // Make sure everything is rendered:
+ if(this.lastRenderedScanline < 239){
+ this.renderFramePartially(
+ this.lastRenderedScanline+1,240-this.lastRenderedScanline
+ );
+ }
+
+ // End frame:
+ this.endFrame();
+
+ // Reset scanline counter:
+ this.lastRenderedScanline = -1;
+
+
+ }
+
+ this.endScanline = function(){
+ switch (this.scanline) {
+ case 19:
+ // Dummy scanline.
+ // May be variable length:
+ if(this.dummyCycleToggle){
+
+ // Remove dead cycle at end of scanline,
+ // for next scanline:
+ this.curX = 1;
+ this.dummyCycleToggle = !this.dummyCycleToggle;
+
+ }
+ break;
+
+ case 20:
+ // Clear VBlank flag:
+ this.setStatusFlag(this.STATUS_VBLANK,false);
+
+ // Clear Sprite #0 hit flag:
+ this.setStatusFlag(this.STATUS_SPRITE0HIT,false);
+ this.hitSpr0 = false;
+ this.spr0HitX = -1;
+ this.spr0HitY = -1;
+
+ if(this.f_bgVisibility == 1 || this.f_spVisibility==1){
+
+ // Update counters:
+ this.cntFV = this.regFV;
+ this.cntV = this.regV;
+ this.cntH = this.regH;
+ this.cntVT = this.regVT;
+ this.cntHT = this.regHT;
+
+ if(this.f_bgVisibility==1){
+ // Render dummy scanline:
+ this.renderBgScanline(false,0);
+ }
+
+ }
+
+ if(this.f_bgVisibility==1 && this.f_spVisibility==1){
+
+ // Check sprite 0 hit for first scanline:
+ this.checkSprite0(0);
+
+ }
+
+ if(this.f_bgVisibility==1 || this.f_spVisibility==1){
+ // Clock mapper IRQ Counter:
+ this.nes.memMapper.clockIrqCounter();
+ }
+ break;
+
+ case 261:
+ // Dead scanline, no rendering.
+ // Set VINT:
+ this.setStatusFlag(this.STATUS_VBLANK,true);
+ this.requestEndFrame = true;
+ this.nmiCounter = 9;
+
+ // Wrap around:
+ this.scanline = -1; // will be incremented to 0
+
+ break;
+
+ default:
+ if(this.scanline>=21 && this.scanline<=260){
+
+ // Render normally:
+ if(this.f_bgVisibility == 1){
+
+ if(!this.scanlineAlreadyRendered){
+ // update scroll:
+ this.cntHT = this.regHT;
+ this.cntH = this.regH;
+ this.renderBgScanline(true,this.scanline+1-21);
+ }
+ this.scanlineAlreadyRendered=false;
+
+ // Check for sprite 0 (next scanline):
+ if(!this.hitSpr0 && this.f_spVisibility==1){
+ if(this.sprX[0]>=-7 && this.sprX[0]<256 && this.sprY[0]+1<=(this.scanline-20) && (this.sprY[0]+1+(this.f_spriteSize==0?8:16))>=(this.scanline-20)){
+
+ if(this.checkSprite0(
+ this.scanline-20)){
+ //console.log("found spr0. curscan="+this.scanline+" hitscan="+this.spr0HitY);
+ this.hitSpr0 = true;
+ }
+ }
+ }
+
+ }
+
+ if(this.f_bgVisibility==1 || this.f_spVisibility==1){
+ // Clock mapper IRQ Counter:
+ this.nes.memMapper.clockIrqCounter();
+ }
+ }
+ }
+
+ this.scanline++;
+ this.regsToAddress();
+ this.cntsToAddress();
+
+ }
+
+
+ this.startFrame = function(){
+
+ // Set background color:
+ var bgColor=0;
+
+ if(this.f_dispType == 0){
+
+ // Color display.
+ // f_color determines color emphasis.
+ // Use first entry of image palette as BG color.
+ bgColor = this.imgPalette[0];
+
+ }else{
+
+ // Monochrome display.
+ // f_color determines the bg color.
+ switch(this.f_color){
+ case 0:{
+ // Black
+ bgColor = 0x00000;
+ break;
+ }
+ case 1:{
+ // Green
+ bgColor = 0x00FF00;
+ }
+ case 2:{
+ // Blue
+ bgColor = 0xFF0000;
+ }
+ case 3:{
+ // Invalid. Use black.
+ bgColor = 0x000000;
+ }
+ case 4:{
+ // Red
+ bgColor = 0x0000FF;
+ }
+ default:{
+ // Invalid. Use black.
+ bgColor = 0x0;
+ }
+ }
+
+ }
+
+ var buffer = this.buffer;
+ for(var i=0;i<256*240;i++) {
+ buffer[i] = bgColor;
+ }
+ var pixrendered = this.pixrendered;
+ for(var i=0;i<pixrendered.length;i++) {
+ pixrendered[i]=65;
+ }
+
+ }
+
+ this.endFrame = function(){
+
+ var buffer = this.buffer;
+
+ // Draw spr#0 hit coordinates:
+ if(this.showSpr0Hit){
+ // Spr 0 position:
+ if(this.sprX[0]>=0 && this.sprX[0]<256 && this.sprY[0]>=0 && this.sprY[0]<240){
+ for(var i=0;i<256;i++){
+ buffer[(this.sprY[0]<<8)+i] = 0xFF5555;
+ }
+ for(var i=0;i<240;i++){
+ buffer[(i<<8)+this.sprX[0]] = 0xFF5555;
+ }
+ }
+ // Hit position:
+ if(this.spr0HitX>=0 && this.spr0HitX<256 && this.spr0HitY>=0 && this.spr0HitY<240){
+ for(var i=0;i<256;i++){
+ buffer[(this.spr0HitY<<8)+i] = 0x55FF55;
+ }
+ for(var i=0;i<240;i++){
+ buffer[(i<<8)+this.spr0HitX] = 0x55FF55;
+ }
+ }
+ }
+
+ // This is a bit lazy..
+ // if either the sprites or the background should be clipped,
+ // both are clipped after rendering is finished.
+ if(this.clipToTvSize || this.f_bgClipping==0 || this.f_spClipping==0){
+ // Clip left 8-pixels column:
+ for(var y=0;y<240;y++){
+ for(var x=0;x<8;x++){
+ buffer[(y<<8)+x] = 0;
+ }
+ }
+ }
+
+ if(this.clipToTvSize){
+ // Clip right 8-pixels column too:
+ for(var y=0;y<240;y++){
+ for(var x=0;x<8;x++){
+ buffer[(y<<8)+255-x] = 0;
+ }
+ }
+ }
+
+ // Clip top and bottom 8 pixels:
+ if(this.clipToTvSize){
+ for(var y=0;y<8;y++){
+ for(var x=0;x<256;x++){
+ buffer[(y<<8)+x] = 0;
+ buffer[((239-y)<<8)+x] = 0;
+ }
+ }
+ }
+
+ if (Globals.showDisplay) {
+ var imageData = this.nes.imageData.data, prevBuffer = this.prevBuffer;
+
+ for (var i=0;i<256*240;i++) {
+ var pixel = buffer[i];
+
+ if (pixel != prevBuffer[i]) {
+ var j = i*4;
+ imageData[j] = pixel&0xFF;
+ imageData[j+1] = (pixel>>8)&0xFF;
+ imageData[j+2] = (pixel>>16)&0xFF;
+ prevBuffer[i] = pixel;
+ }
+ }
+ }
+ }
+
+ this.updateControlReg1 = function(value){
+
+ this.triggerRendering();
+
+ this.f_nmiOnVblank = (value>>7)&1;
+ this.f_spriteSize = (value>>5)&1;
+ this.f_bgPatternTable = (value>>4)&1;
+ this.f_spPatternTable = (value>>3)&1;
+ this.f_addrInc = (value>>2)&1;
+ this.f_nTblAddress = value&3;
+
+ this.regV = (value>>1)&1;
+ this.regH = value&1;
+ this.regS = (value>>4)&1;
+
+ }
+
+
+
+ this.updateControlReg2 = function(value){
+
+ this.triggerRendering();
+
+ this.f_color = (value>>5)&7;
+ this.f_spVisibility = (value>>4)&1;
+ this.f_bgVisibility = (value>>3)&1;
+ this.f_spClipping = (value>>2)&1;
+ this.f_bgClipping = (value>>1)&1;
+ this.f_dispType = value&1;
+
+ if(this.f_dispType == 0){
+ this.nes.palTable.setEmphasis(this.f_color);
+ }
+ this.updatePalettes();
+
+ }
+
+
+ this.setStatusFlag = function(flag, value){
+ var n = 1<<flag;
+ this.nes.cpuMem[0x2002] =
+ ((this.nes.cpuMem[0x2002]&(255-n))|(value?n:0));
+ }
+
+ // CPU Register $2002:
+ // Read the Status Register.
+ this.readStatusRegister = function(){
+
+ this.tmp = this.nes.cpuMem[0x2002];
+
+ // Reset scroll & VRAM Address toggle:
+ this.firstWrite = true;
+
+ // Clear VBlank flag:
+ this.setStatusFlag(this.STATUS_VBLANK,false);
+
+ // Fetch status data:
+ return this.tmp;
+
+ }
+
+
+
+ // CPU Register $2003:
+ // Write the SPR-RAM address that is used for sramWrite (Register 0x2004 in CPU memory map)
+ this.writeSRAMAddress = function(address){
+ this.sramAddress = address;
+ }
+
+
+ // CPU Register $2004 (R):
+ // Read from SPR-RAM (Sprite RAM).
+ // The address should be set first.
+ this.sramLoad = function(){
+ /*short tmp = sprMem.load(sramAddress);
+ sramAddress++; // Increment address
+ sramAddress%=0x100;
+ return tmp;*/
+ return this.sprMem[this.sramAddress]
+ }
+
+
+ // CPU Register $2004 (W):
+ // Write to SPR-RAM (Sprite RAM).
+ // The address should be set first.
+ this.sramWrite = function(value){
+ this.sprMem[this.sramAddress] = value;
+ this.spriteRamWriteUpdate(this.sramAddress,value);
+ this.sramAddress++; // Increment address
+ this.sramAddress%=0x100;
+ }
+
+
+ // CPU Register $2005:
+ // Write to scroll registers.
+ // The first write is the vertical offset, the second is the
+ // horizontal offset:
+ this.scrollWrite = function(value){
+ this.triggerRendering();
+
+ if(this.firstWrite){
+ // First write, horizontal scroll:
+ this.regHT = (value>>3)&31;
+ this.regFH = value&7;
+
+ }else{
+
+ // Second write, vertical scroll:
+ this.regFV = value&7;
+ this.regVT = (value>>3)&31;
+
+ }
+ this.firstWrite = !this.firstWrite;
+
+ }
+
+
+
+ // CPU Register $2006:
+ // Sets the adress used when reading/writing from/to VRAM.
+ // The first write sets the high byte, the second the low byte.
+ this.writeVRAMAddress = function(address){
+
+ if(this.firstWrite){
+
+ this.regFV = (address>>4)&3;
+ this.regV = (address>>3)&1;
+ this.regH = (address>>2)&1;
+ this.regVT = (this.regVT&7) | ((address&3)<<3);
+
+ }else{
+ this.triggerRendering();
+
+ this.regVT = (this.regVT&24) | ((address>>5)&7);
+ this.regHT = address&31;
+
+ this.cntFV = this.regFV;
+ this.cntV = this.regV;
+ this.cntH = this.regH;
+ this.cntVT = this.regVT;
+ this.cntHT = this.regHT;
+
+ this.checkSprite0(this.scanline-20);
+
+ }
+
+ this.firstWrite = !this.firstWrite;
+
+ // Invoke mapper latch:
+ this.cntsToAddress();
+ if(this.vramAddress < 0x2000){
+ this.nes.memMapper.latchAccess(this.vramAddress);
+ }
+ }
+
+
+ // CPU Register $2007(R):
+ // Read from PPU memory. The address should be set first.
+ this.vramLoad = function(){
+
+ this.cntsToAddress();
+ this.regsToAddress();
+
+ // If address is in range 0x0000-0x3EFF, return buffered values:
+ if(this.vramAddress <= 0x3EFF){
+
+ var tmp = this.vramBufferedReadValue;
+
+ // Update buffered value:
+ if(this.vramAddress < 0x2000){
+ this.vramBufferedReadValue = this.ppuMem[this.vramAddress];
+ }else{
+ this.vramBufferedReadValue = this.mirroredLoad(this.vramAddress);
+ }
+
+ // Mapper latch access:
+ if(this.vramAddress < 0x2000){
+ this.nes.memMapper.latchAccess(this.vramAddress);
+ }
+
+ // Increment by either 1 or 32, depending on d2 of Control Register 1:
+ this.vramAddress += (this.f_addrInc==1?32:1);
+
+ this.cntsFromAddress();
+ this.regsFromAddress();
+ return tmp; // Return the previous buffered value.
+
+ }
+
+ // No buffering in this mem range. Read normally.
+ var tmp = this.mirroredLoad(this.vramAddress);
+
+ // Increment by either 1 or 32, depending on d2 of Control Register 1:
+ this.vramAddress += (this.f_addrInc==1?32:1);
+
+ this.cntsFromAddress();
+ this.regsFromAddress();
+
+ return tmp;
+
+ }
+
+ // CPU Register $2007(W):
+ // Write to PPU memory. The address should be set first.
+ this.vramWrite = function(value){
+
+ this.triggerRendering();
+ this.cntsToAddress();
+ this.regsToAddress();
+
+ if(this.vramAddress >= 0x2000){
+ // Mirroring is used.
+ this.mirroredWrite(this.vramAddress,value);
+ }else{
+
+ // Write normally.
+ this.writeMem(this.vramAddress,value);
+
+ // Invoke mapper latch:
+ this.nes.memMapper.latchAccess(this.vramAddress);
+
+ }
+
+ // Increment by either 1 or 32, depending on d2 of Control Register 1:
+ this.vramAddress += (this.f_addrInc==1?32:1);
+ this.regsFromAddress();
+ this.cntsFromAddress();
+
+ }
+
+ // CPU Register $4014:
+ // Write 256 bytes of main memory
+ // into Sprite RAM.
+ this.sramDMA = function(value){
+ var baseAddress = value * 0x100;
+ var data;
+ for(var i=this.sramAddress;i<256;i++){
+ data = this.nes.cpuMem[baseAddress+i];
+ this.sprMem[i] = data;
+ this.spriteRamWriteUpdate(i, data);
+ }
+
+ this.nes.cpu.haltCycles(513);
+
+ }
+
+ // Updates the scroll registers from a new VRAM address.
+ this.regsFromAddress = function(){
+
+ var address = (this.vramTmpAddress>>8)&0xFF;
+ this.regFV = (address>>4)&7;
+ this.regV = (address>>3)&1;
+ this.regH = (address>>2)&1;
+ this.regVT = (this.regVT&7) | ((address&3)<<3);
+
+ address = this.vramTmpAddress&0xFF;
+ this.regVT = (this.regVT&24) | ((address>>5)&7);
+ this.regHT = address&31;
+ }
+
+ // Updates the scroll registers from a new VRAM address.
+ this.cntsFromAddress = function(){
+
+ var address = (this.vramAddress>>8)&0xFF;
+ this.cntFV = (address>>4)&3;
+ this.cntV = (address>>3)&1;
+ this.cntH = (address>>2)&1;
+ this.cntVT = (this.cntVT&7) | ((address&3)<<3);
+
+ address = this.vramAddress&0xFF;
+ this.cntVT = (this.cntVT&24) | ((address>>5)&7);
+ this.cntHT = address&31;
+
+ }
+
+ this.regsToAddress = function(){
+ var b1 = (this.regFV&7)<<4;
+ b1 |= (this.regV&1)<<3;
+ b1 |= (this.regH&1)<<2;
+ b1 |= (this.regVT>>3)&3;
+
+ var b2 = (this.regVT&7)<<5;
+ b2 |= this.regHT&31;
+
+ this.vramTmpAddress = ((b1<<8) | b2)&0x7FFF;
+ }
+
+ this.cntsToAddress = function(){
+ var b1 = (this.cntFV&7)<<4;
+ b1 |= (this.cntV&1)<<3;
+ b1 |= (this.cntH&1)<<2;
+ b1 |= (this.cntVT>>3)&3;
+
+ var b2 = (this.cntVT&7)<<5;
+ b2 |= this.cntHT&31;
+
+ this.vramAddress = ((b1<<8) | b2)&0x7FFF;
+ }
+
+ this.incTileCounter = function(count){
+
+ for(var i=count; i!=0; i--){
+ this.cntHT++;
+ if(this.cntHT==32){
+ this.cntHT=0;
+ this.cntVT++;
+ if(this.cntVT>=30){
+ this.cntH++;
+ if(this.cntH==2){
+ this.cntH=0;
+ this.cntV++;
+ if(this.cntV==2){
+ this.cntV=0;
+ this.cntFV++;
+ this.cntFV&=0x7;
+ }
+ }
+ }
+ }
+ }
+
+ }
+
+ // Reads from memory, taking into account
+ // mirroring/mapping of address ranges.
+ this.mirroredLoad = function(address){
+ return this.ppuMem[this.vramMirrorTable[address]];
+ }
+
+ // Writes to memory, taking into account
+ // mirroring/mapping of address ranges.
+ this.mirroredWrite = function(address, value){
+ if(address>=0x3f00 && address<0x3f20){
+ // Palette write mirroring.
+ if(address==0x3F00 || address==0x3F10){
+ this.writeMem(0x3F00,value);
+ this.writeMem(0x3F10,value);
+
+ }else if(address==0x3F04 || address==0x3F14){
+
+ this.writeMem(0x3F04,value);
+ this.writeMem(0x3F14,value);
+
+ }else if(address==0x3F08 || address==0x3F18){
+
+ this.writeMem(0x3F08,value);
+ this.writeMem(0x3F18,value);
+
+ }else if(address==0x3F0C || address==0x3F1C){
+
+ this.writeMem(0x3F0C,value);
+ this.writeMem(0x3F1C,value);
+
+ }else{
+ this.writeMem(address,value);
+ }
+
+ }else{
+
+ // Use lookup table for mirrored address:
+ if(address<this.vramMirrorTable.length){
+ this.writeMem(this.vramMirrorTable[address],value);
+ }else{
+ // FIXME
+ alert("Invalid VRAM address: "+address.toString(16));
+ }
+
+ }
+
+ }
+
+ this.triggerRendering = function(){
+
+ if(this.scanline >= 21 && this.scanline <= 260){
+
+ // Render sprites, and combine:
+ this.renderFramePartially(
+ this.lastRenderedScanline+1,
+ this.scanline-21-this.lastRenderedScanline
+ );
+
+ // Set last rendered scanline:
+ this.lastRenderedScanline = this.scanline-21;
+
+ }
+
+ }
+
+ this.renderFramePartially = function(startScan, scanCount){
+
+ if(this.f_spVisibility == 1){
+ this.renderSpritesPartially(startScan,scanCount,true);
+ }
+
+ if(this.f_bgVisibility == 1){
+ var si = startScan<<8;
+ var ei = (startScan+scanCount)<<8;
+ if(ei>0xF000) ei=0xF000;
+ var buffer = this.buffer;
+ var bgbuffer = this.bgbuffer;
+ var pixrendered = this.pixrendered;
+ for(var destIndex=si;destIndex<ei;destIndex++){
+ if(pixrendered[destIndex]>0xFF){
+ buffer[destIndex] = bgbuffer[destIndex];
+ }
+ }
+ }
+
+ if(this.f_spVisibility == 1){
+ this.renderSpritesPartially(startScan,scanCount,false);
+ }
+
+
+ this.validTileData = false;
+
+ }
+
+ this.renderBgScanline = function(bgbuffer, scan){
+
+ var baseTile = (this.regS==0?0:256);
+ var destIndex = (scan<<8)-this.regFH;
+
+ this.curNt = this.ntable1[this.cntV+this.cntV+this.cntH];
+
+ this.cntHT = this.regHT;
+ this.cntH = this.regH;
+ this.curNt = this.ntable1[this.cntV+this.cntV+this.cntH];
+
+ if(scan<240 && (scan-this.cntFV)>=0){
+
+ var tscanoffset = this.cntFV<<3;
+ var scantile = this.scantile;
+ var attrib = this.attrib;
+ var ptTile = this.ptTile;
+ var nameTable = this.nameTable;
+ var imgPalette = this.imgPalette;
+ var pixrendered = this.pixrendered;
+ var targetBuffer = bgbuffer ? this.bgbuffer : this.buffer;
+
+ var t, tpix, att, col;
+
+ for(var tile=0;tile<32;tile++){
+
+ if(scan>=0){
+
+ // Fetch tile & attrib data:
+ if(this.validTileData){
+ // Get data from array:
+ t = scantile[tile];
+ tpix = t.pix;
+ att = attrib[tile];
+ }else{
+ // Fetch data:
+ t = ptTile[baseTile+nameTable[this.curNt].getTileIndex(this.cntHT,this.cntVT)];
+ tpix = t.pix;
+ att = nameTable[this.curNt].getAttrib(this.cntHT,this.cntVT);
+ scantile[tile] = t;
+ attrib[tile] = att;
+ }
+
+ // Render tile scanline:
+ var sx = 0;
+ var x = (tile<<3)-this.regFH;
+
+ if(x>-8){
+ if(x<0){
+ destIndex-=x;
+ sx = -x;
+ }
+ if(t.opaque[this.cntFV]){
+ for(;sx<8;sx++){
+ var pix = imgPalette[tpix[tscanoffset+sx]+att];
+ targetBuffer[destIndex] = pix;
+ pixrendered[destIndex] |= 256;
+ destIndex++;
+ }
+ }else{
+ for(;sx<8;sx++){
+ col = tpix[tscanoffset+sx];
+ if(col != 0){
+ var pix = imgPalette[col+att];
+ targetBuffer[destIndex] = pix;
+ pixrendered[destIndex] |= 256;
+ }
+ destIndex++;
+ }
+ }
+ }
+
+ }
+
+ // Increase Horizontal Tile Counter:
+ if(++this.cntHT==32){
+ this.cntHT=0;
+ this.cntH++;
+ this.cntH%=2;
+ this.curNt = this.ntable1[(this.cntV<<1)+this.cntH];
+ }
+
+
+ }
+
+ // Tile data for one row should now have been fetched,
+ // so the data in the array is valid.
+ this.validTileData = true;
+
+ }
+
+ // update vertical scroll:
+ this.cntFV++;
+ if(this.cntFV==8){
+ this.cntFV = 0;
+ this.cntVT++;
+ if(this.cntVT==30){
+ this.cntVT = 0;
+ this.cntV++;
+ this.cntV%=2;
+ this.curNt = this.ntable1[(this.cntV<<1)+this.cntH];
+ }else if(this.cntVT==32){
+ this.cntVT = 0;
+ }
+
+ // Invalidate fetched data:
+ this.validTileData = false;
+
+ }
+
+ }
+
+ this.renderSpritesPartially = function(startscan, scancount, bgPri){
+ if(this.f_spVisibility==1){
+
+ var sprT1,sprT2;
+
+ for(var i=0;i<64;i++){
+ if(this.bgPriority[i]==bgPri && this.sprX[i]>=0 && this.sprX[i]<256 && this.sprY[i]+8>=startscan && this.sprY[i]<startscan+scancount){
+ // Show sprite.
+ if(this.f_spriteSize == 0){
+ // 8x8 sprites
+
+ this.srcy1 = 0;
+ this.srcy2 = 8;
+
+ if(this.sprY[i]<startscan){
+ this.srcy1 = startscan - this.sprY[i]-1;
+ }
+
+ if(this.sprY[i]+8 > startscan+scancount){
+ this.srcy2 = startscan+scancount-this.sprY[i]+1;
+ }
+
+ if(this.f_spPatternTable==0){
+ this.ptTile[this.sprTile[i]].render(0, this.srcy1, 8, this.srcy2, this.sprX[i], this.sprY[i]+1, this.sprCol[i], this.sprPalette, this.horiFlip[i], this.vertFlip[i], i, this.pixrendered);
+ }else{
+ this.ptTile[this.sprTile[i]+256].render(0, this.srcy1, 8, this.srcy2, this.sprX[i], this.sprY[i]+1, this.sprCol[i], this.sprPalette, this.horiFlip[i], this.vertFlip[i], i, this.pixrendered);
+ }
+ }else{
+ // 8x16 sprites
+ var top = this.sprTile[i];
+ if((top&1)!=0){
+ top = this.sprTile[i]-1+256;
+ }
+
+ var srcy1 = 0;
+ var srcy2 = 8;
+
+ if(this.sprY[i]<startscan){
+ srcy1 = startscan - this.sprY[i]-1;
+ }
+
+ if(this.sprY[i]+8 > startscan+scancount){
+ srcy2 = startscan+scancount-this.sprY[i];
+ }
+
+ this.ptTile[top+(this.vertFlip[i]?1:0)].render(0,
+ srcy1,
+ 8,
+ srcy2,
+ this.sprX[i],
+ this.sprY[i]+1,
+ this.sprCol[i],
+ this.sprPalette,
+ this.horiFlip[i],
+ this.vertFlip[i],
+ i,
+ this.pixrendered
+ );
+
+ var srcy1 = 0;
+ var srcy2 = 8;
+
+ if(this.sprY[i]+8<startscan){
+ srcy1 = startscan - (this.sprY[i]+8+1);
+ }
+
+ if(this.sprY[i]+16 > startscan+scancount){
+ srcy2 = startscan+scancount-(this.sprY[i]+8);
+ }
+
+ this.ptTile[top+(this.vertFlip[i]?0:1)].render(
+ 0,
+ srcy1,
+ 8,
+ srcy2,
+ this.sprX[i],
+ this.sprY[i]+1+8,
+ this.sprCol[i],
+ this.sprPalette,
+ this.horiFlip[i],
+ this.vertFlip[i],
+ i,
+ this.pixrendered
+ );
+
+ }
+ }
+ }
+ }
+
+ }
+
+ this.checkSprite0 = function(scan){
+
+ this.spr0HitX = -1;
+ this.spr0HitY = -1;
+
+ var toffset;
+ var tIndexAdd = (this.f_spPatternTable==0?0:256);
+ var x,y;
+ var bufferIndex;
+ var col;
+ var bgPri;
+ var t;
+
+ x = this.sprX[0];
+ y = this.sprY[0]+1;
+
+ if(this.f_spriteSize==0){
+ // 8x8 sprites.
+
+ // Check range:
+ if(y<=scan && y+8>scan && x>=-7 && x<256){
+
+ // Sprite is in range.
+ // Draw scanline:
+ t = this.ptTile[this.sprTile[0]+tIndexAdd];
+ col = this.sprCol[0];
+ bgPri = this.bgPriority[0];
+
+ if(this.vertFlip[0]){
+ toffset = 7-(scan-y);
+ }else{
+ toffset = scan-y;
+ }
+ toffset*=8;
+
+ bufferIndex = scan*256+x;
+ if(this.horiFlip[0]){
+ for(var i=7;i>=0;i--){
+ if(x>=0 && x<256){
+ if(bufferIndex>=0 && bufferIndex<61440 && this.pixrendered[bufferIndex]!=0){
+ if(t.pix[toffset+i] != 0){
+ this.spr0HitX = bufferIndex%256;
+ this.spr0HitY = scan;
+ return true;
+ }
+ }
+ }
+ x++;
+ bufferIndex++;
+ }
+
+ }else{
+ for(var i=0;i<8;i++){
+ if(x>=0 && x<256){
+ if(bufferIndex>=0 && bufferIndex<61440 && this.pixrendered[bufferIndex]!=0){
+ if(t.pix[toffset+i] != 0){
+ this.spr0HitX = bufferIndex%256;
+ this.spr0HitY = scan;
+ return true;
+ }
+ }
+ }
+ x++;
+ bufferIndex++;
+ }
+ }
+ }
+ }else{
+ // 8x16 sprites:
+
+ // Check range:
+ if(y<=scan && y+16>scan && x>=-7 && x<256){
+
+ // Sprite is in range.
+ // Draw scanline:
+
+ if(this.vertFlip[0]){
+ toffset = 15-(scan-y);
+ }else{
+ toffset = scan-y;
+ }
+
+ if(toffset<8){
+ // first half of sprite.
+ t = this.ptTile[this.sprTile[0]+(this.vertFlip[0]?1:0)+((this.sprTile[0]&1)!=0?255:0)];
+ }else{
+ // second half of sprite.
+ t = this.ptTile[this.sprTile[0]+(this.vertFlip[0]?0:1)+((this.sprTile[0]&1)!=0?255:0)];
+ if(this.vertFlip[0]){
+ toffset = 15-toffset;
+ }else{
+ toffset -= 8;
+ }
+ }
+ toffset*=8;
+ col = this.sprCol[0];
+ bgPri = this.bgPriority[0];
+
+ bufferIndex = scan*256+x;
+ if(this.horiFlip[0]){
+ for(var i=7;i>=0;i--){
+ if(x>=0 && x<256){
+ if(bufferIndex>=0 && bufferIndex<61440 && this.pixrendered[bufferIndex]!=0){
+ if(t.pix[toffset+i] != 0){
+ this.spr0HitX = bufferIndex%256;
+ this.spr0HitY = scan;
+ return true;
+ }
+ }
+ }
+ x++;
+ bufferIndex++;
+ }
+
+ }else{
+
+ for(var i=0;i<8;i++){
+ if(x>=0 && x<256){
+ if(bufferIndex>=0 && bufferIndex<61440 && this.pixrendered[bufferIndex]!=0){
+ if(t.pix[toffset+i] != 0){
+ this.spr0HitX = bufferIndex%256;
+ this.spr0HitY = scan;
+ return true;
+ }
+ }
+ }
+ x++;
+ bufferIndex++;
+ }
+
+ }
+
+ }
+
+ }
+
+ return false;
+
+ }
+
+ // This will write to PPU memory, and
+ // update internally buffered data
+ // appropriately.
+ this.writeMem = function(address, value){
+
+ this.ppuMem[address] = value;
+
+ // Update internally buffered data:
+ if(address < 0x2000){
+
+ this.ppuMem[address] = value;
+ this.patternWrite(address,value);
+
+ }else if(address >=0x2000 && address <0x23c0){
+
+ this.nameTableWrite(this.ntable1[0],address-0x2000,value);
+
+ }else if(address >=0x23c0 && address <0x2400){
+
+ this.attribTableWrite(this.ntable1[0],address-0x23c0,value);
+
+ }else if(address >=0x2400 && address <0x27c0){
+
+ this.nameTableWrite(this.ntable1[1],address-0x2400,value);
+
+ }else if(address >=0x27c0 && address <0x2800){
+
+ this.attribTableWrite(this.ntable1[1],address-0x27c0,value);
+
+ }else if(address >=0x2800 && address <0x2bc0){
+
+ this.nameTableWrite(this.ntable1[2],address-0x2800,value);
+
+ }else if(address >=0x2bc0 && address <0x2c00){
+
+ this.attribTableWrite(this.ntable1[2],address-0x2bc0,value);
+
+ }else if(address >=0x2c00 && address <0x2fc0){
+
+ this.nameTableWrite(this.ntable1[3],address-0x2c00,value);
+
+ }else if(address >=0x2fc0 && address <0x3000){
+
+ this.attribTableWrite(this.ntable1[3],address-0x2fc0,value);
+
+ }else if(address >=0x3f00 && address <0x3f20){
+
+ this.updatePalettes();
+
+ }
+
+ }
+
+ // Reads data from $3f00 to $f20
+ // into the two buffered palettes.
+ this.updatePalettes = function(){
+
+ for(var i=0;i<16;i++){
+ if(this.f_dispType == 0){
+ this.imgPalette[i] = this.nes.palTable.getEntry(
+ this.ppuMem[0x3f00+i]&63);
+ }else{
+ this.imgPalette[i] = this.nes.palTable.getEntry(
+ this.ppuMem[0x3f00+i]&32);
+ }
+ }
+ for(var i=0;i<16;i++){
+ if(this.f_dispType == 0){
+ this.sprPalette[i] = this.nes.palTable.getEntry(
+ this.ppuMem[0x3f10+i]&63);
+ }else{
+ this.sprPalette[i] = this.nes.palTable.getEntry(
+ this.ppuMem[0x3f10+i]&32);
+ }
+ }
+
+ //renderPalettes();
+
+ }
+
+ // Updates the internal pattern
+ // table buffers with this new byte.
+ // In vNES, there is a version of this with 4 arguments which isn't used.
+ this.patternWrite = function(address, value){
+ var tileIndex = parseInt(address/16);
+ var leftOver = address%16;
+ if(leftOver<8){
+ this.ptTile[tileIndex].setScanline(
+ leftOver, value, this.ppuMem[address+8]);
+ }else{
+ this.ptTile[tileIndex].setScanline(
+ leftOver-8, this.ppuMem[address-8], value);
+ }
+ }
+
+ // Updates the internal name table buffers
+ // with this new byte.
+ this.nameTableWrite = function(index, address, value){
+ this.nameTable[index].tile[address] = value;
+
+ // Update Sprite #0 hit:
+ //updateSpr0Hit();
+ this.checkSprite0(this.scanline-20);
+
+ }
+
+ // Updates the internal pattern
+ // table buffers with this new attribute
+ // table byte.
+ this.attribTableWrite = function(index, address, value){
+ this.nameTable[index].writeAttrib(address,value);
+ }
+
+ // Updates the internally buffered sprite
+ // data with this new byte of info.
+ this.spriteRamWriteUpdate = function(address, value){
+
+ var tIndex = parseInt(address/4);
+
+ if(tIndex == 0){
+ //updateSpr0Hit();
+ this.checkSprite0(this.scanline-20);
+ }
+
+ if(address%4 == 0){
+ // Y coordinate
+ this.sprY[tIndex] = value;
+ }else if(address%4 == 1){
+ // Tile index
+ this.sprTile[tIndex] = value;
+ }else if(address%4 == 2){
+ // Attributes
+ this.vertFlip[tIndex] = ((value&0x80)!=0);
+ this.horiFlip[tIndex] = ((value&0x40)!=0);
+ this.bgPriority[tIndex] = ((value&0x20)!=0);
+ this.sprCol[tIndex] = (value&3)<<2;
+
+ }else if(address%4 == 3){
+ // X coordinate
+ this.sprX[tIndex] = value;
+ }
+
+ }
+
+ this.doNMI = function(){
+
+ // Set VBlank flag:
+ this.setStatusFlag(this.STATUS_VBLANK,true);
+ //nes.getCpu().doNonMaskableInterrupt();
+ this.nes.cpu.requestIrq(this.nes.cpu.IRQ_NMI);
+
+ }
+
+ this.reset = function() {
+
+ this.vramBufferedReadValue = 0;
+ this.sramAddress = 0;
+ this.curX = 0;
+ this.scanline = 0;
+ this.lastRenderedScanline = 0;
+ this.spr0HitX = 0;
+ this.spr0HitY = 0;
+ this.mapperIrqCounter = 0;
+
+ this.currentMirroring = -1;
+
+ this.firstWrite = true;
+ this.requestEndFrame = false;
+ this.nmiOk = false;
+ this.hitSpr0 = false;
+ this.dummyCycleToggle = false;
+ this.validTileData = false;
+ this.nmiCounter = 0;
+ this.tmp = 0;
+
+ // Control Flags Register 1:
+ this.f_nmiOnVblank = 0; // NMI on VBlank. 0=disable, 1=enable
+ this.f_spriteSize = 0; // Sprite size. 0=8x8, 1=8x16
+ this.f_bgPatternTable = 0; // Background Pattern Table address. 0=0x0000,1=0x1000
+ this.f_spPatternTable = 0; // Sprite Pattern Table address. 0=0x0000,1=0x1000
+ this.f_addrInc = 0; // PPU Address Increment. 0=1,1=32
+ this.f_nTblAddress = 0; // Name Table Address. 0=0x2000,1=0x2400,2=0x2800,3=0x2C00
+
+ // Control Flags Register 2:
+ this.f_color = 0; // Background color. 0=black, 1=blue, 2=green, 4=red
+ this.f_spVisibility = 0; // Sprite visibility. 0=not displayed,1=displayed
+ this.f_bgVisibility = 0; // Background visibility. 0=Not Displayed,1=displayed
+ this.f_spClipping = 0; // Sprite clipping. 0=Sprites invisible in left 8-pixel column,1=No clipping
+ this.f_bgClipping = 0; // Background clipping. 0=BG invisible in left 8-pixel column, 1=No clipping
+ this.f_dispType = 0; // Display type. 0=color, 1=monochrome
+ // Counters:
+ this.cntFV = 0;
+ this.cntV = 0;
+ this.cntH = 0;
+ this.cntVT = 0;
+ this.cntHT = 0;
+ // Registers:
+ this.regFV = 0;
+ this.regV = 0;
+ this.regH = 0;
+ this.regVT = 0;
+ this.regHT = 0;
+ this.regFH = 0;
+ this.regS = 0;
+
+
+ // Initialize stuff:
+ this.init();
+
+ }
+}
diff --git a/js/rom.js b/js/rom.js
new file mode 100644
index 0000000..de53c56
--- /dev/null
+++ b/js/rom.js
@@ -0,0 +1,227 @@
+function ROM(nes) {
+ this.nes = nes;
+
+ // Mirroring types:
+ this.VERTICAL_MIRRORING = 0;
+ this.HORIZONTAL_MIRRORING = 1;
+ this.FOURSCREEN_MIRRORING = 2;
+ this.SINGLESCREEN_MIRRORING = 3;
+ this.SINGLESCREEN_MIRRORING2= 4;
+ this.SINGLESCREEN_MIRRORING3= 5;
+ this.SINGLESCREEN_MIRRORING4= 6;
+ this.CHRROM_MIRRORING = 7;
+
+ this.failedSaveFile=false;
+ this.saveRamUpToDate=true;
+
+ this.header = null;
+ this.rom = null;
+ this.vrom = null;
+ this.saveRam = null;
+ this.vromTile = null;
+
+ this.romCount = null;
+ this.vromCount = null;
+ this.mirroring = null;
+ this.batteryRam = null;
+ this.trainer = null;
+ this.fourScreen = null;
+ this.mapperType = null;
+ this.fileName = null;
+ this.raFile = null;
+ this.enableSave = true;
+ this.valid = false;
+
+ this.mapperName = new Array(92);
+ this.supportedMappers = new Array(92);
+
+ for(var i=0;i<92;i++)
+ this.mapperName[i] = "Unknown Mapper";
+ this.mapperName[ 0] = "Direct Access";
+ this.mapperName[ 1] = "Nintendo MMC1";
+ this.mapperName[ 2] = "UNROM";
+ this.mapperName[ 3] = "CNROM";
+ this.mapperName[ 4] = "Nintendo MMC3";
+ this.mapperName[ 5] = "Nintendo MMC5";
+ this.mapperName[ 6] = "FFE F4xxx";
+ this.mapperName[ 7] = "AOROM";
+ this.mapperName[ 8] = "FFE F3xxx";
+ this.mapperName[ 9] = "Nintendo MMC2";
+ this.mapperName[10] = "Nintendo MMC4";
+ this.mapperName[11] = "Color Dreams Chip";
+ this.mapperName[12] = "FFE F6xxx";
+ this.mapperName[15] = "100-in-1 switch";
+ this.mapperName[16] = "Bandai chip";
+ this.mapperName[17] = "FFE F8xxx";
+ this.mapperName[18] = "Jaleco SS8806 chip";
+ this.mapperName[19] = "Namcot 106 chip";
+ this.mapperName[20] = "Famicom Disk System";
+ this.mapperName[21] = "Konami VRC4a";
+ this.mapperName[22] = "Konami VRC2a";
+ this.mapperName[23] = "Konami VRC2a";
+ this.mapperName[24] = "Konami VRC6";
+ this.mapperName[25] = "Konami VRC4b";
+ this.mapperName[32] = "Irem G-101 chip";
+ this.mapperName[33] = "Taito TC0190/TC0350";
+ this.mapperName[34] = "32kB ROM switch";
+
+ this.mapperName[64] = "Tengen RAMBO-1 chip";
+ this.mapperName[65] = "Irem H-3001 chip";
+ this.mapperName[66] = "GNROM switch";
+ this.mapperName[67] = "SunSoft3 chip";
+ this.mapperName[68] = "SunSoft4 chip";
+ this.mapperName[69] = "SunSoft5 FME-7 chip";
+ this.mapperName[71] = "Camerica chip";
+ this.mapperName[78] = "Irem 74HC161/32-based";
+ this.mapperName[91] = "Pirate HK-SF3 chip";
+
+ // The mappers supported:
+ this.supportedMappers[0] = true; // No Mapper
+ this.supportedMappers[1] = true; // MMC1
+ this.supportedMappers[2] = true; // UNROM
+ /*this.supportedMappers[3] = true; // CNROM*/
+ this.supportedMappers[4] = true; // MMC3
+ /*this.supportedMappers[7] = true; // AOROM
+ this.supportedMappers[9] = true; // MMC2
+ this.supportedMappers[10] = true; // MMC4
+ this.supportedMappers[11] = true; // ColorDreams
+ this.supportedMappers[66] = true; // GNROM
+ this.supportedMappers[68] = true; // SunSoft4 chip
+ this.supportedMappers[71] = true; // Camerica*/
+
+ this.load = function(fileName) {
+ this.fileName = fileName;
+ if (!roms[fileName]) {
+ alert("ROM does not exist.");
+ return;
+ }
+ b = roms[fileName]
+ if (b.indexOf("NES\x1a") == -1) {
+ alert("Not a valid NES ROM.");
+ return;
+ }
+ this.header = new Array(16);
+ for (var i = 0; i < 16; i++)
+ this.header[i] = b.charCodeAt(i);
+ this.romCount = this.header[4];
+ this.vromCount = this.header[5]*2; // Get the number of 4kB banks, not 8kB
+ this.mirroring = ((this.header[6]&1)!=0?1:0);
+ this.batteryRam = (this.header[6]&2)!=0;
+ this.trainer = (this.header[6]&4)!=0;
+ this.fourScreen = (this.header[6]&8)!=0;
+ this.mapperType = (this.header[6]>>4)|(this.header[7]&0xF0);
+ /* TODO
+ if (this.batteryRam)
+ this.loadBatteryRam();*/
+ // Check whether byte 8-15 are zero's:
+ var foundError = false;
+ for(var i=8;i<16;i++){
+ if(this.header[i]!=0){
+ foundError = true;
+ break;
+ }
+ }
+ if (foundError)
+ mapperType &= 0xF; // Ignore byte 7
+ // Load PRG-ROM banks:
+ this.rom = new Array(this.romCount);
+ var offset = 16;
+ for (var i=0; i<this.romCount;i++) {
+ this.rom[i] = new Array(16384);
+ for(var j=0;j<16384;j++){
+ if(offset+j >= b.length){
+ break;
+ }
+ this.rom[i][j] = b.charCodeAt(offset+j);
+ }
+ offset+=16384;
+ }
+ // Load CHR-ROM banks:
+ this.vrom = new Array(this.vromCount);
+ for(var i=0;i<this.vromCount;i++){
+ this.vrom[i] = new Array(4096);
+ for(var j=0;j<4096;j++){
+ if(offset+j >= b.length){
+ break;
+ }
+ this.vrom[i][j] = b.charCodeAt(offset+j);
+ }
+ offset+=4096;
+ }
+
+ // Create VROM tiles:
+ this.vromTile = new Array(this.vromCount);
+ for(var i=0;i<this.vromCount;i++){
+ this.vromTile[i] = new Array(256);
+ for(var j=0;j<256;j++){
+ this.vromTile[i][j] = new Tile();
+ }
+ }
+
+ // Convert CHR-ROM banks to tiles:
+ //System.out.println("Converting CHR-ROM image data..");
+ //System.out.println("VROM bank count: "+vromCount);
+ var tileIndex;
+ var leftOver;
+ for(var v=0;v<this.vromCount;v++){
+ for(var i=0;i<4096;i++){
+ tileIndex = i>>4;
+ leftOver = i%16;
+ if(leftOver<8){
+ this.vromTile[v][tileIndex].setScanline(
+ leftOver,this.vrom[v][i],this.vrom[v][i+8]);
+ }else{
+ this.vromTile[v][tileIndex].setScanline(
+ leftOver-8,this.vrom[v][i-8],this.vrom[v][i]);
+ }
+ }
+ }
+
+ this.valid = true;
+ }
+
+ this.getMirroringType = function() {
+ if (this.fourScreen)
+ return this.FOURSCREEN_MIRRORING;
+ if (this.mirroring == 0)
+ return this.HORIZONTAL_MIRRORING;
+ return this.VERTICAL_MIRRORING;
+ }
+
+ this.getMapperName = function() {
+ if (this.mapperType>=0 && this.mapperType<this.mapperName.length){
+ return this.mapperName[this.mapperType];
+ }
+ return "Unknown Mapper, "+this.mapperType;
+ }
+
+ this.mapperSupported = function() {
+ if (this.mapperType<this.supportedMappers.length && this.mapperType>=0){
+ return this.supportedMappers[this.mapperType];
+ }
+ return false;
+ }
+
+ this.closeRom = function() {
+
+ }
+
+ this.createMapper = function() {
+ if (this.mapperSupported()) {
+ switch(this.mapperType) {
+ case 0: // No mapper
+ return new MapperDefault(this.nes);
+ case 1: // MMC1
+ return new Mapper001(this.nes);
+ case 2: // UNROM
+ return new Mapper002(this.nes);
+ case 4: // MMC3
+ return new Mapper004(this.nes);
+ }
+ }
+ else {
+ alert("Mapper not supported: "+this.mapperType);
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/js/roms.js b/js/roms.js
new file mode 100644
index 0000000..906909e
--- /dev/null
+++ b/js/roms.js
@@ -0,0 +1,777 @@
+var roms_list = {
+ "Working": new Array(
+ 'Bubble Bobble (U).nes',
+ 'Contra (U) [!].nes',
+ 'Donkey Kong (JU).nes',
+ 'Dr. Mario (JU).nes',
+ 'Duck Hunt (JUE) [!].nes',
+ 'Golf (JU).nes',
+ 'Legend of Zelda, The (U) (PRG1).nes',
+ 'Lemmings (U).nes',
+ 'Lifeforce (U).nes',
+ 'Mario Bros. (JU) [!].nes',
+ 'Mega Man (U).nes',
+ 'Pac-Man (U) [!].nes',
+ 'Super Mario Bros. (JU) (PRG0) [!].nes',
+ 'Tennis (JU) [!].nes',
+ 'Tetris (U) [!].nes',
+ 'Tetris 2 (U) [!].nes',
+ 'Zelda II - The Adventure of Link (U).nes'
+ ),
+
+ "Nearly Working": new Array(
+ 'Super Mario Bros. 3 (U) (PRG1) [!].nes'
+ )
+
+ /*
+ "Broken": new Array(
+ 'Mega Man 2 (U).nes',
+ 'Metroid (U) [!].nes'
+ )
+ "Untested": new Array(
+ '10-Yard Fight (U).nes',
+ '1942 (JU) [!].nes',
+ '1943 - The Battle of Midway (U).nes',
+ '3-D Battles of World Runner, The (U).nes',
+ '720 (U).nes',
+ '8 Eyes (U).nes',
+ 'Abadox (U).nes',
+ 'Action 52 (U) [!].nes',
+ 'Addams Family, The (U).nes',
+ 'Addams Family, The - Pugsley\'s Scavenger Hunt (U).nes',
+ 'Advanced Dungeons & Dragons - Dragon Strike (U).nes',
+ 'Advanced Dungeons & Dragons - Heroes of the Lance (U).nes',
+ 'Advanced Dungeons & Dragons - Hillsfar (U) [!].nes',
+ 'Advanced Dungeons & Dragons - Pool of Radiance (U).nes',
+ 'Adventures in the Magic Kingdom (U).nes',
+ 'Adventures of Bayou Billy, The (U).nes',
+ 'Adventures of Captain Comic, The (U).nes',
+ 'Adventures of Dino Riki, The (U).nes',
+ 'Adventures of Lolo (U) [!].nes',
+ 'Adventures of Lolo 2 (U).nes',
+ 'Adventures of Lolo 3 (U).nes',
+ 'Adventures of Rad Gravity, The (U).nes',
+ 'Adventures of Rocky and Bullwinkle and Friends, The (U).nes',
+ 'Adventures of Tom Sawyer (U).nes',
+ 'After Burner (U).nes',
+ 'Air Fortress (U).nes',
+ 'Airwolf (U).nes',
+ 'Al Unser Jr. Turbo Racing (U).nes',
+ 'Aladdin (Unl).nes',
+ 'Alfred Chicken (U).nes',
+ 'Alien 3 (U).nes',
+ 'Alien Syndrome (U).nes',
+ 'All-Pro Basketball (U).nes',
+ 'Alpha Mission (U).nes',
+ 'Amagon (U).nes',
+ 'American Gladiators (U).nes',
+ 'Anticipation (U).nes',
+ 'Arch Rivals - A Basket Brawl! (U).nes',
+ 'Archon (U).nes',
+ 'Arkanoid (U).nes',
+ 'Arkista\'s Ring (U) [!].nes',
+ 'Astyanax (U).nes',
+ 'Athena (U).nes',
+ 'Athletic World (U).nes',
+ 'Attack of the Killer Tomatoes (U).nes',
+ 'Baby Boomer (U).nes',
+ 'Back to the Future (U).nes',
+ 'Back to the Future II & III (U).nes',
+ 'Bad Dudes (U).nes',
+ 'Bad News Baseball (U).nes',
+ 'Bad Street Brawler (U).nes',
+ 'Balloon Fight (JU) [!].nes',
+ 'Bandai Golf - Challenge Pebble Beach (U).nes',
+ 'Bandit Kings of Ancient China (U).nes',
+ 'Barbie (U) (Rev 3).nes',
+ 'Bard\'s Tale, The - Tales of the Unknown (U) [!].nes',
+ 'Barker Bill\'s Trick Shooting (U).nes',
+ 'Base Wars (U).nes',
+ 'Baseball (UE) [!].nes',
+ 'Baseball Simulator 1.000 (U).nes',
+ 'Baseball Stars (U).nes',
+ 'Baseball Stars II (U).nes',
+ 'Bases Loaded (U).nes',
+ 'Bases Loaded 3 (U).nes',
+ 'Bases Loaded 4 (U).nes',
+ 'Bases Loaded II (U).nes',
+ 'Batman (U).nes',
+ 'Batman - Return of the Joker (U).nes',
+ 'Batman Returns (U).nes',
+ 'Battle Chess (U).nes',
+ 'Battle of Olympus, The (U).nes',
+ 'Battle Tank (U).nes',
+ 'Battleship (U).nes',
+ 'Battletoads & Double Dragon - The Ultimate Team (U).nes',
+ 'Battletoads (U).nes',
+ 'Bee 52 (U).nes',
+ 'Beetlejuice (U) [!].nes',
+ 'Best of the Best - Championship Karate (U).nes',
+ 'Bible Adventures (U) (V1.3).nes',
+ 'Bible Buffet (U).nes',
+ 'Big Bird\'s Hide & Speak (U).nes',
+ 'Big Nose Freaks Out (U).nes',
+ 'Big Nose the Caveman (U).nes',
+ 'Bigfoot (U).nes',
+ 'Bill & Ted\'s Excellent Video Game Adventure (U).nes',
+ 'Bill Elliott\'s NASCAR Challenge (U).nes',
+ 'Bionic Commando (U) [!].nes',
+ 'Black Bass USA, The (U).nes',
+ 'Blackjack (U).nes',
+ 'Blades of Steel (U).nes',
+ 'Blaster Master (U).nes',
+ 'Blue Marlin, The (U).nes',
+ 'Blues Brothers, The (U).nes',
+ 'Bo Jackson Baseball (U).nes',
+ 'Bomberman (U).nes',
+ 'Bomberman II (U).nes',
+ 'Bonk\'s Adventure (U).nes',
+ 'Boulder Dash (U).nes',
+ 'Boy and His Blob, A - Trouble on Blobolonia (U).nes',
+ 'Bram Stoker\'s Dracula (U).nes',
+ 'Break Time - The National Pool Tour (U).nes',
+ 'BreakThru (U).nes',
+ 'Bubble Bath Babes (UE) [!].nes',
+ 'Bubble Bobble (U).nes',
+ 'Bubble Bobble Part 2 (U).nes',
+ 'Bucky O\'Hare (U) [!].nes',
+ 'Bugs Bunny Birthday Blowout, The (U).nes',
+ 'Bugs Bunny Crazy Castle, The (U).nes',
+ 'Bugs Bunny Fun House (U) (Prototype).nes',
+ 'Bump\'n\'Jump (U).nes',
+ 'Burai Fighter (U).nes',
+ 'Burger Time (U) [!].nes',
+ 'Cabal (U).nes',
+ 'Caesars Palace (U).nes',
+ 'California Games (U).nes',
+ 'California Raisins - The Grape Escape (U).nes',
+ 'Caltron 6-in-1 (U).nes',
+ 'Captain America and The Avengers (U).nes',
+ 'Captain Planet and The Planeteers (U).nes',
+ 'Captain Skyhawk (U).nes',
+ 'Casino Kid (U).nes',
+ 'Casino Kid 2 (U).nes',
+ 'Castelian (U).nes',
+ 'Castle of Deceit (U).nes',
+ 'Castle of Dragon (U).nes',
+ 'Castlequest (U).nes',
+ 'Castlevania (U) (PRG1).nes',
+ 'Castlevania II - Simon\'s Quest (U).nes',
+ 'Castlevania III - Dracula\'s Curse (U) [!].nes',
+ 'Caveman Games (U).nes',
+ 'Challenge of the Dragon (U).nes',
+ 'Championship Bowling (U) [!].nes',
+ 'Championship Pool (U).nes',
+ 'Cheetah Men II (U).nes',
+ 'Chessmaster, The (U).nes',
+ 'Chiller (U) [!].nes',
+ 'Chip \'n Dale Rescue Rangers (U) [!].nes',
+ 'Chip \'n Dale Rescue Rangers 2 (U).nes',
+ 'Chubby Cherub (U).nes',
+ 'Circus Caper (U).nes',
+ 'City Connection (U).nes',
+ 'Clash at Demonhead (U).nes',
+ 'Classic Concentration (U).nes',
+ 'Cliffhanger (U).nes',
+ 'Clu Clu Land (JU).nes',
+ 'Cobra Command (U).nes',
+ 'Cobra Triangle (U).nes',
+ 'Code Name - Viper (U).nes',
+ 'Color A Dinosaur (U).nes',
+ 'Commando (U).nes',
+ 'Conan (U).nes',
+ 'Conflict (U) [!].nes',
+ 'Conquest of the Crystal Palace (U).nes',
+ 'Contra Force (U).nes',
+ 'Cool World (U).nes',
+ 'Cowboy Kid (U).nes',
+ 'Crash \'n the Boys - Street Challenge (U).nes',
+ 'Crystal Mines (U).nes',
+ 'Crystalis (U).nes',
+ 'Cyberball (U).nes',
+ 'Cybernoid - The Fighting Machine (U).nes',
+ 'Danny Sullivan\'s Indy Heat (U).nes',
+ 'Darkman (U).nes',
+ 'Darkwing Duck (U).nes',
+ 'Dash Galaxy in the Alien Asylum (U).nes',
+ 'Day Dreamin\' Davey (U).nes',
+ 'Days of Thunder (U).nes',
+ 'Deadly Towers (U).nes',
+ 'Death Race (U).nes',
+ 'Deathbots (U).nes',
+ 'Defender II (U).nes',
+ 'Defender of the Crown (U).nes',
+ 'Defenders of Dynatron City (U).nes',
+ 'Deja Vu (U).nes',
+ 'Demon Sword (U).nes',
+ 'Desert Commander (U).nes',
+ 'Destination Earthstar (U).nes',
+ 'Destiny of an Emperor (U).nes',
+ 'Dick Tracy (U).nes',
+ 'Die Hard (U).nes',
+ 'Dig Dug II (U) [!].nes',
+ 'Digger - The Legend of the Lost City (U).nes',
+ 'Dirty Harry (U).nes',
+ 'Dizzy The Adventurer (U).nes',
+ 'Donkey Kong 3 (JUE).nes',
+ 'Donkey Kong Classics (U) [!].nes',
+ 'Donkey Kong Jr. (JU).nes',
+ 'Donkey Kong Jr. Math (U).nes',
+ 'Double Dare (U).nes',
+ 'Double Dragon (U).nes',
+ 'Double Dragon II - The Revenge (U).nes',
+ 'Double Dragon III - The Sacred Stones (U).nes',
+ 'Double Dribble (U) (PRG1) [!].nes',
+ 'Double Strike (U) (V1.1).nes',
+ 'Dr. Chaos (U).nes',
+ 'Dr. Jekyll and Mr. Hyde (U).nes',
+ 'Dragon Fighter (U).nes',
+ 'Dragon Power (U).nes',
+ 'Dragon Spirit - The New Legend (U).nes',
+ 'Dragon Warrior (U) (PRG1).nes',
+ 'Dragon Warrior II (U).nes',
+ 'Dragon Warrior III (U).nes',
+ 'Dragon Warrior IV (U).nes',
+ 'Dragon\'s Lair (U).nes',
+ 'Duck Tales (U).nes',
+ 'Duck Tales 2 (U).nes',
+ 'Dudes With Attitude (U).nes',
+ 'Dungeon Magic - Sword of the Elements (U).nes',
+ 'Dusty Diamond\'s All-Star Softball (U).nes',
+ 'Dynowarz - Destruction of Spondylus (U).nes',
+ 'Elevator Action (U).nes',
+ 'Eliminator Boat Duel (U).nes',
+ 'Excitebike (JU) [!].nes',
+ 'Exodus (U) (V4.0) [!].nes',
+ 'F-117A Stealth Fighter (U) [!].nes',
+ 'F-15 City War (U).nes',
+ 'F-15 Strike Eagle (U).nes',
+ 'Family Feud (U).nes',
+ 'Fantastic Adventures of Dizzy, The (U).nes',
+ 'Fantasy Zone (U).nes',
+ 'Faria - A World of Mystery and Danger! (U).nes',
+ 'Faxanadu (U).nes',
+ 'Felix the Cat (U).nes',
+ 'Ferrari - Grand Prix Challenge (U) [!].nes',
+ 'Fester\'s Quest (U) [!].nes',
+ 'Final Fantasy (U) [!].nes',
+ 'Fire \'n Ice (U).nes',
+ 'Fire Hawk (Unl).nes',
+ 'Firehouse Rescue (U).nes',
+ 'Fist of the North Star (U).nes',
+ 'Flight of the Intruder (U).nes',
+ 'Flintstones, The - The Rescue of Dino & Hoppy (U).nes',
+ 'Flintstones, The - The Surprise at Dinosaur Peak! (U).nes',
+ 'Flying Dragon - The Secret Scroll (U).nes',
+ 'Flying Warriors (U).nes',
+ 'Formula One - Built To Win (U) [o1].nes',
+ 'Frankenstein - The Monster Returns (U).nes',
+ 'Freedom Force (U) [!].nes',
+ 'Friday the 13th (U).nes',
+ 'Fun House (U).nes',
+ 'G.I. Joe (U).nes',
+ 'G.I. Joe - The Atlantis Factor (U).nes',
+ 'Galaga (U).nes',
+ 'Galaxy 5000 (U).nes',
+ 'Gargoyle\'s Quest II - The Demon Darkness (U).nes',
+ 'Gauntlet (U) [!].nes',
+ 'Gauntlet II (U).nes',
+ 'Gemfire (U).nes',
+ 'Genghis Khan (U).nes',
+ 'George Foreman\'s KO Boxing (U).nes',
+ 'Ghostbusters (U).nes',
+ 'Ghostbusters 2 (U).nes',
+ 'Ghosts\'n Goblins (U) [!].nes',
+ 'Ghoul School (U).nes',
+ 'Gilligan\'s Island (U).nes',
+ 'Goal! (U).nes',
+ 'Goal! Two (U).nes',
+ 'Godzilla - Monster of Monsters! (U).nes',
+ 'Godzilla 2 - War of the Monsters (U).nes',
+ 'Gold Medal Challenge \'92 (U).nes',
+ 'Golf Grand Slam (U).nes',
+ 'Golgo 13 - Top Secret Episode (U).nes',
+ 'Goonies II, The (U).nes',
+ 'Gotcha! - The Sport! (U).nes',
+ 'Gradius (U).nes',
+ 'Great Waldo Search, The (U).nes',
+ 'Greg Norman\'s Golf Power (U).nes',
+ 'Gremlins 2 - The New Batch (U).nes',
+ 'Guardian Legend, The (U).nes',
+ 'Guerrilla War (U).nes',
+ 'Gumshoe (UE).nes',
+ 'Gun Nac (U).nes',
+ 'Gun.Smoke (U).nes',
+ 'Gyromite (JUE).nes',
+ 'Gyruss (U).nes',
+ 'Harlem Globetrotters (U).nes',
+ 'Hatris (U).nes',
+ 'Heavy Barrel (U).nes',
+ 'Heavy Shreddin\' (U).nes',
+ 'High Speed (U).nes',
+ 'Hogan\'s Alley (JU) [!].nes',
+ 'Hollywood Squares (U).nes',
+ 'Home Alone (U) [!].nes',
+ 'Home Alone 2 - Lost in New York (U).nes',
+ 'Hook (U).nes',
+ 'Hoops (U).nes',
+ 'Hot Slot (UE).nes',
+ 'Hudson Hawk (U).nes',
+ 'Hudson\'s Adventure Island (U).nes',
+ 'Hudson\'s Adventure Island II (U).nes',
+ 'Hudson\'s Adventure Island III (U).nes',
+ 'Hunt for Red October, The (U).nes',
+ 'Hydlide (U).nes',
+ 'I Can Remember (U).nes',
+ 'Ice Climber (U) [!].nes',
+ 'Ice Hockey (U) [!].nes',
+ 'Ikari III - The Rescue (U).nes',
+ 'Ikari Warriors (U) (PRG1) [!].nes',
+ 'Ikari Warriors II - Victory Road (U).nes',
+ 'Image Fight (U).nes',
+ 'Immortal, The (U).nes',
+ 'Impossible Mission II (U).nes',
+ 'Incredible Crash Dummies, The (U).nes',
+ 'Indiana Jones and the Last Crusade (U) (Taito).nes',
+ 'Indiana Jones and the Temple of Doom (U) (Tengen).nes',
+ 'Infiltrator (U).nes',
+ 'Iron Tank (U).nes',
+ 'Ironsword - Wizards & Warriors II (U).nes',
+ 'Isolated Warrior (U).nes',
+ 'Ivan Ironman Stewart\'s Super Off-Road (U).nes',
+ 'Jack Nicklaus\' Greatest 18 Holes of Major Championship Golf (U).nes',
+ 'Jackal (U).nes',
+ 'Jackie Chan\'s Action Kung Fu (U).nes',
+ 'James Bond Jr (U).nes',
+ 'Jaws (U) [!].nes',
+ 'Jeopardy! (U).nes',
+ 'Jeopardy! 25th Anniversary Edition (U).nes',
+ 'Jeopardy! Junior Edition (U).nes',
+ 'Jetsons, The - Cogswell\'s Caper! (U).nes',
+ 'Jimmy Connor\'s Tennis (U).nes',
+ 'Joe & Mac (U).nes',
+ 'John Elway\'s Quarterback (U).nes',
+ 'Joshua (U) (V6.0) [!].nes',
+ 'Journey to Silius (U).nes',
+ 'Joust (U).nes',
+ 'Jungle Book, The (U).nes',
+ 'Jurassic Park (U).nes',
+ 'Kabuki - Quantum Fighter (U).nes',
+ 'Karate Champ (U).nes',
+ 'Karate Kid, The (U).nes',
+ 'Karnov (U).nes',
+ 'Kick Master (U).nes',
+ 'Kickle Cubicle (U).nes',
+ 'Kid Icarus (UE).nes',
+ 'Kid Klown (U).nes',
+ 'Kid Kool (U).nes',
+ 'Kid Niki - Radical Ninja (U) (PRG1) [!].nes',
+ 'King Neptune\'s Adventure (U).nes',
+ 'King of Kings, The (U) (V1.2) [!].nes',
+ 'King\'s Knight (U).nes',
+ 'King\'s Quest V (U).nes',
+ 'Kings of the Beach (U).nes',
+ 'Kirby\'s Adventure (U) (PRG1) [!].nes',
+ 'Kiwi Kraze (U).nes',
+ 'Klash Ball (U).nes',
+ 'Klax (U).nes',
+ 'Knight Rider (U).nes',
+ 'Krazy Kreatures (U).nes',
+ 'Krion Conquest, The (U).nes',
+ 'Krusty\'s Fun House (U).nes',
+ 'Kung Fu (U) [!].nes',
+ 'Kung-Fu Heroes (U).nes',
+ 'L\'Empereur (U).nes',
+ 'Laser Invasion (U).nes',
+ 'Last Action Hero (U) [!].nes',
+ 'Last Ninja, The (U).nes',
+ 'Last Starfighter, The (U).nes',
+ 'Lee Trevino\'s Fighting Golf (U).nes',
+ 'Legacy of the Wizard (U).nes',
+ 'Legend of Kage, The (U).nes',
+ 'Legend of the Ghost Lion (U).nes',
+ 'Legendary Wings (U).nes',
+ 'Legends of the Diamond (U).nes',
+ 'Lethal Weapon (U).nes',
+ 'Lifeforce (U).nes',
+ 'Linus Spacehead\'s Cosmic Crusade (Aladdin) (U).nes',
+ 'Little League Baseball - Championship Series (U).nes',
+ 'Little Mermaid, The (U).nes',
+ 'Little Nemo - The Dream Master (U).nes',
+ 'Little Ninja Brothers (U).nes',
+ 'Little Samson (U).nes',
+ 'Lode Runner (U).nes',
+ 'Lone Ranger, The (U).nes',
+ 'Loopz (U).nes',
+ 'Low G Man (U) [!].nes',
+ 'Lunar Pool (U).nes',
+ 'M.C. Kids (U).nes',
+ 'M.U.L.E. (U).nes',
+ 'M.U.S.C.L.E. (U).nes',
+ 'Mach Rider (JU).nes',
+ 'Mad Max (U) [!].nes',
+ 'Mafat Conspiracy - Golgo 13 (U).nes',
+ 'Magic Darts (U).nes',
+ 'Magic Johnson\'s Fast Break (U).nes',
+ 'Magic of Scheherazade, The (U).nes',
+ 'Magician (U).nes',
+ 'Magmax (U).nes',
+ 'Major League Baseball (U).nes',
+ 'Maniac Mansion (U).nes',
+ 'Mappy-Land (U).nes',
+ 'Marble Madness (U).nes',
+ 'Mario is Missing! (U).nes',
+ 'Mario\'s Time Machine! (U).nes',
+ 'Marvel\'s X-Men (U).nes',
+ 'Master Chu & The Drunkard Hu (U).nes',
+ 'Mechanized Attack (U).nes',
+ 'Mega Man (U).nes',
+ 'Mega Man 2 (U).nes',
+ 'Mega Man 3 (U) [!].nes',
+ 'Mega Man 4 (U).nes',
+ 'Mega Man 5 (U).nes',
+ 'Mega Man 6 (U).nes',
+ 'Menace Beach (U).nes',
+ 'Mendel Palace (U).nes',
+ 'Mermaids of Atlantis (U).nes',
+ 'Metal Fighter (U).nes',
+ 'Metal Gear (U).nes',
+ 'Metal Mech (U).nes',
+ 'Metal Storm (U).nes',
+ 'metalgear2.nes',
+ 'Metroid (U) [!].nes',
+ 'Michael Andretti\'s World Grand Prix (U).nes',
+ 'Mickey Mousecapade (U).nes',
+ 'Mickey\'s Adventures in Numberland (U).nes',
+ 'Mickey\'s Safari in Letterland (U).nes',
+ 'Micro Machines (U).nes',
+ 'MiG 29 - Soviet Fighter (U).nes',
+ 'Might and Magic (U).nes',
+ 'Mighty Bomb Jack (U).nes',
+ 'Mighty Final Fight (U).nes',
+ 'Mike Tyson\'s Punch-Out!! (U) (PRG1).nes',
+ 'Millipede (U).nes',
+ 'Milon\'s Secret Castle (U).nes',
+ 'Miracle Piano Teaching System, The (U).nes',
+ 'Mission Cobra (U).nes',
+ 'Mission Impossible (U).nes',
+ 'Monopoly (U).nes',
+ 'Monster In My Pocket (U).nes',
+ 'Monster Party (U).nes',
+ 'Monster Truck Rally (U).nes',
+ 'Moon Ranger (U).nes',
+ 'Motor City Patrol (U).nes',
+ 'Ms. Pac-Man (U) (Tengen).nes',
+ 'Muppet Adventure - Chaos at the Carnival (U).nes',
+ 'Mutant Virus, The (U).nes',
+ 'Mystery Quest (U).nes',
+ 'NARC (U).nes',
+ 'NES Open Tournament Golf (U).nes',
+ 'NES Play Action Football (U).nes',
+ 'NFL Football (U).nes',
+ 'Nigel Mansell\'s World Championship Challenge (U).nes',
+ 'Nightmare on Elm Street, A (U).nes',
+ 'Nightshade (U).nes',
+ 'Ninja Crusaders (U).nes',
+ 'Ninja Gaiden (U) [!].nes',
+ 'Ninja Gaiden 2 - The Dark Sword of Chaos (U).nes',
+ 'Ninja Gaiden 3 - The Ancient Ship of Doom (U) [!].nes',
+ 'Ninja Kid (U).nes',
+ 'Nintendo World Cup (U).nes',
+ 'Nobunaga\'s Ambition (U).nes',
+ 'Nobunaga\'s Ambition 2 (U).nes',
+ 'North & South (U).nes',
+ 'official.nes',
+ 'Operation Secret Storm (U).nes',
+ 'Operation Wolf (U).nes',
+ 'Orb 3D (U).nes',
+ 'Othello (U).nes',
+ 'Overlord (U).nes',
+ 'P\'radikus Conflict, The (U).nes',
+ 'Pac-Mania (U).nes',
+ 'Palamedes (U).nes',
+ 'Panic Restaurant (U).nes',
+ 'Paperboy (U).nes',
+ 'Paperboy 2 (U).nes',
+ 'Peek-A-Boo Poker (UE) [!].nes',
+ 'Perfect Fit (U).nes',
+ 'Pesterminator - The Western Exterminator (U).nes',
+ 'Peter Pan & The Pirates (U).nes',
+ 'Phantom Fighter (U).nes',
+ 'Pictionary (U).nes',
+ 'Pinball (JU).nes',
+ 'Pinball Quest (U).nes',
+ 'Pinbot (U) [!].nes',
+ 'Pipe Dream (U).nes',
+ 'Pirates! (U).nes',
+ 'Platoon (U) (PRG1) [!].nes',
+ 'Popeye (JU) (PRG1) [!].nes',
+ 'POW - Prisoners of War (U).nes',
+ 'Power Blade (U) [!].nes',
+ 'Power Blade 2 (U).nes',
+ 'Power Pad Dance Aerobics (U).nes',
+ 'Power Punch 2 (U).nes',
+ 'Predator (U).nes',
+ 'Prince of Persia (U).nes',
+ 'Princess Tomato in Salad Kingdom (U).nes',
+ 'Pro Sport Hockey (U).nes',
+ 'Pro Wrestling (U) (PRG1) [!].nes',
+ 'Punch-Out!! (U).nes',
+ 'Punisher, The (U).nes',
+ 'Puss \'n Boots - Pero\'s Great Adventure (U).nes',
+ 'Puzznic (U).nes',
+ 'Q-bert (U).nes',
+ 'Qix (U).nes',
+ 'Quattro Adventure (U).nes',
+ 'Quattro Arcade (U).nes',
+ 'Quattro Sports (U).nes',
+ 'R.B.I. Baseball (U).nes',
+ 'R.B.I. Baseball 2 (U).nes',
+ 'R.B.I. Baseball 3 (U).nes',
+ 'R.C. Pro-Am (U) (PRG1).nes',
+ 'R.C. Pro-Am 2 (U).nes',
+ 'Race America (U).nes',
+ 'Racket Attack (U).nes',
+ 'Rad Racer (U).nes',
+ 'Rad Racer 2 (U) [!].nes',
+ 'Rad Racket - Deluxe Tennis II (U) [!].nes',
+ 'Raid 2020 (U) [!].nes',
+ 'Raid on Bungeling Bay (U).nes',
+ 'Rainbow Islands - The Story of Bubble Bobble 2 (U).nes',
+ 'Rally Bike (U).nes',
+ 'Rambo (U).nes',
+ 'Rampage (U).nes',
+ 'Rampart (U).nes',
+ 'Remote Control (U).nes',
+ 'Ren & Stimpy Show, The (U).nes',
+ 'Renegade (U).nes',
+ 'Rescue - The Embassy Mission (U).nes',
+ 'Ring King (U).nes',
+ 'River City Ransom (U).nes',
+ 'Road Runner (U).nes',
+ 'RoadBlasters (U).nes',
+ 'Robin Hood - Prince of Thieves (U).nes',
+ 'Robo Warrior (U).nes',
+ 'Robocop (U).nes',
+ 'Robocop 2 (U).nes',
+ 'Robocop 3 (U).nes',
+ 'Robodemons (U).nes',
+ 'Rock \'n\' Ball (U).nes',
+ 'Rocket Ranger (U) [!].nes',
+ 'Rocketeer, The (U).nes',
+ 'Rockin\' Kats (U) [!].nes',
+ 'Roger Clemens MVP Baseball (U).nes',
+ 'Rollerball (U).nes',
+ 'Rollerblade Racer (U).nes',
+ 'Rollergames (U) [!].nes',
+ 'Rolling Thunder (U).nes',
+ 'Romance of the Three Kingdoms (U).nes',
+ 'Romance of the Three Kingdoms II (U) [!].nes',
+ 'Roundball - 2-on-2 Challenge (U).nes',
+ 'Rush\'n Attack (U).nes',
+ 'Rygar (U).nes',
+ 'SCAT - Special Cybernetic Attack Team (U).nes',
+ 'Secret Scout (Unl).nes',
+ 'Section Z (U).nes',
+ 'Seicross (U).nes',
+ 'Sesame Street 123 (U).nes',
+ 'Sesame Street ABC (U).nes',
+ 'Sesame Street ABC - 123 (U).nes',
+ 'Sesame Street Countdown (U).nes',
+ 'Shadow of the Ninja (U).nes',
+ 'Shadowgate (U).nes',
+ 'Shatterhand (U).nes',
+ 'Shingen The Ruler (U).nes',
+ 'Shinobi (U).nes',
+ 'Shockwave (U).nes',
+ 'Shooting Range (U).nes',
+ 'Short Order - Eggsplode (U) [!].nes',
+ 'Side Pocket (U).nes',
+ 'Silent Assault (U).nes',
+ 'Silent Service (U).nes',
+ 'Silk Worm (U).nes',
+ 'Silver Surfer (U).nes',
+ 'Simpsons, The - Bart Vs. the Space Mutants (U).nes',
+ 'Simpsons, The - Bart Vs. the World (U).nes',
+ 'Simpsons, The - Bartman Meets Radioactive Man (U).nes',
+ 'Skate or Die 2 - The Search for Double Trouble (U).nes',
+ 'Skate or Die! (U).nes',
+ 'Ski or Die (U).nes',
+ 'Skull & Crossbones (U).nes',
+ 'Sky Kid (U).nes',
+ 'Sky Shark (U).nes',
+ 'Slalom (U).nes',
+ 'Smash T.V. (U) [!].nes',
+ 'Snake Rattle\'n Roll (U).nes',
+ 'Snake\'s Revenge (U).nes',
+ 'Snoopy\'s Silly Sports Spectacular (U).nes',
+ 'Snow Bros (U).nes',
+ 'Soccer (JU).nes',
+ 'Solar Jetman - Hunt for the Golden Warpship (U).nes',
+ 'Solitaire (U).nes',
+ 'Solomon\'s Key (U) [!].nes',
+ 'Solstice (U).nes',
+ 'Space Shuttle Project (U).nes',
+ 'Spelunker (U) [!].nes',
+ 'Spider-Man - Return of the Sinister Six (U) [!].nes',
+ 'Spiritual Warfare (U) (V6.1).nes',
+ 'Spot (U).nes',
+ 'Spy Hunter (U).nes',
+ 'Spy Vs Spy (U).nes',
+ 'Sqoon (U).nes',
+ 'Stack Up (Robot Block) (JU).nes',
+ 'Stadium Events (U) [!].nes',
+ 'Stanley - The Search for Dr. Livingston (U).nes',
+ 'Star Force (U).nes',
+ 'Star Soldier (U).nes',
+ 'Star Trek - 25th Anniversary (U).nes',
+ 'Star Trek - The Next Generation (U).nes',
+ 'Star Voyager (U).nes',
+ 'Star Wars (U).nes',
+ 'Star Wars - The Empire Strikes Back (U).nes',
+ 'Starship Hector (U).nes',
+ 'Startropics (U).nes',
+ 'Startropics 2 - Zoda\'s Revenge (U).nes',
+ 'Stealth ATF (U).nes',
+ 'Stinger (U).nes',
+ 'Stinger.nes',
+ 'Street Cop (U).nes',
+ 'Street Fighter 2010 (U).nes',
+ 'Strider (U).nes',
+ 'Stunt Kids (U).nes',
+ 'Sunday Funday (U).nes',
+ 'Super C (U) [!].nes',
+ 'Super Cars (U).nes',
+ 'Super Dodge Ball (U).nes',
+ 'Super Glove Ball (U).nes',
+ 'Super Jeopardy! (U).nes',
+ 'Super Mario Bros. + Duck Hunt (U).nes',
+ 'Super Mario Bros. + Duck Hunt + World Class Track Meet (U).nes',
+ 'Super Mario Bros. 2 (U) (PRG0) [!].nes',
+ 'Super Pitfall (U).nes',
+ 'Super Spike V\'Ball (U).nes',
+ 'Super Spike V\'Ball + Nintendo World Cup (U) [!].nes',
+ 'Super Sprint (U).nes',
+ 'Super Spy Hunter (U).nes',
+ 'Super Team Games (U).nes',
+ 'Superman (U).nes',
+ 'Swamp Thing (U) [!].nes',
+ 'Sword Master (U).nes',
+ 'Swords and Serpents (U).nes',
+ 'T&C 2 - Thrilla\'s Surfari (U).nes',
+ 'T&C Surf Design (U).nes',
+ 'Taboo - The Sixth Sense (U).nes',
+ 'Tag Team Wrestling (U).nes',
+ 'Tagin\' Dragon (U).nes',
+ 'TaleSpin (U).nes',
+ 'Target Renegade (U).nes',
+ 'Tecmo Baseball (U).nes',
+ 'Tecmo Bowl (U) (PRG1).nes',
+ 'Tecmo Cup - Soccer Game (U).nes',
+ 'Tecmo NBA Basketball (U).nes',
+ 'Tecmo Super Bowl (U).nes',
+ 'Tecmo World Wrestling (U).nes',
+ 'Teenage Mutant Ninja Turtles (U) [!].nes',
+ 'Teenage Mutant Ninja Turtles - Tournament Fighters (U).nes',
+ 'Teenage Mutant Ninja Turtles II - The Arcade Game (U) [!].nes',
+ 'Teenage Mutant Ninja Turtles III - The Manhattan Project (U).nes',
+ 'Terminator 2 - Judgment Day (U).nes',
+ 'Terminator, The (U).nes',
+ 'Terra Cresta (U).nes',
+ 'Three Stooges (U).nes',
+ 'Thunder & Lightning (U).nes',
+ 'Thunderbirds (U).nes',
+ 'Thundercade (U).nes',
+ 'Tiger-Heli (U).nes',
+ 'Tiles of Fate (U).nes',
+ 'Time Lord (U).nes',
+ 'Times of Lore (U).nes',
+ 'Tiny Toon Adventures (U).nes',
+ 'Tiny Toon Adventures 2 - Trouble in Wackyland (U).nes',
+ 'Tiny Toon Adventures Cartoon Workshop (U).nes',
+ 'To The Earth (U).nes',
+ 'Toki (U).nes',
+ 'Tom & Jerry and Tuffy (U).nes',
+ 'Tombs and Treasure (U).nes',
+ 'Toobin\' (U).nes',
+ 'Top Gun (U) (PRG1).nes',
+ 'Top Gun - The Second Mission (U).nes',
+ 'Top Players\' Tennis - Featuring Chris Evert & Ivan Lendl (U).nes',
+ 'Total Recall (U).nes',
+ 'Totally Rad (U).nes',
+ 'Touch Down Fever (U).nes',
+ 'Toxic Crusaders (U).nes',
+ 'Track & Field (U).nes',
+ 'Track & Field 2 (U).nes',
+ 'Transformers.nes',
+ 'Treasure Master (U).nes',
+ 'Trog (U).nes',
+ 'Trojan (U).nes',
+ 'Trolls on Treasure Island (U).nes',
+ 'Twin Cobra (U).nes',
+ 'Twin Eagle (U).nes',
+ 'Ultima - Exodus (U).nes',
+ 'Ultima - Quest of the Avatar (U).nes',
+ 'Ultima - Warriors of Destiny (U).nes',
+ 'Ultimate Air Combat (U).nes',
+ 'Ultimate Basketball (U).nes',
+ 'Ultimate League Soccer (U).nes',
+ 'Ultimate Stuntman (U).nes',
+ 'Uncharted Waters (U).nes',
+ 'Uninvited (U).nes',
+ 'Untouchables, The (U).nes',
+ 'Urban Champion (JU).nes',
+ 'Vegas Dream (U).nes',
+ 'Venice Beach Volleyball (U).nes',
+ 'Vice - Project Doom (U).nes',
+ 'Videomation (U).nes',
+ 'Vindicators (U).nes',
+ 'Volleyball (U).nes',
+ 'Wacky Races (U).nes',
+ 'Wall Street Kid (U).nes',
+ 'Wally Bear and the No Gang (U).nes',
+ 'Wario\'s Woods (U) [!].nes',
+ 'Wayne Gretzky Hockey (U).nes',
+ 'Wayne\'s World (U).nes',
+ 'WCW World Championship Wrestling (U).nes',
+ 'Werewolf - The Last Warrior (U).nes',
+ 'Wheel of Fortune (U) (PRG1) [!].nes',
+ 'Wheel of Fortune - Starring Vanna White (U).nes',
+ 'Wheel of Fortune Family Edition (U).nes',
+ 'Wheel of Fortune Junior Edition (U).nes',
+ 'Where in Time is Carmen Sandiego (U).nes',
+ 'Where\'s Waldo (U).nes',
+ 'Who Framed Roger Rabbit (U).nes',
+ 'Whomp\'Em (U).nes',
+ 'Widget (U).nes',
+ 'Wild Gunman (U) (PRG1) [!].nes',
+ 'Willow (U).nes',
+ 'Win, Lose or Draw (U).nes',
+ 'Winter Games (U).nes',
+ 'Wizardry - Proving Grounds of the Mad Overlord (U).nes',
+ 'Wizardry - The Knight of Diamonds (U).nes',
+ 'Wizards & Warriors (U).nes',
+ 'Wizards & Warriors 3 (U).nes',
+ 'Wolverine (U).nes',
+ 'World Champ (U) [!].nes',
+ 'World Games (U).nes',
+ 'Wrath of the Black Manta (U).nes',
+ 'Wrecking Crew (JUE) [t1].nes',
+ 'Wurm (U).nes',
+ 'WWF King of the Ring (U).nes',
+ 'WWF Steel Cage Challenge (U).nes',
+ 'WWF Wrestlemania (U).nes',
+ 'WWF Wrestlemania Challenge (U).nes',
+ 'Xenophobe (U).nes',
+ 'Xevious (U).nes',
+ 'Xexyz (U).nes',
+ 'Yo! Noid (U).nes',
+ 'Yo!Noid.nes',
+ 'Yoshi (U).nes',
+ 'Yoshi\'s Cookie (U).nes',
+ 'Young Indiana Jones Chronicles, The (U).nes',
+ 'Zanac (U).nes',
+ 'Zen Intergalactic Ninja (U).nes',
+ 'Zombie Nation (U).nes'
+ )*/
+} \ No newline at end of file
diff --git a/js/tile.js b/js/tile.js
new file mode 100644
index 0000000..d3494f0
--- /dev/null
+++ b/js/tile.js
@@ -0,0 +1,157 @@
+function Tile() {
+ // Tile data:
+ this.pix = new Array(64);
+
+ this.fbIndex = null;
+ this.tIndex = null;
+ this.x = null;
+ this.y = null;
+ this.w = null;
+ this.h = null;
+ this.incX = null;
+ this.incY = null;
+ this.palIndex = null;
+ this.tpri = null;
+ this.c = null;
+ this.initialized = false;
+ this.opaque = new Array(8);
+}
+
+Tile.prototype.setBuffer = function(scanline){
+ for(this.y=0;this.y<8;this.y++){
+ this.setScanline(this.y,scanline[this.y],scanline[this.y+8]);
+ }
+}
+
+Tile.prototype.setScanline = function(sline, b1, b2){
+ this.initialized = true;
+ this.tIndex = sline<<3;
+ for(this.x=0;this.x<8;this.x++){
+ this.pix[this.tIndex+this.x] = ((b1>>(7-this.x))&1)
+ + (((b2>>(7-this.x))&1)<<1);
+ if(this.pix[this.tIndex+this.x]==0) this.opaque[sline]=false;
+ }
+}
+
+
+Tile.prototype.render = function(srcx1, srcy1, srcx2, srcy2, dx, dy, palAdd, palette, flipHorizontal, flipVertical, pri, priTable){
+
+ if(dx<-7 || dx>=256 || dy<-7 || dy>=240){
+ return;
+ }
+
+ this.w=srcx2-srcx1;
+ this.h=srcy2-srcy1;
+
+ if(dx<0){
+ srcx1-=dx;
+ }
+ if(dx+srcx2>=256){
+ srcx2=256-dx;
+ }
+
+ if(dy<0){
+ srcy1-=dy;
+ }
+ if(dy+srcy2>=240){
+ srcy2=240-dy;
+ }
+
+ if(!flipHorizontal && !flipVertical){
+
+ this.fbIndex = (dy<<8)+dx;
+ this.tIndex = 0;
+ for(this.y=0;this.y<8;this.y++){
+ for(this.x=0;this.x<8;this.x++){
+ if(this.x>=srcx1 && this.x<srcx2 && this.y>=srcy1 && this.y<srcy2){
+ this.palIndex = this.pix[this.tIndex];
+ this.tpri = priTable[this.fbIndex];
+ if(this.palIndex!=0 && pri<=(this.tpri&0xFF)){
+ //console.log("Rendering upright tile to buffer");
+ Globals.nes.ppu.buffer[this.fbIndex] = palette[this.palIndex+palAdd];
+ this.tpri = (this.tpri&0xF00)|pri;
+ priTable[this.fbIndex] =this.tpri;
+ }
+ }
+ this.fbIndex++;
+ this.tIndex++;
+ }
+ this.fbIndex-=8;
+ this.fbIndex+=256;
+ }
+
+ }else if(flipHorizontal && !flipVertical){
+
+ this.fbIndex = (dy<<8)+dx;
+ this.tIndex = 7;
+ for(this.y=0;this.y<8;this.y++){
+ for(this.x=0;this.x<8;this.x++){
+ if(this.x>=srcx1 && this.x<srcx2 && this.y>=srcy1 && this.y<srcy2){
+ this.palIndex = this.pix[this.tIndex];
+ this.tpri = priTable[this.fbIndex];
+ if(this.palIndex!=0 && pri<=(this.tpri&0xFF)){
+ Globals.nes.ppu.buffer[this.fbIndex] = palette[this.palIndex+palAdd];
+ this.tpri = (this.tpri&0xF00)|pri;
+ priTable[this.fbIndex] =this.tpri;
+ }
+ }
+ this.fbIndex++;
+ this.tIndex--;
+ }
+ this.fbIndex-=8;
+ this.fbIndex+=256;
+ this.tIndex+=16;
+ }
+
+ }else if(flipVertical && !flipHorizontal){
+
+ this.fbIndex = (dy<<8)+dx;
+ this.tIndex = 56;
+ for(this.y=0;this.y<8;this.y++){
+ for(this.x=0;this.x<8;this.x++){
+ if(this.x>=srcx1 && this.x<srcx2 && this.y>=srcy1 && this.y<srcy2){
+ this.palIndex = this.pix[this.tIndex];
+ this.tpri = priTable[this.fbIndex];
+ if(this.palIndex!=0 && pri<=(this.tpri&0xFF)){
+ Globals.nes.ppu.buffer[this.fbIndex] = palette[this.palIndex+palAdd];
+ this.tpri = (this.tpri&0xF00)|pri;
+ priTable[this.fbIndex] =this.tpri;
+ }
+ }
+ this.fbIndex++;
+ this.tIndex++;
+ }
+ this.fbIndex-=8;
+ this.fbIndex+=256;
+ this.tIndex-=16;
+ }
+
+ }else{
+
+ this.fbIndex = (dy<<8)+dx;
+ this.tIndex = 63;
+ for(this.y=0;this.y<8;this.y++){
+ for(this.x=0;this.x<8;this.x++){
+ if(this.x>=srcx1 && this.x<srcx2 && this.y>=srcy1 && this.y<srcy2){
+ this.palIndex = this.pix[this.tIndex];
+ this.tpri = priTable[this.fbIndex];
+ if(this.palIndex!=0 && pri<=(this.tpri&0xFF)){
+ Globals.nes.ppu.buffer[this.fbIndex] = palette[this.palIndex+palAdd];
+ this.tpri = (this.tpri&0xF00)|pri;
+ priTable[this.fbIndex] =this.tpri;
+ }
+ }
+ this.fbIndex++;
+ this.tIndex--;
+ }
+ this.fbIndex-=8;
+ this.fbIndex+=256;
+ }
+
+ }
+
+}
+
+Tile.prototype.isTransparent = function(x, y){
+ return (this.pix[(y<<3)+x]==0);
+}
diff --git a/js/utils.js b/js/utils.js
new file mode 100644
index 0000000..21b32a1
--- /dev/null
+++ b/js/utils.js
@@ -0,0 +1,26 @@
+function arraycopy(src, srcPos, dest, destPos, length) {
+ // we're assuming they're different arrays, and everything is within bounds
+ for (var i=0; i<length; ++i) {
+ dest[destPos+i] = src[srcPos+i];
+ }
+}
+
+Array.prototype.has = function(value) {
+ var i;
+ for (var i = 0, loopCnt = this.length; i < loopCnt; i++) {
+ if (this[i] === value) {
+ return true;
+ }
+ }
+ return false;
+};
+
+// http://www.sitepoint.com/blogs/2006/01/17/javascript-inheritance/
+function copyPrototype(descendant, parent) {
+ var sConstructor = parent.toString();
+ var aMatch = sConstructor.match( /\s*function (.*)\(/ );
+ if ( aMatch != null ) { descendant.prototype[aMatch[1]] = parent; }
+ for (var m in parent.prototype) {
+ descendant.prototype[m] = parent.prototype[m];
+ }
+}; \ No newline at end of file