import { Apu , Channel , GamePad , WaveType } from './apu' ; import { Bus } from './bus' ; import { Cpu , IrqType } from './cpu/cpu' ; import { MirrorMode } from './ppu/types' ; import { Ppu } from './ppu/ppu' ; import { Address , Byte } from './types' ; import { Util } from '../util/util' ; import { CPU_HZ , VBlank } from './const' ; import { Mapper , PrgBankController } from './mapper/mapper' ; import { kMapperTable } from './mapper/mapper_table' ; import * as md5 from 'md5' ; const RAM_SIZE = 0x0800 ; const VCYCLE = ( VBlank . VRETURN * 341 / 3 ) | 0 ; const OAMDMA = 0x4014 ; function getHblankCount ( cpuCycle ) { return ( cpuCycle * ( 3 / 341 ) ) | 0 ; } function isRomValid ( romData ) { return romData [ 0 ] === 0x4e && romData [ 1 ] === 0x45 && romData [ 2 ] === 0x53 && romData [ 3 ] === 0x1a ; } function getMapperNo ( romData ) { return ( ( romData [ 6 ] >> 4 ) & 0x0f ) | ( romData [ 7 ] & 0xf0 ) ; } function loadPrgRom ( romData ) { const start = 16 , size = romData [ 4 ] * ( 16 * 1024 ) ; return romData . slice ( start , start + size ) ; } function loadChrRom ( romData ) { const start = 16 + romData [ 4 ] * ( 16 * 1024 ) , size = romData [ 5 ] * ( 8 * 1024 ) ; return romData . slice ( start , start + size ) ; } export class Nes implements PrgBankController { protected ram = new Uint8Array ( RAM_SIZE ) ; protected bus : Bus ; protected cpu : Cpu ; protected ppu : Ppu ; protected apu : Apu ; protected cycleCount = 0 ; protected mapper : Mapper ; private prgRom = new Uint8Array ( 0 ) ; private vblankCallback : ( leftV : number ) => void ; private breakPointCallback : ( ) => void ; private prgBank : number [ ] = [ ] ; private apuChannelCount = 0 ; private channelWaveTypes : WaveType [ ] ; private gamePads = [ new GamePad ( ) , new GamePad ( ) ] ; public static create ( ) { return new Nes ( ) ; } constructor ( ) { this . bus = new Bus ( ) ; this . cpu = new Cpu ( this . bus ) ; this . ppu = new Ppu ( this . cpu . nmi . bind ( this . cpu ) ) ; this . apu = new Apu ( this . gamePads , ( ) => this . cpu . requestIrq ( IrqType . APU ) ) ; this . vblankCallback = _leftV => { } ; this . breakPointCallback = ( ) => { } ; const mapperNo = 0 ; this . mapper = this . createMapper ( mapperNo , - 1 ) ; this . channelWaveTypes = this . apu . getWaveTypes ( ) ; this . apuChannelCount = this . channelWaveTypes . length ; this . setMemoryMap ( ) ; } public getBus ( ) { return this . bus ; } public getCpu ( ) { return this . cpu ; } public getPpu ( ) { return this . ppu ; } public getCycleCount ( ) { return this . cycleCount ; } public setVblankCallback ( callback ) { this . vblankCallback = callback ; } public setBreakPointCallback ( callback ) { this . breakPointCallback = callback ; } public setRomData ( romData ) { if ( ! isRomValid ( romData ) ) return 'Invalid format' ; const mapperNo = getMapperNo ( romData ) ; if ( ! ( mapperNo in kMapperTable ) ) return `Mapper ${ mapperNo } not supported` ; this . prgRom = loadPrgRom ( romData ) ; this . ppu . setChrData ( loadChrRom ( romData ) ) ; this . ppu . setMirrorMode ( ( romData [ 6 ] & 1 ) === 0 ? MirrorMode . HORZ : MirrorMode . VERT ) ; this . cpu . deleteAllBreakPoints ( ) ; this . setMemoryMap ( ) ; const romHash = md5 ( romData ) ; this . mapper = this . createMapper ( mapperNo , this . prgRom . length , romHash ) ; let channels = this . apu . getWaveTypes ( ) ; const extras = this . mapper . getExtraChannelWaveTypes ( ) ; if ( extras != null ) channels = channels . concat ( extras ) ; this . channelWaveTypes = channels ; return true ; } public setMapper ( mapper ) { this . mapper = mapper ; } public save ( ) { return { cpu : this . cpu . save ( ) , ppu : this . ppu . save ( ) , apu : this . apu . save ( ) , mapper : this . mapper != null ? this . mapper . save ( ) : null , ram : Util . convertUint8ArrayToBase64String ( this . ram ) , } ; } public load ( saveData ) { this . cpu . load ( saveData . cpu ) ; this . ppu . load ( saveData . ppu ) ; this . apu . load ( saveData . apu ) ; this . mapper . load ( saveData . mapper ) ; this . ram = Util . convertBase64StringToUint8Array ( saveData . ram ) ; } public reset ( ) { this . cpu . reset ( ) ; this . ppu . reset ( ) ; this . apu . reset ( ) ; this . cycleCount = 0 ; if ( this . mapper != null ) this . mapper . reset ( ) ; } public setPadStatus ( no , status ) { this . gamePads [ no ] . setStatus ( status ) ; } public runMilliseconds ( msec ) { let leftCycles = ( msec * ( CPU_HZ / 1000 ) ) | 0 ; try { const notPaused = ! this . cpu . isPaused ( ) ; do { const cycle = this . cpu . step ( ) ; leftCycles -= cycle ; this . tryHblankEvent ( cycle , leftCycles ) ; if ( notPaused && this . cpu . isPaused ( ) ) { this . breakPointCallback ( ) ; return 0 ; } } while ( leftCycles > 0 ) ; return - leftCycles ; } catch ( e ) { this . cpu . pause ( true ) ; throw e ; } } public getChannelWaveTypes ( ) { return this . channelWaveTypes ; } public getSoundChannel ( ch ) { if ( ch < this . apuChannelCount ) return this . apu . getChannel ( ch ) ; return this . mapper . getSoundChannel ( ch - this . apuChannelCount ) ; } public render ( pixels ) { this . ppu . render ( pixels ) ; } public setPrgBank ( bank , page ) { this . prgBank [ bank ] = page << 13 ; } public createMapper ( mapperNo , prgSize , romHash ? ) { const mapperFunc = kMapperTable [ mapperNo ] ; return mapperFunc ( { bus : this . bus , cpu : this . cpu , ppu : this . ppu , prgBankCtrl : this , prgSize , romHash , writeToApu : ( adr , value ) => this . writeToApu ( adr , value ) , readFromApu : ( adr ) => this . readFromApu ( adr ) , } ) ; } public readFromApu ( adr ) { return this . apu . read ( adr ) ; } public writeToApu ( adr , value ) { switch ( adr ) { case OAMDMA : if ( 0 <= value && value <= 0x1f ) { this . ppu . copyWithDma ( this . ram , value << 8 ) ; } else { console . error ( `OAMDMA not implemented except for RAM: ${ Util . hex ( value , 2 ) }` ) ; } break ; default : this . apu . write ( adr , value ) ; break ; } } protected setMemoryMap ( ) { const bus = this . bus ; bus . clearMemoryMap ( ) ; bus . setReadMemory ( 0x2000 , 0x3fff , adr => { const reg = adr & 7 ; return this . ppu . read ( reg ) ; } ) ; bus . setWriteMemory ( 0x2000 , 0x3fff , ( adr , value ) => { const reg = adr & 7 ; this . ppu . write ( reg , value ) ; } ) ; bus . setReadMemory ( 0x4000 , 0x5fff , adr => this . readFromApu ( adr ) ) ; bus . setWriteMemory ( 0x4000 , 0x5fff , ( adr , value ) => this . writeToApu ( adr , value ) ) ; const prgMask = ( this . prgRom . length - 1 ) | 0 ; this . prgBank = [ 0x0000 , 0x2000 , - 0x4000 & prgMask , - 0x2000 & prgMask , ] ; bus . setReadMemory ( 0x8000 , 0xffff , adr => { adr = adr | 0 ; const bank = ( adr - 0x8000 ) >> 13 ; const offset = ( adr & ( ( 1 << 13 ) - 1 ) ) | 0 ; return this . prgRom [ ( ( this . prgBank [ bank ] | 0 ) + offset ) & prgMask ] | 0 ; } ) ; bus . setReadMemory ( 0x0000 , 0x1fff , adr => this . ram [ adr & ( RAM_SIZE - 1 ) ] ) ; bus . setWriteMemory ( 0x0000 , 0x1fff , ( adr , value ) => { this . ram [ adr & ( RAM_SIZE - 1 ) ] = value ; } ) ; } private tryHblankEvent ( cycle , leftCycles ) { let nextCycleCount = this . cycleCount + cycle ; const prevHcount = getHblankCount ( this . cycleCount ) ; let hcount = getHblankCount ( nextCycleCount ) ; if ( hcount > prevHcount ) { if ( hcount >= VBlank . VRETURN ) { nextCycleCount -= VCYCLE ; hcount = 0 ; } this . ppu . setHcount ( hcount ) ; this . apu . onHblank ( hcount ) ; if ( hcount === VBlank . START ) this . vblankCallback ( ( leftCycles / VCYCLE ) | 0 ) ; this . mapper . onHblank ( hcount ) ; } this . cycleCount = nextCycleCount ; } }