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 <cstdlib> |
19 | |
20 | #include "bspf.hxx" |
21 | #include "Logger.hxx" |
22 | #include "MediaFactory.hxx" |
23 | #include "Console.hxx" |
24 | #include "Event.hxx" |
25 | #include "EventHandler.hxx" |
26 | #include "FrameBuffer.hxx" |
27 | #include "PropsSet.hxx" |
28 | #include "Sound.hxx" |
29 | #include "Settings.hxx" |
30 | #include "FSNode.hxx" |
31 | #include "OSystem.hxx" |
32 | #include "PNGLibrary.hxx" |
33 | #include "System.hxx" |
34 | #include "TIASurface.hxx" |
35 | #include "ProfilingRunner.hxx" |
36 | |
37 | #include "ThreadDebugging.hxx" |
38 | |
39 | #ifdef DEBUGGER_SUPPORT |
40 | #include "Debugger.hxx" |
41 | #endif |
42 | |
43 | #ifdef CHEATCODE_SUPPORT |
44 | #include "CheatManager.hxx" |
45 | #endif |
46 | |
47 | /** |
48 | Parse the commandline arguments and store into the appropriate hashmap. |
49 | |
50 | Keys without a corresponding value are assumed to be boolean, and set to true. |
51 | Some keys are used only by the main function; these are placed in localOpts. |
52 | The rest are needed globally, and are placed in globalOpts. |
53 | */ |
54 | void parseCommandLine(int ac, char* av[], |
55 | Settings::Options& globalOpts, Settings::Options& localOpts); |
56 | |
57 | /** |
58 | Checks the commandline for special settings that are used by various ports |
59 | to use a specific 'base directory'. |
60 | |
61 | This needs to be done separately, before either an OSystem or Settings |
62 | object can be created, since they both depend on each other, and a |
63 | variable basedir implies a different location for the settings file. |
64 | |
65 | This function will call OSystem::overrideBaseDir() when either of the |
66 | applicable arguments are found, and then remove them from the argument |
67 | list. |
68 | */ |
69 | void checkForCustomBaseDir(Settings::Options& options); |
70 | |
71 | /** |
72 | Checks whether the commandline contains an argument corresponding to |
73 | starting a profile session. |
74 | */ |
75 | bool isProfilingRun(int ac, char* av[]); |
76 | |
77 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
78 | void parseCommandLine(int ac, char* av[], |
79 | Settings::Options& globalOpts, Settings::Options& localOpts) |
80 | { |
81 | localOpts["ROMFILE" ] = "" ; // make sure we have an entry for this |
82 | |
83 | for(int i = 1; i < ac; ++i) |
84 | { |
85 | string key = av[i]; |
86 | if(key[0] == '-') |
87 | { |
88 | key = key.substr(1); |
89 | |
90 | // Certain options are used only in the main function |
91 | if(key == "help" || key == "listrominfo" || key == "rominfo" || key == "takesnapshot" ) |
92 | { |
93 | localOpts[key] = true; |
94 | continue; |
95 | } |
96 | // Take care of arguments without an option that are needed globally |
97 | if(key == "debug" || key == "holdselect" || key == "holdreset" ) |
98 | { |
99 | globalOpts[key] = true; |
100 | continue; |
101 | } |
102 | // Some ports have the ability to override the base directory where all |
103 | // configuration files are stored; we check for those next |
104 | if(key == "baseinappdir" ) |
105 | { |
106 | localOpts[key] = true; |
107 | continue; |
108 | } |
109 | |
110 | if(++i >= ac) |
111 | { |
112 | cerr << "Missing argument for '" << key << "'" << endl; |
113 | continue; |
114 | } |
115 | if(key == "basedir" || key == "break" ) |
116 | localOpts[key] = av[i]; |
117 | else |
118 | globalOpts[key] = av[i]; |
119 | } |
120 | else |
121 | localOpts["ROMFILE" ] = key; |
122 | } |
123 | |
124 | #if 0 |
125 | cout << "Global opts:" << endl; |
126 | for(const auto& x: globalOpts) |
127 | cout << " -> " << x.first << ": " << x.second << endl; |
128 | cout << "Local opts:" << endl; |
129 | for(const auto& x: localOpts) |
130 | cout << " -> " << x.first << ": " << x.second << endl; |
131 | #endif |
132 | } |
133 | |
134 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
135 | void checkForCustomBaseDir(Settings::Options& options) |
136 | { |
137 | // If both of these are activated, the 'base in app dir' takes precedence |
138 | auto it = options.find("baseinappdir" ); |
139 | if(it != options.end()) |
140 | OSystem::overrideBaseDirWithApp(); |
141 | else |
142 | { |
143 | it = options.find("basedir" ); |
144 | if(it != options.end()) |
145 | OSystem::overrideBaseDir(it->second.toString()); |
146 | } |
147 | } |
148 | |
149 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
150 | bool isProfilingRun(int ac, char* av[]) { |
151 | if (ac <= 1) return false; |
152 | |
153 | return string(av[1]) == "-profile" ; |
154 | } |
155 | |
156 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
157 | #if defined(BSPF_MACOS) |
158 | int stellaMain(int ac, char* av[]) |
159 | #else |
160 | int main(int ac, char* av[]) |
161 | #endif |
162 | { |
163 | SET_MAIN_THREAD; |
164 | |
165 | std::ios_base::sync_with_stdio(false); |
166 | |
167 | if (isProfilingRun(ac, av)) { |
168 | ProfilingRunner runner(ac, av); |
169 | |
170 | return runner.run() ? 0 : 1; |
171 | } |
172 | |
173 | unique_ptr<OSystem> theOSystem; |
174 | |
175 | auto Cleanup = [&theOSystem]() { |
176 | if(theOSystem) |
177 | { |
178 | Logger::debug("Cleanup from main" ); |
179 | theOSystem->saveConfig(); |
180 | theOSystem.reset(); // Force delete of object |
181 | } |
182 | MediaFactory::cleanUp(); // Finish any remaining cleanup |
183 | |
184 | return 0; |
185 | }; |
186 | |
187 | // Parse the commandline arguments |
188 | // They are placed in different maps depending on whether they're used |
189 | // locally or globally |
190 | Settings::Options globalOpts, localOpts; |
191 | parseCommandLine(ac, av, globalOpts, localOpts); |
192 | |
193 | // Check for custom base directory; some ports make use of this |
194 | checkForCustomBaseDir(localOpts); |
195 | |
196 | // Create the parent OSystem object and initialize settings |
197 | theOSystem = MediaFactory::createOSystem(); |
198 | theOSystem->loadConfig(globalOpts); |
199 | |
200 | // Create the full OSystem after the settings, since settings are |
201 | // probably needed for defaults |
202 | Logger::debug("Creating the OSystem ..." ); |
203 | if(!theOSystem->create()) |
204 | { |
205 | Logger::error("ERROR: Couldn't create OSystem" ); |
206 | return Cleanup(); |
207 | } |
208 | |
209 | // Check to see if the user requested info about a specific ROM, |
210 | // or the list of internal ROMs |
211 | // If so, show the information and immediately exit |
212 | string romfile = localOpts["ROMFILE" ].toString(); |
213 | if(localOpts["listrominfo" ].toBool()) |
214 | { |
215 | Logger::debug("Showing output from 'listrominfo' ..." ); |
216 | theOSystem->propSet().print(); |
217 | return Cleanup(); |
218 | } |
219 | else if(localOpts["rominfo" ].toBool()) |
220 | { |
221 | Logger::debug("Showing output from 'rominfo' ..." ); |
222 | FilesystemNode romnode(romfile); |
223 | Logger::error(theOSystem->getROMInfo(romnode)); |
224 | |
225 | return Cleanup(); |
226 | } |
227 | else if(localOpts["help" ].toBool()) |
228 | { |
229 | Logger::debug("Displaying usage" ); |
230 | theOSystem->settings().usage(); |
231 | return Cleanup(); |
232 | } |
233 | |
234 | //// Main loop //// |
235 | // First we check if a ROM is specified on the commandline. If so, and if |
236 | // the ROM actually exists, use it to create a new console. |
237 | // Next we check if a directory is specified on the commandline. If so, |
238 | // open the rom launcher in that directory. |
239 | // If not, use the built-in ROM launcher. In this case, we enter 'launcher' |
240 | // mode and let the main event loop take care of opening a new console/ROM. |
241 | FilesystemNode romnode(romfile); |
242 | if(romfile == "" || romnode.isDirectory()) |
243 | { |
244 | Logger::debug("Attempting to use ROM launcher ..." ); |
245 | bool launcherOpened = romfile != "" ? |
246 | theOSystem->createLauncher(romnode.getPath()) : theOSystem->createLauncher(); |
247 | if(!launcherOpened) |
248 | { |
249 | Logger::debug("Launcher could not be started, showing usage" ); |
250 | theOSystem->settings().usage(); |
251 | return Cleanup(); |
252 | } |
253 | } |
254 | else |
255 | { |
256 | try |
257 | { |
258 | const string& result = theOSystem->createConsole(romnode); |
259 | if(result != EmptyString) |
260 | return Cleanup(); |
261 | |
262 | #if 0 |
263 | TODO: Fix this to use functionality from OSystem::mainLoop |
264 | if(localOpts["takesnapshot" ].toBool()) |
265 | { |
266 | for(int i = 0; i < 30; ++i) theOSystem->frameBuffer().update(); |
267 | // theOSystem->frameBuffer().tiaSurface().saveSnapShot(); |
268 | theOSystem->png().takeSnapshot(); |
269 | return Cleanup(); |
270 | } |
271 | #endif |
272 | } |
273 | catch(const runtime_error& e) |
274 | { |
275 | Logger::error(e.what()); |
276 | return Cleanup(); |
277 | } |
278 | |
279 | #ifdef DEBUGGER_SUPPORT |
280 | // Set up any breakpoint that was on the command line |
281 | if(localOpts["break" ].toString() != "" ) |
282 | { |
283 | Debugger& dbg = theOSystem->debugger(); |
284 | uInt16 bp = uInt16(dbg.stringToValue(localOpts["break" ].toString())); |
285 | dbg.setBreakPoint(bp); |
286 | } |
287 | #endif |
288 | } |
289 | |
290 | // Start the main loop, and don't exit until the user issues a QUIT command |
291 | Logger::debug("Starting main loop ..." ); |
292 | theOSystem->mainLoop(); |
293 | Logger::debug("Finished main loop ..." ); |
294 | |
295 | // Cleanup time ... |
296 | return Cleanup(); |
297 | } |
298 | |