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 "Console.hxx"
20#include "Cheat.hxx"
21#include "Settings.hxx"
22#include "CheetahCheat.hxx"
23#include "BankRomCheat.hxx"
24#include "RamCheat.hxx"
25#include "Vec.hxx"
26
27#include "CheatManager.hxx"
28
29// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
30CheatManager::CheatManager(OSystem& osystem)
31 : myOSystem(osystem),
32 myListIsDirty(false)
33{
34}
35
36// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
37bool CheatManager::add(const string& name, const string& code,
38 bool enable, int idx)
39{
40 shared_ptr<Cheat> cheat = createCheat(name, code);
41 if(!cheat)
42 return false;
43
44 // Delete duplicate entries
45 for(uInt32 i = 0; i < myCheatList.size(); ++i)
46 {
47 if(myCheatList[i]->name() == name || myCheatList[i]->code() == code)
48 {
49 Vec::removeAt(myCheatList, i);
50 break;
51 }
52 }
53
54 // Add the cheat to the main cheat list
55 if(idx == -1)
56 myCheatList.push_back(cheat);
57 else
58 Vec::insertAt(myCheatList, idx, cheat);
59
60 // And enable/disable it (the cheat knows how to enable or disable itself)
61 if(enable)
62 cheat->enable();
63 else
64 cheat->disable();
65
66 return true;
67}
68
69// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
70void CheatManager::remove(int idx)
71{
72 if(uInt32(idx) < myCheatList.size())
73 {
74 // This will also remove it from the per-frame list (if applicable)
75 myCheatList[idx]->disable();
76
77 // Then remove it from the cheatlist entirely
78 Vec::removeAt(myCheatList, idx);
79 }
80}
81
82// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
83void CheatManager::addPerFrame(const string& name, const string& code, bool enable)
84{
85 // The actual cheat will always be in the main list; we look there first
86 shared_ptr<Cheat> cheat;
87 for(auto& c: myCheatList)
88 {
89 if(c->name() == name || c->code() == code)
90 {
91 cheat = c;
92 break;
93 }
94 }
95
96 // Make sure there are no duplicates
97 bool found = false;
98 uInt32 i;
99 for(i = 0; i < myPerFrameList.size(); ++i)
100 {
101 if(myPerFrameList[i]->code() == cheat->code())
102 {
103 found = true;
104 break;
105 }
106 }
107
108 if(enable)
109 {
110 if(!found)
111 myPerFrameList.push_back(cheat);
112 }
113 else
114 {
115 if(found)
116 Vec::removeAt(myPerFrameList, i);
117 }
118}
119
120// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
121void CheatManager::addOneShot(const string& name, const string& code)
122{
123 // Evaluate this cheat once, and then immediately discard it
124 shared_ptr<Cheat> cheat = createCheat(name, code);
125 if(cheat)
126 cheat->evaluate();
127}
128
129// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
130shared_ptr<Cheat> CheatManager::createCheat(const string& name, const string& code) const
131{
132 if(!isValidCode(code))
133 return nullptr;
134
135 // Create new cheat based on string length
136 switch(code.size())
137 {
138 case 4: return make_shared<RamCheat>(myOSystem, name, code);
139 case 6: return make_shared<CheetahCheat>(myOSystem, name, code);
140 case 7: return make_shared<BankRomCheat>(myOSystem, name, code);
141 case 8: return make_shared<BankRomCheat>(myOSystem, name, code);
142 default: return nullptr;
143 }
144}
145
146// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
147void CheatManager::parse(const string& cheats)
148{
149 StringList s;
150 string::size_type lastPos = cheats.find_first_not_of(",", 0);
151 string::size_type pos = cheats.find_first_of(",", lastPos);
152 string cheat, name, code;
153
154 // Split string by comma, getting each cheat
155 while(string::npos != pos || string::npos != lastPos)
156 {
157 // Get the next cheat
158 cheat = cheats.substr(lastPos, pos - lastPos);
159
160 // Split cheat by colon, separating each part
161 string::size_type lastColonPos = cheat.find_first_not_of(":", 0);
162 string::size_type colonPos = cheat.find_first_of(":", lastColonPos);
163 while(string::npos != colonPos || string::npos != lastColonPos)
164 {
165 s.push_back(cheat.substr(lastColonPos, colonPos - lastColonPos));
166 lastColonPos = cheat.find_first_not_of(":", colonPos);
167 colonPos = cheat.find_first_of(":", lastColonPos);
168 }
169
170 // Account for variable number of items specified for cheat
171 switch(s.size())
172 {
173 case 1:
174 name = s[0];
175 code = name;
176 add(name, code, true);
177 break;
178
179 case 2:
180 name = s[0];
181 code = s[1];
182 add(name, code, true);
183 break;
184
185 case 3:
186 name = s[0];
187 code = s[1];
188 add(name, code, s[2] == "1");
189 break;
190 }
191 s.clear();
192
193 lastPos = cheats.find_first_not_of(",", pos);
194 pos = cheats.find_first_of(",", lastPos);
195 }
196}
197
198// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
199void CheatManager::enable(const string& code, bool enable)
200{
201 for(const auto& cheat: myCheatList)
202 {
203 if(cheat->code() == code)
204 {
205 if(enable)
206 cheat->enable();
207 else
208 cheat->disable();
209 break;
210 }
211 }
212}
213
214// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
215void CheatManager::loadCheatDatabase()
216{
217 const string& cheatfile = myOSystem.cheatFile();
218 ifstream in(cheatfile);
219 if(!in)
220 return;
221
222 string line, md5, cheat;
223 string::size_type one, two, three, four;
224
225 // Loop reading cheats
226 while(getline(in, line))
227 {
228 if(line.length() == 0)
229 continue;
230
231 one = line.find("\"", 0);
232 two = line.find("\"", one + 1);
233 three = line.find("\"", two + 1);
234 four = line.find("\"", three + 1);
235
236 // Invalid line if it doesn't contain 4 quotes
237 if((one == string::npos) || (two == string::npos) ||
238 (three == string::npos) || (four == string::npos))
239 break;
240
241 // Otherwise get the ms5sum and associated cheats
242 md5 = line.substr(one + 1, two - one - 1);
243 cheat = line.substr(three + 1, four - three - 1);
244
245 myCheatMap.emplace(md5, cheat);
246 }
247
248 myListIsDirty = false;
249}
250
251// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
252void CheatManager::saveCheatDatabase()
253{
254 if(!myListIsDirty)
255 return;
256
257 const string& cheatfile = myOSystem.cheatFile();
258 ofstream out(cheatfile);
259 if(!out)
260 return;
261
262 for(const auto& iter: myCheatMap)
263 out << "\"" << iter.first << "\" " << "\"" << iter.second << "\"" << endl;
264}
265
266// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
267void CheatManager::loadCheats(const string& md5sum)
268{
269 myPerFrameList.clear();
270 myCheatList.clear();
271 myCurrentCheat = "";
272
273 // Set up any cheatcodes that was on the command line
274 // (and remove the key from the settings, so they won't get set again)
275 const string& cheats = myOSystem.settings().getString("cheat");
276 if(cheats != "")
277 myOSystem.settings().setValue("cheat", "");
278
279 const auto& iter = myCheatMap.find(md5sum);
280 if(iter == myCheatMap.end() && cheats == "")
281 return;
282
283 // Remember the cheats for this ROM
284 myCurrentCheat = iter->second;
285
286 // Parse the cheat list, constructing cheats and adding them to the manager
287 parse(iter->second + cheats);
288}
289
290// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
291void CheatManager::saveCheats(const string& md5sum)
292{
293 ostringstream cheats;
294 for(uInt32 i = 0; i < myCheatList.size(); ++i)
295 {
296 cheats << myCheatList[i]->name() << ":"
297 << myCheatList[i]->code() << ":"
298 << myCheatList[i]->enabled();
299 if(i+1 < myCheatList.size())
300 cheats << ",";
301 }
302
303 bool changed = cheats.str() != myCurrentCheat;
304
305 // Only update the list if absolutely necessary
306 if(changed)
307 {
308 auto iter = myCheatMap.find(md5sum);
309
310 // Erase old entry and add a new one only if it's changed
311 if(iter != myCheatMap.end())
312 myCheatMap.erase(iter);
313
314 // Add new entry only if there are any cheats defined
315 if(cheats.str() != "")
316 myCheatMap.emplace(md5sum, cheats.str());
317 }
318
319 // Update the dirty flag
320 myListIsDirty = myListIsDirty || changed;
321 myPerFrameList.clear();
322 myCheatList.clear();
323}
324
325// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
326bool CheatManager::isValidCode(const string& code) const
327{
328 for(char c: code)
329 if(!isxdigit(c))
330 return false;
331
332 uInt32 length = uInt32(code.length());
333 return (length == 4 || length == 6 || length == 7 || length == 8);
334}
335