1 //////////////////////////////////////////////////////////////////////////////////////// 2 // 3 // Nestopia - NES/Famicom emulator written in C++ 4 // 5 // Copyright (C) 2003-2008 Martin Freij 6 // Copyright (C) 2018-2018 Phil Smith 7 // 8 // This file is part of Nestopia. 9 // 10 // Nestopia is free software; you can redistribute it and/or modify 11 // it under the terms of the GNU General Public License as published by 12 // the Free Software Foundation; either version 2 of the License, or 13 // (at your option) any later version. 14 // 15 // Nestopia is distributed in the hope that it will be useful, 16 // but WITHOUT ANY WARRANTY; without even the implied warranty of 17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 // GNU General Public License for more details. 19 // 20 // You should have received a copy of the GNU General Public License 21 // along with Nestopia; if not, write to the Free Software 22 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 23 // 24 //////////////////////////////////////////////////////////////////////////////////////// 25 26 #include "NstMachine.hpp" 27 #include "NstCartridge.hpp" 28 #include "NstCheats.hpp" 29 #include "NstHomebrew.hpp" 30 #include "NstNsf.hpp" 31 #include "NstImageDatabase.hpp" 32 #include "input/NstInpDevice.hpp" 33 #include "input/NstInpAdapter.hpp" 34 #include "input/NstInpPad.hpp" 35 #include "api/NstApiMachine.hpp" 36 #include "api/NstApiUser.hpp" 37 38 namespace Nes 39 { 40 namespace Core 41 { 42 #ifdef NST_MSVC_OPTIMIZE 43 #pragma optimize("s", on) 44 #endif 45 Machine()46 Machine::Machine() 47 : 48 state (Api::Machine::NTSC), 49 frame (0), 50 extPort (new Input::AdapterTwo( *new Input::Pad(cpu,0), *new Input::Pad(cpu,1) )), 51 expPort (new Input::Device( cpu )), 52 image (NULL), 53 cheats (NULL), 54 homebrew (NULL), 55 imageDatabase (NULL), 56 ppu (cpu) 57 { 58 } 59 ~Machine()60 Machine::~Machine() 61 { 62 Unload(); 63 64 delete imageDatabase; 65 delete cheats; 66 delete homebrew; 67 delete expPort; 68 69 for (uint ports=extPort->NumPorts(), i=0; i < ports; ++i) 70 delete &extPort->GetDevice(i); 71 72 delete extPort; 73 } 74 Load(std::istream & imageStream,FavoredSystem system,bool ask,std::istream * const patchStream,bool patchBypassChecksum,Result * patchResult,uint type)75 Result Machine::Load 76 ( 77 std::istream& imageStream, 78 FavoredSystem system, 79 bool ask, 80 std::istream* const patchStream, 81 bool patchBypassChecksum, 82 Result* patchResult, 83 uint type 84 ) 85 { 86 Unload(); 87 88 Image::Context context 89 ( 90 static_cast<Image::Type>(type), 91 cpu, 92 cpu.GetApu(), 93 ppu, 94 imageStream, 95 patchStream, 96 patchBypassChecksum, 97 patchResult, 98 system, 99 ask, 100 imageDatabase 101 ); 102 103 image = Image::Load( context ); 104 105 switch (image->GetType()) 106 { 107 case Image::CARTRIDGE: 108 109 state |= Api::Machine::CARTRIDGE; 110 111 if ((static_cast<const Cartridge*>(image)->GetProfile().system.type) == Api::Cartridge::Profile::System::VS_UNISYSTEM) 112 { 113 114 state |= Api::Machine::VS; 115 } 116 else if ((static_cast<const Cartridge*>(image)->GetProfile().system.type) == Api::Cartridge::Profile::System::PLAYCHOICE_10) 117 { 118 119 state |= Api::Machine::PC10; 120 } 121 break; 122 123 case Image::DISK: 124 125 state |= Api::Machine::DISK; 126 break; 127 128 case Image::SOUND: 129 130 state |= Api::Machine::SOUND; 131 break; 132 133 case Image::UNKNOWN: 134 135 default: 136 break; 137 } 138 139 UpdateModels(); 140 141 Api::Machine::eventCallback( Api::Machine::EVENT_LOAD, context.result ); 142 143 return context.result; 144 } 145 Unload()146 Result Machine::Unload() 147 { 148 if (!image) 149 return RESULT_OK; 150 151 const Result result = PowerOff(); 152 153 tracker.Unload(); 154 155 Image::Unload( image ); 156 image = NULL; 157 158 state &= (Api::Machine::NTSC|Api::Machine::PAL); 159 160 Api::Machine::eventCallback( Api::Machine::EVENT_UNLOAD, result ); 161 162 return result; 163 } 164 UpdateModels()165 void Machine::UpdateModels() 166 { 167 const Region region = (state & Api::Machine::NTSC) ? REGION_NTSC : REGION_PAL; 168 169 CpuModel cpuModel; 170 PpuModel ppuModel; 171 172 if (image) 173 { 174 image->GetDesiredSystem( region, &cpuModel, &ppuModel ); 175 } 176 else 177 { 178 cpuModel = (region == REGION_NTSC ? CPU_RP2A03 : CPU_RP2A07); 179 ppuModel = (region == REGION_NTSC ? PPU_RP2C02 : PPU_RP2C07); 180 } 181 182 cpu.SetModel( cpuModel ); 183 UpdateVideo( ppuModel, GetColorMode() ); 184 185 renderer.EnableForcedFieldMerging( ppuModel != PPU_RP2C02 ); 186 } 187 GetColorMode() const188 Machine::ColorMode Machine::GetColorMode() const 189 { 190 return 191 ( 192 renderer.GetPaletteType() == Video::Renderer::PALETTE_YUV ? COLORMODE_YUV : 193 renderer.GetPaletteType() == Video::Renderer::PALETTE_CUSTOM ? COLORMODE_CUSTOM : 194 COLORMODE_RGB 195 ); 196 } 197 UpdateColorMode()198 Result Machine::UpdateColorMode() 199 { 200 return UpdateColorMode( GetColorMode() ); 201 } 202 UpdateColorMode(const ColorMode mode)203 Result Machine::UpdateColorMode(const ColorMode mode) 204 { 205 return UpdateVideo( ppu.GetModel(), mode ); 206 } 207 UpdateVideo(const PpuModel ppuModel,const ColorMode mode)208 Result Machine::UpdateVideo(const PpuModel ppuModel,const ColorMode mode) 209 { 210 ppu.SetModel( ppuModel, mode == COLORMODE_YUV ); 211 212 Video::Renderer::PaletteType palette; 213 214 switch (mode) 215 { 216 case COLORMODE_RGB: 217 218 switch (ppuModel) 219 { 220 case PPU_RP2C04_0001: palette = Video::Renderer::PALETTE_VS1; break; 221 case PPU_RP2C04_0002: palette = Video::Renderer::PALETTE_VS2; break; 222 case PPU_RP2C04_0003: palette = Video::Renderer::PALETTE_VS3; break; 223 case PPU_RP2C04_0004: palette = Video::Renderer::PALETTE_VS4; break; 224 default: palette = Video::Renderer::PALETTE_PC10; break; 225 } 226 break; 227 228 case COLORMODE_CUSTOM: 229 230 palette = Video::Renderer::PALETTE_CUSTOM; 231 break; 232 233 default: 234 235 palette = Video::Renderer::PALETTE_YUV; 236 break; 237 } 238 239 return renderer.SetPaletteType( palette ); 240 } 241 PowerOff(Result result)242 Result Machine::PowerOff(Result result) 243 { 244 if (state & Api::Machine::ON) 245 { 246 tracker.PowerOff(); 247 248 if (image && !image->PowerOff() && NES_SUCCEEDED(result)) 249 result = RESULT_WARN_SAVEDATA_LOST; 250 251 ppu.PowerOff(); 252 cpu.PowerOff(); 253 254 state &= ~uint(Api::Machine::ON); 255 frame = 0; 256 257 Api::Machine::eventCallback( Api::Machine::EVENT_POWER_OFF, result ); 258 } 259 260 return result; 261 } 262 Reset(bool hard)263 void Machine::Reset(bool hard) 264 { 265 if (state & Api::Machine::SOUND) 266 hard = true; 267 268 try 269 { 270 frame = 0; 271 cpu.Reset( hard ); 272 273 if (!(state & Api::Machine::SOUND)) 274 { 275 InitializeInputDevices(); 276 277 cpu.Map( 0x4016 ).Set( this, &Machine::Peek_4016, &Machine::Poke_4016 ); 278 cpu.Map( 0x4017 ).Set( this, &Machine::Peek_4017, &Machine::Poke_4017 ); 279 280 extPort->Reset(); 281 expPort->Reset(); 282 283 bool acknowledged = true; 284 285 if (image) 286 { 287 System desiredSystem = image->GetDesiredSystem((state & Api::Machine::NTSC) ? REGION_NTSC : REGION_PAL); 288 289 if (desiredSystem == SYSTEM_FAMICOM || desiredSystem == SYSTEM_DENDY) 290 acknowledged = false; 291 } 292 293 ppu.Reset( hard, acknowledged ); 294 295 if (image) 296 image->Reset( hard ); 297 298 if (cheats) 299 cheats->Reset(); 300 301 if (homebrew) 302 homebrew->Reset(); 303 304 tracker.Reset(); 305 } 306 else 307 { 308 image->Reset( true ); 309 } 310 311 cpu.Boot( hard ); 312 313 if (state & Api::Machine::ON) 314 { 315 Api::Machine::eventCallback( hard ? Api::Machine::EVENT_RESET_HARD : Api::Machine::EVENT_RESET_SOFT ); 316 } 317 else 318 { 319 state |= Api::Machine::ON; 320 Api::Machine::eventCallback( Api::Machine::EVENT_POWER_ON ); 321 } 322 } 323 catch (...) 324 { 325 PowerOff(); 326 throw; 327 } 328 } 329 SetRamPowerState(uint state)330 void Machine::SetRamPowerState(uint state) 331 { 332 cpu.SetRamPowerState(state); 333 } 334 SwitchMode()335 void Machine::SwitchMode() 336 { 337 NST_ASSERT( !(state & Api::Machine::ON) ); 338 339 if (state & Api::Machine::NTSC) 340 state = (state & ~uint(Api::Machine::NTSC)) | Api::Machine::PAL; 341 else 342 state = (state & ~uint(Api::Machine::PAL)) | Api::Machine::NTSC; 343 344 UpdateModels(); 345 346 Api::Machine::eventCallback( (state & Api::Machine::NTSC) ? Api::Machine::EVENT_MODE_NTSC : Api::Machine::EVENT_MODE_PAL ); 347 } 348 InitializeInputDevices() const349 void Machine::InitializeInputDevices() const 350 { 351 if (state & Api::Machine::GAME) 352 { 353 const bool arcade = state & Api::Machine::VS; 354 355 extPort->Initialize( arcade ); 356 expPort->Initialize( arcade ); 357 } 358 } 359 SaveState(State::Saver & saver) const360 void Machine::SaveState(State::Saver& saver) const 361 { 362 NST_ASSERT( (state & (Api::Machine::GAME|Api::Machine::ON)) > Api::Machine::ON ); 363 364 saver.Begin( AsciiId<'N','S','T'>::V | 0x1AUL << 24 ); 365 366 saver.Begin( AsciiId<'N','F','O'>::V ).Write32( image->GetPrgCrc() ).Write32( frame ).End(); 367 368 cpu.SaveState( saver, AsciiId<'C','P','U'>::V, AsciiId<'A','P','U'>::V ); 369 ppu.SaveState( saver, AsciiId<'P','P','U'>::V ); 370 image->SaveState( saver, AsciiId<'I','M','G'>::V ); 371 372 saver.Begin( AsciiId<'P','R','T'>::V ); 373 374 if (extPort->NumPorts() == 4) 375 { 376 static_cast<const Input::AdapterFour*>(extPort)->SaveState 377 ( 378 saver, AsciiId<'4','S','C'>::V 379 ); 380 } 381 382 for (uint i=0; i < extPort->NumPorts(); ++i) 383 extPort->GetDevice( i ).SaveState( saver, Ascii<'0'>::V + i ); 384 385 expPort->SaveState( saver, Ascii<'X'>::V ); 386 387 saver.End(); 388 389 saver.End(); 390 } 391 LoadState(State::Loader & loader,const bool resetOnError)392 bool Machine::LoadState(State::Loader& loader,const bool resetOnError) 393 { 394 NST_ASSERT( (state & (Api::Machine::GAME|Api::Machine::ON)) > Api::Machine::ON ); 395 396 try 397 { 398 if (loader.Begin() != (AsciiId<'N','S','T'>::V | 0x1AUL << 24)) 399 throw RESULT_ERR_INVALID_FILE; 400 401 while (const dword chunk = loader.Begin()) 402 { 403 switch (chunk) 404 { 405 case AsciiId<'N','F','O'>::V: 406 { 407 const dword crc = loader.Read32(); 408 409 if 410 ( 411 loader.CheckCrc() && !(state & Api::Machine::DISK) && 412 crc && crc != image->GetPrgCrc() && 413 Api::User::questionCallback( Api::User::QUESTION_NST_PRG_CRC_FAIL_CONTINUE ) == Api::User::ANSWER_NO 414 ) 415 { 416 for (uint i=0; i < 2; ++i) 417 loader.End(); 418 419 return false; 420 } 421 422 frame = loader.Read32(); 423 break; 424 } 425 426 case AsciiId<'C','P','U'>::V: 427 case AsciiId<'A','P','U'>::V: 428 429 cpu.LoadState( loader, AsciiId<'C','P','U'>::V, AsciiId<'A','P','U'>::V, chunk ); 430 break; 431 432 case AsciiId<'P','P','U'>::V: 433 434 ppu.LoadState( loader ); 435 break; 436 437 case AsciiId<'I','M','G'>::V: 438 439 image->LoadState( loader ); 440 break; 441 442 case AsciiId<'P','R','T'>::V: 443 444 extPort->Reset(); 445 expPort->Reset(); 446 447 while (const dword subId = loader.Begin()) 448 { 449 if (subId == AsciiId<'4','S','C'>::V) 450 { 451 if (extPort->NumPorts() == 4) 452 static_cast<Input::AdapterFour*>(extPort)->LoadState( loader ); 453 } 454 else switch (const uint index = (subId >> 16 & 0xFF)) 455 { 456 case Ascii<'2'>::V: 457 case Ascii<'3'>::V: 458 459 if (extPort->NumPorts() != 4) 460 break; 461 462 case Ascii<'0'>::V: 463 case Ascii<'1'>::V: 464 465 extPort->GetDevice( index - Ascii<'0'>::V ).LoadState( loader, subId & 0xFF00FFFF ); 466 break; 467 468 case Ascii<'X'>::V: 469 470 expPort->LoadState( loader, subId & 0xFF00FFFF ); 471 break; 472 } 473 474 loader.End(); 475 } 476 break; 477 } 478 479 loader.End(); 480 } 481 482 loader.End(); 483 } 484 catch (...) 485 { 486 if (resetOnError) 487 Reset( true ); 488 489 throw; 490 } 491 492 return true; 493 } 494 495 #ifdef NST_MSVC_OPTIMIZE 496 #pragma optimize("", on) 497 #endif 498 Execute(Video::Output * const video,Sound::Output * const sound,Input::Controllers * const input)499 void Machine::Execute 500 ( 501 Video::Output* const video, 502 Sound::Output* const sound, 503 Input::Controllers* const input 504 ) 505 { 506 NST_ASSERT( state & Api::Machine::ON ); 507 508 if (!(state & Api::Machine::SOUND)) 509 { 510 if (state & Api::Machine::CARTRIDGE) 511 static_cast<Cartridge*>(image)->BeginFrame( Api::Input(*this), input ); 512 513 extPort->BeginFrame( input ); 514 expPort->BeginFrame( input ); 515 516 ppu.BeginFrame( tracker.IsFrameLocked() ); 517 518 if (cheats) 519 cheats->BeginFrame( tracker.IsFrameLocked() ); 520 521 cpu.ExecuteFrame( sound ); 522 ppu.EndFrame(); 523 524 renderer.bgColor = ppu.output.bgColor; 525 526 if (video) 527 renderer.Blit( *video, ppu.GetScreen(), ppu.GetBurstPhase() ); 528 529 cpu.EndFrame(); 530 531 if (image) 532 image->VSync(); 533 534 extPort->EndFrame(); 535 expPort->EndFrame(); 536 537 frame++; 538 } 539 else 540 { 541 static_cast<Nsf*>(image)->BeginFrame(); 542 543 cpu.ExecuteFrame( sound ); 544 cpu.EndFrame(); 545 546 image->VSync(); 547 } 548 } 549 550 NES_POKE_D(Machine,4016) 551 { 552 extPort->Poke( data ); 553 expPort->Poke( data ); 554 } 555 556 NES_PEEK_A(Machine,4016) 557 { 558 cpu.Update( address ); 559 return OPEN_BUS | extPort->Peek(0) | expPort->Peek(0); 560 } 561 562 NES_POKE_D(Machine,4017) 563 { 564 cpu.GetApu().WriteFrameCtrl( data ); 565 } 566 567 NES_PEEK_A(Machine,4017) 568 { 569 cpu.Update( address ); 570 return OPEN_BUS | extPort->Peek(1) | expPort->Peek(1); 571 } 572 } 573 } 574