1 /*****************************************************************************
2 * Copyright (c) 2014-2020 OpenRCT2 developers
3 *
4 * For a complete list of all authors, please refer to contributors.md
5 * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6 *
7 * OpenRCT2 is licensed under the GNU General Public License version 3.
8 *****************************************************************************/
9
10 #include "../Context.h"
11 #include "../OpenRCT2.h"
12 #include "../PlatformEnvironment.h"
13 #include "../Version.h"
14 #include "../config/Config.h"
15 #include "../core/Console.hpp"
16 #include "../core/Guard.hpp"
17 #include "../core/Memory.hpp"
18 #include "../core/Path.hpp"
19 #include "../core/String.hpp"
20 #include "../localisation/Language.h"
21 #include "../network/network.h"
22 #include "../object/ObjectRepository.h"
23 #include "../platform/Crash.h"
24 #include "../platform/Platform2.h"
25 #include "CommandLine.hpp"
26
27 #include <ctime>
28 #include <iterator>
29 #include <string>
30
31 #ifdef USE_BREAKPAD
32 # define IMPLIES_SILENT_BREAKPAD ", implies --silent-breakpad"
33 #else
34 # define IMPLIES_SILENT_BREAKPAD
35 #endif // USE_BREAKPAD
36
37 #ifndef DISABLE_NETWORK
38 int32_t gNetworkStart = NETWORK_MODE_NONE;
39 std::string gNetworkStartHost;
40 int32_t gNetworkStartPort = NETWORK_DEFAULT_PORT;
41 std::string gNetworkStartAddress;
42
43 static uint32_t _port = 0;
44 static char* _address = nullptr;
45 #endif
46
47 static bool _help = false;
48 static bool _version = false;
49 static bool _noInstall = false;
50 static bool _all = false;
51 static bool _about = false;
52 static bool _verbose = false;
53 static bool _headless = false;
54 static utf8* _password = nullptr;
55 static utf8* _userDataPath = nullptr;
56 static utf8* _openrct2DataPath = nullptr;
57 static utf8* _rct1DataPath = nullptr;
58 static utf8* _rct2DataPath = nullptr;
59 static bool _silentBreakpad = false;
60
61 // clang-format off
62 static constexpr const CommandLineOptionDefinition StandardOptions[]
63 {
64 { CMDLINE_TYPE_SWITCH, &_help, 'h', "help", "show this help message and exit" },
65 { CMDLINE_TYPE_SWITCH, &_version, 'v', "version", "show version information and exit" },
66 { CMDLINE_TYPE_SWITCH, &_noInstall, 'n', "no-install", "do not install scenario if passed" },
67 { CMDLINE_TYPE_SWITCH, &_all, 'a', "all", "show help for all commands" },
68 { CMDLINE_TYPE_SWITCH, &_about, NAC, "about", "show information about " OPENRCT2_NAME },
69 { CMDLINE_TYPE_SWITCH, &_verbose, NAC, "verbose", "log verbose messages" },
70 { CMDLINE_TYPE_SWITCH, &_headless, NAC, "headless", "run " OPENRCT2_NAME " headless" IMPLIES_SILENT_BREAKPAD },
71 #ifndef DISABLE_NETWORK
72 { CMDLINE_TYPE_INTEGER, &_port, NAC, "port", "port to use for hosting or joining a server" },
73 { CMDLINE_TYPE_STRING, &_address, NAC, "address", "address to listen on when hosting a server" },
74 #endif
75 { CMDLINE_TYPE_STRING, &_password, NAC, "password", "password needed to join the server" },
76 { CMDLINE_TYPE_STRING, &_userDataPath, NAC, "user-data-path", "path to the user data directory (containing config.ini)" },
77 { CMDLINE_TYPE_STRING, &_openrct2DataPath, NAC, "openrct2-data-path", "path to the OpenRCT2 data directory (containing languages)" },
78 { CMDLINE_TYPE_STRING, &_rct1DataPath, NAC, "rct1-data-path", "path to the RollerCoaster Tycoon 1 data directory (containing data/csg1.dat)" },
79 { CMDLINE_TYPE_STRING, &_rct2DataPath, NAC, "rct2-data-path", "path to the RollerCoaster Tycoon 2 data directory (containing data/g1.dat)" },
80 #ifdef USE_BREAKPAD
81 { CMDLINE_TYPE_SWITCH, &_silentBreakpad, NAC, "silent-breakpad", "make breakpad crash reporting silent" },
82 #endif // USE_BREAKPAD
83 OptionTableEnd
84 };
85
86 static exitcode_t HandleNoCommand(CommandLineArgEnumerator * enumerator);
87 static exitcode_t HandleCommandEdit(CommandLineArgEnumerator * enumerator);
88 static exitcode_t HandleCommandIntro(CommandLineArgEnumerator * enumerator);
89 #ifndef DISABLE_NETWORK
90 static exitcode_t HandleCommandHost(CommandLineArgEnumerator * enumerator);
91 static exitcode_t HandleCommandJoin(CommandLineArgEnumerator * enumerator);
92 #endif
93 static exitcode_t HandleCommandSetRCT2(CommandLineArgEnumerator * enumerator);
94 static exitcode_t HandleCommandScanObjects(CommandLineArgEnumerator * enumerator);
95
96 #if defined(_WIN32) && !defined(__MINGW32__)
97
98 static bool _removeShell = false;
99
100 static constexpr const CommandLineOptionDefinition RegisterShellOptions[]
101 {
102 { CMDLINE_TYPE_SWITCH, &_removeShell, 'd', "remove", "remove shell integration" },
103 };
104
105 static exitcode_t HandleCommandRegisterShell(CommandLineArgEnumerator * enumerator);
106
107 #endif
108
109 static void PrintAbout();
110 static void PrintVersion();
111 static void PrintLaunchInformation();
112
113 const CommandLineCommand CommandLine::RootCommands[]
114 {
115 // Main commands
116 #ifndef DISABLE_HTTP
117 DefineCommand("", "<uri>", StandardOptions, HandleNoCommand ),
118 DefineCommand("edit", "<uri>", StandardOptions, HandleCommandEdit ),
119 #else
120 DefineCommand("", "<path>", StandardOptions, HandleNoCommand ),
121 DefineCommand("edit", "<path>", StandardOptions, HandleCommandEdit ),
122 #endif
123 DefineCommand("intro", "", StandardOptions, HandleCommandIntro ),
124 #ifndef DISABLE_NETWORK
125 DefineCommand("host", "<uri>", StandardOptions, HandleCommandHost ),
126 DefineCommand("join", "<hostname>", StandardOptions, HandleCommandJoin ),
127 #endif
128 DefineCommand("set-rct2", "<path>", StandardOptions, HandleCommandSetRCT2),
129 DefineCommand("convert", "<source> <destination>", StandardOptions, CommandLine::HandleCommandConvert),
130 DefineCommand("scan-objects", "<path>", StandardOptions, HandleCommandScanObjects),
131 DefineCommand("handle-uri", "openrct2://.../", StandardOptions, CommandLine::HandleCommandUri),
132
133 #if defined(_WIN32) && !defined(__MINGW32__)
134 DefineCommand("register-shell", "", RegisterShellOptions, HandleCommandRegisterShell),
135 #endif
136
137 // Sub-commands
138 DefineSubCommand("screenshot", CommandLine::ScreenshotCommands ),
139 DefineSubCommand("sprite", CommandLine::SpriteCommands ),
140 DefineSubCommand("benchgfx", CommandLine::BenchGfxCommands ),
141 DefineSubCommand("benchspritesort", CommandLine::BenchSpriteSortCommands ),
142 DefineSubCommand("benchsimulate", CommandLine::BenchUpdateCommands ),
143 DefineSubCommand("simulate", CommandLine::SimulateCommands ),
144 CommandTableEnd
145 };
146
147 const CommandLineExample CommandLine::RootExamples[]
148 {
149 { "./my_park.sv6", "open a saved park" },
150 { "./SnowyPark.sc6", "install and open a scenario" },
151 { "./ShuttleLoop.td6", "install a track" },
152 #ifndef DISABLE_HTTP
153 { "https://openrct2.io/files/SnowyPark.sv6", "download and open a saved park" },
154 #endif
155 #ifndef DISABLE_NETWORK
156 { "host ./my_park.sv6 --port 11753 --headless", "run a headless server for a saved park" },
157 #endif
158 ExampleTableEnd
159 };
160 // clang-format on
161
HandleCommandDefault()162 exitcode_t CommandLine::HandleCommandDefault()
163 {
164 exitcode_t result = EXITCODE_CONTINUE;
165
166 if (_about)
167 {
168 PrintAbout();
169 result = EXITCODE_OK;
170 }
171 else
172 {
173 if (_verbose)
174 {
175 _log_levels[static_cast<uint8_t>(DiagnosticLevel::Verbose)] = true;
176 PrintLaunchInformation();
177 }
178
179 if (_version)
180 {
181 if (!_verbose)
182 {
183 PrintVersion();
184 }
185 result = EXITCODE_OK;
186 }
187 }
188
189 if (_help || _all)
190 {
191 CommandLine::PrintHelp(_all);
192 result = EXITCODE_OK;
193 }
194
195 gOpenRCT2Headless = _headless;
196 gOpenRCT2NoGraphics = _headless;
197 gOpenRCT2SilentBreakpad = _silentBreakpad || _headless;
198
199 if (_userDataPath != nullptr)
200 {
201 utf8 absolutePath[MAX_PATH]{};
202 Path::GetAbsolute(absolutePath, std::size(absolutePath), _userDataPath);
203 String::Set(gCustomUserDataPath, std::size(gCustomUserDataPath), absolutePath);
204 Memory::Free(_userDataPath);
205 }
206
207 if (_openrct2DataPath != nullptr)
208 {
209 utf8 absolutePath[MAX_PATH]{};
210 Path::GetAbsolute(absolutePath, std::size(absolutePath), _openrct2DataPath);
211 String::Set(gCustomOpenRCT2DataPath, std::size(gCustomOpenRCT2DataPath), absolutePath);
212 Memory::Free(_openrct2DataPath);
213 }
214
215 if (_rct1DataPath != nullptr)
216 {
217 String::Set(gCustomRCT1DataPath, std::size(gCustomRCT1DataPath), _rct1DataPath);
218 Memory::Free(_rct1DataPath);
219 }
220
221 if (_rct2DataPath != nullptr)
222 {
223 String::Set(gCustomRCT2DataPath, std::size(gCustomRCT2DataPath), _rct2DataPath);
224 Memory::Free(_rct2DataPath);
225 }
226
227 if (_password != nullptr)
228 {
229 String::Set(gCustomPassword, std::size(gCustomPassword), _password);
230 Memory::Free(_password);
231 }
232
233 return result;
234 }
235
HandleNoCommand(CommandLineArgEnumerator * enumerator)236 exitcode_t HandleNoCommand(CommandLineArgEnumerator* enumerator)
237 {
238 exitcode_t result = CommandLine::HandleCommandDefault();
239 if (result != EXITCODE_CONTINUE)
240 {
241 return result;
242 }
243
244 const char* parkUri;
245 if (enumerator->TryPopString(&parkUri) && parkUri[0] != '-')
246 {
247 String::Set(gOpenRCT2StartupActionPath, sizeof(gOpenRCT2StartupActionPath), parkUri);
248 gOpenRCT2StartupAction = StartupAction::Open;
249 }
250
251 return EXITCODE_CONTINUE;
252 }
253
HandleCommandEdit(CommandLineArgEnumerator * enumerator)254 exitcode_t HandleCommandEdit(CommandLineArgEnumerator* enumerator)
255 {
256 exitcode_t result = CommandLine::HandleCommandDefault();
257 if (result != EXITCODE_CONTINUE)
258 {
259 return result;
260 }
261
262 const char* parkUri;
263 if (!enumerator->TryPopString(&parkUri))
264 {
265 Console::Error::WriteLine("Expected path or URL to a saved park.");
266 return EXITCODE_FAIL;
267 }
268 String::Set(gOpenRCT2StartupActionPath, sizeof(gOpenRCT2StartupActionPath), parkUri);
269
270 gOpenRCT2StartupAction = StartupAction::Edit;
271 return EXITCODE_CONTINUE;
272 }
273
HandleCommandIntro(CommandLineArgEnumerator * enumerator)274 exitcode_t HandleCommandIntro([[maybe_unused]] CommandLineArgEnumerator* enumerator)
275 {
276 exitcode_t result = CommandLine::HandleCommandDefault();
277 if (result != EXITCODE_CONTINUE)
278 {
279 return result;
280 }
281
282 gOpenRCT2StartupAction = StartupAction::Intro;
283 return EXITCODE_CONTINUE;
284 }
285
286 #ifndef DISABLE_NETWORK
287
HandleCommandHost(CommandLineArgEnumerator * enumerator)288 exitcode_t HandleCommandHost(CommandLineArgEnumerator* enumerator)
289 {
290 exitcode_t result = CommandLine::HandleCommandDefault();
291 if (result != EXITCODE_CONTINUE)
292 {
293 return result;
294 }
295
296 const char* parkUri;
297 if (!enumerator->TryPopString(&parkUri))
298 {
299 Console::Error::WriteLine("Expected path or URL to a scenario or saved park.");
300 return EXITCODE_FAIL;
301 }
302
303 gOpenRCT2StartupAction = StartupAction::Open;
304 String::Set(gOpenRCT2StartupActionPath, sizeof(gOpenRCT2StartupActionPath), parkUri);
305
306 gNetworkStart = NETWORK_MODE_SERVER;
307 gNetworkStartPort = _port;
308 gNetworkStartAddress = String::ToStd(_address);
309
310 return EXITCODE_CONTINUE;
311 }
312
HandleCommandJoin(CommandLineArgEnumerator * enumerator)313 exitcode_t HandleCommandJoin(CommandLineArgEnumerator* enumerator)
314 {
315 exitcode_t result = CommandLine::HandleCommandDefault();
316 if (result != EXITCODE_CONTINUE)
317 {
318 return result;
319 }
320
321 const char* hostname;
322 if (!enumerator->TryPopString(&hostname))
323 {
324 Console::Error::WriteLine("Expected a hostname or IP address to the server to connect to.");
325 return EXITCODE_FAIL;
326 }
327
328 gNetworkStart = NETWORK_MODE_CLIENT;
329 gNetworkStartPort = _port;
330 gNetworkStartHost = hostname;
331 return EXITCODE_CONTINUE;
332 }
333
334 #endif // DISABLE_NETWORK
335
HandleCommandSetRCT2(CommandLineArgEnumerator * enumerator)336 static exitcode_t HandleCommandSetRCT2(CommandLineArgEnumerator* enumerator)
337 {
338 exitcode_t result = CommandLine::HandleCommandDefault();
339 if (result != EXITCODE_CONTINUE)
340 {
341 return result;
342 }
343
344 // Get the path that was passed
345 const utf8* rawPath;
346 if (!enumerator->TryPopString(&rawPath))
347 {
348 Console::Error::WriteLine("Expected a path.");
349 return EXITCODE_FAIL;
350 }
351
352 utf8 path[MAX_PATH];
353 Path::GetAbsolute(path, sizeof(path), rawPath);
354
355 // Check if path exists
356 Console::WriteLine("Checking path...");
357 if (!platform_directory_exists(path))
358 {
359 Console::Error::WriteLine("The path '%s' does not exist", path);
360 return EXITCODE_FAIL;
361 }
362
363 // Check if g1.dat exists (naive but good check)
364 Console::WriteLine("Checking g1.dat...");
365
366 utf8 pathG1Check[MAX_PATH];
367 String::Set(pathG1Check, sizeof(pathG1Check), path);
368 Path::Append(pathG1Check, sizeof(pathG1Check), "Data");
369 Path::Append(pathG1Check, sizeof(pathG1Check), "g1.dat");
370 if (!Platform::FileExists(pathG1Check))
371 {
372 Console::Error::WriteLine("RCT2 path not valid.");
373 Console::Error::WriteLine("Unable to find %s.", pathG1Check);
374 return EXITCODE_FAIL;
375 }
376
377 // Update RCT2 path in config
378 auto env = OpenRCT2::CreatePlatformEnvironment();
379 auto configPath = env->GetFilePath(OpenRCT2::PATHID::CONFIG);
380 config_set_defaults();
381 config_open(configPath.c_str());
382 String::DiscardDuplicate(&gConfigGeneral.rct2_path, path);
383 if (config_save(configPath.c_str()))
384 {
385 Console::WriteFormat("Updating RCT2 path to '%s'.", path);
386 Console::WriteLine();
387 Console::WriteLine("Updated config.ini");
388 return EXITCODE_OK;
389 }
390
391 Console::Error::WriteLine("Unable to update config.ini");
392 return EXITCODE_FAIL;
393 }
394
HandleCommandScanObjects(CommandLineArgEnumerator * enumerator)395 static exitcode_t HandleCommandScanObjects([[maybe_unused]] CommandLineArgEnumerator* enumerator)
396 {
397 exitcode_t result = CommandLine::HandleCommandDefault();
398 if (result != EXITCODE_CONTINUE)
399 {
400 return result;
401 }
402
403 gOpenRCT2Headless = true;
404 gOpenRCT2NoGraphics = true;
405
406 auto context = OpenRCT2::CreateContext();
407 auto env = context->GetPlatformEnvironment();
408 auto objectRepository = CreateObjectRepository(env);
409 objectRepository->Construct(gConfigGeneral.language);
410 return EXITCODE_OK;
411 }
412
413 #if defined(_WIN32) && !defined(__MINGW32__)
HandleCommandRegisterShell(CommandLineArgEnumerator * enumerator)414 static exitcode_t HandleCommandRegisterShell([[maybe_unused]] CommandLineArgEnumerator* enumerator)
415 {
416 exitcode_t result = CommandLine::HandleCommandDefault();
417 if (result != EXITCODE_CONTINUE)
418 {
419 return result;
420 }
421
422 if (!_removeShell)
423 {
424 Platform::SetUpFileAssociations();
425 }
426 else
427 {
428 Platform::RemoveFileAssociations();
429 }
430 return EXITCODE_OK;
431 }
432 #endif // defined(_WIN32) && !defined(__MINGW32__)
433
PrintAbout()434 static void PrintAbout()
435 {
436 PrintVersion();
437 Console::WriteLine();
438 Console::WriteLine("OpenRCT2 is an amusement park simulation game based upon the popular game");
439 Console::WriteLine("RollerCoaster Tycoon 2, written by Chris Sawyer. It attempts to mimic the ");
440 Console::WriteLine("original game as closely as possible while extending it with new features.");
441 Console::WriteLine("OpenRCT2 is licensed under the GNU General Public License version 3.0, but");
442 Console::WriteLine("includes some 3rd party software under different licenses. See the file");
443 Console::WriteLine("\"licence.txt\" shipped with the game for details.");
444 Console::WriteLine();
445 Console::WriteLine("Website: https://openrct2.io");
446 Console::WriteLine("GitHub: https://github.com/OpenRCT2/OpenRCT2");
447 Console::WriteLine("Contributors: https://github.com/OpenRCT2/OpenRCT2/blob/develop/contributors.md");
448 Console::WriteLine();
449 }
450
PrintVersion()451 static void PrintVersion()
452 {
453 char buffer[256];
454 openrct2_write_full_version_info(buffer, sizeof(buffer));
455 Console::WriteLine(buffer);
456 Console::WriteFormat("%s (%s)", OPENRCT2_PLATFORM, OPENRCT2_ARCHITECTURE);
457 Console::WriteLine();
458 }
459
PrintLaunchInformation()460 static void PrintLaunchInformation()
461 {
462 char buffer[256];
463 time_t timer;
464 struct tm* tmInfo;
465
466 // Print name and version information
467 openrct2_write_full_version_info(buffer, sizeof(buffer));
468 Console::WriteFormat("%s", buffer);
469 Console::WriteLine();
470 Console::WriteFormat("%s (%s)", OPENRCT2_PLATFORM, OPENRCT2_ARCHITECTURE);
471 Console::WriteLine();
472 Console::WriteFormat("@ %s", OPENRCT2_CUSTOM_INFO);
473 Console::WriteLine();
474 Console::WriteLine();
475
476 // Print current time
477 time(&timer);
478 tmInfo = localtime(&timer);
479 strftime(buffer, sizeof(buffer), "%Y/%m/%d %H:%M:%S", tmInfo);
480 Console::WriteFormat("VERBOSE: time is %s", buffer);
481 Console::WriteLine();
482
483 // TODO Print other potential information (e.g. user, hardware)
484 }
485