1 
2 
3 #include "HumanClientApp.h"
4 #include "../../util/OptionsDB.h"
5 #include "../../util/Directories.h"
6 #include "../../util/Logger.h"
7 #include "../../util/Version.h"
8 #include "../../util/i18n.h"
9 #include "../../UI/Hotkeys.h"
10 
11 #include <GG/utf8/checked.h>
12 
13 #include <boost/format.hpp>
14 #include <boost/filesystem/operations.hpp>
15 #include <boost/filesystem/fstream.hpp>
16 
17 #include <thread>
18 #include <chrono>
19 #include <iostream>
20 
21 #if defined(FREEORION_LINUX)
22 /* Freeorion aims to have exceptions handled and operation continue normally.
23 An example of good exception handling is the exceptions caught around config.xml loading.
24 After catching and informing the user it continues normally with the default values.
25 
26 An exception that can not be handled should allow freeorion to crash and keep
27 a complete stack trace of the intial exception.
28 Some platforms do not support this behavior.
29 
30 When FREEORION_CHMAIN_KEEP_BACKTRACE is defined, do not catch an unhandled exceptions,
31 unroll and hide the stack trace, print a message and still crash anyways. */
32 #define FREEORION_CHMAIN_KEEP_STACKTRACE
33 #endif
34 
35 // The STORE_FULLSCREEN_FLAG parameter below controls whether the fullscreen
36 // option is stored in the XML config file.  On Win32 it is not, because the
37 // installed version of FO is run with the command-line flag added in as
38 // appropriate.
39 #ifdef FREEORION_WIN32
40 const bool STORE_FULLSCREEN_FLAG = false;
41 // Windows keeps good care of the resolution state itself,
42 // so there is no reason to default to not touching it.
43 const bool FAKE_MODE_CHANGE_FLAG = false;
44 #else
45 const bool  STORE_FULLSCREEN_FLAG = true;
46 // The X window system does not always work
47 // well with resolution changes, so we avoid them
48 // by default
49 const bool FAKE_MODE_CHANGE_FLAG = true;
50 #endif
51 
52 int mainSetupAndRun();
53 int mainConfigOptionsSetup(const std::vector<std::string>& args);
54 
55 
56 #if defined(FREEORION_LINUX) || defined(FREEORION_FREEBSD) || defined(FREEORION_OPENBSD) || defined(FREEORION_DRAGONFLY)
main(int argc,char * argv[])57 int main(int argc, char* argv[]) {
58     // copy command line arguments to vector
59     std::vector<std::string> args;
60     for (int i = 0; i < argc; ++i)
61         args.push_back(argv[i]);
62 
63     // set options from command line or config.xml, or generate config.xml
64     if (mainConfigOptionsSetup(args) != 0) {
65         std::cerr << "main() failed config." << std::endl;
66         ShutdownLoggingSystemFileSink() ;
67         return 1;
68     }
69 #endif
70 #ifdef FREEORION_WIN32
71 int wmain(int argc, wchar_t* argv[], wchar_t* envp[]) {
72     // copy UTF-16 command line arguments to UTF-8 vector
73     std::vector<std::string> args;
74     for (int i = 0; i < argc; ++i) {
75         std::wstring argi16(argv[i]);
76         std::string argi8;
77         utf8::utf16to8(argi16.begin(), argi16.end(), std::back_inserter(argi8));
78         args.push_back(argi8);
79     }
80 
81     // set options from command line or config.xml, or generate config.xml
82     if (mainConfigOptionsSetup(args) != 0) {
83         std::cerr << "main() failed config." << std::endl;
84         return 1;
85     }
86 #endif
87 #ifndef FREEORION_MACOSX
88     // did the player request help output?
89     auto help_arg = GetOptionsDB().Get<std::string>("help");
90     if (help_arg != "NOOP") {
91         ShutdownLoggingSystemFileSink();
92         GetOptionsDB().GetUsage(std::cout, help_arg, true);
93         return 0;   // quit without actually starting game
94     }
95 
96     // did the player request the version output?
97     if (GetOptionsDB().Get<bool>("version")) {
98         ShutdownLoggingSystemFileSink();
99         std::cout << "FreeOrionCH " << FreeOrionVersionString() << std::endl;
100         return 0;   // quit without actually starting game
101     }
102 
103     // set up rendering and run game
104     if (mainSetupAndRun() != 0) {
105         ShutdownLoggingSystemFileSink();
106         std::cerr << "main() failed to setup or run SDL." << std::endl;
107         return 1;
108     }
109     ShutdownLoggingSystemFileSink();
110     return 0;
111 }
112 #endif
113 
114 
115 int mainConfigOptionsSetup(const std::vector<std::string>& args) {
116     InitDirs((args.empty() ? "" : *args.begin()));
117 
118     // read and process command-line arguments, if any
119 #ifndef FREEORION_CHMAIN_KEEP_STACKTRACE
120     try {
121 #endif
122         // add entries in options DB that have no other obvious place
123         GetOptionsDB().Add<std::string>('h', "help",                UserStringNop("OPTIONS_DB_HELP"),                   "NOOP",
124                                         Validator<std::string>(),                                                       false);
125         GetOptionsDB().AddFlag('v', "version",                      UserStringNop("OPTIONS_DB_VERSION"),                false,
126                                "version");
127         GetOptionsDB().AddFlag('g', "generate-config-xml",          UserStringNop("OPTIONS_DB_GENERATE_CONFIG_XML"),    false);
128         GetOptionsDB().AddFlag('f', "video.fullscreen.enabled",     UserStringNop("OPTIONS_DB_FULLSCREEN"),             STORE_FULLSCREEN_FLAG);
129         GetOptionsDB().Add("video.fullscreen.reset",                UserStringNop("OPTIONS_DB_RESET_FSSIZE"),           true);
130         GetOptionsDB().Add<bool>("video.fullscreen.fake.enabled",   UserStringNop("OPTIONS_DB_FAKE_MODE_CHANGE"),       FAKE_MODE_CHANGE_FLAG);
131         GetOptionsDB().Add<int>("video.monitor.id",                 UserStringNop("OPTIONS_DB_FULLSCREEN_MONITOR_ID"),  0,
132                                 RangedValidator<int>(0, 5));
133         GetOptionsDB().AddFlag("continue",                          UserStringNop("OPTIONS_DB_CONTINUE"),               false);
134         GetOptionsDB().AddFlag("auto-quit",                         UserStringNop("OPTIONS_DB_AUTO_QUIT"),              false);
135         GetOptionsDB().Add<int>("auto-advance-n-turns",             UserStringNop("OPTIONS_DB_AUTO_N_TURNS"),           0,
136                                 RangedValidator<int>(0, 400),                                                           false);
137         GetOptionsDB().Add("audio.music.enabled",                   UserStringNop("OPTIONS_DB_MUSIC_ON"),               true);
138         GetOptionsDB().Add("audio.effects.enabled",                 UserStringNop("OPTIONS_DB_SOUND_ON"),               true);
139         GetOptionsDB().Add<std::string>("version.string",           UserStringNop("OPTIONS_DB_VERSION_STRING"),         FreeOrionVersionString(),
140                                         Validator<std::string>(),                                                       true);
141         GetOptionsDB().AddFlag('r', "render-simple",                UserStringNop("OPTIONS_DB_RENDER_SIMPLE"),          false);
142 
143         // add sections for option sorting
144         GetOptionsDB().AddSection("audio", UserStringNop("OPTIONS_DB_SECTION_AUDIO"));
145         GetOptionsDB().AddSection("audio.music", UserStringNop("OPTIONS_DB_SECTION_AUDIO_MUSIC"));
146         GetOptionsDB().AddSection("audio.effects", UserStringNop("OPTIONS_DB_SECTION_AUDIO_EFFECTS"));
147         GetOptionsDB().AddSection("audio.effects.paths", UserStringNop("OPTIONS_DB_SECTION_AUDIO_EFFECTS_PATHS"),
148                                   [](const std::string& name)->bool {
149                                       std::string suffix { "sound.path" };
150                                       return name.size() > suffix.size() &&
151                                              name.substr(name.size() - suffix.size()) == suffix;
152                                   });
153         GetOptionsDB().AddSection("effects", UserStringNop("OPTIONS_DB_SECTION_EFFECTS"));
154         GetOptionsDB().AddSection("logging", UserStringNop("OPTIONS_DB_SECTION_LOGGING"));
155         GetOptionsDB().AddSection("network", UserStringNop("OPTIONS_DB_SECTION_NETWORK"));
156         GetOptionsDB().AddSection("resource", UserStringNop("OPTIONS_DB_SECTION_RESOURCE"));
157         GetOptionsDB().AddSection("save", UserStringNop("OPTIONS_DB_SECTION_SAVE"));
158         GetOptionsDB().AddSection("setup", UserStringNop("OPTIONS_DB_SECTION_SETUP"));
159         GetOptionsDB().AddSection("ui", UserStringNop("OPTIONS_DB_SECTION_UI"));
160         GetOptionsDB().AddSection("ui.colors", UserStringNop("OPTIONS_DB_SECTION_UI_COLORS"),
161                                   [](const std::string& name)->bool {
162                                       std::string suffix { ".color" };
163                                       return name.size() > suffix.size() &&
164                                              name.substr(name.size() - suffix.size()) == suffix;
165                                   });
166         GetOptionsDB().AddSection("ui.hotkeys", UserStringNop("OPTIONS_DB_SECTION_UI_HOTKEYS"),
167                                   [](const std::string& name)->bool {
168                                       std::string suffix { ".hotkey" };
169                                       return name.size() > suffix.size() &&
170                                              name.substr(name.size() - suffix.size()) == suffix;
171                                   });
172         GetOptionsDB().AddSection("version", UserStringNop("OPTIONS_DB_SECTION_VERSION"));
173         GetOptionsDB().AddSection("video", UserStringNop("OPTIONS_DB_SECTION_VIDEO"));
174         GetOptionsDB().AddSection("video.fullscreen", UserStringNop("OPTIONS_DB_SECTION_VIDEO_FULLSCREEN"));
175         GetOptionsDB().AddSection("video.windowed", UserStringNop("OPTIONS_DB_SECTION_VIDEO_WINDOWED"));
176 
177         // Add the keyboard shortcuts
178         Hotkey::AddOptions(GetOptionsDB());
179 
180         // if config.xml and persistent_config.xml are present, read and set options entries
181         GetOptionsDB().SetFromFile(GetConfigPath(), FreeOrionVersionString());
182         GetOptionsDB().SetFromFile(GetPersistentConfigPath());
183 
184         // override previously-saved and default options with command line parameters and flags
185         GetOptionsDB().SetFromCommandLine(args);
186 
187         CompleteXDGMigration();
188 
189         // Handle the case where the resource.path does not exist anymore
190         // gracefully by resetting it to the standard path into the
191         // application bundle.  This may happen if a previous installed
192         // version of FreeOrion was residing in a different directory.
193         if (!boost::filesystem::exists(GetResourceDir()) ||
194             !boost::filesystem::exists(GetResourceDir() / "credits.xml") ||
195             !boost::filesystem::exists(GetResourceDir() / "data" / "art" / "misc" / "missing.png"))
196         {
197             DebugLogger() << "Resources directory from config.xml missing or does not contain expected files. Resetting to default.";
198 
199             GetOptionsDB().Set<std::string>("resource.path", "");
200 
201             // double-check that resetting actually fixed things...
202             if (!boost::filesystem::exists(GetResourceDir()) ||
203                 !boost::filesystem::exists(GetResourceDir() / "credits.xml") ||
204                 !boost::filesystem::exists(GetResourceDir() / "data" / "art" / "misc" / "missing.png"))
205             {
206                 DebugLogger() << "Default Resources directory missing or does not contain expected files. Cannot start game.";
207                 throw std::runtime_error("Unable to load game resources at default location: " +
208                                          PathToString(GetResourceDir()) + " : Install may be broken.");
209             }
210         }
211 
212 
213         // did the player request generation of config.xml, saving the default (or current) options to disk?
214         if (GetOptionsDB().Get<bool>("generate-config-xml")) {
215             try {
216                 GetOptionsDB().Commit(false);
217             } catch (...) {
218                 std::cerr << UserString("UNABLE_TO_WRITE_CONFIG_XML") << std::endl;
219             }
220         }
221 
222         if (GetOptionsDB().Get<bool>("render-simple")) {
223             GetOptionsDB().Set<bool>("ui.map.background.gas.shown", false);
224             GetOptionsDB().Set<bool>("ui.map.background.starfields.shown", false);
225             GetOptionsDB().Set<bool>("video.fps.shown", true);
226         }
227 
228 #ifndef FREEORION_CHMAIN_KEEP_STACKTRACE
229     } catch (const std::invalid_argument& e) {
230         std::cerr << "main() caught exception(std::invalid_argument): " << e.what() << std::endl;
231         std::this_thread::sleep_for(std::chrono::seconds(3));
232         return 1;
233     } catch (const std::runtime_error& e) {
234         std::cerr << "main() caught exception(std::runtime_error): " << e.what() << std::endl;
235         std::this_thread::sleep_for(std::chrono::seconds(3));
236         return 1;
237     } catch (const std::exception& e) {
238         std::cerr << "main() caught exception(std::exception): " << e.what() << std::endl;
239         std::this_thread::sleep_for(std::chrono::seconds(3));
240         return 1;
241     }
242 #endif
243 
244     return 0;
245 }
246 
247 
248 int mainSetupAndRun() {
249 #ifndef FREEORION_CHMAIN_KEEP_STACKTRACE
250     try {
251 #endif
252         RegisterOptions(&HumanClientApp::AddWindowSizeOptionsAfterMainStart);
253 
254         bool fullscreen = GetOptionsDB().Get<bool>("video.fullscreen.enabled");
255         bool fake_mode_change = GetOptionsDB().Get<bool>("video.fullscreen.fake.enabled");
256 
257         auto width_height = HumanClientApp::GetWindowWidthHeight();
258         int width(width_height.first), height(width_height.second);
259         auto left_top = HumanClientApp::GetWindowLeftTop();
260         int left(left_top.first), top(left_top.second);
261 
262 #ifdef FREEORION_WIN32
263 #  ifdef IDI_ICON1
264         // set window icon to embedded application icon
265         HWND hwnd;
266         window->getCustomAttribute("WINDOW", &hwnd);
267         HINSTANCE hInst = (HINSTANCE)GetModuleHandle(nullptr);
268         SetClassLong (hwnd, GCL_HICON,
269             (LONG)LoadIcon (hInst, MAKEINTRESOURCE (IDI_ICON1)));
270 #  endif
271 #endif
272 
273         HumanClientApp app(width, height, true, "FreeOrion " + FreeOrionVersionString(),
274                            left, top, fullscreen, fake_mode_change);
275 
276         if (GetOptionsDB().Get<bool>("quickstart")) {
277             // immediately start the server, establish network connections, and
278             // go into a single player game, using default universe options (a
279             // standard quickstart, without requiring the user to click the
280             // quickstart button).
281             app.NewSinglePlayerGame(true);  // acceptable to call before app()
282         }
283 
284         if (GetOptionsDB().Get<bool>("continue")) {
285             // immediately start the server, establish network connections, and
286             // go into a single player game, continuing from the newest
287             // save game.
288             app.ContinueSinglePlayerGame();  // acceptable to call before app()
289         }
290 
291         std::string load_filename = GetOptionsDB().Get<std::string>("load");
292         if (!load_filename.empty()) {
293             // immediately start the server, establish network connections, and
294             // go into a single player game, loading the indicated file
295             // (without requiring the user to click the load button).
296             app.LoadSinglePlayerGame(load_filename);  // acceptable to call before app()
297         }
298 
299         // run rendering loop
300         app.Run();
301 #ifndef FREEORION_CHMAIN_KEEP_STACKTRACE
302     } catch (const std::invalid_argument& e) {
303         ErrorLogger() << "main() caught exception(std::invalid_argument): " << e.what();
304         std::cerr << "main() caught exception(std::invalid_arg): " << e.what() << std::endl;
305         return 1;
306     } catch (const std::runtime_error& e) {
307         ErrorLogger() << "main() caught exception(std::runtime_error): " << e.what();
308         std::cerr << "main() caught exception(std::runtime_error): " << e.what() << std::endl;
309         return 1;
310     } catch (const boost::io::format_error& e) {
311         ErrorLogger() << "main() caught exception(boost::io::format_error): " << e.what();
312         std::cerr << "main() caught exception(boost::io::format_error): " << e.what() << std::endl;
313         return 1;
314     } catch (const std::exception& e) {
315         ErrorLogger() << "main() caught exception(std::exception): " << e.what();
316         std::cerr << "main() caught exception(std::exception): " << e.what() << std::endl;
317         return 1;
318     }
319 #endif
320 
321     DebugLogger() << "Human client main exited cleanly.";
322     return 0;
323 }
324 
325 #undef FREEORION_CHMAIN_KEEP_STACKTRACE
326