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