1//============================================================================
2//
3// SSSS tt lll lll
4// SS SS tt ll ll
5// SS tttttt eeee ll ll aaaa
6// SSSS tt ee ee ll ll aa
7// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8// SS SS tt ee ll ll aa aa
9// SSSS ttt eeeee llll llll aaaaa
10//
11// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
12// and the Stella Team
13//
14// See the file "License.txt" for information on usage and redistribution of
15// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16//============================================================================
17
18#include "OSystem.hxx"
19#include "Settings.hxx"
20#include "Console.hxx"
21#include "Cart.hxx"
22#include "Control.hxx"
23#include "Switches.hxx"
24#include "System.hxx"
25#include "Serializable.hxx"
26#include "RewindManager.hxx"
27
28#include "StateManager.hxx"
29
30// #define MOVIE_HEADER "03030000movie"
31
32// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
33StateManager::StateManager(OSystem& osystem)
34 : myOSystem(osystem),
35 myCurrentSlot(0),
36 myActiveMode(Mode::Off)
37{
38 myRewindManager = make_unique<RewindManager>(myOSystem, *this);
39 reset();
40}
41
42// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
43StateManager::~StateManager()
44{
45}
46
47#if 0
48// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
49void StateManager::toggleRecordMode()
50{
51 if(myActiveMode != kMovieRecordMode) // Turn on movie record mode
52 {
53 myActiveMode = kOffMode;
54
55 string moviefile = /*myOSystem.baseDir() +*/ "test.inp";
56 if(myMovieWriter.isOpen())
57 myMovieWriter.close();
58 if(!myMovieWriter.open(moviefile))
59 return false;
60
61 // Prepend the ROM md5 so this state file only works with that ROM
62 myMovieWriter.putString(myOSystem.console().properties().get(Cartridge_MD5));
63
64 if(!myOSystem.console().save(myMovieWriter))
65 return false;
66
67 // Save controller types for this ROM
68 // We need to check this, since some controllers save more state than
69 // normal, and those states files wouldn't be compatible with normal
70 // controllers.
71 myMovieWriter.putString(
72 myOSystem.console().controller(Controller::Jack::Left).name());
73 myMovieWriter.putString(
74 myOSystem.console().controller(Controller::Jack::Right).name());
75
76 // If we get this far, we're really in movie record mode
77 myActiveMode = kMovieRecordMode;
78 }
79 else // Turn off movie record mode
80 {
81 myActiveMode = kOffMode;
82 myMovieWriter.close();
83 return false;
84 }
85
86 return myActiveMode == kMovieRecordMode;
87////////////////////////////////////////////////////////
88// FIXME - For now, I'm going to use this to activate movie playback
89 // Close the writer, since we're about to re-open in read mode
90 myMovieWriter.close();
91
92 if(myActiveMode != kMoviePlaybackMode) // Turn on movie playback mode
93 {
94 myActiveMode = kOffMode;
95
96 string moviefile = /*myOSystem.baseDir() + */ "test.inp";
97 if(myMovieReader.isOpen())
98 myMovieReader.close();
99 if(!myMovieReader.open(moviefile))
100 return false;
101
102 // Check the ROM md5
103 if(myMovieReader.getString() !=
104 myOSystem.console().properties().get(Cartridge_MD5))
105 return false;
106
107 if(!myOSystem.console().load(myMovieReader))
108 return false;
109
110 // Check controller types
111 const string& left = myMovieReader.getString();
112 const string& right = myMovieReader.getString();
113
114 if(left != myOSystem.console().controller(Controller::Jack::Left).name() ||
115 right != myOSystem.console().controller(Controller::Jack::Right).name())
116 return false;
117
118 // If we get this far, we're really in movie record mode
119 myActiveMode = kMoviePlaybackMode;
120 }
121 else // Turn off movie playback mode
122 {
123 myActiveMode = kOffMode;
124 myMovieReader.close();
125 return false;
126 }
127}
128#endif
129
130// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
131void StateManager::toggleTimeMachine()
132{
133 bool devSettings = myOSystem.settings().getBool("dev.settings");
134
135 myActiveMode = myActiveMode == Mode::TimeMachine ? Mode::Off : Mode::TimeMachine;
136 if(myActiveMode == Mode::TimeMachine)
137 myOSystem.frameBuffer().showMessage("Time Machine enabled");
138 else
139 myOSystem.frameBuffer().showMessage("Time Machine disabled");
140 myOSystem.settings().setValue(devSettings ? "dev.timemachine" : "plr.timemachine", myActiveMode == Mode::TimeMachine);
141}
142
143// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
144bool StateManager::addExtraState(const string& message)
145{
146 if(myActiveMode == Mode::TimeMachine)
147 {
148 RewindManager& r = myOSystem.state().rewindManager();
149 return r.addState(message);
150 }
151 return false;
152}
153
154// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
155bool StateManager::rewindStates(uInt32 numStates)
156{
157 RewindManager& r = myOSystem.state().rewindManager();
158 return r.rewindStates(numStates);
159}
160
161// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
162bool StateManager::unwindStates(uInt32 numStates)
163{
164 RewindManager& r = myOSystem.state().rewindManager();
165 return r.unwindStates(numStates);
166}
167
168// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
169bool StateManager::windStates(uInt32 numStates, bool unwind)
170{
171 RewindManager& r = myOSystem.state().rewindManager();
172 return r.windStates(numStates, unwind);
173}
174
175// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
176void StateManager::update()
177{
178 switch(myActiveMode)
179 {
180 case Mode::TimeMachine:
181 myRewindManager->addState("Time Machine", true);
182 break;
183
184#if 0
185 case Mode::MovieRecord:
186 myOSystem.console().controller(Controller::Jack::Left).save(myMovieWriter);
187 myOSystem.console().controller(Controller::Jack::Right).save(myMovieWriter);
188 myOSystem.console().switches().save(myMovieWriter);
189 break;
190
191 case Mode::MoviePlayback:
192 myOSystem.console().controller(Controller::Jack::Left).load(myMovieReader);
193 myOSystem.console().controller(Controller::Jack::Right).load(myMovieReader);
194 myOSystem.console().switches().load(myMovieReader);
195 break;
196#endif
197 default:
198 break;
199 }
200}
201
202// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
203void StateManager::loadState(int slot)
204{
205 if(myOSystem.hasConsole())
206 {
207 if(slot < 0) slot = myCurrentSlot;
208
209 ostringstream buf;
210 buf << myOSystem.stateDir()
211 << myOSystem.console().properties().get(PropType::Cart_Name)
212 << ".st" << slot;
213
214 // Make sure the file can be opened in read-only mode
215 Serializer in(buf.str(), Serializer::Mode::ReadOnly);
216 if(!in)
217 {
218 buf.str("");
219 buf << "Can't open/load from state file " << slot;
220 myOSystem.frameBuffer().showMessage(buf.str());
221 return;
222 }
223
224 // First test if we have a valid header
225 // If so, do a complete state load using the Console
226 buf.str("");
227 try
228 {
229 if(in.getString() != STATE_HEADER)
230 buf << "Incompatible state " << slot << " file";
231 else
232 {
233 if(myOSystem.console().load(in))
234 buf << "State " << slot << " loaded";
235 else
236 buf << "Invalid data in state " << slot << " file";
237 }
238 }
239 catch(...)
240 {
241 buf << "Invalid data in state " << slot << " file";
242 }
243
244 myOSystem.frameBuffer().showMessage(buf.str());
245 }
246}
247
248// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
249void StateManager::saveState(int slot)
250{
251 if(myOSystem.hasConsole())
252 {
253 if(slot < 0) slot = myCurrentSlot;
254
255 ostringstream buf;
256 buf << myOSystem.stateDir()
257 << myOSystem.console().properties().get(PropType::Cart_Name)
258 << ".st" << slot;
259
260 // Make sure the file can be opened for writing
261 Serializer out(buf.str());
262 if(!out)
263 {
264 buf.str("");
265 buf << "Can't open/save to state file " << slot;
266 myOSystem.frameBuffer().showMessage(buf.str());
267 return;
268 }
269
270 try
271 {
272 // Add header so that if the state format changes in the future,
273 // we'll know right away, without having to parse the rest of the file
274 out.putString(STATE_HEADER);
275 }
276 catch(...)
277 {
278 buf << "Error saving state " << slot;
279 myOSystem.frameBuffer().showMessage(buf.str());
280 return;
281 }
282
283 // Do a complete state save using the Console
284 buf.str("");
285 if(myOSystem.console().save(out))
286 {
287 buf << "State " << slot << " saved";
288 if(myOSystem.settings().getBool("autoslot"))
289 {
290 myCurrentSlot = (slot + 1) % 10;
291 buf << ", switching to slot " << myCurrentSlot;
292 }
293 }
294 else
295 buf << "Error saving state " << slot;
296
297 myOSystem.frameBuffer().showMessage(buf.str());
298 }
299}
300
301// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
302void StateManager::changeState()
303{
304 myCurrentSlot = (myCurrentSlot + 1) % 10;
305
306 // Print appropriate message
307 ostringstream buf;
308 buf << "Changed to slot " << myCurrentSlot;
309 myOSystem.frameBuffer().showMessage(buf.str());
310}
311
312// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
313void StateManager::toggleAutoSlot()
314{
315 bool autoSlot = !myOSystem.settings().getBool("autoslot");
316
317 // Print appropriate message
318 ostringstream buf;
319 buf << "Automatic slot change " << (autoSlot ? "enabled" : "disabled");
320 myOSystem.frameBuffer().showMessage(buf.str());
321
322 myOSystem.settings().setValue("autoslot", autoSlot);
323}
324
325// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
326bool StateManager::loadState(Serializer& in)
327{
328 try
329 {
330 if(myOSystem.hasConsole())
331 {
332 // Make sure the file can be opened for reading
333 if(in)
334 {
335 // First test if we have a valid header
336 // If so, do a complete state load using the Console
337 return in.getString() == STATE_HEADER &&
338 myOSystem.console().load(in);
339 }
340 }
341 }
342 catch(...)
343 {
344 cerr << "ERROR: StateManager::loadState(Serializer&)" << endl;
345 }
346 return false;
347}
348
349// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
350bool StateManager::saveState(Serializer& out)
351{
352 try
353 {
354 if(myOSystem.hasConsole())
355 {
356 // Make sure the file can be opened for writing
357 if(out)
358 {
359 // Add header so that if the state format changes in the future,
360 // we'll know right away, without having to parse the rest of the file
361 out.putString(STATE_HEADER);
362
363 // Do a complete state save using the Console
364 if(myOSystem.console().save(out))
365 return true;
366 }
367 }
368 }
369 catch(...)
370 {
371 cerr << "ERROR: StateManager::saveState(Serializer&)" << endl;
372 }
373 return false;
374}
375
376// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
377void StateManager::reset()
378{
379 myRewindManager->clear();
380 myActiveMode = myOSystem.settings().getBool(
381 myOSystem.settings().getBool("dev.settings") ? "dev.timemachine" : "plr.timemachine") ? Mode::TimeMachine : Mode::Off;
382
383#if 0
384 myCurrentSlot = 0;
385
386 switch(myActiveMode)
387 {
388 case kMovieRecordMode:
389 myMovieWriter.close();
390 break;
391
392 case kMoviePlaybackMode:
393 myMovieReader.close();
394 break;
395
396 default:
397 break;
398 }
399 myActiveMode = kOffMode;
400#endif
401}
402