nes-js
Version:
JavaScript NES emulator
500 lines (391 loc) • 9.5 kB
JavaScript
import {Register8bit} from './Register.js';
/**
*
*/
/**
*
*/
function MapperFactory() {
}
Object.assign(MapperFactory.prototype, {
isMapperFactory: true,
MAPPERS: {
0: {'name': 'NROM', class: NROMMapper},
1: {'name': 'MMC1', class: MMC1Mapper},
2: {'name': 'UNROM', class: UNROMMapper},
3: {'name': 'CNROM', class: CNROMMapper},
//4: {'name': 'MMC3', class: MMC3Mapper},
76: {'name': 'Mapper76', class: Mapper76}
},
/**
*
*/
getMapperParam: function(number) {
if(this.MAPPERS[number] === undefined)
throw new Error('unsupport No.' + number + ' Mapper');
return this.MAPPERS[number];
},
/**
*
*/
create: function(number, rom) {
return new (this.getMapperParam(number)).class(rom);
},
/**
*
*/
getName: function(number) {
return this.getMapperParam(number).name;
}
});
/**
*
*/
function Mapper(rom) {
this.rom = rom;
this.prgBankNum = rom.header.getPRGROMBanksNum();
this.chrBankNum = rom.header.getCHRROMBanksNum();
}
Object.assign(Mapper.prototype, {
isMapper: true,
/**
*
*/
map: function(address) {
return address;
},
/**
*
*/
mapForChrRom: function(address) {
return address;
},
/**
*
*/
store: function(address, value) {
},
/**
*
*/
getMirroringType: function() {
return this.rom.header.isHorizontalMirroring() === true
? this.rom.MIRRORINGS.HORIZONTAL : this.rom.MIRRORINGS.VERTICAL;
}
});
/**
*
*/
function NROMMapper(rom) {
Mapper.call(this, rom);
}
NROMMapper.prototype = Object.assign(Object.create(Mapper.prototype), {
isNROMMapper: true,
/**
*
*/
map: function(address) {
// 0x8000 - 0xBFFF: First 16 KB of ROM
// 0xC000 - 0xFFFF: Last 16 KB of ROM (NROM-256) or
// mirror of 0x8000 - 0xBFFF (NROM-128).
if(this.prgBankNum === 1 && address >= 0xC000)
address -= 0x4000;
return address;
}
});
/**
*
*/
function MMC1Mapper(rom) {
Mapper.call(this, rom);
this.controlRegister = new Register8bit(); // register 0
this.chrBank0Register = new Register8bit(); // register 1
this.chrBank1Register = new Register8bit(); // register 2
this.prgBankRegister = new Register8bit(); // register 3
this.latch = new Register8bit();
this.registerWriteCount = 0;
this.controlRegister.store(0x0C); // seems like 0xC would be default value
}
MMC1Mapper.prototype = Object.assign(Object.create(Mapper.prototype), {
isMMC1Mapper: true,
/**
*
*/
map: function(address) {
var bank = 0;
var offset = address & 0x3FFF;
var bankNum = this.prgBankRegister.load() & 0x0F;
switch(this.controlRegister.loadBits(2, 2)) {
case 0:
case 1:
// switch 32KB at 0x8000, ignoring low bit of bank number
// TODO: Fix me
offset = offset | (address & 0x4000);
bank = bankNum & 0x0E;
break;
case 2:
// fix first bank at 0x8000 and switch 16KB bank at 0xC000
bank = (address < 0xC000) ? 0 : bankNum;
break;
case 3:
// fix last bank at 0xC000 and switch 16KB bank at 0x8000
bank = (address >= 0xC000) ? this.prgBankNum - 1 : bankNum;
break;
}
return bank * 0x4000 + offset + 0x8000;
},
/**
*
*/
mapForChrRom: function(address) {
var bank;
var offset = address & 0x0FFF;
if(this.controlRegister.loadBit(4) === 0) {
// switch 8KB at a time
bank = (this.chrBank0Register.load() & 0x1E);
offset = offset | (address & 0x1000);
} else {
// switch two separate 4KB banks
bank = ((address < 0x1000) ? this.chrBank0Register.load() : this.chrBank1Register.load()) & 0x1F;
}
return bank * 0x1000 + offset;
},
/**
*
*/
store: function(address, value) {
if(value & 0x80) {
this.registerWriteCount = 0;
this.latch.clear();
if((address & 0x6000) === 0)
this.controlRegister.storeBits(2, 2, 3)
} else {
this.latch.store(((value & 1) << 4) | (this.latch.load() >> 1));
this.registerWriteCount++;
if(this.registerWriteCount >= 5) {
var val = this.latch.load();
switch(address & 0x6000) {
case 0x0000:
this.controlRegister.store(val);
break;
case 0x2000:
this.chrBank0Register.store(val);
break;
case 0x4000:
this.chrBank1Register.store(val);
break;
case 0x6000:
this.prgBankRegister.store(val);
break;
}
this.registerWriteCount = 0;
this.latch.clear();
}
}
},
/**
* TODO: Fix me
*/
getMirroringType: function() {
switch(this.controlRegister.loadBits(0, 2)) {
case 0:
case 1:
return this.rom.MIRRORINGS.SINGLE_SCREEN;
case 2:
return this.rom.MIRRORINGS.VERTICAL;
case 3:
return this.rom.MIRRORINGS.HORIZONTAL;
}
}
});
/**
*
*/
function UNROMMapper(rom) {
Mapper.call(this, rom);
this.reg = new Register8bit();
}
UNROMMapper.prototype = Object.assign(Object.create(Mapper.prototype), {
isUNROMMapper: true,
/**
*
*/
map: function(address) {
var bank = (address < 0xC000) ? this.reg.load() : this.prgBankNum - 1;
var offset = address & 0x3FFF;
return 0x4000 * bank + offset + 0x8000;
},
/**
*
*/
store: function(address, value) {
this.reg.store(value & 0xF);
}
});
/**
*
*/
function CNROMMapper(rom) {
Mapper.call(this, rom);
this.reg = new Register8bit();
}
CNROMMapper.prototype = Object.assign(Object.create(Mapper.prototype), {
isCNROMMapper: true,
/**
*
*/
mapForChrRom: function(address) {
return this.reg.load() * 0x2000 + (address & 0x1FFF);
},
/**
*
*/
store: function(address, value) {
this.reg.store(value & 0xF);
}
});
/**
*
*/
function MMC3Mapper(rom) {
Mapper.call(this, rom);
}
MMC3Mapper.prototype = Object.assign(Object.create(Mapper.prototype), {
isMMC3Mapper: true,
/**
*
*/
map: function(address) {
// TODO: Fix me
return address;
},
/**
*
*/
mapForChrRom: function(address) {
// TODO: Fix me
return address;
},
/**
*
*/
store: function(address, value) {
// TODO: Fix me
address = address & 0xFFFF; // just in case
if(address >= 0x8000 && address < 0xA000) {
if(address & 1 === 0) {
} else {
}
} else if(address >= 0xA000 && address < 0xC000) {
if(address & 1 === 0) {
} else {
}
} else if(address >= 0xC000 && address < 0xE000) {
if(address & 1 === 0) {
} else {
}
} else {
if(address & 1 === 0) {
} else {
}
}
}
});
/**
*
*/
function Mapper76(rom) {
Mapper.call(this, rom);
this.addrReg = new Register8bit();
this.chrReg0 = new Register8bit();
this.chrReg1 = new Register8bit();
this.chrReg2 = new Register8bit();
this.chrReg3 = new Register8bit();
this.prgReg0 = new Register8bit();
this.prgReg1 = new Register8bit();
}
Mapper76.prototype = Object.assign(Object.create(Mapper.prototype), {
isMapper76: true,
/**
*
*/
map: function(address) {
var bank;
var offset = address & 0x1FFF;
switch(address & 0xE000) {
case 0x8000:
bank = this.prgReg0.load();
break;
case 0xA000:
bank = this.prgReg1.load();
break;
case 0xC000:
bank = this.prgBankNum * 2 - 2;
break;
case 0xE000:
bank = this.prgBankNum * 2 - 1;
break;
}
return bank * 0x2000 + offset + 0x8000;
},
/**
*
*/
mapForChrRom: function(address) {
var bank;
var offset = address & 0x7FF;
switch(address & 0x1800) {
case 0x0000:
bank = this.chrReg0.load();
break;
case 0x0800:
bank = this.chrReg1.load();
break;
case 0x1000:
bank = this.chrReg2.load();
break;
case 0x1800:
bank = this.chrReg3.load();
break;
}
return bank * 0x800 + offset;
},
/**
*
*/
store: function(address, value) {
switch(address & 0xE001) {
case 0x8000:
this.addrReg.store(value & 0x7);
break;
case 0x8001:
var reg;
switch(this.addrReg.load()) {
case 0:
case 1:
return;
case 2:
reg = this.chrReg0;
break;
case 3:
reg = this.chrReg1;
break;
case 4:
reg = this.chrReg2;
break;
case 5:
reg = this.chrReg3;
break;
case 6:
reg = this.prgReg0;
break;
case 7:
reg = this.prgReg1;
break;
}
reg.store(value & 0x3F);
break;
}
}
});
export {Mapper, MapperFactory};