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