1 ////////////////////////////////////////////////////////////////////////////////////////
2 //
3 // Nestopia - NES/Famicom emulator written in C++
4 //
5 // Copyright (C) 2003-2008 Martin Freij
6 //
7 // This file is part of Nestopia.
8 //
9 // Nestopia is free software; you can redistribute it and/or modify
10 // it under the terms of the GNU General Public License as published by
11 // the Free Software Foundation; either version 2 of the License, or
12 // (at your option) any later version.
13 //
14 // Nestopia is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 // GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with Nestopia; if not, write to the Free Software
21 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 //
23 ////////////////////////////////////////////////////////////////////////////////////////
24 
25 #include <algorithm>
26 #include "NstResourceString.hpp"
27 #include "NstIoScreen.hpp"
28 #include "NstManagerPaths.hpp"
29 #include "NstDialogAutoSaver.hpp"
30 #include "NstManagerSaveStates.hpp"
31 #include "NstWindowMain.hpp"
32 
33 namespace Nestopia
34 {
35 	namespace Managers
36 	{
37 		NST_COMPILE_ASSERT
38 		(
39 			IDM_FILE_QUICK_SAVE_STATE_SLOT_1 == IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST + 1 &&
40 			IDM_FILE_QUICK_SAVE_STATE_SLOT_2 == IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST + 2 &&
41 			IDM_FILE_QUICK_SAVE_STATE_SLOT_3 == IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST + 3 &&
42 			IDM_FILE_QUICK_SAVE_STATE_SLOT_4 == IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST + 4 &&
43 			IDM_FILE_QUICK_SAVE_STATE_SLOT_5 == IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST + 5 &&
44 			IDM_FILE_QUICK_SAVE_STATE_SLOT_6 == IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST + 6 &&
45 			IDM_FILE_QUICK_SAVE_STATE_SLOT_7 == IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST + 7 &&
46 			IDM_FILE_QUICK_SAVE_STATE_SLOT_8 == IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST + 8 &&
47 			IDM_FILE_QUICK_SAVE_STATE_SLOT_9 == IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST + 9 &&
48 
49 			IDM_FILE_QUICK_LOAD_STATE_SLOT_1 == IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST + 1 &&
50 			IDM_FILE_QUICK_LOAD_STATE_SLOT_2 == IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST + 2 &&
51 			IDM_FILE_QUICK_LOAD_STATE_SLOT_3 == IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST + 3 &&
52 			IDM_FILE_QUICK_LOAD_STATE_SLOT_4 == IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST + 4 &&
53 			IDM_FILE_QUICK_LOAD_STATE_SLOT_5 == IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST + 5 &&
54 			IDM_FILE_QUICK_LOAD_STATE_SLOT_6 == IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST + 6 &&
55 			IDM_FILE_QUICK_LOAD_STATE_SLOT_7 == IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST + 7 &&
56 			IDM_FILE_QUICK_LOAD_STATE_SLOT_8 == IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST + 8 &&
57 			IDM_FILE_QUICK_LOAD_STATE_SLOT_9 == IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST + 9
58 		);
59 
60 		struct SaveStates::Slot::Compare
61 		{
operator ()Nestopia::Managers::SaveStates::Slot::Compare62 			bool operator () (const Slot& a,const Slot& b) const
63 			{
64 				return a.time == b.time ? &a < &b : a.time < b.time;
65 			}
66 		};
67 
SaveStates(Emulator & e,Window::Menu & m,const Paths & p,const Window::Main & w)68 		SaveStates::SaveStates(Emulator& e,Window::Menu& m,const Paths& p,const Window::Main& w)
69 		:
70 		Manager         ( e, m, this, &SaveStates::OnEmuEvent ),
71 		window          ( w ),
72 		paths           ( p ),
73 		autoSaveEnabled ( false ),
74 		autoSaver       ( new Window::AutoSaver(paths) )
75 		{
76 			static const Window::Menu::CmdHandler::Entry<SaveStates> commands[] =
77 			{
78 				{ IDM_FILE_LOAD_NST,                     &SaveStates::OnCmdStateLoad        },
79 				{ IDM_FILE_SAVE_NST,                     &SaveStates::OnCmdStateSave        },
80 				{ IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST, &SaveStates::OnCmdSlotLoadNewest   },
81 				{ IDM_FILE_QUICK_LOAD_STATE_SLOT_1,      &SaveStates::OnCmdSlotLoad         },
82 				{ IDM_FILE_QUICK_LOAD_STATE_SLOT_2,      &SaveStates::OnCmdSlotLoad         },
83 				{ IDM_FILE_QUICK_LOAD_STATE_SLOT_3,      &SaveStates::OnCmdSlotLoad         },
84 				{ IDM_FILE_QUICK_LOAD_STATE_SLOT_4,      &SaveStates::OnCmdSlotLoad         },
85 				{ IDM_FILE_QUICK_LOAD_STATE_SLOT_5,      &SaveStates::OnCmdSlotLoad         },
86 				{ IDM_FILE_QUICK_LOAD_STATE_SLOT_6,      &SaveStates::OnCmdSlotLoad         },
87 				{ IDM_FILE_QUICK_LOAD_STATE_SLOT_7,      &SaveStates::OnCmdSlotLoad         },
88 				{ IDM_FILE_QUICK_LOAD_STATE_SLOT_8,      &SaveStates::OnCmdSlotLoad         },
89 				{ IDM_FILE_QUICK_LOAD_STATE_SLOT_9,      &SaveStates::OnCmdSlotLoad         },
90 				{ IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST, &SaveStates::OnCmdSlotSaveOldest   },
91 				{ IDM_FILE_QUICK_SAVE_STATE_SLOT_1,      &SaveStates::OnCmdSlotSave         },
92 				{ IDM_FILE_QUICK_SAVE_STATE_SLOT_2,      &SaveStates::OnCmdSlotSave         },
93 				{ IDM_FILE_QUICK_SAVE_STATE_SLOT_3,      &SaveStates::OnCmdSlotSave         },
94 				{ IDM_FILE_QUICK_SAVE_STATE_SLOT_4,      &SaveStates::OnCmdSlotSave         },
95 				{ IDM_FILE_QUICK_SAVE_STATE_SLOT_5,      &SaveStates::OnCmdSlotSave         },
96 				{ IDM_FILE_QUICK_SAVE_STATE_SLOT_6,      &SaveStates::OnCmdSlotSave         },
97 				{ IDM_FILE_QUICK_SAVE_STATE_SLOT_7,      &SaveStates::OnCmdSlotSave         },
98 				{ IDM_FILE_QUICK_SAVE_STATE_SLOT_8,      &SaveStates::OnCmdSlotSave         },
99 				{ IDM_FILE_QUICK_SAVE_STATE_SLOT_9,      &SaveStates::OnCmdSlotSave         },
100 				{ IDM_OPTIONS_AUTOSAVER,                 &SaveStates::OnCmdAutoSaverOptions },
101 				{ IDM_OPTIONS_AUTOSAVER_START,           &SaveStates::OnCmdAutoSaverStart   }
102 			};
103 
104 			menu.Commands().Add( this, commands );
105 
106 			menu[IDM_FILE_LOAD_NST].Disable();
107 			menu[IDM_FILE_SAVE_NST].Disable();
108 			menu[IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST].Disable();
109 			menu[IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST].Disable();
110 
111 			for (uint i=0; i < NUM_SLOTS; ++i)
112 			{
113 				menu[IDM_FILE_QUICK_LOAD_STATE_SLOT_1 + i].Disable();
114 				menu[IDM_FILE_QUICK_SAVE_STATE_SLOT_1 + i].Disable();
115 			}
116 
117 			menu[IDM_POS_FILE][IDM_POS_FILE_QUICKLOADSTATE].Disable();
118 			menu[IDM_POS_FILE][IDM_POS_FILE_QUICKSAVESTATE].Disable();
119 			menu[IDM_OPTIONS_AUTOSAVER_START].Disable();
120 
121 			ToggleAutoSaver( false );
122 
123 			UpdateMenuTexts();
124 		}
125 
~SaveStates()126 		SaveStates::~SaveStates()
127 		{
128 			ToggleAutoSaver( false );
129 		}
130 
UpdateMenuTexts() const131 		void SaveStates::UpdateMenuTexts() const
132 		{
133 			bool useSeconds = false;
134 
135 			for (const Slot* a=slots; a != slots+NUM_SLOTS-1; ++a)
136 			{
137 				if (a->data.Size())
138 				{
139 					for (const Slot* b=a+1; b != slots+NUM_SLOTS; ++b)
140 					{
141 						if (b->data.Size() && a->time.Almost( b->time ))
142 						{
143 							useSeconds = true;
144 							a = slots+NUM_SLOTS-2;
145 							break;
146 						}
147 					}
148 				}
149 			}
150 
151 			HeapString string( "&1  ..." );
152 
153 			for (uint i=0; i < NUM_SLOTS; ++i)
154 			{
155 				string[1] = '1' + i;
156 
157 				if (slots[i].data.Size())
158 				{
159 					string.ShrinkTo( 4 );
160 					string << slots[i].time.ToString( useSeconds );
161 				}
162 				else
163 				{
164 					string.ShrinkTo( 7 );
165 					string[4] = '.';
166 					string[5] = '.';
167 					string[6] = '.';
168 				}
169 
170 				menu[ IDM_FILE_QUICK_LOAD_STATE_SLOT_1 + i ].Text() << string;
171 				menu[ IDM_FILE_QUICK_SAVE_STATE_SLOT_1 + i ].Text() << string;
172 			}
173 		}
174 
OnEmuEvent(const Emulator::Event event,const Emulator::Data data)175 		void SaveStates::OnEmuEvent(const Emulator::Event event,const Emulator::Data data)
176 		{
177 			switch (event)
178 			{
179 				case Emulator::EVENT_POWER_ON:
180 				case Emulator::EVENT_POWER_OFF:
181 
182 					if (emulator.IsGame())
183 					{
184 						const bool on = (event == Emulator::EVENT_POWER_ON);
185 						const bool single = (on && emulator.NetPlayers() == 0);
186 
187 						menu[ IDM_FILE_SAVE_NST ].Enable( single );
188 						menu[ IDM_POS_FILE ][ IDM_POS_FILE_QUICKSAVESTATE ].Enable( on );
189 						menu[ IDM_OPTIONS_AUTOSAVER_START ].Enable( single );
190 
191 						for (uint i=IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST; i <= IDM_FILE_QUICK_SAVE_STATE_SLOT_9; ++i)
192 							menu[i].Enable( on );
193 
194 						ToggleAutoSaver( false );
195 					}
196 					break;
197 
198 				case Emulator::EVENT_LOAD:
199 
200 					if (emulator.IsGame())
201 					{
202 						const bool single = (emulator.NetPlayers() == 0);
203 
204 						menu[ IDM_FILE_LOAD_NST ].Enable( single );
205 						menu[ IDM_POS_FILE ][ IDM_POS_FILE_QUICKLOADSTATE ].Enable( single );
206 
207 						if (paths.SaveSlotImportingEnabled())
208 							ImportSlots( single );
209 					}
210 					break;
211 
212 				case Emulator::EVENT_UNLOAD:
213 
214 					menu[ IDM_FILE_LOAD_NST ].Disable();
215 					menu[ IDM_POS_FILE ][ IDM_POS_FILE_QUICKLOADSTATE ].Disable();
216 					menu[ IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST ].Disable();
217 
218 					for (uint i=0; i < NUM_SLOTS; ++i)
219 					{
220 						menu[ IDM_FILE_QUICK_LOAD_STATE_SLOT_1 + i ].Disable();
221 						slots[i].time.Clear();
222 						slots[i].data.Destroy();
223 					}
224 
225 					UpdateMenuTexts();
226 					break;
227 
228 				case Emulator::EVENT_MOVIE_PLAYING:
229 				case Emulator::EVENT_MOVIE_PLAYING_STOPPED:
230 				{
231 					const bool notPlaying = (event != Emulator::EVENT_MOVIE_PLAYING);
232 
233 					menu[ IDM_FILE_LOAD_NST ].Enable( notPlaying );
234 					menu[ IDM_POS_FILE ][ IDM_POS_FILE_QUICKLOADSTATE ].Enable( notPlaying );
235 					break;
236 				}
237 
238 				case Emulator::EVENT_NETPLAY_MODE:
239 
240 					menu[IDM_POS_OPTIONS][IDM_POS_OPTIONS_AUTOSAVER].Enable( !data );
241 					break;
242 			}
243 		}
244 
SaveToSlot(const uint index,const bool notify)245 		void SaveStates::SaveToSlot(const uint index,const bool notify)
246 		{
247 			if (emulator.SaveState( slots[index].data, paths.UseStateCompression(), Emulator::STICKY ))
248 			{
249 				if (emulator.NetPlayers() == 0)
250 				{
251 					menu[ IDM_FILE_QUICK_LOAD_STATE_SLOT_1 + index ].Enable();
252 					menu[ IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST ].Enable();
253 				}
254 
255 				slots[index].time.Set();
256 
257 				if (paths.SaveSlotExportingEnabled())
258 					ExportSlot( index );
259 
260 				if (notify)
261 					Io::Screen() << Resource::String( IDS_SCREEN_SAVE_STATE_TO_SLOT ).Invoke( wchar_t('1'+index) );
262 			}
263 			else
264 			{
265 				slots[index].time.Clear();
266 				slots[index].data.Destroy();
267 			}
268 
269 			UpdateMenuTexts();
270 		}
271 
LoadFromSlot(const uint index,const bool notify)272 		void SaveStates::LoadFromSlot(const uint index,const bool notify)
273 		{
274 			if (emulator.LoadState( slots[index].data, Emulator::STICKY ))
275 			{
276 				if (notify)
277 					Io::Screen() << Resource::String( IDS_SCREEN_LOAD_STATE_FROM_SLOT ).Invoke( wchar_t('1'+index) );
278 			}
279 		}
280 
Load(Collection::Buffer & data,const GenericString name) const281 		void SaveStates::Load(Collection::Buffer& data,const GenericString name) const
282 		{
283 			if (emulator.LoadState( data ))
284 			{
285 				const uint length = window.GetMaxMessageLength();
286 
287 				if (name.Length() && length > 20)
288 					Io::Screen() << Resource::String( IDS_SCREEN_LOAD_STATE_FROM ).Invoke( Path::Compact( name, length - 18 ) );
289 			}
290 		}
291 
ExportSlot(const uint index)292 		void SaveStates::ExportSlot(const uint index)
293 		{
294 			Path path( emulator.GetImagePath().Target().File() );
295 			NST_ASSERT( slots[index].data.Size() && path.Length() );
296 
297 			path.Extension() = L"ns1";
298 			path.Back() = '1' + index;
299 
300 			paths.Save( slots[index].data.Ptr(), slots[index].data.Size(), Paths::File::SLOTS, path );
301 		}
302 
ImportSlots(const bool canLoad)303 		void SaveStates::ImportSlots(const bool canLoad)
304 		{
305 			bool anyLoaded = false;
306 
307 			Path path( emulator.GetImagePath().Target().File() );
308 			NST_ASSERT( path.Length() );
309 
310 			path.Extension() = L"ns1";
311 
312 			Paths::File file;
313 
314 			for (uint i=0; i < NUM_SLOTS; ++i)
315 			{
316 				path.Back() = '1' + i;
317 
318 				if (paths.Load( file, Paths::File::SLOTS, path, Paths::QUIETLY ))
319 				{
320 					anyLoaded = true;
321 
322 					slots[i].data.Import( file.data );
323 					slots[i].time.Set( file.name.Ptr() );
324 
325 					if (canLoad)
326 					{
327 						menu[ IDM_FILE_QUICK_LOAD_STATE_SLOT_1 + i ].Enable();
328 						menu[ IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST ].Enable();
329 					}
330 				}
331 			}
332 
333 			if (anyLoaded)
334 				UpdateMenuTexts();
335 		}
336 
OnCmdStateLoad(uint)337 		void SaveStates::OnCmdStateLoad(uint)
338 		{
339 			Paths::File file;
340 
341 			if (paths.Load( file, Paths::File::STATE|Paths::File::SLOTS|Paths::File::ARCHIVE ) && emulator.LoadState( file.data ))
342 			{
343 				const uint length = window.GetMaxMessageLength();
344 
345 				if (length > 20)
346 					Io::Screen() << Resource::String( IDS_SCREEN_LOAD_STATE_FROM ).Invoke( Path::Compact( file.name, length - 18 ) );
347 			}
348 		}
349 
OnCmdStateSave(uint)350 		void SaveStates::OnCmdStateSave(uint)
351 		{
352 			const Path path( paths.BrowseSave( Paths::File::STATE, Paths::SUGGEST ) );
353 
354 			if (path.Length())
355 			{
356 				Collection::Buffer buffer;
357 
358 				if (emulator.SaveState( buffer, paths.UseStateCompression() ) && paths.Save( buffer.Ptr(), buffer.Size(), Paths::File::STATE, path ))
359 				{
360 					const uint length = window.GetMaxMessageLength();
361 
362 					if (length > 20)
363 						Io::Screen() << Resource::String( IDS_SCREEN_SAVE_STATE_TO ).Invoke( Path::Compact( path, length - 18 ) );
364 				}
365 			}
366 		}
367 
OnCmdSlotSave(uint id)368 		void SaveStates::OnCmdSlotSave(uint id)
369 		{
370 			SaveToSlot( id - IDM_FILE_QUICK_SAVE_STATE_SLOT_1 );
371 			Resume();
372 		}
373 
OnCmdSlotSaveOldest(uint)374 		void SaveStates::OnCmdSlotSaveOldest(uint)
375 		{
376 			SaveToSlot( std::min_element( slots, slots + NUM_SLOTS, Slot::Compare() ) - slots );
377 			Resume();
378 		}
379 
OnCmdSlotLoad(uint id)380 		void SaveStates::OnCmdSlotLoad(uint id)
381 		{
382 			LoadFromSlot( id - IDM_FILE_QUICK_LOAD_STATE_SLOT_1 );
383 			Resume();
384 		}
385 
OnCmdSlotLoadNewest(uint)386 		void SaveStates::OnCmdSlotLoadNewest(uint)
387 		{
388 			LoadFromSlot( std::max_element( slots, slots + NUM_SLOTS, Slot::Compare() ) - slots );
389 			Resume();
390 		}
391 
OnCmdAutoSaverOptions(uint)392 		void SaveStates::OnCmdAutoSaverOptions(uint)
393 		{
394 			autoSaver->Open();
395 			ToggleAutoSaver( false );
396 		}
397 
OnCmdAutoSaverStart(uint)398 		void SaveStates::OnCmdAutoSaverStart(uint)
399 		{
400 			ToggleAutoSaver( !autoSaveEnabled );
401 			Resume();
402 		}
403 
OnTimerAutoSave()404 		uint SaveStates::OnTimerAutoSave()
405 		{
406 			if (autoSaveEnabled)
407 			{
408 				const Path stateFile( autoSaver->GetStateFile() );
409 
410 				if (stateFile.Length())
411 				{
412 					Collection::Buffer buffer;
413 
414 					if
415 					(
416 						emulator.SaveState( buffer, paths.UseStateCompression(), Emulator::STICKY ) &&
417 						paths.Save( buffer.Ptr(), buffer.Size(), Paths::File::STATE, stateFile, Paths::STICKY ) &&
418 						autoSaver->ShouldNotify()
419 					)
420 					{
421 						const uint length = window.GetMaxMessageLength();
422 
423 						if (length > 20)
424 							Io::Screen() << Resource::String( IDS_SCREEN_SAVE_STATE_TO ).Invoke( Path::Compact( stateFile, length - 18 ) );
425 					}
426 				}
427 				else
428 				{
429 					SaveToSlot( Window::AutoSaver::DEFAULT_SAVE_SLOT, autoSaver->ShouldNotify() );
430 				}
431 			}
432 
433 			return autoSaveEnabled;
434 		}
435 
ToggleAutoSaver(const bool enable)436 		void SaveStates::ToggleAutoSaver(const bool enable)
437 		{
438 			menu[ IDM_OPTIONS_AUTOSAVER_START ].Text() << Resource::String(enable ? IDS_TEXT_STOP : IDS_TEXT_START);
439 
440 			if (autoSaveEnabled != enable)
441 			{
442 				autoSaveEnabled = enable;
443 				Io::Screen() << Resource::String( enable ? IDS_AUTOSAVER_START : IDS_AUTOSAVER_STOP );
444 			}
445 
446 			if (enable)
447 				window.Get().StartTimer( this, &SaveStates::OnTimerAutoSave, autoSaver->GetInterval() );
448 			else
449 				window.Get().StopTimer( this, &SaveStates::OnTimerAutoSave );
450 		}
451 	}
452 }
453