////////////////////////////////////////////////////////////////////////// // // // NES PPU core // // Norix // // written 2001/02/22 // // last modify ----/--/-- // ////////////////////////////////////////////////////////////////////////// #define WIN32_LEAN_AND_MEAN #include #include #include #include "VirtuaNESres.h" #include "typedef.h" #include "macro.h" #include "DebugOut.h" #include "App.h" #include "nes.h" #include "mmu.h" #include "cpu.h" #include "ppu.h" #include "rom.h" #include "mapper.h" BYTE PPU::VSColorMap[5][64] = { { 0x35, 0xFF, 0x16, 0x22, 0x1C, 0xFF, 0xFF, 0x15, 0xFF, 0x00, 0x27, 0x05, 0x04, 0x27, 0x08, 0x30, 0x21, 0xFF, 0xFF, 0x29, 0x3C, 0xFF, 0x36, 0x12, 0xFF, 0x2B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0x31, 0xFF, 0x2A, 0x2C, 0x0C, 0xFF, 0xFF, 0xFF, 0x07, 0x34, 0x06, 0x13, 0xFF, 0x26, 0x0F, 0xFF, 0x19, 0x10, 0x0A, 0xFF, 0xFF, 0xFF, 0x17, 0xFF, 0x11, 0x09, 0xFF, 0xFF, 0x25, 0x18, 0xFF }, { 0xFF, 0x27, 0x18, 0xFF, 0x3A, 0x25, 0xFF, 0x31, // 0x16, 0x13, 0x38, 0x34, 0x20, 0x23, 0xFF, 0x0B, 0x16, 0x13, 0x38, 0x34, 0x20, 0x23, 0xFF, 0x1A, 0xFF, 0x21, 0x06, 0xFF, 0x1B, 0x29, 0xFF, 0x22, 0xFF, 0x24, 0xFF, 0xFF, 0xFF, 0x08, 0xFF, 0x03, 0xFF, 0x36, 0x26, 0x33, 0x11, 0xFF, 0x10, 0x02, 0x14, 0xFF, 0x00, 0x09, 0x12, 0x0F, 0xFF, 0x30, 0xFF, 0xFF, 0x2A, 0x17, 0x0C, 0x01, 0x15, 0x19, 0xFF, 0x2C, 0x07, 0x37, 0xFF, 0x05, 0xFF, 0xFF }, #if 1 { 0xFF, 0xFF, 0xFF, 0x10, 0x1A, 0x30, 0x31, 0x09, 0x01, 0x0F, 0x36, 0x08, 0x15, 0xFF, 0xFF, 0xF0, 0x22, 0x1C, 0xFF, 0x12, 0x19, 0x18, 0x17, 0xFF, 0x00, 0xFF, 0xFF, 0x02, 0x16, 0x06, 0xFF, 0x35, 0x23, 0xFF, 0x8B, 0xF7, 0xFF, 0x27, 0x26, 0x20, 0x29, 0xFF, 0x21, 0x24, 0x11, 0xFF, 0xEF, 0xFF, 0x2C, 0xFF, 0xFF, 0xFF, 0x07, 0xF9, 0x28, 0xFF, 0x0A, 0xFF, 0x32, 0x37, 0x13, 0xFF, 0xFF, 0x0C }, #else { 0xFF, 0xFF, 0xFF, 0x10, 0x0B, 0x30, 0x31, 0x09, // 00-07 0x01, 0x0F, 0x36, 0x08, 0x15, 0xFF, 0xFF, 0x3C, // 08-0F 0x22, 0x1C, 0xFF, 0x12, 0x19, 0x18, 0x17, 0x1B, // 10-17 0x00, 0xFF, 0xFF, 0x02, 0x16, 0x06, 0xFF, 0x35, // 18-1F 0x23, 0xFF, 0x8B, 0x3C, 0xFF, 0x27, 0x26, 0x20, // 20-27 0x29, 0x04, 0x21, 0x24, 0x11, 0xFF, 0xEF, 0xFF, // 28-2F 0x2C, 0xFF, 0xFF, 0xFF, 0x07, 0x39, 0x28, 0xFF, // 30-37 0x0A, 0xFF, 0x32, 0x38, 0x13, 0x3B, 0xFF, 0x0C // 38-3F }, #endif #if 0 { 0x18, 0xFF, 0x1C, 0x89, 0x0F, 0xFF, 0x01, 0x17, 0x10, 0x0F, 0x2A, 0xFF, 0x36, 0x37, 0x1A, 0xFF, 0x25, 0xFF, 0x12, 0xFF, 0x0F, 0xFF, 0xFF, 0x26, 0xFF, 0xFF, 0x22, 0xFF, 0xFF, 0x0F, 0x3A, 0x21, 0x05, 0x0A, 0x07, 0xC2, 0x13, 0xFF, 0x00, 0x15, 0x0C, 0xFF, 0x11, 0xFF, 0xFF, 0x38, 0xFF, 0xFF, 0xFF, 0xFF, 0x08, 0x45, 0xFF, 0xFF, 0x30, 0x3C, 0x0F, 0x27, 0xFF, 0x60, 0x29, 0xFF, 0x30, 0x09 }, #else { 0x18, 0xFF, 0x1C, 0x89, 0x0F, 0xFF, 0x01, 0x17, // 00-07 0x10, 0x0F, 0x2A, 0xFF, 0x36, 0x37, 0x1A, 0xFF, // 08-0F 0x25, 0xFF, 0x12, 0xFF, 0x0F, 0xFF, 0xFF, 0x26, // 10-17 0xFF, 0xFF, 0x22, 0xFF, 0xFF, 0x0F, 0x3A, 0x21, // 18-1F 0x05, 0x0A, 0x07, 0xC2, 0x13, 0xFF, 0x00, 0x15, // 20-27 0x0C, 0xFF, 0x11, 0xFF, 0xFF, 0x38, 0xFF, 0xFF, // 28-2F 0xFF, 0xFF, 0x08, 0x16, 0xFF, 0xFF, 0x30, 0x3C, // 30-37 0x0F, 0x27, 0xFF, 0x60, 0x29, 0xFF, 0x30, 0x09 // 38-3F }, #endif { // Super Xevious/Gradius 0x35, 0xFF, 0x16, 0x22, 0x1C, 0x09, 0xFF, 0x15, // 00-07 0x20, 0x00, 0x27, 0x05, 0x04, 0x28, 0x08, 0x30, // 08-0F 0x21, 0xFF, 0xFF, 0x29, 0x3C, 0xFF, 0x36, 0x12, // 10-17 0xFF, 0x2B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, // 18-1F 0xFF, 0x31, 0xFF, 0x2A, 0x2C, 0x0C, 0x1B, 0xFF, // 20-27 0xFF, 0x07, 0x34, 0x06, 0xFF, 0x25, 0x26, 0x0F, // 28-2F 0xFF, 0x19, 0x10, 0x0A, 0xFF, 0xFF, 0xFF, 0x17, // 30-37 0xFF, 0x11, 0x1A, 0xFF, 0x38, 0xFF, 0x18, 0x3A, // 38-3F } }; PPU::PPU( NES* parent ) : nes(parent) { lpScreen = NULL; lpColormode = NULL; bVSMode = FALSE; nVSColorMap = -1; VSSecurityData = 0; bVromWe = 0; // 左右反転マスクテーブル for( INT i = 0; i < 256; i++ ) { BYTE m = 0x80; BYTE c = 0; for( INT j = 0; j < 8; j++ ) { if( i&(1<>= 1; } Bit2Rev[i] = c; } } PPU::~PPU() { } void PPU::Reset() { bExtLatch = FALSE; bChrLatch = FALSE; bExtNameTable = FALSE; bExtMono = FALSE; PPUREG[0] = PPUREG[1] = 0; PPU56Toggle = 0; // PPU7_Temp = 0xFF; // VS Excitebikeでおかしくなる($2006を読みに行くバグがある) PPU7_Temp = 0; loopy_v = loopy_t = 0; loopy_x = loopy_y = 0; loopy_shift = 0; if( lpScreen ) ::memset( lpScreen, 0x3F, SCREEN_WIDTH*SCREEN_HEIGHT*sizeof(BYTE) ); if( lpColormode ) ::memset( lpColormode, 0, SCREEN_HEIGHT*sizeof(BYTE) ); } BYTE PPU::Read( WORD addr ) { BYTE data = 0x00; switch( addr ) { // Write only Register case 0x2000: // PPU Control Register #1(W) case 0x2001: // PPU Control Register #2(W) case 0x2003: // SPR-RAM Address Register(W) case 0x2005: // PPU Scroll Register(W2) case 0x2006: // VRAM Address Register(W2) data = PPU7_Temp; // 多分 break; // Read/Write Register case 0x2002: // PPU Status Register(R) data = PPUREG[2] | VSSecurityData; PPU56Toggle = 0; PPUREG[2] &= ~PPU_VBLANK_FLAG; break; case 0x2004: // SPR_RAM I/O Register(RW) data = SPRAM[ PPUREG[3]++ ]; break; case 0x2007: // VRAM I/O Register(RW) WORD addr = loopy_v & 0x3FFF; data = PPU7_Temp; if( PPUREG[0] & PPU_INC32_BIT ) loopy_v+=32; else loopy_v++; if( addr >= 0x3000 ) { if( addr >= 0x3F00 ) { // data &= 0x3F; if( !(addr&0x0010) ) { return BGPAL[addr&0x000F]; } else { return SPPAL[addr&0x000F]; } } addr &= 0xEFFF; } if( bChrLatch ) { nes->mapper->PPU_ChrLatch( addr ); } PPU7_Temp = PPU_MEM_BANK[addr>>10][addr&0x03FF]; } return data; } void PPU::Write( WORD addr, BYTE data ) { if( bVSMode && VSSecurityData ) { if( addr == 0x2000 ) { addr = 0x2001; } else if( addr == 0x2001 ){ addr = 0x2000; } } switch( addr ) { case 0x2008: //for SB2000 break; case 0x2010: case 0x2011: case 0x2012: case 0x2013: case 0x2014: case 0x2015: case 0x2016: case 0x2017: case 0x2018: case 0x2019: case 0x201A: case 0x201B: case 0x201C: case 0x201D: case 0x201E: case 0x201F: nes->mapper->WriteExPPU(addr, data); break; // Read only Register case 0x2002: // PPU Status register(R) break; // Write Register case 0x2000: // PPU Control Register #1(W) // NameTable select // t:0000110000000000=d:00000011 loopy_t = (loopy_t & 0xF3FF)|(((WORD)data & 0x03)<<10); if( (data & 0x80) && !(PPUREG[0] & 0x80) && (PPUREG[2] & 0x80) ) { nes->cpu->NMI(); // hmm.. } PPUREG[0] = data; break; case 0x2001: // PPU Control Register #2(W) PPUREG[1] = data; break; case 0x2003: // SPR-RAM Address Register(W) PPUREG[3] = data; break; case 0x2004: // SPR_RAM I/O Register(RW) SPRAM[ PPUREG[3]++ ] = data; break; case 0x2005: // PPU Scroll Register(W2) if( !PPU56Toggle ) { // First write // tile X t:0000000000011111=d:11111000 loopy_t = (loopy_t & 0xFFE0)|(((WORD)data)>>3); // scroll offset X x=d:00000111 loopy_x = data & 0x07; } else { // Second write // tile Y t:0000001111100000=d:11111000 loopy_t = (loopy_t & 0xFC1F)|((((WORD)data) & 0xF8)<<2); // scroll offset Y t:0111000000000000=d:00000111 loopy_t = (loopy_t & 0x8FFF)|((((WORD)data) & 0x07)<<12); } PPU56Toggle = !PPU56Toggle; break; case 0x2006: // VRAM Address Register(W2) if( !PPU56Toggle ) { // First write // t:0011111100000000=d:00111111 // t:1100000000000000=0 loopy_t = (loopy_t & 0x00FF)|((((WORD)data) & 0x3F)<<8); } else { // Second write // t:0000000011111111=d:11111111 loopy_t = (loopy_t & 0xFF00)|(WORD)data; // v=t loopy_v = loopy_t; nes->mapper->PPU_Latch( loopy_v ); } PPU56Toggle = !PPU56Toggle; break; case 0x2007: // VRAM I/O Register(RW) WORD vaddr = loopy_v & 0x3FFF; if( PPUREG[0] & PPU_INC32_BIT ) loopy_v+=32; else loopy_v++; if( vaddr >= 0x3000 ) { if( vaddr >= 0x3F00 ) { data &= 0x3F; if( bVSMode && nVSColorMap != -1 ) { BYTE temp = VSColorMap[nVSColorMap][data]; if( temp != 0xFF ) { data = temp & 0x3F; } } if( !(vaddr&0x000F) ) { BGPAL[0] = SPPAL[0] = data; } else if( !(vaddr&0x0010) ) { BGPAL[vaddr&0x000F] = data; } else { SPPAL[vaddr&0x000F] = data; } BGPAL[0x04] = BGPAL[0x08] = BGPAL[0x0C] = BGPAL[0x00]; SPPAL[0x00] = SPPAL[0x04] = SPPAL[0x08] = SPPAL[0x0C] = BGPAL[0x00]; return; } vaddr &= 0xEFFF; } int bank = vaddr>>10; vaddr &= 0x03ff; if( PPU_MEM_TYPE[bank] != BANKTYPE_VROM ){ PPU_MEM_BANK[bank][vaddr] = data; }else if( bVromWe ) { PPU_MEM_BANK[bank][vaddr] = data; VROM_WRITED[PPU_MEM_PAGE[bank]] = 1; } break; } } void PPU::DMA( BYTE data ) { WORD addr = data<<8; for( INT i = 0; i < 256; i++ ) { SPRAM[i] = nes->Read( addr+i ); } } void PPU::VBlankStart() { PPUREG[2] |= PPU_VBLANK_FLAG; // PPUREG[2] |= PPU_SPHIT_FLAG; // VBlank突入時に必ずON? } void PPU::VBlankEnd() { PPUREG[2] &= ~PPU_VBLANK_FLAG; // VBlank脱出時にクリアされる // エキサイトバイクで重要 PPUREG[2] &= ~PPU_SPHIT_FLAG; } void PPU::FrameStart() { if( PPUREG[1] & (PPU_SPDISP_BIT|PPU_BGDISP_BIT) ) { loopy_v = loopy_t; loopy_shift = loopy_x; loopy_y = (loopy_v&0x7000)>>12; } if( lpScreen ) { ::memset( lpScreen, 0x3F, SCREEN_WIDTH*sizeof(BYTE) ); } if( lpColormode ) { lpColormode[0] = 0; } } void PPU::FrameEnd() { } void PPU::SetRenderScanline( INT scanline ) { ScanlineNo = scanline; if( scanline < 240 ) { lpScanline = lpScreen+SCREEN_WIDTH*scanline; } } void PPU::ScanlineStart() { if( PPUREG[1] & (PPU_BGDISP_BIT|PPU_SPDISP_BIT) ) { loopy_v = (loopy_v & 0xFBE0)|(loopy_t & 0x041F); loopy_shift = loopy_x; loopy_y = (loopy_v&0x7000)>>12; nes->mapper->PPU_Latch( 0x2000 + (loopy_v & 0x0FFF) ); } } void PPU::ScanlineNext() { if( PPUREG[1] & (PPU_BGDISP_BIT|PPU_SPDISP_BIT) ) { if( (loopy_v & 0x7000) == 0x7000 ) { loopy_v &= 0x8FFF; if( (loopy_v & 0x03E0) == 0x03A0 ) { loopy_v ^= 0x0800; loopy_v &= 0xFC1F; } else { if( (loopy_v & 0x03E0) == 0x03E0 ) { loopy_v &= 0xFC1F; } else { loopy_v += 0x0020; } } } else { loopy_v += 0x1000; } loopy_y = (loopy_v&0x7000)>>12; } } void PPU::Scanline( INT scanline, BOOL bMax, BOOL bLeftClip ) { BYTE BGwrite[33+1]; BYTE BGmono[33+1]; BYTE sppadr_offset; //for YuXing and PYRAMID ZEROMEMORY( BGwrite, sizeof(BGwrite) ); ZEROMEMORY( BGmono, sizeof(BGmono) ); sppadr_offset = 0; // Linecolor mode lpColormode[scanline] = ((PPUREG[1]&PPU_BGCOLOR_BIT)>>5)|((PPUREG[1]&PPU_COLORMODE_BIT)<<7); // Render BG if( !(PPUREG[1]&PPU_BGDISP_BIT) ) { ::memset( lpScanline, BGPAL[0], SCREEN_WIDTH ); if( nes->GetRenderMethod() == NES::TILE_RENDER ) { nes->EmulationCPU( FETCH_CYCLES*4*32 ); } } else { if( nes->GetRenderMethod() != NES::TILE_RENDER ) { if( !bExtLatch ) { // Without Extension Latch LPBYTE pScn = lpScanline+(8-loopy_shift); LPBYTE pBGw = BGwrite; INT tileofs = (PPUREG[0]&PPU_BGTBL_BIT)<<8; INT ntbladr = 0x2000+(loopy_v&0x0FFF); INT attradr = 0x23C0+(loopy_v&0x0C00)+((loopy_v&0x0380)>>4); INT ntbl_x = ntbladr&0x001F; INT attrsft = (ntbladr&0x0040)>>4; LPBYTE pNTBL = PPU_MEM_BANK[ntbladr>>10]; INT tileadr; INT cache_tile = 0xFFFF0000; BYTE cache_attr = 0xFF; BYTE chr_h, chr_l, attr; attradr &= 0x3FF; for( INT i = 0; i < 33; i++ ) { tileadr = tileofs+pNTBL[ntbladr&0x03FF]*0x10+loopy_y; attr = ((pNTBL[attradr+(ntbl_x>>2)]>>((ntbl_x&2)+attrsft))&3)<<2; if( cache_tile == tileadr && cache_attr == attr ) { *(LPDWORD)(pScn+0) = *(LPDWORD)(pScn-8); *(LPDWORD)(pScn+4) = *(LPDWORD)(pScn-4); *(pBGw+0) = *(pBGw-1); } else { cache_tile = tileadr; cache_attr = attr; chr_l = PPU_MEM_BANK[tileadr>>10][ tileadr&0x03FF ]; chr_h = PPU_MEM_BANK[tileadr>>10][(tileadr&0x03FF)+8]; *pBGw = chr_h|chr_l; LPBYTE pBGPAL = &BGPAL[attr]; { register INT c1 = ((chr_l>>1)&0x55)|(chr_h&0xAA); register INT c2 = (chr_l&0x55)|((chr_h<<1)&0xAA); pScn[0] = pBGPAL[(c1>>6)]; pScn[4] = pBGPAL[(c1>>2)&3]; pScn[1] = pBGPAL[(c2>>6)]; pScn[5] = pBGPAL[(c2>>2)&3]; pScn[2] = pBGPAL[(c1>>4)&3]; pScn[6] = pBGPAL[c1&3]; pScn[3] = pBGPAL[(c2>>4)&3]; pScn[7] = pBGPAL[c2&3]; } } pScn+=8; pBGw++; // Character latch(For MMC2/MMC4) if( bChrLatch ) { nes->mapper->PPU_ChrLatch( tileadr ); } if( ++ntbl_x == 32 ) { ntbl_x = 0; ntbladr ^= 0x41F; attradr = 0x03C0+((ntbladr&0x0380)>>4); pNTBL = PPU_MEM_BANK[ntbladr>>10]; } else { ntbladr++; } } } else { // With Extension Latch(For MMC5) LPBYTE pScn = lpScanline+(8-loopy_shift); LPBYTE pBGw = BGwrite; INT ntbladr = 0x2000+(loopy_v&0x0FFF); INT ntbl_x = ntbladr & 0x1F; INT cache_tile = 0xFFFF0000; BYTE cache_attr = 0xFF; BYTE chr_h, chr_l, attr, exattr; sppadr_offset = nes->mapper->PPU_ExtLatchSP(); //for YuXing and PYRAMID for( INT i = 0; i < 33; i++ ) { nes->mapper->PPU_ExtLatchX( i ); nes->mapper->PPU_ExtLatch( ntbladr, chr_l, chr_h, exattr ); attr = exattr&0x0C; if( cache_tile != (((INT)chr_h<<8)+(INT)chr_l) || cache_attr != attr ) { cache_tile = (((INT)chr_h<<8)+(INT)chr_l); cache_attr = attr; *pBGw = chr_h|chr_l; LPBYTE pBGPAL = &BGPAL[attr]; { register INT c1 = ((chr_l>>1)&0x55)|(chr_h&0xAA); register INT c2 = (chr_l&0x55)|((chr_h<<1)&0xAA); pScn[0] = pBGPAL[(c1>>6)]; pScn[4] = pBGPAL[(c1>>2)&3]; pScn[1] = pBGPAL[(c2>>6)]; pScn[5] = pBGPAL[(c2>>2)&3]; pScn[2] = pBGPAL[(c1>>4)&3]; pScn[6] = pBGPAL[c1&3]; pScn[3] = pBGPAL[(c2>>4)&3]; pScn[7] = pBGPAL[c2&3]; } } else { *(DWORD*)(pScn+0) = *(DWORD*)(pScn-8); *(DWORD*)(pScn+4) = *(DWORD*)(pScn-4); *(pBGw+0) = *(pBGw-1); } pScn+=8; pBGw++; if( ++ntbl_x == 32 ) { ntbl_x = 0; ntbladr ^= 0x41F; } else { ntbladr++; } } } } else { if( !bExtLatch ) { // Without Extension Latch if( !bExtNameTable ) { LPBYTE pScn = lpScanline+(8-loopy_shift); LPBYTE pBGw = BGwrite; INT ntbladr = 0x2000+(loopy_v&0x0FFF); INT attradr = 0x03C0+((loopy_v&0x0380)>>4); INT ntbl_x = ntbladr&0x001F; INT attrsft = (ntbladr&0x0040)>>4; LPBYTE pNTBL = PPU_MEM_BANK[ntbladr>>10]; INT tileadr; INT cache_tile = 0xFFFF0000; BYTE cache_attr = 0xFF; BYTE cache_mono = 0x00; BYTE chr_h, chr_l, attr; for( INT i = 0; i < 33; i++ ) { tileadr = ((PPUREG[0]&PPU_BGTBL_BIT)<<8)+pNTBL[ntbladr&0x03FF]*0x10+loopy_y; if( i != 0 ) { nes->EmulationCPU( FETCH_CYCLES*4 ); } attr = ((pNTBL[attradr+(ntbl_x>>2)]>>((ntbl_x&2)+attrsft))&3)<<2; if( cache_tile != tileadr || cache_attr != attr ) { cache_tile = tileadr; cache_attr = attr; chr_l = PPU_MEM_BANK[tileadr>>10][ tileadr&0x03FF ]; chr_h = PPU_MEM_BANK[tileadr>>10][(tileadr&0x03FF)+8]; *pBGw = chr_l|chr_h; LPBYTE pBGPAL = &BGPAL[attr]; { register INT c1 = ((chr_l>>1)&0x55)|(chr_h&0xAA); register INT c2 = (chr_l&0x55)|((chr_h<<1)&0xAA); pScn[0] = pBGPAL[(c1>>6)]; pScn[4] = pBGPAL[(c1>>2)&3]; pScn[1] = pBGPAL[(c2>>6)]; pScn[5] = pBGPAL[(c2>>2)&3]; pScn[2] = pBGPAL[(c1>>4)&3]; pScn[6] = pBGPAL[c1&3]; pScn[3] = pBGPAL[(c2>>4)&3]; pScn[7] = pBGPAL[c2&3]; } } else { *(DWORD*)(pScn+0) = *(DWORD*)(pScn-8); *(DWORD*)(pScn+4) = *(DWORD*)(pScn-4); *(pBGw+0) = *(pBGw-1); } pScn+=8; pBGw++; // Character latch(For MMC2/MMC4) if( bChrLatch ) { nes->mapper->PPU_ChrLatch( tileadr ); } if( ++ntbl_x == 32 ) { ntbl_x = 0; ntbladr ^= 0x41F; attradr = 0x03C0+((ntbladr&0x0380)>>4); pNTBL = PPU_MEM_BANK[ntbladr>>10]; } else { ntbladr++; } } } else { LPBYTE pScn = lpScanline+(8-loopy_shift); LPBYTE pBGw = BGwrite; INT ntbladr; INT tileadr; INT cache_tile = 0xFFFF0000; BYTE cache_attr = 0xFF; BYTE cache_mono = 0x00; BYTE chr_h, chr_l, attr; WORD loopy_v_tmp = loopy_v; for( INT i = 0; i < 33; i++ ) { if( i != 0 ) { nes->EmulationCPU( FETCH_CYCLES*4 ); } ntbladr = 0x2000+(loopy_v&0x0FFF); tileadr = ((PPUREG[0]&PPU_BGTBL_BIT)<<8)+PPU_MEM_BANK[ntbladr>>10][ntbladr&0x03FF]*0x10+((loopy_v&0x7000)>>12); attr = ((PPU_MEM_BANK[ntbladr>>10][0x03C0+((ntbladr&0x0380)>>4)+((ntbladr&0x001C)>>2)]>>(((ntbladr&0x40)>>4)+(ntbladr&0x02)))&3)<<2; if( cache_tile != tileadr || cache_attr != attr ) { cache_tile = tileadr; cache_attr = attr; chr_l = PPU_MEM_BANK[tileadr>>10][ tileadr&0x03FF ]; chr_h = PPU_MEM_BANK[tileadr>>10][(tileadr&0x03FF)+8]; *pBGw = chr_l|chr_h; LPBYTE pBGPAL = &BGPAL[attr]; { register INT c1 = ((chr_l>>1)&0x55)|(chr_h&0xAA); register INT c2 = (chr_l&0x55)|((chr_h<<1)&0xAA); pScn[0] = pBGPAL[(c1>>6)]; pScn[4] = pBGPAL[(c1>>2)&3]; pScn[1] = pBGPAL[(c2>>6)]; pScn[5] = pBGPAL[(c2>>2)&3]; pScn[2] = pBGPAL[(c1>>4)&3]; pScn[6] = pBGPAL[c1&3]; pScn[3] = pBGPAL[(c2>>4)&3]; pScn[7] = pBGPAL[c2&3]; } } else { *(DWORD*)(pScn+0) = *(DWORD*)(pScn-8); *(DWORD*)(pScn+4) = *(DWORD*)(pScn-4); *(pBGw+0) = *(pBGw-1); } pScn+=8; pBGw++; // Character latch(For MMC2/MMC4) if( bChrLatch ) { nes->mapper->PPU_ChrLatch( tileadr ); } if( (loopy_v & 0x1F) == 0x1F ) { loopy_v ^= 0x041F; } else { loopy_v++; } } loopy_v = loopy_v_tmp; } } else { // With Extension Latch(For MMC5) LPBYTE pScn = lpScanline+(8-loopy_shift); LPBYTE pBGw = BGwrite; INT ntbladr = 0x2000+(loopy_v&0x0FFF); INT ntbl_x = ntbladr & 0x1F; INT cache_tile = 0xFFFF0000; BYTE cache_attr = 0xFF; BYTE chr_h, chr_l, attr, exattr; sppadr_offset = nes->mapper->PPU_ExtLatchSP(); //for YuXing and PYRAMID for( INT i = 0; i < 33; i++ ) { if( i != 0 ) { nes->EmulationCPU( FETCH_CYCLES*4 ); } nes->mapper->PPU_ExtLatchX( i ); nes->mapper->PPU_ExtLatch( ntbladr, chr_l, chr_h, exattr ); attr = exattr&0x0C; if( cache_tile != (((INT)chr_h<<8)+(INT)chr_l) || cache_attr != attr ) { cache_tile = (((INT)chr_h<<8)+(INT)chr_l); cache_attr = attr; *pBGw = chr_l|chr_h; LPBYTE pBGPAL = &BGPAL[attr]; { register INT c1 = ((chr_l>>1)&0x55)|(chr_h&0xAA); register INT c2 = (chr_l&0x55)|((chr_h<<1)&0xAA); pScn[0] = pBGPAL[(c1>>6)]; pScn[4] = pBGPAL[(c1>>2)&3]; pScn[1] = pBGPAL[(c2>>6)]; pScn[5] = pBGPAL[(c2>>2)&3]; pScn[2] = pBGPAL[(c1>>4)&3]; pScn[6] = pBGPAL[c1&3]; pScn[3] = pBGPAL[(c2>>4)&3]; pScn[7] = pBGPAL[c2&3]; } } else { *(DWORD*)(pScn+0) = *(DWORD*)(pScn-8); *(DWORD*)(pScn+4) = *(DWORD*)(pScn-4); *(pBGw+0) = *(pBGw-1); } pScn+=8; pBGw++; if( ++ntbl_x == 32 ) { ntbl_x = 0; ntbladr ^= 0x41F; } else { ntbladr++; } } } } if( !(PPUREG[1]&PPU_BGCLIP_BIT) && bLeftClip ) { LPBYTE pScn = lpScanline+8; for( INT i = 0; i < 8; i++ ) { pScn[i] = BGPAL[0]; } } } // Render sprites PPUREG[2] &= ~PPU_SPMAX_FLAG; // 表示期間外であればキャンセル if( scanline > 239 ) return; if( !(PPUREG[1]&PPU_SPDISP_BIT) ) { return; } BYTE SPwrite[33+1]; INT spmax; INT spraddr, sp_y, sp_h; BYTE chr_h, chr_l; LPSPRITE sp; LPBYTE pBGw = BGwrite; LPBYTE pSPw = SPwrite; LPBYTE pBit2Rev = Bit2Rev; ZEROMEMORY( SPwrite, sizeof(SPwrite) ); spmax = 0; sp = (LPSPRITE)SPRAM; sp_h = (PPUREG[0]&PPU_SP16_BIT)?15:7; // Left clip if( !(PPUREG[1]&PPU_SPCLIP_BIT) && bLeftClip ) { SPwrite[0] = 0xFF; } for( INT i = 0; i < 64; i++, sp++ ) { sp_y = scanline - (sp->y+1); // スキャンライン内にSPRITEが存在するかをチェック if( sp_y != (sp_y & sp_h) ) continue; if( !(PPUREG[0]&PPU_SP16_BIT) ) { // 8x8 Sprite spraddr = (((INT)PPUREG[0]&PPU_SPTBL_BIT)<<9)+((INT)sp->tile<<4); if( !(sp->attr&SP_VMIRROR_BIT) ) spraddr += sp_y; else spraddr += 7-sp_y; } else { // 8x16 Sprite spraddr = (((INT)sp->tile&1)<<12)+(((INT)sp->tile&0xFE)<<4); if( !(sp->attr&SP_VMIRROR_BIT) ) spraddr += ((sp_y&8)<<1)+(sp_y&7); else spraddr += ((~sp_y&8)<<1)+(7-(sp_y&7)); } // Character pattern chr_l = PPU_MEM_BANK[spraddr>>10][ spraddr&0x3FF ]; chr_h = PPU_MEM_BANK[spraddr>>10][(spraddr&0x3FF)+8]; // Character latch(For MMC2/MMC4) if( bChrLatch ) { nes->mapper->PPU_ChrLatch( spraddr ); } // pattern mask if( sp->attr&SP_HMIRROR_BIT ) { chr_l = pBit2Rev[chr_l]; chr_h = pBit2Rev[chr_h]; } BYTE SPpat = chr_l|chr_h; // Sprite hitcheck if( i == 0 && !(PPUREG[2]&PPU_SPHIT_FLAG) ) { INT BGpos = ((sp->x&0xF8)+((loopy_shift+(sp->x&7))&8))>>3; INT BGsft = 8-((loopy_shift+sp->x)&7); BYTE BGmsk = (((WORD)pBGw[BGpos+0]<<8)|(WORD)pBGw[BGpos+1])>>BGsft; if( SPpat & BGmsk ) { PPUREG[2] |= PPU_SPHIT_FLAG; } } // Sprite mask INT SPpos = sp->x/8; INT SPsft = 8-(sp->x&7); BYTE SPmsk = (((WORD)pSPw[SPpos+0]<<8)|(WORD)pSPw[SPpos+1])>>SPsft; WORD SPwrt = (WORD)SPpat<> 8; pSPw[SPpos+1] |= SPwrt & 0xFF; SPpat &= ~SPmsk; if( sp->attr&SP_PRIORITY_BIT ) { // BG > SP priority INT BGpos = ((sp->x&0xF8)+((loopy_shift+(sp->x&7))&8))>>3; INT BGsft = 8-((loopy_shift+sp->x)&7); BYTE BGmsk = (((WORD)pBGw[BGpos+0]<<8)|(WORD)pBGw[BGpos+1])>>BGsft; SPpat &= ~BGmsk; } // Attribute LPBYTE pSPPAL = &SPPAL[(sp->attr&SP_COLOR_BIT)<<2] + sppadr_offset; //"sppadr_offset" for YuXing and PYRAMID // Ptr LPBYTE pScn = lpScanline+sp->x+8; if( !bExtMono ) { register INT c1 = ((chr_l>>1)&0x55)|(chr_h&0xAA); register INT c2 = (chr_l&0x55)|((chr_h<<1)&0xAA); if( SPpat&0x80 ) pScn[0] = pSPPAL[(c1>>6)]; if( SPpat&0x08 ) pScn[4] = pSPPAL[(c1>>2)&3]; if( SPpat&0x40 ) pScn[1] = pSPPAL[(c2>>6)]; if( SPpat&0x04 ) pScn[5] = pSPPAL[(c2>>2)&3]; if( SPpat&0x20 ) pScn[2] = pSPPAL[(c1>>4)&3]; if( SPpat&0x02 ) pScn[6] = pSPPAL[c1&3]; if( SPpat&0x10 ) pScn[3] = pSPPAL[(c2>>4)&3]; if( SPpat&0x01 ) pScn[7] = pSPPAL[c2&3]; } else { // Monocrome effect (for Final Fantasy) BYTE mono = BGmono[((sp->x&0xF8)+((loopy_shift+(sp->x&7))&8))>>3]; register INT c1 = ((chr_l>>1)&0x55)|(chr_h&0xAA); register INT c2 = (chr_l&0x55)|((chr_h<<1)&0xAA); if( SPpat&0x80 ) pScn[0] = pSPPAL[c1>>6] |mono; if( SPpat&0x08 ) pScn[4] = pSPPAL[(c1>>2)&3] |mono; if( SPpat&0x40 ) pScn[1] = pSPPAL[c2>>6] |mono; if( SPpat&0x04 ) pScn[5] = pSPPAL[(c2>>2)&3] |mono; if( SPpat&0x20 ) pScn[2] = pSPPAL[(c1>>4)&3] |mono; if( SPpat&0x02 ) pScn[6] = pSPPAL[c1&3] |mono; if( SPpat&0x10 ) pScn[3] = pSPPAL[(c2>>4)&3] |mono; if( SPpat&0x01 ) pScn[7] = pSPPAL[c2&3] |mono; } if( ++spmax > 8-1 ) { if( !bMax ) break; } } if( spmax > 8-1 ) { PPUREG[2] |= PPU_SPMAX_FLAG; } } // スプライト0がヒットするかも知れないライン? BOOL PPU::IsSprite0( INT scanline ) { // スプライトorBG非表示はキャンセル(ヒットしない) if( (PPUREG[1]&(PPU_SPDISP_BIT|PPU_BGDISP_BIT)) != (PPU_SPDISP_BIT|PPU_BGDISP_BIT) ) return FALSE; // 既にヒットしていたらキャンセル if( PPUREG[2]&PPU_SPHIT_FLAG ) return FALSE; if( !(PPUREG[0]&PPU_SP16_BIT) ) { // 8x8 if( (scanline < (INT)SPRAM[0]+1) || (scanline > ((INT)SPRAM[0]+7+1)) ) return FALSE; } else { // 8x16 if( (scanline < (INT)SPRAM[0]+1) || (scanline > ((INT)SPRAM[0]+15+1)) ) return FALSE; } return TRUE; } void PPU::DummyScanline( INT scanline ) { INT i; INT spmax; INT sp_h; LPSPRITE sp; PPUREG[2] &= ~PPU_SPMAX_FLAG; // スプライト非表示はキャンセル if( !(PPUREG[1]&PPU_SPDISP_BIT) ) return; // 表示期間外であればキャンセル if( scanline < 0 || scanline > 239 ) return; sp = (LPSPRITE)SPRAM; sp_h = (PPUREG[0]&PPU_SP16_BIT)?15:7; spmax = 0; // Sprite Max check for( i = 0; i < 64; i++, sp++ ) { // スキャンライン内にSPRITEが存在するかをチェック if( (scanline < (INT)sp->y+1) || (scanline > ((INT)sp->y+sp_h+1)) ) { continue; } if( ++spmax > 8-1 ) { PPUREG[2] |= PPU_SPMAX_FLAG; break; } } }