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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
33 | StateManager::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
43 | StateManager::~StateManager() |
44 | { |
45 | } |
46 | |
47 | #if 0 |
48 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
49 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
131 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
144 | bool StateManager::(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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
155 | bool StateManager::rewindStates(uInt32 numStates) |
156 | { |
157 | RewindManager& r = myOSystem.state().rewindManager(); |
158 | return r.rewindStates(numStates); |
159 | } |
160 | |
161 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
162 | bool StateManager::unwindStates(uInt32 numStates) |
163 | { |
164 | RewindManager& r = myOSystem.state().rewindManager(); |
165 | return r.unwindStates(numStates); |
166 | } |
167 | |
168 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
169 | bool StateManager::windStates(uInt32 numStates, bool unwind) |
170 | { |
171 | RewindManager& r = myOSystem.state().rewindManager(); |
172 | return r.windStates(numStates, unwind); |
173 | } |
174 | |
175 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
176 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
203 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
249 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
302 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
313 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
326 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
350 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
377 | void 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 | |