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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
30 | CheatManager::CheatManager(OSystem& osystem) |
31 | : myOSystem(osystem), |
32 | myListIsDirty(false) |
33 | { |
34 | } |
35 | |
36 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
37 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
70 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
83 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
121 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
130 | shared_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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
147 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
199 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
215 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
252 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
267 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
291 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
326 | bool 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 | |