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