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