AxibugEmuOnline/virtuanessrc097-master/NES/Nes.cpp

4012 lines
200 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//////////////////////////////////////////////////////////////////////////
// //
// NES Emulation core //
// Norix //
// written 2001/02/22 //
// last modify ----/--/-- //
//////////////////////////////////////////////////////////////////////////
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include "typedef.h"
#include "macro.h"
#include "VirtuaNESres.h"
#include "DebugOut.h"
#include "App.h"
#include "Pathlib.h"
#include "Config.h"
#include "Crclib.h"
#include "nes.h"
#include "mmu.h"
#include "cpu.h"
#include "ppu.h"
#include "apu.h"
#include "pad.h"
#include "rom.h"
#include "mapper.h"
#include "DirectDraw.h"
#include "DirectSound.h"
#include "DirectInput.h"
#include "pngwrite.h"
NESCONFIG NESCONFIG_NTSC = {
21477270.0f, // Base clock
1789772.5f, // Cpu clock
262, // Total scanlines
1364, // Scanline total cycles(15.75KHz)
1024, // H-Draw cycles
340, // H-Blank cycles
4, // End cycles
1364*262, // Frame cycles
29830, // FrameIRQ cycles
60, // Frame rate(Be originally 59.94Hz)
1000.0f/60.0f // Frame period(ms)
};
NESCONFIG NESCONFIG_PAL = {
// 21281364.0f, // Base clock
26601714.0f, // Base clock
// 1773447.0f, // Cpu clock
1662607.125f, // Cpu clock
312, // Total scanlines
// 1362, // Scanline total cycles(15.625KHz)
1278, // Scanline total cycles(15.625KHz)
// 1024, // H-Draw cycles
960, // H-Draw cycles
// 338, // H-Blank cycles
318, // H-Blank cycles
2, // End cycles
// 1362*312, // Frame cycles
1278*312, // Frame cycles
// 35469, // FrameIRQ cycles
33252, // FrameIRQ cycles
50, // Frame rate(Hz)
1000.0f/50.0f // Frame period(ms)
};
// Pad disp
BYTE NES::m_PadImg[] = {
28, 8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00,
0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x00,
0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x00, 0x0F,
0x0F, 0x0F, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x00,
0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x00, 0x0F,
0x0F, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
BYTE NES::m_KeyImg0[] = {
2, 2,
0x2A, 0x2A,
0x2A, 0x2A,
};
BYTE NES::m_KeyImg1[] = {
3, 3,
0x2A, 0x2A, 0x2A,
0x2A, 0x2A, 0x2A,
};
BYTE NES::m_KeyImg2[] = {
4, 4,
0xFF, 0x2A, 0x2A, 0xFF,
0x2A, 0x2A, 0x2A, 0x2A,
0x2A, 0x2A, 0x2A, 0x2A,
0xFF, 0x2A, 0x2A, 0xFF,
};
//----------
#define ASMVER 0
#if ASMVER
static INT cache = 0;
static UINT cache_hiRem = 0;
#endif
//
//
//
NES::NES( const char* fname )
{
DEBUGOUT( "VirtuaNES - NES Emulator for Win32 by Norix (C)2001\n" );
m_bDiskThrottle = FALSE;
m_CommandRequest = 0;
m_nSnapNo = 0;
m_bNsfPlaying = FALSE;
m_bMoviePlay = m_bMovieRec = FALSE;
m_fpMovie = NULL;
m_bTapePlay = m_bTapeRec = FALSE;
m_fpTape = NULL;
m_TapeCycles = 0.0;
m_TapeIn = m_TapeOut = 0;
m_bBarcode = FALSE;
m_BarcodeOut = 0;
m_BarcodePtr = 0;
m_BarcodeCycles = 0;
m_bBarcode2 = FALSE;
m_TurboFileBank = 0;
cpu = NULL;
ppu = NULL;
apu = NULL;
rom = NULL;
pad = NULL;
mapper = NULL;
SAVERAM_SIZE = 8*1024; // 8K byte
// IRQ type
nIRQtype = 0;
// FrameIRQ mode
bFrameIRQ = TRUE;
// NTSC/PAL VideoMode
bVideoMode = FALSE;
// Default config
nescfg = &NESCONFIG_NTSC;
// Cheat
CheatInitial();
// TEST
#if NES_PROFILER
m_dwTotalCycle = 0;
m_dwTotalTempCycle = 0;
m_dwProfileTotalCycle = 0;
m_dwProfileTotalCount = 0;
m_dwProfileCycle = 0;
m_dwProfileTempCycle = 0;
m_dwProfileAveCycle = 0;
m_dwProfileMaxCycle = 0;
#endif
//-------
#if ASMVER
cache = 0;
cache_hiRem = 0;
#endif
try {
DEBUGOUT( "Allocating CPU..." );
if( !(cpu = new CPU(this)) )
throw "Allocating CPU failed.";
DEBUGOUT( "Ok.\n" );
DEBUGOUT( "Allocating PPU..." );
if( !(ppu = new PPU(this)) )
throw "Allocating PPU failed.";
DEBUGOUT( "Ok.\n" );
DEBUGOUT( "Allocating APU..." );
if( !(apu = new APU(this)) )
throw "Allocating APU failed.";
DEBUGOUT( "Ok.\n" );
DEBUGOUT( "Allocating PAD..." );
if( !(pad = new PAD(this)) )
throw "Allocating PAD failed.";
DEBUGOUT( "Ok.\n" );
DEBUGOUT( "Loading ROM Image...\n" );
if( !(rom = new ROM(fname)) )
throw "Allocating ROM failed.";
if( !(mapper = CreateMapper(this, rom->GetMapperNo())) ) {
// 未サポートのマッパーです
LPCSTR szErrStr = CApp::GetErrorString( IDS_ERROR_UNSUPPORTMAPPER );
sprintf( szErrorString, szErrStr, rom->GetMapperNo() );
throw szErrorString;
}
DEBUGOUT( "Ok.\n" );
DEBUGOUT( "ROM status\n" );
DEBUGOUT( " %s\n", rom->GetRomName() );
DEBUGOUT( " Mapper : %03d\n", rom->GetMapperNo() );
DEBUGOUT( " PRG SIZE : %4dK\n", 16*(INT)rom->GetPROM_SIZE() );
DEBUGOUT( " CHR SIZE : %4dK\n", 8*(INT)rom->GetVROM_SIZE() );
DEBUGOUT( " V MIRROR : " );
if( rom->IsVMIRROR() ) DEBUGOUT( "Yes\n" );
else DEBUGOUT( "No\n" );
DEBUGOUT( " 4 SCREEN : " );
if( rom->Is4SCREEN() ) DEBUGOUT( "Yes\n" );
else DEBUGOUT( "No\n" );
DEBUGOUT( " SAVE RAM : " );
if( rom->IsSAVERAM() ) DEBUGOUT( "Yes\n" );
else DEBUGOUT( "No\n" );
DEBUGOUT( " TRAINER : " );
if( rom->IsTRAINER() ) DEBUGOUT( "Yes\n" );
else DEBUGOUT( "No\n" );
DEBUGOUT( " VS-Unisystem : " );
if( rom->IsVSUNISYSTEM() ) DEBUGOUT( "Yes\n" );
else DEBUGOUT( "No\n" );
NesSub_MemoryInitial();
LoadSRAM();
LoadDISK();
{
// Padクラス内だと初期化タイミングが遅いのでここで
DWORD crc = rom->GetPROM_CRC();
if( crc == 0xe792de94 // Best Play - Pro Yakyuu (New) (J)
|| crc == 0xf79d684a // Best Play - Pro Yakyuu (Old) (J)
|| crc == 0xc2ef3422 // Best Play - Pro Yakyuu 2 (J)
|| crc == 0x974e8840 // Best Play - Pro Yakyuu '90 (J)
|| crc == 0xb8747abf // Best Play - Pro Yakyuu Special (J)
|| crc == 0x9fa1c11f // Castle Excellent (J)
|| crc == 0x0b0d4d1b // Derby Stallion - Zenkoku Ban (J)
|| crc == 0x728c3d98 // Downtown - Nekketsu Monogatari (J)
|| crc == 0xd68a6f33 // Dungeon Kid (J)
|| crc == 0x3a51eb04 // Fleet Commander (J)
|| crc == 0x7c46998b // Haja no Fuuin (J)
|| crc == 0x7e5d2f1a // Itadaki Street - Watashi no Mise ni Yottette (J)
|| crc == 0xcee5857b // Ninjara Hoi! (J)
|| crc == 0x50ec5e8b // Wizardry - Legacy of Llylgamyn (J)
|| crc == 0x343e9146 // Wizardry - Proving Grounds of the Mad Overlord (J)
|| crc == 0x33d07e45 ) { // Wizardry - The Knight of Diamonds (J)
pad->SetExController( PAD::EXCONTROLLER_TURBOFILE );
}
}
LoadTurboFile();
// VS-Unisystemのデフォルト設定
if( rom->IsVSUNISYSTEM() ) {
DWORD crc = rom->GetPROM_CRC();
m_VSDipValue = GetVSDefaultDipSwitchValue( crc );
m_VSDipTable = FindVSDipSwitchTable( crc );
#include "VS_Setting.h"
} else {
m_VSDipValue = 0;
m_VSDipTable = vsdip_default;
}
Reset();
// ゲーム固有のデフォルトオプションを設定(設定戻す時に使う為)
GameOption.defRenderMethod = (INT)GetRenderMethod();
GameOption.defIRQtype = (INT)GetIrqType();
GameOption.defFrameIRQ = GetFrameIRQmode();
GameOption.defVideoMode = GetVideoMode();
// 設定をロードして設定する(エントリが無ければデフォルトが入る)
if( rom->GetMapperNo() != 20 ) {
GameOption.Load( rom->GetPROM_CRC() );
} else {
GameOption.Load( rom->GetGameID(), rom->GetMakerID() );
}
SetRenderMethod( (RENDERMETHOD)GameOption.nRenderMethod );
SetIrqType ( GameOption.nIRQtype );
SetFrameIRQmode( GameOption.bFrameIRQ );
SetVideoMode ( GameOption.bVideoMode );
} catch( CHAR* str ) {
DELETEPTR( cpu );
DELETEPTR( ppu );
DELETEPTR( apu );
DELETEPTR( pad );
DELETEPTR( rom );
DELETEPTR( mapper );
throw str;
#ifndef _DEBUG
} catch( ... ) {
DELETEPTR( cpu );
DELETEPTR( ppu );
DELETEPTR( apu );
DELETEPTR( pad );
DELETEPTR( rom );
DELETEPTR( mapper );
// 不明なエラーが発生しました
throw CApp::GetErrorString( IDS_ERROR_UNKNOWN );
#endif
}
DEBUGOUT( "Starting emulation!\n" );
}
NES::~NES()
{
MovieStop();
SaveSRAM();
SaveDISK();
SaveTurboFile();
DEBUGOUT( "Free NES..." );
DELETEPTR( cpu );
DELETEPTR( ppu );
DELETEPTR( apu );
DELETEPTR( pad );
DELETEPTR( rom );
DELETEPTR( mapper );
DEBUGOUT( "Ok.\n" );
}
void NES::SetVideoMode( BOOL bMode )
{
bVideoMode = bMode;
if( !bVideoMode ) {
nescfg = &NESCONFIG_NTSC;
} else {
nescfg = &NESCONFIG_PAL;
}
apu->SoundSetup();
}
void NES::Reset()
{
SaveSRAM();
SaveDISK();
SaveTurboFile();
// RAM Clear
ZEROMEMORY( RAM, sizeof(RAM) );
if( rom->GetPROM_CRC() == 0x29401686 ) { // Minna no Taabou no Nakayoshi Dai Sakusen(J)
::memset( RAM, 0xFF, sizeof(RAM) );
}
// RAM set
if( !rom->IsSAVERAM() && rom->GetMapperNo() != 20 ) {
::memset( WRAM, 0xFF, sizeof(WRAM) );
}
ZEROMEMORY( CRAM, sizeof(CRAM) );
ZEROMEMORY( VRAM, sizeof(VRAM) );
ZEROMEMORY( SPRAM, sizeof(SPRAM) );
ZEROMEMORY( BGPAL, sizeof(BGPAL) );
ZEROMEMORY( SPPAL, sizeof(SPPAL) );
ZEROMEMORY( CPUREG, sizeof(CPUREG) );
ZEROMEMORY( PPUREG, sizeof(PPUREG) );
m_bDiskThrottle = FALSE;
SetRenderMethod( PRE_RENDER );
if( rom->IsPAL() ) {
SetVideoMode( TRUE );
}
PROM = rom->GetPROM();
VROM = rom->GetVROM();
PROM_8K_SIZE = rom->GetPROM_SIZE()*2;
PROM_16K_SIZE = rom->GetPROM_SIZE();
PROM_32K_SIZE = rom->GetPROM_SIZE()/2;
VROM_1K_SIZE = rom->GetVROM_SIZE()*8;
VROM_2K_SIZE = rom->GetVROM_SIZE()*4;
VROM_4K_SIZE = rom->GetVROM_SIZE()*2;
VROM_8K_SIZE = rom->GetVROM_SIZE();
// デフォルトバンク
if( VROM_8K_SIZE ) {
SetVROM_8K_Bank( 0 );
} else {
SetCRAM_8K_Bank( 0 );
}
// ミラー
if( rom->Is4SCREEN() ) {
SetVRAM_Mirror( VRAM_MIRROR4 );
} else if( rom->IsVMIRROR() ) {
SetVRAM_Mirror( VRAM_VMIRROR );
} else {
SetVRAM_Mirror( VRAM_HMIRROR );
}
apu->SelectExSound( 0 );
ppu->Reset();
mapper->Reset();
// Trainer
if( rom->IsTRAINER() ) {
::memcpy( WRAM+0x1000, rom->GetTRAINER(), 512 );
}
pad->Reset();
cpu->Reset();
apu->Reset();
if( rom->IsNSF() ) {
mapper->Reset();
}
base_cycles = emul_cycles = 0;
}
void NES::SoftReset()
{
pad->Reset();
cpu->Reset();
apu->Reset();
if( rom->IsNSF() ) {
mapper->Reset();
}
m_bDiskThrottle = FALSE;
base_cycles = emul_cycles = 0;
}
void NES::EmulationCPU( INT basecycles )
{
INT cycles;
base_cycles += basecycles;
#if !ASMVER
cycles = (INT)((base_cycles/12)-emul_cycles);
#else
SQWORD& rBase = base_cycles;
SQWORD& rEmul = emul_cycles;
__asm {
// check if high DWORD changed
mov edi, 12
mov eax, cache
mov ebx, DWORD PTR [rBase]
mov ecx, DWORD PTR [rEmul]
cmp eax, [ebx+4]
jz low_dword
// high_dword:
// temp = (base_cycles/12)
// Calculate upper DWORD and remainder
xor edx, edx
mov eax, [ebx+4]
mov cache, eax
div edi
mov cache_hiRem, edx
low_dword:
// Calculate bottom DWORD with upper remainder
mov edx, cache_hiRem
mov eax, [ebx]
div edi
// diff:
// cycles = temp - emul_cycles
// eax carried from DIV above
xor edx, edx
sub edx, [ecx]
add eax, edx
mov cycles, eax
}
#endif
if( cycles > 0 ) {
emul_cycles += cpu->EXEC( cycles );
}
}
void NES::EmulationCPU_BeforeNMI( INT cycles )
{
base_cycles += cycles;
emul_cycles += cpu->EXEC( cycles/12 );
}
/*
描画シーケンス
0 ダミースキャンライン(描画しない)
1 - 239 描画
240 ダミースキャンライン,VBLANKフラグON
241 VINT期間,NMI発生
242-261 VINT期間
261 VINT期間,VBLANKフラグOFF
*/
void NES::EmulateFrame( BOOL bDraw )
{
INT scanline = 0;
// NSFプレイヤの時
if( rom->IsNSF() ) {
EmulateNSF();
return;
}
// Cheat
CheatCodeProcess();
//
NES_scanline = scanline;
if( RenderMethod != TILE_RENDER ) {
bZapper = FALSE;
while( TRUE ) {
ppu->SetRenderScanline( scanline );
if( scanline == 0 ) {
// ダミースキャンライン
if( RenderMethod < POST_RENDER ) {
EmulationCPU( nescfg->ScanlineCycles );
ppu->FrameStart();
ppu->ScanlineNext();
mapper->HSync( scanline );
ppu->ScanlineStart();
} else {
EmulationCPU( nescfg->HDrawCycles );
ppu->FrameStart();
ppu->ScanlineNext();
mapper->HSync( scanline );
EmulationCPU( FETCH_CYCLES*32 );
ppu->ScanlineStart();
EmulationCPU( FETCH_CYCLES*10+nescfg->ScanlineEndCycles );
}
} else if( scanline < 240 ) {
if( RenderMethod < POST_RENDER ) {
if( RenderMethod == POST_ALL_RENDER )
EmulationCPU( nescfg->ScanlineCycles );
if( bDraw ) {
ppu->Scanline( scanline, Config.graphics.bAllSprite, Config.graphics.bLeftClip );
} else {
if( pad->IsZapperMode() && scanline == ZapperY ) {
ppu->Scanline( scanline, Config.graphics.bAllSprite, Config.graphics.bLeftClip );
} else {
if( !ppu->IsSprite0( scanline ) ) {
ppu->DummyScanline( scanline );
} else {
ppu->Scanline( scanline, Config.graphics.bAllSprite, Config.graphics.bLeftClip );
}
}
}
ppu->ScanlineNext(); // これの位置でラスター系は画面が違う
if( RenderMethod == PRE_ALL_RENDER )
EmulationCPU( nescfg->ScanlineCycles );
// ppu->ScanlineNext(); // これの位置でラスター系は画面が違う
mapper->HSync( scanline );
ppu->ScanlineStart();
} else {
if( RenderMethod == POST_RENDER )
EmulationCPU( nescfg->HDrawCycles );
if( bDraw ) {
ppu->Scanline( scanline, Config.graphics.bAllSprite, Config.graphics.bLeftClip );
} else {
if( pad->IsZapperMode() && scanline == ZapperY ) {
ppu->Scanline( scanline, Config.graphics.bAllSprite, Config.graphics.bLeftClip );
} else {
if( !ppu->IsSprite0( scanline ) ) {
ppu->DummyScanline( scanline );
} else {
ppu->Scanline( scanline, Config.graphics.bAllSprite, Config.graphics.bLeftClip );
}
}
}
if( RenderMethod == PRE_RENDER )
EmulationCPU( nescfg->HDrawCycles );
ppu->ScanlineNext();
mapper->HSync( scanline );
EmulationCPU( FETCH_CYCLES*32 );
ppu->ScanlineStart();
EmulationCPU( FETCH_CYCLES*10+nescfg->ScanlineEndCycles );
}
} else if( scanline == 240 ) {
mapper->VSync();
if( RenderMethod < POST_RENDER ) {
EmulationCPU( nescfg->ScanlineCycles );
mapper->HSync( scanline );
} else {
EmulationCPU( nescfg->HDrawCycles );
mapper->HSync( scanline );
EmulationCPU( nescfg->HBlankCycles );
}
} else if( scanline <= nescfg->TotalScanlines-1 ) {
pad->VSync();
// VBLANK期間
if( scanline == nescfg->TotalScanlines-1 ) {
ppu->VBlankEnd();
}
if( RenderMethod < POST_RENDER ) {
if( scanline == 241 ) {
ppu->VBlankStart();
if( PPUREG[0] & PPU_VBLANK_BIT ) {
cpu->NMI();
}
}
EmulationCPU( nescfg->ScanlineCycles );
mapper->HSync( scanline );
} else {
if( scanline == 241 ) {
ppu->VBlankStart();
if( PPUREG[0] & PPU_VBLANK_BIT ) {
cpu->NMI();
}
}
EmulationCPU( nescfg->HDrawCycles );
mapper->HSync( scanline );
EmulationCPU( nescfg->HBlankCycles );
}
if( scanline == nescfg->TotalScanlines-1 ) {
break;
}
}
if( pad->IsZapperMode() ) {
if( scanline == ZapperY )
bZapper = TRUE;
else
bZapper = FALSE;
}
scanline++;
NES_scanline = scanline;
}
} else {
bZapper = FALSE;
while( TRUE ) {
ppu->SetRenderScanline( scanline );
if( scanline == 0 ) {
// ダミースキャンライン
// H-Draw (4fetches*32)
EmulationCPU( FETCH_CYCLES*128 );
ppu->FrameStart();
ppu->ScanlineNext();
EmulationCPU( FETCH_CYCLES*10 );
mapper->HSync( scanline );
EmulationCPU( FETCH_CYCLES*22 );
ppu->ScanlineStart();
EmulationCPU( FETCH_CYCLES*10+nescfg->ScanlineEndCycles );
} else if( scanline < 240 ) {
// スクリーン描画(Scanline 1〜239)
if( bDraw ) {
ppu->Scanline( scanline, Config.graphics.bAllSprite, Config.graphics.bLeftClip );
ppu->ScanlineNext();
EmulationCPU( FETCH_CYCLES*10 );
mapper->HSync( scanline );
EmulationCPU( FETCH_CYCLES*22 );
ppu->ScanlineStart();
EmulationCPU( FETCH_CYCLES*10+nescfg->ScanlineEndCycles );
} else {
if( pad->IsZapperMode() && scanline == ZapperY ) {
ppu->Scanline( scanline, Config.graphics.bAllSprite, Config.graphics.bLeftClip );
ppu->ScanlineNext();
EmulationCPU( FETCH_CYCLES*10 );
mapper->HSync( scanline );
EmulationCPU( FETCH_CYCLES*22 );
ppu->ScanlineStart();
EmulationCPU( FETCH_CYCLES*10+nescfg->ScanlineEndCycles );
} else {
if( !ppu->IsSprite0( scanline ) ) {
// H-Draw (4fetches*32)
EmulationCPU( FETCH_CYCLES*128 );
ppu->DummyScanline( scanline );
ppu->ScanlineNext();
EmulationCPU( FETCH_CYCLES*10 );
mapper->HSync( scanline );
EmulationCPU( FETCH_CYCLES*22 );
ppu->ScanlineStart();
EmulationCPU( FETCH_CYCLES*10+nescfg->ScanlineEndCycles );
} else {
ppu->Scanline( scanline, Config.graphics.bAllSprite, Config.graphics.bLeftClip );
ppu->ScanlineNext();
EmulationCPU( FETCH_CYCLES*10 );
mapper->HSync( scanline );
EmulationCPU( FETCH_CYCLES*22 );
ppu->ScanlineStart();
EmulationCPU( FETCH_CYCLES*10+nescfg->ScanlineEndCycles );
}
}
}
} else if( scanline == 240 ) {
// ダミースキャンライン (Scanline 240)
mapper->VSync();
EmulationCPU( nescfg->HDrawCycles );
// H-Sync
mapper->HSync( scanline );
EmulationCPU( nescfg->HBlankCycles );
} else if( scanline <= nescfg->TotalScanlines-1 ) {
pad->VSync();
// VBLANK期間
if( scanline == nescfg->TotalScanlines-1 ) {
ppu->VBlankEnd();
}
if( scanline == 241 ) {
ppu->VBlankStart();
if( PPUREG[0]&PPU_VBLANK_BIT ) {
cpu->NMI();
}
}
EmulationCPU( nescfg->HDrawCycles );
// H-Sync
mapper->HSync( scanline );
EmulationCPU( nescfg->HBlankCycles );
if( scanline == nescfg->TotalScanlines-1 ) {
break;
}
}
if( pad->IsZapperMode() ) {
if( scanline == ZapperY )
bZapper = TRUE;
else
bZapper = FALSE;
}
scanline++;
NES_scanline = scanline;
}
}
// Movie pad
// if( Config.movie.bPadDisplay && bDraw ) {
// DrawPad();
// }
// Movie pad
if( bDraw ) {
DrawPad();
}
#if NES_PROFILER
{
CHAR str[256];
::wsprintf( str, "Cyc:%10d", m_dwProfileCycle );
DrawString( 9, 240-32, str, 0x1F );
DrawString( 8, 240-33, str, 0x30 );
::wsprintf( str, "Ave:%10d", m_dwProfileAveCycle );
DrawString( 9, 240-23, str, 0x1F );
DrawString( 8, 240-24, str, 0x30 );
::wsprintf( str, "Max:%10d", m_dwProfileMaxCycle );
DrawString( 9, 240-14, str, 0x1F );
DrawString( 8, 240-15, str, 0x30 );
}
#endif
}
void NES::EmulateNSF()
{
R6502 reg;
ppu->Reset();
mapper->VSync();
//DEBUGOUT( "Frame\n" );
if( m_bNsfPlaying ) {
if( m_bNsfInit ) {
ZEROMEMORY( RAM, sizeof(RAM) );
if( !(rom->GetNsfHeader()->ExtraChipSelect&0x04) ) {
ZEROMEMORY( WRAM, 0x2000 );
}
apu->Reset();
apu->Write( 0x4015, 0x0F );
apu->Write( 0x4017, 0xC0 );
apu->ExWrite( 0x4080, 0x80 ); // FDS Volume 0
apu->ExWrite( 0x408A, 0xE8 ); // FDS Envelope Speed
cpu->GetContext( reg );
reg.PC = 0x4710; // Init Address
reg.A = (BYTE)m_nNsfSongNo;
reg.X = (BYTE)m_nNsfSongMode;
reg.Y = 0;
reg.S = 0xFF;
reg.P = Z_FLAG|R_FLAG|I_FLAG;
cpu->SetContext( reg );
// 安全対策を兼ねてあえてループに(1秒分)
for( INT i = 0; i < nescfg->TotalScanlines*60; i++ ) {
EmulationCPU( nescfg->ScanlineCycles );
cpu->GetContext( reg );
// 無限ループに入ったことを確認したら抜ける
if( reg.PC == 0x4700 ) {
break;
}
}
m_bNsfInit = FALSE;
}
cpu->GetContext( reg );
// 無限ループに入っていたら再設定する
if( reg.PC == 0x4700 ) {
reg.PC = 0x4720; // Play Address
reg.A = 0;
reg.S = 0xFF;
cpu->SetContext( reg );
}
for( INT i = 0; i < nescfg->TotalScanlines; i++ ) {
EmulationCPU( nescfg->ScanlineCycles );
}
} else {
cpu->GetContext( reg );
reg.PC = 0x4700; // 無限ループ
reg.S = 0xFF;
cpu->SetContext( reg );
EmulationCPU( nescfg->ScanlineCycles*nescfg->TotalScanlines );
}
}
void NES::SetNsfPlay( INT songno, INT songmode )
{
m_bNsfPlaying = TRUE;
m_bNsfInit = TRUE;
m_nNsfSongNo = songno;
m_nNsfSongMode = songmode;
}
void NES::SetNsfStop()
{
m_bNsfPlaying = FALSE;
apu->Reset();
}
void NES::Clock( INT cycles )
{
Tape( cycles );
Barcode( cycles );
}
BYTE NES::Read( WORD addr )
{
switch( addr>>13 ) {
case 0x00: // $0000-$1FFF
return RAM[addr&0x07FF];
case 0x01: // $2000-$3FFF
return ppu->Read( addr&0xE007 );
case 0x02: // $4000-$5FFF
if( addr < 0x4100 ) {
return ReadReg( addr );
} else {
return mapper->ReadLow( addr );
}
break;
case 0x03: // $6000-$7FFF
return mapper->ReadLow( addr );
case 0x04: // $8000-$9FFF
case 0x05: // $A000-$BFFF
case 0x06: // $C000-$DFFF
case 0x07: // $E000-$FFFF
return CPU_MEM_BANK[addr>>13][addr&0x1FFF];
}
return 0x00; // Warning予防
}
void NES::Write( WORD addr, BYTE data )
{
switch( addr>>13 ) {
case 0x00: // $0000-$1FFF
RAM[addr&0x07FF] = data;
break;
case 0x01: // $2000-$3FFF
if( !rom->IsNSF() ) {
ppu->Write( addr&0xE007, data );
}
break;
case 0x02: // $4000-$5FFF
if( addr < 0x4100 ) {
WriteReg( addr, data );
} else {
mapper->WriteLow( addr, data );
}
break;
case 0x03: // $6000-$7FFF
mapper->WriteLow( addr, data );
break;
case 0x04: // $8000-$9FFF
case 0x05: // $A000-$BFFF
case 0x06: // $C000-$DFFF
case 0x07: // $E000-$FFFF
mapper->Write( addr, data );
GenieCodeProcess();
break;
}
}
BYTE NES::ReadReg( WORD addr )
{
switch( addr & 0xFF ) {
case 0x00: case 0x01: case 0x02: case 0x03:
case 0x04: case 0x05: case 0x06: case 0x07:
case 0x08: case 0x09: case 0x0A: case 0x0B:
case 0x0C: case 0x0D: case 0x0E: case 0x0F:
case 0x10: case 0x11: case 0x12: case 0x13:
return apu->Read( addr );
break;
case 0x15:
return apu->Read( addr );
break;
case 0x14:
return addr&0xFF;
break;
case 0x16:
if( rom->IsVSUNISYSTEM() ) {
return pad->Read( addr );
} else {
return pad->Read( addr ) | 0x40 | m_TapeOut;
}
break;
case 0x17:
if( rom->IsVSUNISYSTEM() ) {
return pad->Read( addr );
} else {
return pad->Read( addr ) | apu->Read( addr );
}
break;
default:
return mapper->ExRead( addr );
break;
}
}
void NES::WriteReg( WORD addr, BYTE data )
{
switch( addr & 0xFF ) {
case 0x00: case 0x01: case 0x02: case 0x03:
case 0x04: case 0x05: case 0x06: case 0x07:
case 0x08: case 0x09: case 0x0A: case 0x0B:
case 0x0C: case 0x0D: case 0x0E: case 0x0F:
case 0x10: case 0x11: case 0x12: case 0x13:
case 0x15:
apu->Write( addr, data );
CPUREG[addr & 0xFF] = data;
break;
case 0x14:
ppu->DMA( data );
cpu->DMA( 514 ); // DMA Pending cycle
CPUREG[addr & 0xFF] = data;
break;
case 0x16:
mapper->ExWrite( addr, data ); // For VS-Unisystem
pad->Write( addr, data );
CPUREG[addr & 0xFF] = data;
m_TapeIn = data;
break;
case 0x17:
CPUREG[addr & 0xFF] = data;
pad->Write( addr, data );
apu->Write( addr, data );
break;
// VirtuaNES固有ポート
case 0x18:
apu->Write( addr, data );
break;
#if NES_PROFILER
case 0x1D:
m_dwProfileAveCycle = 0;
m_dwProfileMaxCycle = 0;
break;
case 0x1E:
m_dwProfileTempCycle = cpu->GetTotalCycles();
break;
case 0x1F:
m_dwProfileCycle = cpu->GetTotalCycles()-m_dwProfileTempCycle-4;
if( !m_dwProfileAveCycle ) {
m_dwProfileAveCycle += m_dwProfileCycle;
} else {
m_dwProfileAveCycle += m_dwProfileCycle;
m_dwProfileAveCycle /= 2;
}
if( m_dwProfileMaxCycle < m_dwProfileCycle ) {
m_dwProfileMaxCycle = m_dwProfileCycle;
}
break;
#endif
#if 0
case 0x1C:
m_dwProfileTempCycle = cpu->GetTotalCycles();
break;
case 0x1D:
m_dwProfileCycle = cpu->GetTotalCycles()-m_dwProfileTempCycle-4;
m_dwProfileTotalCycle += m_dwProfileCycle;
m_dwProfileTotalCount++;
break;
case 0x1E:
m_dwProfileTotalCount = 0;
m_dwProfileTotalCycle = 0;
m_dwTotalTempCycle = cpu->GetTotalCycles();
break;
case 0x1F:
m_dwTotalCycle = cpu->GetTotalCycles()-m_dwTotalTempCycle-4;
break;
#endif
default:
mapper->ExWrite( addr, data );
break;
}
}
void NES::LoadSRAM()
{
if( rom->IsNSF() )
return;
ZEROMEMORY( WRAM, sizeof(WRAM) );
if( !rom->IsSAVERAM() )
return;
string pathstr, tempstr;
if( Config.path.bSavePath ) {
pathstr = CPathlib::CreatePath( CApp::GetModulePath(), Config.path.szSavePath );
} else {
pathstr = rom->GetRomPath();
}
tempstr = CPathlib::MakePathExt( pathstr.c_str(), rom->GetRomName(), "sav" );
DEBUGOUT( "Path: %s\n", tempstr.c_str() );
FILE* fp = NULL;
try
{
if( !(fp = ::fopen( tempstr.c_str(), "rb" )) ) {
// xxx ファイルを開けません
LPCSTR szErrStr = CApp::GetErrorString( IDS_ERROR_OPEN );
sprintf( szErrorString, szErrStr, tempstr.c_str() );
throw szErrorString;
}
DEBUGOUT( "Loading SAVERAM..." );
LONG size;
// ファイルサイズ取得
::fseek( fp, 0, SEEK_END );
size = ftell( fp );
::fseek( fp, 0, SEEK_SET );
if( size <= 128*1024 ) {
if( ::fread( WRAM, size, 1, fp ) != 1 )
throw "File Read error.";
}
DEBUGOUT( "Ok.\n" );
FCLOSE( fp );
} catch( CHAR* str ) {
FCLOSE( fp );
DEBUGOUT( "Loading SAVERAM Error.\n" );
DEBUGOUT( "%s\n", str );
// throw str;
#ifndef _DEBUG
} catch(...) {
FCLOSE( fp );
DEBUGOUT( "Loading SAVERAM Error.\n" );
// 不明なエラーが発生しました
throw CApp::GetErrorString( IDS_ERROR_UNKNOWN );
#endif
}
}
void NES::SaveSRAM()
{
INT i;
if( rom->IsNSF() )
return;
if( !rom->IsSAVERAM() )
return;
for( i = 0; i < SAVERAM_SIZE; i++ ) {
if( WRAM[i] != 0x00 )
break;
}
if( i < SAVERAM_SIZE ) {
DEBUGOUT( "Saving SAVERAM...\n" );
string pathstr, tempstr;
if( Config.path.bSavePath ) {
pathstr = CPathlib::CreatePath( CApp::GetModulePath(), Config.path.szSavePath );
::CreateDirectory( pathstr.c_str(), NULL );
} else {
pathstr = rom->GetRomPath();
}
tempstr = CPathlib::MakePathExt( pathstr.c_str(), rom->GetRomName(), "sav" );
DEBUGOUT( "Path: %s\n", tempstr.c_str() );
FILE* fp = NULL;
try
{
if( !(fp = ::fopen( tempstr.c_str(), "wb" )) ) {
// xxx ファイルを開けません
LPCSTR szErrStr = CApp::GetErrorString( IDS_ERROR_OPEN );
sprintf( szErrorString, szErrStr, tempstr.c_str() );
throw szErrorString;
}
if( ::fwrite( WRAM, SAVERAM_SIZE, 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
DEBUGOUT( "Ok.\n" );
FCLOSE( fp );
} catch( CHAR* str ) {
DEBUGOUT( "Writing SAVERAM Error.\n" );
FCLOSE( fp );
throw str;
#ifndef _DEBUG
} catch(...) {
DEBUGOUT( "Writing SAVERAM Error.\n" );
FCLOSE( fp );
// 不明なエラーが発生しました
throw CApp::GetErrorString( IDS_ERROR_UNKNOWN );
#endif
}
}
}
void NES::LoadDISK()
{
if( rom->GetMapperNo() != 20 )
return;
BOOL bExit = FALSE;
INT i, j, diskno;
FILE* fp = NULL;
DISKIMGFILEHDR ifh;
DISKIMGHDR hdr;
LPBYTE disk;
WORD Version;
string pathstr, tempstr;
if( Config.path.bSavePath ) {
pathstr = CPathlib::CreatePath( CApp::GetModulePath(), Config.path.szSavePath );
} else {
pathstr = rom->GetRomPath();
}
tempstr = CPathlib::MakePathExt( pathstr.c_str(), rom->GetRomName(), "dsv" );
DEBUGOUT( "Path: %s\n", tempstr.c_str() );
try
{
if( !(fp = ::fopen( tempstr.c_str(), "rb" )) ) {
// xxx ファイルを開けません
LPCSTR szErrStr = CApp::GetErrorString( IDS_ERROR_OPEN );
sprintf( szErrorString, szErrStr, tempstr.c_str() );
throw szErrorString;
}
if( ::fread( &ifh, sizeof(DISKIMGFILEHDR), 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
if( ::memcmp( ifh.ID, "VirtuaNES DI", sizeof(ifh.ID) ) == 0 ) {
if( ifh.BlockVersion < 0x0100 && ifh.BlockVersion > 0x200 ) {
// 未対応形式です
throw CApp::GetErrorString( IDS_ERROR_UNSUPPORTFORMAT );
}
Version = ifh.BlockVersion;
} else {
// 未対応形式です
throw CApp::GetErrorString( IDS_ERROR_UNSUPPORTFORMAT );
}
if( Version == 0x0100 ) {
// Ver0.24以前
if( ifh.DiskNumber > 4 ) {
// 未対応形式です
throw CApp::GetErrorString( IDS_ERROR_UNSUPPORTFORMAT );
}
for( i = 0; i < (INT)ifh.DiskNumber; i++ ) {
if( ::fread( &hdr, sizeof(DISKIMGHDR), 1, fp ) != 1 ) {
if( i == 0 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
} else {
break;
}
}
if( ::memcmp( hdr.ID, "SIDE0A", sizeof(hdr.ID) ) == 0 ) {
diskno = 0;
} else if( ::memcmp( hdr.ID, "SIDE0B", sizeof(hdr.ID) ) == 0 ) {
diskno = 1;
} else if( ::memcmp( hdr.ID, "SIDE1A", sizeof(hdr.ID) ) == 0 ) {
diskno = 2;
} else if( ::memcmp( hdr.ID, "SIDE1B", sizeof(hdr.ID) ) == 0 ) {
diskno = 3;
} else {
// 未対応形式です
throw CApp::GetErrorString( IDS_ERROR_UNSUPPORTFORMAT );
}
for( j = 0; j < 16; j++ ) {
if( hdr.DiskTouch[j] ) {
disk = rom->GetPROM()+16+65500*diskno+(4*1024)*j;
if( j < 15 ) {
if( ::fread( disk, 4*1024, 1, fp ) != 1 ) {
bExit = TRUE;
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
} else {
if( ::fread( disk, 4*1024-36, 1, fp ) != 1 ) {
bExit = TRUE;
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
}
}
}
}
} else
if( Version == 0x0200 || Version == 0x0210 ) {
// Ver0.30以降
DISKFILEHDR dfh;
LPBYTE lpDisk = rom->GetPROM();
LPBYTE lpWrite = rom->GetDISK();
LONG DiskSize = 16+65500*rom->GetDiskNo();
DWORD pos;
BYTE data;
// 書き換えFLAG消去
::ZeroMemory( lpWrite, 16+65500*rom->GetDiskNo() );
// ヘッダ読み直し
if( ::fseek( fp, 0, SEEK_SET ) ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
if( ::fread( &dfh, sizeof(DISKFILEHDR), 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
if( Config.emulator.bCrcCheck ) {
// 現在ロード中のタイトルと違うかをチェック
if( dfh.ProgID != rom->GetGameID()
|| dfh.MakerID != (WORD)rom->GetMakerID()
|| dfh.DiskNo != (WORD)rom->GetDiskNo() ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
}
for( i = 0; i < dfh.DifferentSize; i++ ) {
if( ::fread( &pos, sizeof(DWORD), 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
bExit = TRUE;
throw CApp::GetErrorString( IDS_ERROR_READ );
}
data = (BYTE)(pos>>24);
pos &= 0x00FFFFFF;
if( pos >= 16 && pos < DiskSize ) {
lpDisk[pos] = data;
lpWrite[pos] = 0xFF;
}
}
}
FCLOSE( fp );
} catch( CHAR* str ) {
FCLOSE( fp );
DEBUGOUT( "%s\n", str );
if( bExit )
throw str;
#ifndef _DEBUG
} catch(...) {
FCLOSE( fp );
DEBUGOUT( "Loading DISKIMAGE Error.\n" );
// 不明なエラーが発生しました
throw CApp::GetErrorString( IDS_ERROR_UNKNOWN );
#endif
}
}
void NES::SaveDISK()
{
if( rom->GetMapperNo() != 20 )
return;
INT i;
FILE* fp = NULL;
DISKFILEHDR ifh;
LPBYTE lpDisk = rom->GetPROM();
LPBYTE lpWrite = rom->GetDISK();
LONG DiskSize = 16+65500*rom->GetDiskNo();
DWORD data;
try
{
::ZeroMemory( &ifh, sizeof(ifh) );
::memcpy( ifh.ID, "VirtuaNES DI", sizeof(ifh.ID) );
ifh.BlockVersion = 0x0210;
ifh.ProgID = rom->GetGameID();
ifh.MakerID = (WORD)rom->GetMakerID();
ifh.DiskNo = (WORD)rom->GetDiskNo();
// 相違数をカウント
for( i = 16; i < DiskSize; i++ ) {
if( lpWrite[i] )
ifh.DifferentSize++;
}
if( !ifh.DifferentSize )
return;
string pathstr, tempstr;
if( Config.path.bSavePath ) {
pathstr = CPathlib::CreatePath( CApp::GetModulePath(), Config.path.szSavePath );
::CreateDirectory( pathstr.c_str(), NULL );
} else {
pathstr = rom->GetRomPath();
}
tempstr = CPathlib::MakePathExt( pathstr.c_str(), rom->GetRomName(), "dsv" );
DEBUGOUT( "Path: %s\n", tempstr.c_str() );
if( !(fp = ::fopen( tempstr.c_str(), "wb" )) ) {
// xxx ファイルを開けません
LPCSTR szErrStr = CApp::GetErrorString( IDS_ERROR_OPEN );
::wsprintf( szErrorString, szErrStr, tempstr.c_str() );
throw szErrorString;
}
if( ::fwrite( &ifh, sizeof(DISKFILEHDR), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
for( i = 16; i < DiskSize; i++ ) {
if( lpWrite[i] ) {
data = i & 0x00FFFFFF;
data |= ((DWORD)lpDisk[i]&0xFF)<<24;
// Write File
if( ::fwrite( &data, sizeof(DWORD), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
}
}
FCLOSE( fp );
} catch( CHAR* str ) {
FCLOSE( fp );
DEBUGOUT( "%s\n", str );
#ifndef _DEBUG
} catch(...) {
FCLOSE( fp );
DEBUGOUT( "Saving DISKIMAGE Error.\n" );
// 不明なエラーが発生しました
throw CApp::GetErrorString( IDS_ERROR_UNKNOWN );
#endif
}
}
void NES::LoadTurboFile()
{
ZEROMEMORY( ERAM, sizeof(ERAM) );
if( pad->GetExController() != PAD::EXCONTROLLER_TURBOFILE )
return;
string pathstr, tempstr;
if( Config.path.bSavePath ) {
pathstr = CPathlib::CreatePath( CApp::GetModulePath(), Config.path.szSavePath );
} else {
pathstr = rom->GetRomPath();
}
tempstr = CPathlib::MakePathExt( pathstr.c_str(), "TurboFile", "vtf" );
DEBUGOUT( "Path: %s\n", tempstr.c_str() );
FILE* fp = NULL;
try
{
if( !(fp = ::fopen( tempstr.c_str(), "rb" )) ) {
// xxx ファイルを開けません
LPCSTR szErrStr = CApp::GetErrorString( IDS_ERROR_OPEN );
sprintf( szErrorString, szErrStr, tempstr.c_str() );
throw szErrorString;
}
DEBUGOUT( "Loading TURBOFILE..." );
LONG size;
// ファイルサイズ取得
::fseek( fp, 0, SEEK_END );
size = ftell( fp );
::fseek( fp, 0, SEEK_SET );
if( size > 32*1024 ) {
size = 32*1024;
}
if( ::fread( ERAM, size, 1, fp ) != 1 )
throw "File Read error.";
DEBUGOUT( "Ok.\n" );
FCLOSE( fp );
} catch( CHAR* str ) {
FCLOSE( fp );
DEBUGOUT( "Loading TurboFile Error.\n" );
DEBUGOUT( "%s\n", str );
// throw str;
#ifndef _DEBUG
} catch(...) {
FCLOSE( fp );
DEBUGOUT( "Loading TurboFile Error.\n" );
// 不明なエラーが発生しました
throw CApp::GetErrorString( IDS_ERROR_UNKNOWN );
#endif
}
}
void NES::SaveTurboFile()
{
INT i;
if( pad->GetExController() != PAD::EXCONTROLLER_TURBOFILE )
return;
for( i = 0; i < sizeof(ERAM); i++ ) {
if( ERAM[i] != 0x00 )
break;
}
if( i < sizeof(ERAM) ) {
DEBUGOUT( "Saving TURBOFILE...\n" );
string pathstr, tempstr;
if( Config.path.bSavePath ) {
pathstr = CPathlib::CreatePath( CApp::GetModulePath(), Config.path.szSavePath );
::CreateDirectory( pathstr.c_str(), NULL );
} else {
pathstr = rom->GetRomPath();
}
tempstr = CPathlib::MakePathExt( pathstr.c_str(), "TurboFile", "vtf" );
DEBUGOUT( "Path: %s\n", tempstr.c_str() );
FILE* fp = NULL;
try
{
if( !(fp = ::fopen( tempstr.c_str(), "wb" )) ) {
// xxx ファイルを開けません
LPCSTR szErrStr = CApp::GetErrorString( IDS_ERROR_OPEN );
sprintf( szErrorString, szErrStr, tempstr.c_str() );
throw szErrorString;
}
if( ::fwrite( ERAM, sizeof(ERAM), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
DEBUGOUT( "Ok.\n" );
FCLOSE( fp );
} catch( CHAR* str ) {
DEBUGOUT( "Writing TurboFile Error.\n" );
FCLOSE( fp );
throw str;
#ifndef _DEBUG
} catch(...) {
DEBUGOUT( "Writing TurboFile Error.\n" );
FCLOSE( fp );
// 不明なエラーが発生しました
throw CApp::GetErrorString( IDS_ERROR_UNKNOWN );
#endif
}
}
}
INT NES::IsStateFile( const char* fname, ROM* rom )
{
FILE* fp = NULL;
FILEHDR2 header;
if( !(fp = ::fopen( fname, "rb" )) )
return -1;
if( ::fread( &header, sizeof(header), 1, fp ) != 1 ) {
FCLOSE( fp );
return -1;
}
FCLOSE( fp );
if( ::memcmp( header.ID, "VirtuaNES ST", sizeof(header.ID) ) == 0 ) {
if( header.BlockVersion < 0x0100 )
return 0;
if( Config.emulator.bCrcCheck ) {
if( header.BlockVersion >= 0x200 ) {
if( rom->GetMapperNo() != 20 ) {
// FDS以外
if( header.Ext0 != rom->GetPROM_CRC() ) {
return IDS_ERROR_ILLEGALSTATECRC; // 違うじゃん
}
} else {
// FDS
if( header.Ext0 != rom->GetGameID() ||
header.Ext1 != (WORD)rom->GetMakerID() ||
header.Ext2 != (WORD)rom->GetDiskNo() )
return IDS_ERROR_ILLEGALSTATECRC; // 違うじゃん
}
}
}
return 0;
}
return -1;
}
BOOL NES::LoadState( const char* fname )
{
FILE* fp = NULL;
BOOL bRet = FALSE;
if( rom->IsNSF() )
return TRUE;
try {
if( !(fp = ::fopen( fname, "rb" )) ) {
// xxx ファイルを開けません
LPCSTR szErrStr = CApp::GetErrorString( IDS_ERROR_OPEN );
sprintf( szErrorString, szErrStr, fname );
throw szErrorString;
}
bRet = ReadState( fp );
FCLOSE( fp );
} catch( CHAR* str ) {
DEBUGOUT( "State load error.\n" );
DEBUGOUT( "%s\n", str );
FCLOSE( fp );
return FALSE;
#ifndef _DEBUG
} catch(...) {
DEBUGOUT( "State load error.\n" );
FCLOSE( fp );
// 不明なエラーが発生しました
throw CApp::GetErrorString( IDS_ERROR_UNKNOWN );
#endif
}
return bRet;
}
BOOL NES::SaveState( const char* fname )
{
FILE* fp = NULL;
if( rom->IsNSF() )
return TRUE;
try {
if( !(fp = ::fopen( fname, "wb" )) ) {
// xxx ファイルを開けません
LPCSTR szErrStr = CApp::GetErrorString( IDS_ERROR_OPEN );
sprintf( szErrorString, szErrStr, fname );
throw szErrorString;
}
WriteState( fp );
FCLOSE( fp );
} catch( CHAR* str ) {
DEBUGOUT( "State save error.\n" );
FCLOSE( fp );
throw str;
#ifndef _DEBUG
} catch(...) {
DEBUGOUT( "State save error.\n" );
FCLOSE( fp );
// 不明なエラーが発生しました
throw CApp::GetErrorString( IDS_ERROR_UNKNOWN );
#endif
}
return TRUE;
}
BOOL NES::ReadState( FILE* fp )
{
INT i;
BOOL bHeader = FALSE;
WORD Version = 0;
BLOCKHDR hdr;
INT type;
while( TRUE ) {
// Read File
if( ::fread( &hdr, sizeof(BLOCKHDR), 1, fp ) != 1 )
break;
// File Header check
if( !bHeader ) {
LPFILEHDR fh = (LPFILEHDR)&hdr;
if( ::memcmp( fh->ID, "VirtuaNES ST", sizeof(fh->ID) ) == 0 ) {
Version = fh->BlockVersion;
if( Version == 0x0100 ) {
// Ver0.24まで
bHeader = TRUE;
// 古い奴はムービー中はロード出来ません
if( m_bMoviePlay ) {
return FALSE;
}
// 古い奴のFDSはロード出来ません
if( rom->GetMapperNo() == 20 ) {
// 未対応形式です
throw CApp::GetErrorString( IDS_ERROR_UNSUPPORTFORMAT );
}
} else
if( Version == 0x0200 || Version == 0x0210 ) {
// Ver0.30以降 Ver0.60以降
FILEHDR2 hdr2;
// ヘッダ部読み直し
if( ::fseek( fp, -sizeof(BLOCKHDR), SEEK_CUR ) ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
// Read File
if( ::fread( &hdr2, sizeof(FILEHDR2), 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
#if 0
if( Config.emulator.bCrcCheck ) {
// 現在ロード中のタイトルと違うかをチェック
if( rom->GetMapperNo() != 20 ) {
// FDS以外
if( hdr2.Ext0 != rom->GetPROM_CRC() ) {
return FALSE; // 違うじゃん
}
} else {
// FDS
if( hdr2.Ext0 != rom->GetGameID() ||
hdr2.Ext1 != (WORD)rom->GetMakerID() ||
hdr2.Ext2 != (WORD)rom->GetDiskNo() )
return FALSE; // 違うじゃん
}
}
#endif
// ムービー中はファイルポインタとステップ数を
// 変更して,ムービーを記録モードに変更
if( m_bMoviePlay || m_bMovieRec ) {
// 撮り直し可能?
if( m_hedMovie.Control & 0x80 ) {
if( hdr2.MovieOffset && hdr2.MovieStep ) {
if( m_bMoviePlay ) {
// 再生中
// ステートのステップ数が記録より進んでたらダメ
if( hdr2.MovieStep > m_hedMovie.MovieStep )
return FALSE;
} else {
// 記録中
// ステートのステップ数が現在より進んでたらダメ
if( hdr2.MovieStep > m_MovieStep )
return FALSE;
}
//DEBUGOUT( "LD STEP=%d POS=%d\n", hdr2.MovieStep, hdr2.MovieOffset );
m_bMoviePlay = FALSE;
m_bMovieRec = TRUE;
m_MovieStep = hdr2.MovieStep;
m_hedMovie.RecordTimes++; // 撮り直し回数+1
if( ::fseek( m_fpMovie, hdr2.MovieOffset, SEEK_SET ) ) {
//DEBUGOUT( "MOVIE:STATE LOAD SEEK 失敗\n" );
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
} else {
return FALSE;
}
} else {
return FALSE;
}
}
}
bHeader = TRUE;
continue;
}
}
if( !bHeader ) {
// 未対応形式です
throw CApp::GetErrorString( IDS_ERROR_UNSUPPORTFORMAT );
}
//DEBUGOUT( "HEADER ID=%8s\n", hdr.ID );
type = -1;
if( ::memcmp( hdr.ID, "REG DATA", sizeof(hdr.ID) ) == 0 )
type = 0;
if( ::memcmp( hdr.ID, "RAM DATA", sizeof(hdr.ID) ) == 0 )
type = 1;
if( ::memcmp( hdr.ID, "MMU DATA", sizeof(hdr.ID) ) == 0 )
type = 2;
if( ::memcmp( hdr.ID, "MMC DATA", sizeof(hdr.ID) ) == 0 )
type = 3;
if( ::memcmp( hdr.ID, "CTR DATA", sizeof(hdr.ID) ) == 0 )
type = 4;
if( ::memcmp( hdr.ID, "SND DATA", sizeof(hdr.ID) ) == 0 )
type = 5;
if( rom->GetMapperNo() == 20 ) {
if( ::memcmp( hdr.ID, "DISKDATA", sizeof(hdr.ID) ) == 0 )
type = 6;
}
if( ::memcmp( hdr.ID, "EXCTRDAT", sizeof(hdr.ID) ) == 0 )
type = 7;
if( type == -1 ) {
//DEBUGOUT( "UNKNOWN HEADER ID=%8s\n", hdr.ID );
break;
}
switch( type ) {
case 0:
// REGISTER STATE
{
if( hdr.BlockVersion < 0x0200 ) {
REGSTAT_O reg;
if( ::fread( &reg, sizeof(REGSTAT_O), 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
// LOAD CPU STATE
R6502 R;
R.PC = reg.cpureg.cpu.PC;
R.A = reg.cpureg.cpu.A;
R.X = reg.cpureg.cpu.X;
R.Y = reg.cpureg.cpu.Y;
R.S = reg.cpureg.cpu.S;
R.P = reg.cpureg.cpu.P;
R.INT_pending = reg.cpureg.cpu.I;
cpu->SetContext( R );
// FrameIRQ = reg.cpureg.cpu.FrameIRQ;
if( hdr.BlockVersion < 0x0110 ) {
emul_cycles = 0;
base_cycles = reg.cpureg.cpu.mod_cycles;
} else if( hdr.BlockVersion == 0x0110 ) {
// FrameIRQ_cycles = reg.cpureg.cpu.mod_cycles;
emul_cycles = reg.cpureg.cpu.emul_cycles;
base_cycles = reg.cpureg.cpu.base_cycles;
}
// LOAD PPU STATE
PPUREG[0] = reg.ppureg.ppu.reg0;
PPUREG[1] = reg.ppureg.ppu.reg1;
PPUREG[2] = reg.ppureg.ppu.reg2;
PPUREG[3] = reg.ppureg.ppu.reg3;
PPU7_Temp = reg.ppureg.ppu.reg7;
loopy_t = reg.ppureg.ppu.loopy_t;
loopy_v = reg.ppureg.ppu.loopy_v;
loopy_x = reg.ppureg.ppu.loopy_x;
PPU56Toggle = reg.ppureg.ppu.toggle56;
} else {
REGSTAT reg;
if( ::fread( &reg, sizeof(REGSTAT), 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
// LOAD CPU STATE
R6502 R;
R.PC = reg.cpureg.cpu.PC;
R.A = reg.cpureg.cpu.A;
R.X = reg.cpureg.cpu.X;
R.Y = reg.cpureg.cpu.Y;
R.S = reg.cpureg.cpu.S;
R.P = reg.cpureg.cpu.P;
R.INT_pending = reg.cpureg.cpu.I;
cpu->SetContext( R );
if( hdr.BlockVersion == 0x0200 ) {
// FrameIRQ = reg.cpureg.cpu.FrameIRQ;
// bFrameIRQ_occur = (reg.cpureg.cpu.FrameIRQ_occur!=0)?TRUE:FALSE;
// FrameIRQ_cycles = reg.cpureg.cpu.FrameIRQ_cycles;
} else {
apu->SetFrameIRQ( reg.cpureg.cpu.FrameIRQ_cycles,
reg.cpureg.cpu.FrameIRQ_count,
reg.cpureg.cpu.FrameIRQ_type,
reg.cpureg.cpu.FrameIRQ,
reg.cpureg.cpu.FrameIRQ_occur );
}
emul_cycles = reg.cpureg.cpu.emul_cycles;
base_cycles = reg.cpureg.cpu.base_cycles;
cpu->SetDmaCycles( (INT)reg.cpureg.cpu.DMA_cycles );
// LOAD PPU STATE
PPUREG[0] = reg.ppureg.ppu.reg0;
PPUREG[1] = reg.ppureg.ppu.reg1;
PPUREG[2] = reg.ppureg.ppu.reg2;
PPUREG[3] = reg.ppureg.ppu.reg3;
PPU7_Temp = reg.ppureg.ppu.reg7;
loopy_t = reg.ppureg.ppu.loopy_t;
loopy_v = reg.ppureg.ppu.loopy_v;
loopy_x = reg.ppureg.ppu.loopy_x;
PPU56Toggle = reg.ppureg.ppu.toggle56;
}
// APU STATE
// キューを消す
apu->QueueClear();
// APUステートを保存するようにしたのでヤメ
// // DMCは止めないとまずい事が起こる
// for( i = 0x4010; i <= 0x4013; i++ ) {
// apu->Write( i, 0 );
// }
}
break;
case 1:
// RAM STATE
{
RAMSTAT ram;
if( ::fread( &ram, sizeof(RAMSTAT), 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
::memcpy( RAM, ram.RAM, sizeof(ram.RAM) );
::memcpy( BGPAL, ram.BGPAL, sizeof(ram.BGPAL) );
::memcpy( SPPAL, ram.SPPAL, sizeof(ram.SPPAL) );
::memcpy( SPRAM, ram.SPRAM, sizeof(ram.SPRAM) );
if( rom->IsSAVERAM() ) {
if( ::fread( WRAM, SAVERAM_SIZE, 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
}
}
break;
case 2:
// BANK STATE
{
MMUSTAT mmu;
if( ::fread( &mmu, sizeof(MMUSTAT), 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
if( hdr.BlockVersion == 0x100 ) {
// ちょっと前のバージョン
if( mmu.CPU_MEM_TYPE[3] == BANKTYPE_RAM
|| mmu.CPU_MEM_TYPE[3] == BANKTYPE_DRAM ) {
if( ::fread( CPU_MEM_BANK[3], 8*1024, 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
} else if( !rom->IsSAVERAM() ) {
SetPROM_8K_Bank( 3, mmu.CPU_MEM_PAGE[3] );
}
// バンク0〜3以外ロード
for( i = 4; i < 8; i++ ) {
CPU_MEM_TYPE[i] = mmu.CPU_MEM_TYPE[i];
CPU_MEM_PAGE[i] = mmu.CPU_MEM_PAGE[i];
if( CPU_MEM_TYPE[i] == BANKTYPE_ROM ) {
SetPROM_8K_Bank( i, CPU_MEM_PAGE[i] );
} else {
if( ::fread( CPU_MEM_BANK[i], 8*1024, 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
}
}
} else if( hdr.BlockVersion == 0x200 ) {
// 最新バージョン
// SRAMがあっても全部ロードしなおし
for( i = 3; i < 8; i++ ) {
CPU_MEM_TYPE[i] = mmu.CPU_MEM_TYPE[i];
CPU_MEM_PAGE[i] = mmu.CPU_MEM_PAGE[i];
if( CPU_MEM_TYPE[i] == BANKTYPE_ROM ) {
SetPROM_8K_Bank( i, CPU_MEM_PAGE[i] );
} else {
if( ::fread( CPU_MEM_BANK[i], 8*1024, 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
}
}
}
// VRAM
if( ::fread( VRAM, 4*1024, 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
// CRAM
for( i = 0; i < 8; i++ ) {
if( mmu.CRAM_USED[i] != 0 ) {
if( ::fread( &CRAM[0x1000*i], 4*1024, 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
}
}
// BANK
for( i = 0; i < 12; i++ ) {
if( mmu.PPU_MEM_TYPE[i] == BANKTYPE_VROM ) {
SetVROM_1K_Bank( i, mmu.PPU_MEM_PAGE[i] );
} else if( mmu.PPU_MEM_TYPE[i] == BANKTYPE_CRAM ) {
SetCRAM_1K_Bank( i, mmu.PPU_MEM_PAGE[i] );
} else if( mmu.PPU_MEM_TYPE[i] == BANKTYPE_VRAM ) {
SetVRAM_1K_Bank( i, mmu.PPU_MEM_PAGE[i] );
} else {
throw "Unknown bank types.";
}
}
}
break;
case 3:
// MMC STATE
{
MMCSTAT mmc;
if( ::fread( &mmc, sizeof(MMCSTAT), 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
mapper->LoadState( mmc.mmcdata );
}
break;
case 4:
// CTR STATE
{
CTRSTAT ctr;
if( ::fread( &ctr, sizeof(CTRSTAT), 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
pad->pad1bit = ctr.ctrreg.ctr.pad1bit;
pad->pad2bit = ctr.ctrreg.ctr.pad2bit;
pad->pad3bit = ctr.ctrreg.ctr.pad3bit;
pad->pad4bit = ctr.ctrreg.ctr.pad4bit;
pad->SetStrobe( (ctr.ctrreg.ctr.strobe!=0)?TRUE:FALSE );
}
break;
case 5:
// SND STATE
{
SNDSTAT snd;
if( ::fread( &snd, sizeof(SNDSTAT), 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
apu->LoadState( snd.snddata );
}
break;
// Disk Images
// Ver0.30以降
case 6:
{
DISKDATA ddata;
DWORD pos;
BYTE data;
LONG DiskSize = 16+65500*rom->GetDiskNo();
LPBYTE lpDisk = rom->GetPROM();
LPBYTE lpWrite = rom->GetDISK();
// 書き換えFLAG消去
::ZeroMemory( lpWrite, 16+65500*rom->GetDiskNo() );
if( ::fread( &ddata, sizeof(DISKDATA), 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
for( i = 0; i < ddata.DifferentSize; i++ ) {
if( ::fread( &pos, sizeof(DWORD), 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
data = (BYTE)(pos>>24);
pos &= 0x00FFFFFF;
if( pos >= 16 && pos < DiskSize ) {
lpDisk[pos] = data;
lpWrite[pos] = 0xFF;
}
}
}
break;
// EXCTR STATE
case 7:
{
EXCTRSTAT exctr;
if( ::fread( &exctr, sizeof(EXCTRSTAT), 1, fp ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
pad->SetSyncExData( exctr.data );
}
break;
}
}
return TRUE;
}
void NES::WriteState( FILE* fp )
{
INT i;
// HEADER
{
FILEHDR2 hdr;
ZEROMEMORY( &hdr, sizeof(FILEHDR2) );
::memcpy( hdr.ID, "VirtuaNES ST", sizeof(hdr.ID) );
hdr.BlockVersion = 0x0200;
if( rom->GetMapperNo() != 20 ) {
hdr.Ext0 = rom->GetPROM_CRC();
} else {
hdr.Ext0 = rom->GetGameID();
hdr.Ext1 = (WORD)rom->GetMakerID();
hdr.Ext2 = (WORD)rom->GetDiskNo();
}
// ムービー再生や記録中であればその位置を記録する
if( m_bMoviePlay || m_bMovieRec ) {
hdr.MovieStep = m_MovieStep;
hdr.MovieOffset = ::ftell( m_fpMovie );
//DEBUGOUT( "\nSV STEP=%d POS=%d\n", m_MovieStep, hdr.MovieOffset );
}
// Write File
if( ::fwrite( &hdr, sizeof(FILEHDR2), 1, fp ) != 1 )
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
BLOCKHDR hdr;
// REGISTER STATE
{
REGSTAT reg;
ZEROMEMORY( &hdr, sizeof(BLOCKHDR) );
ZEROMEMORY( &reg, sizeof(REGSTAT) );
// Create Header
::memcpy( hdr.ID, "REG DATA", sizeof(hdr.ID) );
hdr.BlockVersion = 0x0210;
hdr.BlockSize = sizeof(REGSTAT);
// SAVE CPU STATE
R6502 R;
cpu->GetContext( R );
reg.cpureg.cpu.PC = R.PC;
reg.cpureg.cpu.A = R.A;
reg.cpureg.cpu.X = R.X;
reg.cpureg.cpu.Y = R.Y;
reg.cpureg.cpu.S = R.S;
reg.cpureg.cpu.P = R.P;
reg.cpureg.cpu.I = R.INT_pending;
INT cycles;
apu->GetFrameIRQ( cycles,
reg.cpureg.cpu.FrameIRQ_count,
reg.cpureg.cpu.FrameIRQ_type,
reg.cpureg.cpu.FrameIRQ,
reg.cpureg.cpu.FrameIRQ_occur );
reg.cpureg.cpu.FrameIRQ_cycles = (LONG)cycles; // 参照がINTな為
reg.cpureg.cpu.DMA_cycles = (LONG)cpu->GetDmaCycles();
reg.cpureg.cpu.emul_cycles = emul_cycles;
reg.cpureg.cpu.base_cycles = base_cycles;
// SAVE PPU STATE
reg.ppureg.ppu.reg0 = PPUREG[0];
reg.ppureg.ppu.reg1 = PPUREG[1];
reg.ppureg.ppu.reg2 = PPUREG[2];
reg.ppureg.ppu.reg3 = PPUREG[3];
reg.ppureg.ppu.reg7 = PPU7_Temp;
reg.ppureg.ppu.loopy_t = loopy_t;
reg.ppureg.ppu.loopy_v = loopy_v;
reg.ppureg.ppu.loopy_x = loopy_x;
reg.ppureg.ppu.toggle56 = PPU56Toggle;
// Write File
if( ::fwrite( &hdr, sizeof(BLOCKHDR), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
if( ::fwrite( &reg, sizeof(REGSTAT), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
}
// RAM STATE
{
RAMSTAT ram;
DWORD size = 0;
::ZeroMemory( &hdr, sizeof(BLOCKHDR) );
::ZeroMemory( &ram, sizeof(RAMSTAT) );
// SAVE RAM STATE
::memcpy( ram.RAM, RAM, sizeof(ram.RAM) );
::memcpy( ram.BGPAL, BGPAL, sizeof(ram.BGPAL) );
::memcpy( ram.SPPAL, SPPAL, sizeof(ram.SPPAL) );
::memcpy( ram.SPRAM, SPRAM, sizeof(ram.SPRAM) );
// S-RAM STATE(使用/未使用に関わらず存在すればセーブする)
if( rom->IsSAVERAM() ) {
size = SAVERAM_SIZE;
}
// Create Header
::memcpy( hdr.ID, "RAM DATA", sizeof(hdr.ID) );
hdr.BlockVersion = 0x0100;
hdr.BlockSize = size+sizeof(RAMSTAT);
// Write File
if( ::fwrite( &hdr, sizeof(BLOCKHDR), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
if( ::fwrite( &ram, sizeof(RAMSTAT), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
if( rom->IsSAVERAM() ) {
if( ::fwrite( WRAM, SAVERAM_SIZE, 1, fp ) != 1 )
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
}
// BANK STATE
{
MMUSTAT mmu;
DWORD size;
::ZeroMemory( &hdr, sizeof(BLOCKHDR) );
::ZeroMemory( &mmu, sizeof(MMUSTAT) );
size = 0;
// SAVE CPU MEMORY BANK DATA
// BANK0,1,2はバンクセーブに関係なし
// VirtuaNES0.30から
// バンクはSRAM使用に関わらずセーブ
for( i = 3; i < 8; i++ ) {
mmu.CPU_MEM_TYPE[i] = CPU_MEM_TYPE[i];
mmu.CPU_MEM_PAGE[i] = CPU_MEM_PAGE[i];
if( CPU_MEM_TYPE[i] == BANKTYPE_RAM
|| CPU_MEM_TYPE[i] == BANKTYPE_DRAM ) {
size += 8*1024; // 8K BANK
}
}
// SAVE VRAM MEMORY DATA
for( i = 0; i < 12; i++ ) {
mmu.PPU_MEM_TYPE[i] = PPU_MEM_TYPE[i];
mmu.PPU_MEM_PAGE[i] = PPU_MEM_PAGE[i];
}
size += 4*1024; // 1K BANK x 4 (VRAM)
for( i = 0; i < 8; i++ ) {
mmu.CRAM_USED[i] = CRAM_USED[i];
if( CRAM_USED[i] != 0 ) {
size += 4*1024; // 4K BANK
}
}
// Create Header
::memcpy( hdr.ID, "MMU DATA", sizeof(hdr.ID) );
hdr.BlockVersion = 0x0200;
hdr.BlockSize = size+sizeof(MMUSTAT);
// Write File
if( ::fwrite( &hdr, sizeof(BLOCKHDR), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
if( ::fwrite( &mmu, sizeof(MMUSTAT), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
// WRITE CPU RAM MEMORY BANK
for( i = 3; i < 8; i++ ) {
if( mmu.CPU_MEM_TYPE[i] != BANKTYPE_ROM ) {
if( ::fwrite( CPU_MEM_BANK[i], 8*1024, 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
}
}
// WRITE VRAM MEMORY(常に4K分すべて書き込む)
if( ::fwrite( VRAM, 4*1024, 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
// WRITE CRAM MEMORY
for( i = 0; i < 8; i++ ) {
if( CRAM_USED[i] != 0 ) {
if( ::fwrite( &CRAM[0x1000*i], 4*1024, 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
}
}
}
// MMC STATE
{
MMCSTAT mmc;
::ZeroMemory( &hdr, sizeof(BLOCKHDR) );
::ZeroMemory( &mmc, sizeof(MMCSTAT) );
// Create Header
::memcpy( hdr.ID, "MMC DATA", sizeof(hdr.ID) );
hdr.BlockVersion = 0x0100;
hdr.BlockSize = sizeof(MMCSTAT);
if( mapper->IsStateSave() ) {
mapper->SaveState( mmc.mmcdata );
// Write File
if( ::fwrite( &hdr, sizeof(BLOCKHDR), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
if( ::fwrite( &mmc, sizeof(MMCSTAT), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
}
}
// CONTROLLER STATE
{
CTRSTAT ctr;
::ZeroMemory( &hdr, sizeof(BLOCKHDR) );
::ZeroMemory( &ctr, sizeof(CTRSTAT) );
// Create Header
::memcpy( hdr.ID, "CTR DATA", sizeof(hdr.ID) );
hdr.BlockVersion = 0x0100;
hdr.BlockSize = sizeof(CTRSTAT);
ctr.ctrreg.ctr.pad1bit = pad->pad1bit;
ctr.ctrreg.ctr.pad2bit = pad->pad2bit;
ctr.ctrreg.ctr.pad3bit = pad->pad3bit;
ctr.ctrreg.ctr.pad4bit = pad->pad4bit;
ctr.ctrreg.ctr.strobe = pad->GetStrobe()?0xFF:0;
//DEBUGOUT( "SV pad1bit=%08X Strobe=%d\n", pad->pad1bit, pad->GetStrobe()?1:0 );
if( ::fwrite( &hdr, sizeof(BLOCKHDR), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
if( ::fwrite( &ctr, sizeof(CTRSTAT), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
}
// SND STATE
{
SNDSTAT snd;
::ZeroMemory( &hdr, sizeof(BLOCKHDR) );
::ZeroMemory( &snd, sizeof(SNDSTAT) );
// Create Header
::memcpy( hdr.ID, "SND DATA", sizeof(hdr.ID) );
hdr.BlockVersion = 0x0100;
hdr.BlockSize = sizeof(SNDSTAT);
apu->SaveState( snd.snddata );
if( ::fwrite( &hdr, sizeof(BLOCKHDR), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
if( ::fwrite( &snd, sizeof(SNDSTAT), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
}
// DISKIMAGE STATE
if( rom->GetMapperNo() == 20 )
{
DISKDATA dsk;
LPBYTE lpDisk = rom->GetPROM();
LPBYTE lpWrite = rom->GetDISK();
LONG DiskSize = 16+65500*rom->GetDiskNo();
DWORD data;
::ZeroMemory( &hdr, sizeof(BLOCKHDR) );
::ZeroMemory( &dsk, sizeof(DISKDATA) );
// 相違数をカウント
for( i = 16; i < DiskSize; i++ ) {
if( lpWrite[i] )
dsk.DifferentSize++;
}
::memcpy( hdr.ID, "DISKDATA", sizeof(hdr.ID) );
hdr.BlockVersion = 0x0210;
hdr.BlockSize = 0;
// Write File
if( ::fwrite( &hdr, sizeof(BLOCKHDR), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
// Write File
if( ::fwrite( &dsk, sizeof(DISKDATA), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
for( i = 16; i < DiskSize; i++ ) {
if( lpWrite[i] ) {
data = i & 0x00FFFFFF;
data |= ((DWORD)lpDisk[i]&0xFF)<<24;
// Write File
if( ::fwrite( &data, sizeof(DWORD), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
}
}
}
// EXCTR STATE
if( pad->GetExController() ) {
EXCTRSTAT exctr;
::ZeroMemory( &hdr, sizeof(BLOCKHDR) );
::ZeroMemory( &exctr, sizeof(EXCTRSTAT) );
// Create Header
::memcpy( hdr.ID, "EXCTRDAT", sizeof(hdr.ID) );
hdr.BlockVersion = 0x0100;
hdr.BlockSize = sizeof(EXCTRSTAT);
// Some excontrollers will default 0
exctr.data = pad->GetSyncExData();
if( ::fwrite( &hdr, sizeof(BLOCKHDR), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
if( ::fwrite( &exctr, sizeof(EXCTRSTAT), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
}
}
INT NES::GetDiskNo()
{
return rom->GetDiskNo();
}
void NES::SoundSetup()
{
apu->SoundSetup();
}
void NES::Command( NESCOMMAND cmd )
{
CommandParam( cmd, 0 );
}
BOOL NES::CommandParam( NESCOMMAND cmd, INT param )
{
switch( cmd ) {
case NESCMD_NONE:
break;
case NESCMD_DISK_THROTTLE_ON:
if( Config.emulator.bDiskThrottle ) {
m_bDiskThrottle = TRUE;
}
break;
case NESCMD_DISK_THROTTLE_OFF:
m_bDiskThrottle = FALSE;
break;
case NESCMD_DISK_EJECT:
mapper->ExCmdWrite( Mapper::EXCMDWR_DISKEJECT, 0 );
m_CommandRequest = (INT)cmd;
break;
case NESCMD_DISK_0A:
if( rom->GetDiskNo() > 0 ) {
mapper->ExCmdWrite( Mapper::EXCMDWR_DISKINSERT, 0 );
m_CommandRequest = (INT)cmd;
}
break;
case NESCMD_DISK_0B:
if( rom->GetDiskNo() > 1 ) {
mapper->ExCmdWrite( Mapper::EXCMDWR_DISKINSERT, 1 );
m_CommandRequest = (INT)cmd;
}
break;
case NESCMD_DISK_1A:
if( rom->GetDiskNo() > 2 ) {
mapper->ExCmdWrite( Mapper::EXCMDWR_DISKINSERT, 2 );
m_CommandRequest = (INT)cmd;
}
break;
case NESCMD_DISK_1B:
if( rom->GetDiskNo() > 3 ) {
mapper->ExCmdWrite( Mapper::EXCMDWR_DISKINSERT, 3 );
m_CommandRequest = (INT)cmd;
}
break;
case NESCMD_DISK_2A:
if( rom->GetDiskNo() > 4 ) {
mapper->ExCmdWrite( Mapper::EXCMDWR_DISKINSERT, 4 );
m_CommandRequest = (INT)cmd;
}
break;
case NESCMD_DISK_2B:
if( rom->GetDiskNo() > 5 ) {
mapper->ExCmdWrite( Mapper::EXCMDWR_DISKINSERT, 5 );
m_CommandRequest = (INT)cmd;
}
break;
case NESCMD_DISK_3A:
if( rom->GetDiskNo() > 6 ) {
mapper->ExCmdWrite( Mapper::EXCMDWR_DISKINSERT, 6 );
m_CommandRequest = (INT)cmd;
}
break;
case NESCMD_DISK_3B:
if( rom->GetDiskNo() > 7 ) {
mapper->ExCmdWrite( Mapper::EXCMDWR_DISKINSERT, 7 );
m_CommandRequest = (INT)cmd;
}
break;
case NESCMD_HWRESET:
Reset();
m_CommandRequest = (INT)cmd;
break;
case NESCMD_SWRESET:
SoftReset();
m_CommandRequest = (INT)cmd;
break;
case NESCMD_EXCONTROLLER:
pad->SetExController( param&0xFF );
m_CommandRequest = 0x0100|(param&0xFF);
break;
case NESCMD_SOUND_MUTE:
return apu->SetChannelMute( (BOOL)param ); // リターン値は変更後のミュート状態
}
return TRUE;
}
BOOL NES::Snapshot()
{
FILE* fp = NULL;
try {
SYSTEMTIME now;
::GetLocalTime( &now );
CHAR name[_MAX_PATH];
if( !Config.emulator.bPNGsnapshot ) {
sprintf( name, "%s %04d%02d%02d%02d%02d%02d%01d.bmp", rom->GetRomName(),
now.wYear, now.wMonth, now.wDay, now.wHour, now.wMinute, now.wSecond, now.wMilliseconds/100 );
} else {
sprintf( name, "%s %04d%02d%02d%02d%02d%02d%01d.png", rom->GetRomName(),
now.wYear, now.wMonth, now.wDay, now.wHour, now.wMinute, now.wSecond, now.wMilliseconds/100 );
}
string pathstr, tempstr;
if( Config.path.bSnapshotPath ) {
pathstr = CPathlib::CreatePath( CApp::GetModulePath(), Config.path.szSnapshotPath );
::CreateDirectory( pathstr.c_str(), NULL );
} else {
pathstr = rom->GetRomPath();
}
tempstr = CPathlib::MakePath( pathstr.c_str(), name );
DEBUGOUT( "Snapshot: %s\n", tempstr.c_str() );
if( !Config.emulator.bPNGsnapshot ) {
if( !(fp = ::fopen( tempstr.c_str(), "wb" )) ) {
// xxx ファイルを開けません
LPCSTR szErrStr = CApp::GetErrorString( IDS_ERROR_OPEN );
sprintf( szErrorString, szErrStr, tempstr.c_str() );
throw szErrorString;
}
LPBYTE lpScn = ppu->GetScreenPtr();
BITMAPFILEHEADER bfh;
BITMAPINFOHEADER bih;
RGBQUAD rgb[256];
ZEROMEMORY( &bfh, sizeof(bfh) );
ZEROMEMORY( &bih, sizeof(bih) );
ZEROMEMORY( rgb, sizeof(rgb) );
bfh.bfType = 0x4D42; // 'BM'
bfh.bfOffBits = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+sizeof(RGBQUAD)*256;
bfh.bfSize = bfh.bfOffBits+256*240;
bih.biSize = sizeof(bih);
bih.biWidth = 256;
bih.biHeight = 240;
bih.biPlanes = 1;
bih.biBitCount = 8;
bih.biCompression = BI_RGB;
bih.biSizeImage = 0;
bih.biXPelsPerMeter = 0;
bih.biYPelsPerMeter = 0;
bih.biClrUsed = 256;
bih.biClrImportant = 0;
DirectDraw.GetPaletteData( rgb );
if( ::fwrite( &bfh, sizeof(bfh), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
if( ::fwrite( &bih, sizeof(bih), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
if( ::fwrite( &rgb, sizeof(rgb), 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
lpScn += 8;
for( INT i = 239; i >= 0; i-- ) {
if( ::fwrite( &lpScn[(256+16)*i], 256, 1, fp ) != 1 ) {
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
}
FCLOSE( fp );
} else {
LPBYTE lpScn = ppu->GetScreenPtr();
RGBQUAD rgb[256];
ZEROMEMORY( rgb, sizeof(rgb) );
DirectDraw.GetPaletteData( rgb );
PNGWRITE png;
png.Write( tempstr.c_str(), 256, 240, rgb, lpScn+8, CDirectDraw::RENDER_WIDTH );
}
} catch( CHAR* str ) {
DEBUGOUT( "Snapshot error.\n" );
FCLOSE( fp );
throw str;
#ifndef _DEBUG
} catch(...) {
DEBUGOUT( "Snapshot error.\n" );
FCLOSE( fp );
// 不明なエラーが発生しました
throw CApp::GetErrorString( IDS_ERROR_UNKNOWN );
#endif
}
return TRUE;
}
INT NES::IsMovieFile( const char* fname, ROM* rom )
{
FILE* fp = NULL;
MOVIEFILEHDR header;
if( !(fp = ::fopen( fname, "rb" )) )
return -1;
if( ::fread( &header, sizeof(header), 1, fp ) != 1 ) {
FCLOSE( fp );
return -1;
}
FCLOSE( fp );
if( ::memcmp( header.ID, "VirtuaNES MV", sizeof(header.ID) ) == 0 ) {
if( header.BlockVersion < 0x0300 ) {
return IDS_ERROR_ILLEGALMOVIEOLD;
} else
if( header.BlockVersion >= 0x0300 ) {
if( rom->GetMapperNo() != 20 ) {
// FDS以外
if( header.Ext0 != rom->GetPROM_CRC() ) {
return IDS_ERROR_ILLEGALMOVIECRC; // 違うじゃん
}
} else {
// FDS
if( header.Ext0 != rom->GetGameID() ||
header.Ext1 != (WORD)rom->GetMakerID() ||
header.Ext2 != (WORD)rom->GetDiskNo() )
return IDS_ERROR_ILLEGALMOVIECRC; // 違うじゃん
}
if( header.RecordVersion != VIRTUANES_VERSION ) {
return IDS_ERROR_ILLEGALMOVIEVER;
}
return 0;
}
}
return -1;
}
BOOL NES::MoviePlay( const char* fname )
{
if( rom->IsNSF() )
return FALSE;
if( IsMoviePlay() || IsMovieRec() ) {
MovieStop();
}
DEBUGOUT( "NES::MoviePlay\n" );
try {
if( !(m_fpMovie = ::fopen( fname, "rb+" )) ) {
DEBUGOUT( "Movie play error. File not found.\n" );
// ファイル無いです
return FALSE;
}
// 読み込み
if( ::fread( &m_hedMovie, sizeof(m_hedMovie), 1, m_fpMovie ) != 1 ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
if( ::memcmp( m_hedMovie.ID, "VirtuaNES MV", sizeof(m_hedMovie.ID) ) == 0 ) {
m_MovieVersion = m_hedMovie.BlockVersion;
// if( m_hedMovie.BlockVersion == 0x0300 ) {
if( m_hedMovie.BlockVersion >= 0x0300 ) {
if( m_hedMovie.CRC != 0 ) {
if( CRC::Crc( sizeof(m_hedMovie)-sizeof(DWORD), (LPBYTE)&m_hedMovie ) != m_hedMovie.CRC ) {
FCLOSE( m_fpMovie );
return FALSE; // 違うじゃん
}
}
// おっけ〜
} else {
// 先生!古いのでダメっす。
FCLOSE( m_fpMovie );
return FALSE;
}
}
// ゲーム固有オプション
m_saveRenderMethod = (INT)GetRenderMethod();
m_saveIrqType = GetIrqType();
m_saveFrameIRQ = GetFrameIRQmode();
m_saveVideoMode = GetVideoMode();
SetRenderMethod( (RENDERMETHOD)m_hedMovie.RenderMethod );
SetIrqType( (INT)m_hedMovie.IRQtype );
SetFrameIRQmode( (m_hedMovie.FrameIRQ!=0)?TRUE:FALSE );
SetVideoMode( (m_hedMovie.VideoMode!=0)?TRUE:FALSE );
LONG MovieOffset;
m_MovieControl = m_hedMovie.Control;
m_MovieStepTotal = m_hedMovie.MovieStep;
MovieOffset = m_hedMovie.MovieOffset;
if( m_hedMovie.BlockVersion < 0x0400 ) {
// ステート読み込み
ReadState( m_fpMovie );
} else if( !(m_MovieControl & 0x40) ) {
// ステート読み込み
ReadState( m_fpMovie );
} else {
Reset();
}
if( ::fseek( m_fpMovie, MovieOffset, SEEK_SET ) ) {
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
// ムービーが記録されていない?
if( m_MovieStepTotal == 0 ) {
MovieStop();
return FALSE;
}
m_bMoviePlay = TRUE;
m_MovieStep = 0;
} catch( CHAR* str ) {
DEBUGOUT( "Movie play error. %s\n", str );
FCLOSE( m_fpMovie );
throw str;
#ifndef _DEBUG
} catch(...) {
DEBUGOUT( "Movie play error.\n" );
FCLOSE( m_fpMovie );
// 不明なエラーが発生しました
throw CApp::GetErrorString( IDS_ERROR_UNKNOWN );
#endif
}
return TRUE;
}
BOOL NES::MovieRec( const char* fname )
{
if( rom->IsNSF() )
return FALSE;
if( IsMoviePlay() || IsMovieRec() ) {
MovieStop();
}
DEBUGOUT( "NES::MovieRec\n" );
try {
if( !(m_fpMovie = ::fopen( fname, "wb" )) ) {
// xxx ファイルを開けません
LPCSTR szErrStr = CApp::GetErrorString( IDS_ERROR_OPEN );
sprintf( szErrorString, szErrStr, fname );
throw szErrorString;
}
::ZeroMemory( &m_hedMovie, sizeof(m_hedMovie) );
::memcpy( m_hedMovie.ID, "VirtuaNES MV", sizeof(m_hedMovie.ID) );
// m_hedMovie.BlockVersion = 0x0300;
m_hedMovie.BlockVersion = 0x0400;
m_hedMovie.RecordVersion = VIRTUANES_VERSION;
m_hedMovie.StateStOffset = sizeof(m_hedMovie);
m_hedMovie.Control |= Config.movie.bUsePlayer[0]?0x01:0x00;
m_hedMovie.Control |= Config.movie.bUsePlayer[1]?0x02:0x00;
m_hedMovie.Control |= Config.movie.bUsePlayer[2]?0x04:0x00;
m_hedMovie.Control |= Config.movie.bUsePlayer[3]?0x08:0x00;
m_hedMovie.Control |= Config.movie.bResetRec?0x40:0x00;
m_hedMovie.Control |= Config.movie.bRerecord?0x80:0x00;
m_MovieControl = m_hedMovie.Control;
// ゲーム固有オプション
m_hedMovie.RenderMethod = (BYTE)GetRenderMethod();
m_hedMovie.IRQtype = (BYTE)GetIrqType();
m_hedMovie.FrameIRQ = GetFrameIRQmode()?0xFF:0;
m_hedMovie.VideoMode = GetVideoMode()?0xFF:0;
// CRC,ID値を書き込む(誤動作防止用)
if( rom->GetMapperNo() != 20 ) {
// FDS以外
m_hedMovie.Ext0 = rom->GetPROM_CRC();
} else {
// FDS
m_hedMovie.Ext0 = rom->GetGameID();
m_hedMovie.Ext1 = (WORD)rom->GetMakerID();
m_hedMovie.Ext2 = (WORD)rom->GetDiskNo();
}
// ダミー書き込み
if( ::fwrite( &m_hedMovie, sizeof(m_hedMovie), 1, m_fpMovie ) != 1 ) {
FCLOSE( m_fpMovie );
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
if( Config.movie.bResetRec ) {
Reset(); // ハードウェアリセットからの記録開始
} else {
// ステート書き込み
WriteState( m_fpMovie );
}
m_hedMovie.MovieOffset = ::ftell( m_fpMovie );
m_bMovieRec = TRUE;
m_MovieStep = m_MovieStepTotal = 0;
// m_MovieVersion = 0x0300;
m_MovieVersion = 0x0400;
} catch( CHAR* str ) {
DEBUGOUT( "Movie record error. %s\n", str );
FCLOSE( m_fpMovie );
throw str;
#ifndef _DEBUG
} catch(...) {
DEBUGOUT( "Movie record error.\n" );
FCLOSE( m_fpMovie );
// 不明なエラーが発生しました
throw CApp::GetErrorString( IDS_ERROR_UNKNOWN );
#endif
}
return TRUE;
}
BOOL NES::MovieRecAppend( const char* fname )
{
if( rom->IsNSF() )
return FALSE;
// 記録中は意味が無いぞ
if( IsMovieRec() )
return FALSE;
if( IsMoviePlay() ) {
MovieStop();
}
DEBUGOUT( "NES::MovieAppendRec\n" );
try {
if( !(m_fpMovie = ::fopen( fname, "rb" )) ) {
// ファイルが無いとき
if( !(m_fpMovie = ::fopen( fname, "wb" )) ) {
// xxx ファイルを開けません
LPCSTR szErrStr = CApp::GetErrorString( IDS_ERROR_OPEN );
sprintf( szErrorString, szErrStr, fname );
throw szErrorString;
}
::ZeroMemory( &m_hedMovie, sizeof(m_hedMovie) );
::memcpy( m_hedMovie.ID, "VirtuaNES MV", sizeof(m_hedMovie.ID) );
// m_hedMovie.BlockVersion = 0x0300;
m_hedMovie.BlockVersion = 0x0400;
m_hedMovie.RecordVersion = VIRTUANES_VERSION;
m_hedMovie.StateStOffset = sizeof(m_hedMovie);
m_hedMovie.Control |= Config.movie.bUsePlayer[0]?0x01:0x00;
m_hedMovie.Control |= Config.movie.bUsePlayer[1]?0x02:0x00;
m_hedMovie.Control |= Config.movie.bUsePlayer[2]?0x04:0x00;
m_hedMovie.Control |= Config.movie.bUsePlayer[3]?0x08:0x00;
m_hedMovie.Control |= Config.movie.bRerecord?0x80:0x00;
m_hedMovie.Control |= Config.movie.bResetRec?0x40:0x00;
m_MovieControl = m_hedMovie.Control;
// ゲーム固有オプション
m_hedMovie.RenderMethod = (BYTE)GetRenderMethod();
m_hedMovie.IRQtype = (BYTE)GetIrqType();
m_hedMovie.FrameIRQ = GetFrameIRQmode()?0xFF:0;
m_hedMovie.VideoMode = GetVideoMode()?0xFF:0;
// ダミー書き込み
if( ::fwrite( &m_hedMovie, sizeof(m_hedMovie), 1, m_fpMovie ) != 1 ) {
FCLOSE( m_fpMovie );
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
if( Config.movie.bResetRec ) {
Reset(); // ハードウェアリセットからの記録開始
} else {
// ステート書き込み
WriteState( m_fpMovie );
}
m_hedMovie.MovieOffset = ::ftell( m_fpMovie );
m_MovieStep = m_MovieStepTotal = 0;
// m_MovieVersion = 0x0300;
m_MovieVersion = 0x0400;
} else {
if( !(m_fpMovie = ::fopen( fname, "rb+" )) ) {
// xxx ファイルを開けません
LPCSTR szErrStr = CApp::GetErrorString( IDS_ERROR_OPEN );
sprintf( szErrorString, szErrStr, fname );
throw szErrorString;
}
// 読み込み
if( ::fseek( m_fpMovie, 0, SEEK_SET ) ) {
FCLOSE( m_fpMovie );
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
if( ::fread( &m_hedMovie, sizeof(m_hedMovie), 1, m_fpMovie ) != 1 ) {
FCLOSE( m_fpMovie );
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
if( ::memcmp( m_hedMovie.ID, "VirtuaNES MV", sizeof(m_hedMovie.ID) ) != 0 ) {
FCLOSE( m_fpMovie );
return FALSE;
}
// 古いバージョンは捨て
if( m_hedMovie.BlockVersion < 0x0300 ) {
FCLOSE( m_fpMovie );
return FALSE;
}
m_MovieControl = m_hedMovie.Control;
m_MovieStep = m_MovieStepTotal = m_hedMovie.MovieStep;
// m_MovieVersion = 0x0300;
m_MovieVersion = 0x0400;
if( ::fseek( m_fpMovie, m_hedMovie.StateEdOffset, SEEK_SET ) ) {
FCLOSE( m_fpMovie );
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
if( !ReadState( m_fpMovie ) ) {
FCLOSE( m_fpMovie );
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
if( ::fseek( m_fpMovie, m_hedMovie.StateEdOffset, SEEK_SET ) ) {
FCLOSE( m_fpMovie );
// ファイルの読み込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_READ );
}
}
m_bMovieRec = TRUE;
} catch( CHAR* str ) {
DEBUGOUT( "Movie record error. %s\n", str );
FCLOSE( m_fpMovie );
throw str;
#ifndef _DEBUG
} catch(...) {
DEBUGOUT( "Movie record error.\n" );
FCLOSE( m_fpMovie );
// 不明なエラーが発生しました
throw CApp::GetErrorString( IDS_ERROR_UNKNOWN );
#endif
}
return TRUE;
}
BOOL NES::MovieStop()
{
if( !m_fpMovie && !(m_bMoviePlay||m_bMovieRec) )
return FALSE;
DEBUGOUT( "NES::MovieStop\n" );
DirectDraw.SetMessageString( "Movie stop." );
if( m_bMovieRec ) {
m_hedMovie.MovieStep = m_MovieStep;
m_hedMovie.StateEdOffset = ::ftell( m_fpMovie );
WriteState( m_fpMovie );
// // 撮り直し禁止の場合は追記不可能
// if( m_MovieControl & 0x80 ) {
// } else {
// m_hedMovie.StateEdOffset = 0;
// }
if( ::fseek( m_fpMovie, 0, SEEK_SET ) ) {
FCLOSE( m_fpMovie );
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
// CRC 書き込み
m_hedMovie.CRC = CRC::Crc( sizeof(m_hedMovie)-sizeof(DWORD), (LPBYTE)&m_hedMovie );
// 最終的なヘッダ書き込み
if( ::fwrite( &m_hedMovie, sizeof(m_hedMovie), 1, m_fpMovie ) != 1 ) {
FCLOSE( m_fpMovie );
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
FCLOSE( m_fpMovie );
m_bMovieRec = FALSE;
}
if( m_bMoviePlay ) {
FCLOSE( m_fpMovie );
m_bMoviePlay = FALSE;
// ゲーム固有オプションを元に戻す
SetRenderMethod( (RENDERMETHOD)m_saveRenderMethod );
SetIrqType ( m_saveIrqType );
SetFrameIRQmode( m_saveFrameIRQ );
SetVideoMode ( m_saveVideoMode );
}
return TRUE;
}
void NES::GetMovieInfo( WORD& wRecVersion, WORD& wVersion, DWORD& dwRecordFrames, DWORD& dwRecordTimes )
{
wRecVersion = m_hedMovie.RecordVersion;
wVersion = m_hedMovie.BlockVersion;
dwRecordFrames = m_hedMovie.MovieStep;
dwRecordTimes = m_hedMovie.RecordTimes;
}
// 毎フレーム呼び出す
void NES::Movie()
{
if( !m_fpMovie && !(m_bMoviePlay||m_bMovieRec) ) {
m_CommandRequest = 0; // コレ入れないと死ぬ
return;
}
INT exctr = pad->GetExController();
BYTE Data;
WORD wData;
DWORD dwData;
if( m_bMovieRec ) {
// 最初から拡張コントローラが設定されていた場合
if( m_MovieStep == 0 ) {
if( exctr == PAD::EXCONTROLLER_ZAPPER
|| exctr == PAD::EXCONTROLLER_PADDLE
|| exctr == PAD::EXCONTROLLER_CRAZYCLIMBER
|| exctr == PAD::EXCONTROLLER_TOPRIDER
|| exctr == PAD::EXCONTROLLER_SPACESHADOWGUN
|| exctr == PAD::EXCONTROLLER_FAMILYTRAINER_A
|| exctr == PAD::EXCONTROLLER_FAMILYTRAINER_B
|| exctr == PAD::EXCONTROLLER_MAHJANG
|| exctr == PAD::EXCONTROLLER_EXCITINGBOXING
|| exctr == PAD::EXCONTROLLER_OEKAKIDS_TABLET ) {
// コマンドID
Data = 0xF0;
// 書き込み
if( ::fwrite( &Data, sizeof(Data), 1, m_fpMovie ) != 1 ) {
MovieStop();
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
// 種類
wData = (WORD)(0x0100|(pad->GetExController()&0x0FF));
// 書き込み
if( ::fwrite( &wData, sizeof(wData), 1, m_fpMovie ) != 1 ) {
MovieStop();
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
}
}
if( m_CommandRequest ) {
// コマンドID
Data = 0xF0;
// 書き込み
if( ::fwrite( &Data, sizeof(Data), 1, m_fpMovie ) != 1 ) {
MovieStop();
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
// コマンド
wData = (WORD)m_CommandRequest;
// 書き込み
if( ::fwrite( &wData, sizeof(wData), 1, m_fpMovie ) != 1 ) {
MovieStop();
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
}
m_CommandRequest = 0;
// 拡張コントローラ
if( exctr == PAD::EXCONTROLLER_ZAPPER
|| exctr == PAD::EXCONTROLLER_PADDLE
|| exctr == PAD::EXCONTROLLER_CRAZYCLIMBER
|| exctr == PAD::EXCONTROLLER_TOPRIDER
|| exctr == PAD::EXCONTROLLER_SPACESHADOWGUN
|| exctr == PAD::EXCONTROLLER_FAMILYTRAINER_A
|| exctr == PAD::EXCONTROLLER_FAMILYTRAINER_B
|| exctr == PAD::EXCONTROLLER_MAHJANG
|| exctr == PAD::EXCONTROLLER_EXCITINGBOXING
|| exctr == PAD::EXCONTROLLER_OEKAKIDS_TABLET ) {
// 拡張コントローラデータID
Data = 0xF3;
// 書き込み
if( ::fwrite( &Data, sizeof(Data), 1, m_fpMovie ) != 1 ) {
MovieStop();
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
// データ
dwData = pad->GetSyncExData();
// 書き込み
if( ::fwrite( &dwData, sizeof(dwData), 1, m_fpMovie ) != 1 ) {
MovieStop();
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
}
dwData = pad->GetSyncData();
for( INT i = 0; i < 4; i++ ) {
Data = (BYTE)(dwData>>(i*8));
if( m_MovieControl & (1<<i) ) {
// 書き込み
if( ::fwrite( &Data, sizeof(Data), 1, m_fpMovie ) != 1 ) {
MovieStop();
// ファイルの書き込みに失敗しました
throw CApp::GetErrorString( IDS_ERROR_WRITE );
}
}
}
m_MovieStep++;
}
if( m_bMoviePlay ) {
DWORD dwPadData = 0;
INT num = 0;
BYTE PadBuf[4];
PadBuf[0] = PadBuf[1] = PadBuf[2] = PadBuf[3] = 0;
// ムービー再生終了?
if( m_MovieStep >= m_MovieStepTotal ) {
if( !Config.movie.bLoopPlay ) {
MovieStop();
return;
} else {
// 一旦再生中じゃないって事にする
m_bMoviePlay = FALSE;
m_MovieStep = 0;
::fseek( m_fpMovie, m_hedMovie.StateStOffset, SEEK_SET );
// ステート読み込み
ReadState( m_fpMovie );
::fseek( m_fpMovie, m_hedMovie.MovieOffset, SEEK_SET );
// 再生中って事にする
m_bMoviePlay = TRUE;
}
}
do {
// 読み込み
if( ::fread( &Data, sizeof(Data), 1, m_fpMovie ) != 1 ) {
// 終了?
MovieStop();
return;
}
// コマンド?
if( (Data & 0xF0) == 0xF0 ) {
if( Data == 0xF0 ) {
// 読み込み
if( ::fread( &wData, sizeof(wData), 1, m_fpMovie ) != 1 ) {
// 終了?
MovieStop();
return;
}
if( wData < 0x0100 ) {
Command( (NESCOMMAND)((INT)wData) );
} else {
// 拡張コントローラ
CommandParam( NESCMD_EXCONTROLLER, ((INT)wData) & 0x00FF );
}
} else
if( Data == 0xF3 ) {
// 読み込み
if( ::fread( &dwData, sizeof(dwData), 1, m_fpMovie ) != 1 ) {
// 終了?
MovieStop();
return;
}
pad->SetSyncExData( dwData );
} else {
// データぶっ壊れてる?終了じゃ
MovieStop();
return;
}
} else {
// 未使用プレイヤー分すっ飛ばし
while( !(m_MovieControl & (1<<num)) && (num < 4) ) {
PadBuf[num] = 0;
num++;
}
PadBuf[num] = Data;
num++;
// 未使用プレイヤー分すっ飛ばし
while( !(m_MovieControl & (1<<num)) && (num < 4) ) {
PadBuf[num] = 0;
num++;
}
}
} while( num < 4 );
dwData = (((DWORD)PadBuf[3])<<24)|(((DWORD)PadBuf[2])<<16)|(((DWORD)PadBuf[1])<<8)|((DWORD)PadBuf[0]);
pad->SetSyncData( dwData );
// カウンタ増やす
m_MovieStep++;
}
m_CommandRequest = 0;
}
// For Cheat
void NES::CheatInitial()
{
m_CheatCode.clear();
}
BOOL NES::IsCheatCodeAdd()
{
BOOL bRet = m_bCheatCodeAdd;
m_bCheatCodeAdd = FALSE;
return bRet;
}
INT NES::GetCheatCodeNum()
{
return m_CheatCode.size();
}
BOOL NES::GetCheatCode( INT no, CHEATCODE& code )
{
if( m_CheatCode.size()-1 < no )
return FALSE;
code = m_CheatCode[no];
return TRUE;
}
void NES::SetCheatCodeFlag( INT no, BOOL bEnable )
{
if( m_CheatCode.size()-1 < no )
return;
if( bEnable ) {
m_CheatCode[no].enable |= CHEAT_ENABLE;
} else {
m_CheatCode[no].enable &= ~CHEAT_ENABLE;
}
}
void NES::SetCheatCodeAllFlag( BOOL bEnable, BOOL bKey )
{
for( INT i = 0; i < m_CheatCode.size(); i++ ) {
if( !bKey ) {
if( bEnable ) {
m_CheatCode[i].enable |= CHEAT_ENABLE;
} else {
m_CheatCode[i].enable &= ~CHEAT_ENABLE;
}
} else if( !(m_CheatCode[i].enable&CHEAT_KEYDISABLE) ) {
if( bEnable ) {
m_CheatCode[i].enable |= CHEAT_ENABLE;
} else {
m_CheatCode[i].enable &= ~CHEAT_ENABLE;
}
}
}
}
void NES::ReplaceCheatCode( INT no, CHEATCODE code )
{
if( m_CheatCode.size()-1 < no )
return;
m_CheatCode[no] = code;
}
void NES::AddCheatCode( CHEATCODE code )
{
m_CheatCode.push_back( code );
m_bCheatCodeAdd = TRUE;
}
void NES::DelCheatCode( INT no )
{
if( m_CheatCode.size()-1 < no )
return;
m_CheatCode.erase( m_CheatCode.begin() + no );
}
DWORD NES::CheatRead( INT length, WORD addr )
{
DWORD data = 0;
for( INT i = 0; i <= length; i++ ) {
data |= (DWORD)Read( addr+i )*(1<<(i*8));
}
return data;
}
void NES::CheatWrite( INT length, WORD addr, DWORD data )
{
for( INT i = 0; i <= length; i++ ) {
Write( (WORD)(addr+i), data&0xFF );
data >>= 8;
}
}
void NES::CheatCodeProcess()
{
for( vector<CHEATCODE>::iterator it = m_CheatCode.begin(); it != m_CheatCode.end(); it++ ) {
if( !(it->enable & CHEAT_ENABLE) )
continue;
switch( it->type ) {
case CHEAT_TYPE_ALWAYS:
CheatWrite( it->length, it->address, it->data );
break;
case CHEAT_TYPE_ONCE:
CheatWrite( it->length, it->address, it->data );
it->enable = 0;
break;
case CHEAT_TYPE_GREATER:
if( CheatRead( it->length, it->address ) > it->data ) {
CheatWrite( it->length, it->address, it->data );
}
break;
case CHEAT_TYPE_LESS:
if( CheatRead( it->length, it->address ) < it->data ) {
CheatWrite( it->length, it->address, it->data );
}
break;
}
}
}
void NES::GenieInitial()
{
m_bCheatCodeAdd = FALSE;
m_GenieCode.clear();
}
void NES::GenieLoad( char* fname )
{
FILE* fp = NULL;
CHAR buf[256];
GENIECODE code;
BYTE codetmp[9];
INT no;
if( (fp = ::fopen( fname, "r" )) ) {
m_GenieCode.clear();
while( ::fgets( buf, sizeof(buf), fp ) ) {
if( buf[0] == ';' )
continue;
if( buf[0] == 0x0D || buf[0] == 0x0A )
continue;
if( ::strlen( buf ) < 6 )
continue;
code.address = 0;
code.data = 0;
code.cmp = 0;
for( no = 0; isalpha(buf[no]) && no < 8; no++ ) {
switch( buf[no] ) {
case 'A': codetmp[no] = 0x00; break;
case 'P': codetmp[no] = 0x01; break;
case 'Z': codetmp[no] = 0x02; break;
case 'L': codetmp[no] = 0x03; break;
case 'G': codetmp[no] = 0x04; break;
case 'I': codetmp[no] = 0x05; break;
case 'T': codetmp[no] = 0x06; break;
case 'Y': codetmp[no] = 0x07; break;
case 'E': codetmp[no] = 0x08; break;
case 'O': codetmp[no] = 0x09; break;
case 'X': codetmp[no] = 0x0A; break;
case 'U': codetmp[no] = 0x0B; break;
case 'K': codetmp[no] = 0x0C; break;
case 'S': codetmp[no] = 0x0D; break;
case 'V': codetmp[no] = 0x0E; break;
case 'N': codetmp[no] = 0x0F; break;
}
}
if( no == 6 ) {
// Address
code.address |= (WORD)(codetmp[3] & 0x07)<<12;
code.address |= (WORD)(codetmp[4] & 0x08)<< 8;
code.address |= (WORD)(codetmp[5] & 0x07)<< 8;
code.address |= (WORD)(codetmp[1] & 0x08)<< 4;
code.address |= (WORD)(codetmp[2] & 0x07)<< 4;
code.address |= (WORD)(codetmp[3] & 0x08);
code.address |= (WORD)(codetmp[4] & 0x07);
// Data
code.data |= (codetmp[0] & 0x08)<<4;
code.data |= (codetmp[1] & 0x07)<<4;
code.data |= (codetmp[5] & 0x08);
code.data |= (codetmp[0] & 0x07);
m_GenieCode.push_back( code );
} else
if( no == 8 ) {
// Address
code.address |= 0x8000;
code.address |= (WORD)(codetmp[3] & 0x07)<<12;
code.address |= (WORD)(codetmp[4] & 0x08)<< 8;
code.address |= (WORD)(codetmp[5] & 0x07)<< 8;
code.address |= (WORD)(codetmp[1] & 0x08)<< 4;
code.address |= (WORD)(codetmp[2] & 0x07)<< 4;
code.address |= (WORD)(codetmp[3] & 0x08);
code.address |= (WORD)(codetmp[4] & 0x07);
// Data
code.data |= (codetmp[0] & 0x08)<<4;
code.data |= (codetmp[1] & 0x07)<<4;
code.data |= (codetmp[7] & 0x08);
code.data |= (codetmp[0] & 0x07);
// Data
code.cmp |= (codetmp[6] & 0x08)<<4;
code.cmp |= (codetmp[7] & 0x07)<<4;
code.cmp |= (codetmp[5] & 0x08);
code.cmp |= (codetmp[6] & 0x07);
m_GenieCode.push_back( code );
}
}
GenieCodeProcess();
}
FCLOSE( fp );
}
void NES::GenieCodeProcess()
{
WORD addr;
for( INT i = 0; i < m_GenieCode.size(); i++ ) {
addr = m_GenieCode[i].address;
if( addr & 0x8000 ) {
// 8character codes
if( CPU_MEM_BANK[addr>>13][addr&0x1FFF] == m_GenieCode[i].cmp ) {
CPU_MEM_BANK[addr>>13][addr&0x1FFF] = m_GenieCode[i].data;
}
} else {
// 6character codes
addr |= 0x8000;
CPU_MEM_BANK[addr>>13][addr&0x1FFF] = m_GenieCode[i].data;
}
}
}
void NES::DrawPad()
{
if( m_bMoviePlay ) {
INT offset_h = 12;
INT offset_v = Config.graphics.bAllLine?(240-18):(240-22);
if( Config.movie.bPadDisplay ) {
DWORD dwData = pad->GetSyncData();
for( INT i = 0; i < 4; i++ ) {
BYTE Data = (BYTE)(dwData>>(i*8));
if( m_MovieControl & (1<<i) ) {
DrawBitmap( offset_h, offset_v, m_PadImg );
// KEY
if( Data&(1<<4) ) DrawBitmap( offset_h+3, offset_v+1, m_KeyImg0 ); // U
if( Data&(1<<5) ) DrawBitmap( offset_h+3, offset_v+5, m_KeyImg0 ); // D
if( Data&(1<<6) ) DrawBitmap( offset_h+1, offset_v+3, m_KeyImg0 ); // L
if( Data&(1<<7) ) DrawBitmap( offset_h+5, offset_v+3, m_KeyImg0 ); // R
// START,SELECT
if( Data&(1<<2) ) DrawBitmap( offset_h+ 9, offset_v+5, m_KeyImg1 ); // SELECT
if( Data&(1<<3) ) DrawBitmap( offset_h+13, offset_v+5, m_KeyImg1 ); // START
// A,B
if( Data&(1<<0) ) DrawBitmap( offset_h+23, offset_v+3, m_KeyImg2 ); // A
if( Data&(1<<1) ) DrawBitmap( offset_h+18, offset_v+3, m_KeyImg2 ); // B
offset_h += 30;
}
}
}
if( Config.movie.bTimeDisplay ) {
// Time display
INT t = m_MovieStep;
INT h = t / 216000;
t -= h * 216000;
INT m = t / 3600;
t -= m * 3600;
INT s = t / 60;
t -= s * 60;
CHAR szTemp[64];
sprintf( szTemp, "%02d:%02d:%02d.%02d", h, m, s, t * 100 / 60 );
DrawString( 256-80+0, offset_v-1, szTemp, 0x1F );
DrawString( 256-80+0, offset_v+1, szTemp, 0x1F );
DrawString( 256-80-1, offset_v+0, szTemp, 0x1F );
DrawString( 256-80+1, offset_v+0, szTemp, 0x1F );
DrawString( 256-80, offset_v, szTemp, 0x30 );
}
}
}
void NES::DrawBitmap( INT x, INT y, LPBYTE lpBitmap )
{
INT i, j;
INT h, v;
LPBYTE pScn = ppu->GetScreenPtr()+8+(256+16)*y+x;
LPBYTE pPtr;
h = (INT)*lpBitmap++;
v = (INT)*lpBitmap++;
for( j = 0; j < v; j++ ) {
pPtr = pScn;
for( i = 0; i < h; i++ ) {
if( *lpBitmap != 0xFF ) {
*pPtr = *lpBitmap;
}
lpBitmap++;
pPtr++;
}
pScn += 256+16;
}
}
// TapeDevice
BOOL NES::TapePlay( const char* fname )
{
if( rom->IsNSF() )
return FALSE;
if( IsTapePlay() || IsTapeRec() ) {
TapeStop();
}
DEBUGOUT( "NES::TapePlay\n" );
if( !(m_fpTape = ::fopen( fname, "rb" )) ) {
DEBUGOUT( "Tape play error. File not found.\n" );
// ファイル無いです
return FALSE;
}
m_bTapePlay = TRUE;
m_TapeCycles = 0;
m_TapeOut = 0;
cpu->SetClockProcess( TRUE );
return TRUE;
}
BOOL NES::TapeRec( const char* fname )
{
if( rom->IsNSF() )
return FALSE;
if( IsTapePlay() || IsTapeRec() ) {
TapeStop();
}
DEBUGOUT( "NES::TapeRec\n" );
if( !(m_fpTape = ::fopen( fname, "wb" )) ) {
DEBUGOUT( "Tape rec error. File not found.\n" );
// ファイル無いです
return FALSE;
}
m_bTapeRec = TRUE;
m_TapeCycles = 0;
m_TapeIn = 0;
cpu->SetClockProcess( TRUE );
return TRUE;
}
void NES::TapeStop()
{
DirectDraw.SetMessageString( "Tape stop." );
if( !m_bBarcode ) {
cpu->SetClockProcess( FALSE );
}
m_bTapePlay = m_bTapeRec = FALSE;
FCLOSE( m_fpTape );
}
void NES::Tape( INT cycles )
{
if( !(IsTapePlay() || IsTapeRec()) ) {
return;
}
if( (m_TapeCycles-=(double)cycles) > 0 )
return;
m_TapeCycles += (nescfg->CpuClock / 32000.0);
// m_TapeCycles += (nescfg->CpuClock / 22050.0); // 遅すぎてダメっぽい
if( m_bTapePlay ) {
INT data = ::fgetc( m_fpTape );
if( data != EOF ) {
if( (data&0xFF) >= 0x8C ) {
m_TapeOut = 0x02;
} else
if( (data&0xFF) <= 0x74 ) {
m_TapeOut = 0x00;
}
} else {
TapeStop();
}
}
if( m_bTapeRec ) {
::fputc( (int)((m_TapeIn&7)==7)?0x90:0x70, m_fpTape );
}
}
void NES::Barcode( INT cycles )
{
if( m_bBarcode ) {
if( (m_BarcodeCycles+=cycles) > 1000 ) {
m_BarcodeCycles = 0;
// 停止?
if( m_BarcodeData[m_BarcodePtr] != 0xFF ) {
m_BarcodeOut = m_BarcodeData[m_BarcodePtr++];
} else {
m_bBarcode = FALSE;
m_BarcodeOut = 0;
DEBUGOUT( "Barcode data trasnfer complete!!\n" );
if( !(IsTapePlay() || IsTapeRec()) ) {
cpu->SetClockProcess( FALSE );
}
}
}
}
}
void NES::SetBarcodeData( LPBYTE code, INT len )
{
if( rom->GetPROM_CRC() == 0x67898319 ) { // Barcode World (J)
SetBarcode2Data( code, len );
return;
}
DEBUGOUT( "NES::SetBarcodeData code=%s len=%d\n", code, len );
bool prefix_parity_type[10][6] = {
{0,0,0,0,0,0}, {0,0,1,0,1,1}, {0,0,1,1,0,1}, {0,0,1,1,1,0},
{0,1,0,0,1,1}, {0,1,1,0,0,1}, {0,1,1,1,0,0}, {0,1,0,1,0,1},
{0,1,0,1,1,0}, {0,1,1,0,1,0}
};
bool data_left_odd[10][7] = {
{0,0,0,1,1,0,1}, {0,0,1,1,0,0,1}, {0,0,1,0,0,1,1}, {0,1,1,1,1,0,1},
{0,1,0,0,0,1,1}, {0,1,1,0,0,0,1}, {0,1,0,1,1,1,1}, {0,1,1,1,0,1,1},
{0,1,1,0,1,1,1}, {0,0,0,1,0,1,1}
};
bool data_left_even[10][7] = {
{0,1,0,0,1,1,1}, {0,1,1,0,0,1,1}, {0,0,1,1,0,1,1}, {0,1,0,0,0,0,1},
{0,0,1,1,1,0,1}, {0,1,1,1,0,0,1}, {0,0,0,0,1,0,1}, {0,0,1,0,0,0,1},
{0,0,0,1,0,0,1}, {0,0,1,0,1,1,1}
};
bool data_right[10][7] = {
{1,1,1,0,0,1,0}, {1,1,0,0,1,1,0}, {1,1,0,1,1,0,0}, {1,0,0,0,0,1,0},
{1,0,1,1,1,0,0}, {1,0,0,1,1,1,0}, {1,0,1,0,0,0,0}, {1,0,0,0,1,0,0},
{1,0,0,1,0,0,0}, {1,1,1,0,1,0,0}
};
INT i, j, count = 0;;
// 数値に変換
for( i = 0; i < len; i++ ) {
code[i] = code[i]-'0';
}
// レフトマージン
for( i = 0; i < 32; i++ ) {
m_BarcodeData[count++] = 0x08;
}
// レフトガードバー
m_BarcodeData[count++] = 0x00;
m_BarcodeData[count++] = 0x08;
m_BarcodeData[count++] = 0x00;
if( len == 13 ) {
#if 0
// チェックディジットの再計算
INT sum = 0;
for( i = 0; i < 12; i++ ) {
sum += (i&1)?(code[i]*3):code[i];
}
code[12] = (10-(sum%10))%10;
// test start
// INT cs = (10-(sum%10))%10;
// if( cs == 0 ) {
// cs = 9;
// } else {
// cs--;
// }
// code[12] = cs;
// test end
#endif
// 左側6キャラクタ
for( i = 0; i < 6; i++ ) {
if( prefix_parity_type[code[0]][i] ) {
// 偶数パリティ
for( j = 0; j < 7; j++ ) {
m_BarcodeData[count++] = data_left_even[code[i+1]][j]?0x00:0x08;
}
} else {
// 奇数パリティ
for( j = 0; j < 7; j++ ) {
m_BarcodeData[count++] = data_left_odd[code[i+1]][j]?0x00:0x08;
}
}
}
// センターバー
m_BarcodeData[count++] = 0x08;
m_BarcodeData[count++] = 0x00;
m_BarcodeData[count++] = 0x08;
m_BarcodeData[count++] = 0x00;
m_BarcodeData[count++] = 0x08;
// 右側5キャラクタとチェックディジット
for( i = 7; i < 13; i++ ) {
// 偶数パリティ
for( j = 0; j < 7; j++ ) {
m_BarcodeData[count++] = data_right[code[i]][j]?0x00:0x08;
}
}
} else
if( len == 8 ) {
// チェックディジットの再計算
INT sum = 0;
for( i = 0; i < 7; i++ ) {
sum += (i&1)?code[i]:(code[i]*3);
}
code[7] = (10-(sum%10))%10;
// 左側4キャラクタ
for( i = 0; i < 4; i++ ) {
// 奇数パリティ
for( j = 0; j < 7; j++ ) {
m_BarcodeData[count++] = data_left_odd[code[i]][j]?0x00:0x08;
}
}
// センターバー
m_BarcodeData[count++] = 0x08;
m_BarcodeData[count++] = 0x00;
m_BarcodeData[count++] = 0x08;
m_BarcodeData[count++] = 0x00;
m_BarcodeData[count++] = 0x08;
// 右側3キャラクタとチェックディジット
for( i = 4; i < 8; i++ ) {
// 偶数パリティ
for( j = 0; j < 7; j++ ) {
m_BarcodeData[count++] = data_right[code[i]][j]?0x00:0x08;
}
}
}
// ライトガードバー
m_BarcodeData[count++] = 0x00;
m_BarcodeData[count++] = 0x08;
m_BarcodeData[count++] = 0x00;
// ライトマージン
for( i = 0; i < 32; i++ ) {
m_BarcodeData[count++] = 0x08;
}
// 終了マーク
m_BarcodeData[count++] = 0xFF;
// 転送開始
m_bBarcode = TRUE;
m_BarcodeCycles = 0;
m_BarcodePtr = 0;
m_BarcodeOut = 0x08;
cpu->SetClockProcess( TRUE );
DEBUGOUT( "BARCODE DATA MAX:%d\n", count );
}
BYTE NES::Barcode2( void )
{
BYTE ret = 0x00;
if( !m_bBarcode2 || m_Barcode2seq < 0 )
return ret;
switch( m_Barcode2seq ) {
case 0:
m_Barcode2seq++;
m_Barcode2ptr = 0;
ret = 0x04; // d3
break;
case 1:
m_Barcode2seq++;
m_Barcode2bit = m_Barcode2data[m_Barcode2ptr];
m_Barcode2cnt = 0;
ret = 0x04; // d3
break;
case 2:
ret = (m_Barcode2bit&0x01)?0x00:0x04; // Bit rev.
m_Barcode2bit >>= 1;
if( ++m_Barcode2cnt > 7 ) {
m_Barcode2seq++;
}
break;
case 3:
if( ++m_Barcode2ptr > 19 ) {
m_bBarcode2 = FALSE;
m_Barcode2seq = -1;
} else {
m_Barcode2seq = 1;
}
break;
default:
break;
}
return ret;
}
void NES::SetBarcode2Data( LPBYTE code, INT len )
{
DEBUGOUT( "NES::SetBarcodeData2 code=%s len=%d\n", code, len );
if( len < 13 )
return;
m_bBarcode2 = TRUE;
m_Barcode2seq = 0;
m_Barcode2ptr = 0;
::strcpy( (char*)m_Barcode2data, (char*)code );
m_Barcode2data[13] = 'S';
m_Barcode2data[14] = 'U';
m_Barcode2data[15] = 'N';
m_Barcode2data[16] = 'S';
m_Barcode2data[17] = 'O';
m_Barcode2data[18] = 'F';
m_Barcode2data[19] = 'T';
}
//#if NES_PROFILER
#if 1
BYTE NES::Font6x8[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x20,0x20,0x20,0x00,0x20,0x00,
0x50,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x50,0x50,0xF8,0x50,0xF8,0x50,0x50,0x00,
0x20,0x78,0xA0,0x70,0x28,0xF0,0x20,0x00,0x48,0xB0,0x50,0x20,0x50,0x68,0x90,0x00,
0x40,0xA0,0xA8,0x68,0x90,0x90,0x68,0x00,0x30,0x20,0x00,0x00,0x00,0x00,0x00,0x00,
0x10,0x20,0x40,0x40,0x40,0x20,0x10,0x00,0x40,0x20,0x10,0x10,0x10,0x20,0x40,0x00,
0x00,0x88,0x50,0x20,0x50,0x88,0x00,0x00,0x00,0x20,0x20,0xF8,0x20,0x20,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x20,0x40,0x00,0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x08,0x10,0x10,0x20,0x40,0x40,0x80,0x00,
0x70,0x88,0x98,0xA8,0xC8,0x88,0x70,0x00,0x20,0x60,0x20,0x20,0x20,0x20,0xF8,0x00,
0x70,0x88,0x08,0x30,0x40,0x80,0xF8,0x00,0x70,0x88,0x08,0x30,0x08,0x88,0x70,0x00,
0x30,0x50,0x90,0x90,0xF8,0x10,0x10,0x00,0xF8,0x80,0x80,0xF0,0x08,0x08,0xF0,0x00,
0x70,0x88,0x80,0xF0,0x88,0x88,0x70,0x00,0xF8,0x08,0x10,0x10,0x20,0x20,0x20,0x00,
0x70,0x88,0x88,0x70,0x88,0x88,0x70,0x00,0x70,0x88,0x88,0x78,0x08,0x88,0x70,0x00,
0x00,0x20,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x20,0x40,0x00,
0x10,0x20,0x40,0x80,0x40,0x20,0x10,0x00,0x00,0x00,0xF8,0x00,0xF8,0x00,0x00,0x00,
0x40,0x20,0x10,0x08,0x10,0x20,0x40,0x00,0x70,0x88,0x08,0x10,0x20,0x00,0x20,0x00,
0x30,0x48,0x88,0x98,0xA8,0xA8,0x78,0x00,0x20,0x50,0x50,0x88,0xF8,0x88,0x88,0x00,
0xF0,0x88,0x88,0xF0,0x88,0x88,0xF0,0x00,0x70,0x88,0x80,0x80,0x80,0x88,0x70,0x00,
0xF0,0x88,0x88,0x88,0x88,0x88,0xF0,0x00,0xF8,0x80,0x80,0xF0,0x80,0x80,0xF8,0x00,
0xF8,0x80,0x80,0xF0,0x80,0x80,0x80,0x00,0x70,0x88,0x80,0xB8,0x88,0x88,0x70,0x00,
0x88,0x88,0x88,0xF8,0x88,0x88,0x88,0x00,0xF8,0x20,0x20,0x20,0x20,0x20,0xF8,0x00,
0x38,0x08,0x08,0x08,0x08,0x88,0x70,0x00,0x88,0x88,0x90,0xE0,0x90,0x88,0x88,0x00,
0x80,0x80,0x80,0x80,0x80,0x80,0xF8,0x00,0x88,0xD8,0xA8,0xA8,0xA8,0xA8,0xA8,0x00,
0x88,0xC8,0xA8,0xA8,0xA8,0x98,0x88,0x00,0x70,0x88,0x88,0x88,0x88,0x88,0x70,0x00,
0xF0,0x88,0x88,0xF0,0x80,0x80,0x80,0x00,0x70,0x88,0x88,0x88,0xA8,0x90,0x68,0x00,
0xF0,0x88,0x88,0xF0,0x88,0x88,0x88,0x00,0x70,0x88,0x80,0x70,0x08,0x88,0x70,0x00,
0xF8,0x20,0x20,0x20,0x20,0x20,0x20,0x00,0x88,0x88,0x88,0x88,0x88,0x88,0x70,0x00,
0x88,0x88,0x88,0x50,0x50,0x50,0x20,0x00,0x88,0xA8,0xA8,0xA8,0xA8,0xD8,0x88,0x00,
0x88,0x88,0x50,0x20,0x50,0x88,0x88,0x00,0x88,0x88,0x88,0x70,0x20,0x20,0x20,0x00,
0xF8,0x08,0x10,0x20,0x40,0x80,0xF8,0x00,0x70,0x40,0x40,0x40,0x40,0x40,0x70,0x00,
0x88,0x50,0xF8,0x20,0xF8,0x20,0x20,0x00,0x70,0x10,0x10,0x10,0x10,0x10,0x70,0x00,
0x20,0x50,0x88,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0x00,
0x80,0xC0,0xE0,0xF0,0xE0,0xC0,0x80,0x00,0x00,0x00,0x70,0x08,0x78,0x88,0xF8,0x00,
0x80,0x80,0x80,0xF0,0x88,0x88,0xF0,0x00,0x00,0x00,0x78,0x80,0x80,0x80,0x78,0x00,
0x08,0x08,0x08,0x78,0x88,0x88,0x78,0x00,0x00,0x00,0x70,0x88,0xF8,0x80,0x78,0x00,
0x18,0x20,0xF8,0x20,0x20,0x20,0x20,0x00,0x00,0x00,0x78,0x88,0x78,0x08,0xF0,0x00,
0x80,0x80,0x80,0xF0,0x88,0x88,0x88,0x00,0x20,0x00,0x20,0x20,0x20,0x20,0x20,0x00,
0x20,0x00,0x20,0x20,0x20,0x20,0xC0,0x00,0x80,0x80,0x88,0x90,0xE0,0x90,0x88,0x00,
0x20,0x20,0x20,0x20,0x20,0x20,0x30,0x00,0x00,0x00,0xF0,0xA8,0xA8,0xA8,0xA8,0x00,
0x00,0x00,0xF0,0x88,0x88,0x88,0x88,0x00,0x00,0x00,0x70,0x88,0x88,0x88,0x70,0x00,
0x00,0x00,0xF0,0x88,0xF0,0x80,0x80,0x00,0x00,0x00,0x78,0x88,0x78,0x08,0x08,0x00,
0x00,0x00,0xB8,0xC0,0x80,0x80,0x80,0x00,0x00,0x00,0x78,0x80,0x70,0x08,0xF0,0x00,
0x20,0x20,0xF8,0x20,0x20,0x20,0x20,0x00,0x00,0x00,0x88,0x88,0x88,0x88,0x70,0x00,
0x00,0x00,0x88,0x88,0x50,0x50,0x20,0x00,0x00,0x00,0x88,0xA8,0xA8,0xD8,0x88,0x00,
0x00,0x00,0x88,0x50,0x20,0x50,0x88,0x00,0x00,0x00,0x88,0x88,0x78,0x08,0xF0,0x00,
0x00,0x00,0xF8,0x08,0x70,0x80,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};
void NES::DrawFont( INT x, INT y, BYTE chr, BYTE col )
{
INT i;
LPBYTE pFnt;
LPBYTE pPtr;
LPBYTE pScn = ppu->GetScreenPtr()+8;
if( chr < 0x20 || chr > 0x7F )
return;
chr -= 0x20;
pFnt = &Font6x8[chr*8];
pPtr = pScn+(256+16)*y+x;
for( i = 0; i < 8; i++ ) {
if( pFnt[i] & 0x80 ) pPtr[0] = col;
if( pFnt[i] & 0x40 ) pPtr[1] = col;
if( pFnt[i] & 0x20 ) pPtr[2] = col;
if( pFnt[i] & 0x10 ) pPtr[3] = col;
if( pFnt[i] & 0x08 ) pPtr[4] = col;
if( pFnt[i] & 0x04 ) pPtr[5] = col;
pPtr += (256+16);
}
}
void NES::DrawString( INT x, INT y, LPSTR str, BYTE col )
{
while( *str ) {
DrawFont( x, y, *str, col );
str++;
x += 6;
}
}
#endif