• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Return-To-The-Roots / s25client / 4621997437

pending completion
4621997437

push

github

Flow86
fix path

21886 of 43348 relevant lines covered (50.49%)

31880.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

13.5
/libs/s25client/s25client.cpp
1
// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org)
2
//
3
// SPDX-License-Identifier: GPL-2.0-or-later
4

5
#include "Debug.h"
6
#include "GameManager.h"
7
#include "QuickStartGame.h"
8
#include "RTTR_AssertError.h"
9
#include "RTTR_Version.h"
10
#include "RttrConfig.h"
11
#include "Settings.h"
12
#include "SignalHandler.h"
13
#include "WindowManager.h"
14
#include "commands.h"
15
#include "drivers/AudioDriverWrapper.h"
16
#include "drivers/VideoDriverWrapper.h"
17
#include "files.h"
18
#include "helpers/format.hpp"
19
#include "mygettext/mygettext.h"
20
#include "ogl/glAllocator.h"
21
#include "libsiedler2/libsiedler2.h"
22
#include "s25util/LocaleHelper.h"
23
#include "s25util/Log.h"
24
#include "s25util/StringConversion.h"
25
#include "s25util/System.h"
26
#include "s25util/error.h"
27
#include <boost/filesystem.hpp>
28
#include <boost/nowide/args.hpp>
29
#include <boost/nowide/iostream.hpp>
30
#include <boost/program_options.hpp>
31
#include <array>
32
#include <cstdlib>
33
#include <ctime>
34
#include <iostream>
35
#include <limits>
36
#include <vector>
37
#if RTTR_HAS_VLD
38
#    include <vld.h>
39
#endif
40

41
#ifdef _WIN32
42
#    include <boost/nowide/convert.hpp>
43
#    include <windows.h>
44
#    include <s25clientResources.h>
45
#    if defined _DEBUG && defined _MSC_VER && defined RTTR_HWETRANS
46
#        include <eh.h>
47
#    endif
48
#endif
49
#ifndef _MSC_VER
50
#    include <csignal>
51
#endif
52

53
namespace bfs = boost::filesystem;
54
namespace bnw = boost::nowide;
55
namespace po = boost::program_options;
56

57
/// Calls a setGlobalInstance function for the (previously) singleton type T
58
/// on construction and destruction
59
template<class T>
60
class SetGlobalInstanceWrapper : public T
61
{
62
    using Setter = void (*)(T*);
63
    Setter setter_;
64

65
public:
66
    template<typename... Args>
67
    SetGlobalInstanceWrapper(Setter setter, Args&&... args) : T(std::forward<Args>(args)...), setter_(setter)
×
68
    {
69
        setter_(static_cast<T*>(this));
×
70
    }
×
71
    ~SetGlobalInstanceWrapper() noexcept { setter_(nullptr); }
×
72
};
73

74
// Throw this to terminate gracefully
75
struct RttrExitException : std::exception
76
{
77
    int code;
78
    RttrExitException(int code) : code(code) {}
×
79
};
80

81
namespace {
82
void WaitForEnter()
×
83
{
84
    static bool waited = false;
85
    if(waited)
×
86
        return;
×
87
    waited = true;
×
88
    bnw::cout << "\n\nPress ENTER to close this window . . ." << std::endl;
×
89
    bnw::cin.clear();
×
90
    bnw::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
×
91
    bnw::cin.get();
×
92
}
93

94
std::string GetProgramDescription()
1✔
95
{
96
    std::stringstream s;
1✔
97
    s << rttr::version::GetTitle() << " v" << rttr::version::GetVersion() << "-" << rttr::version::GetRevision() << "\n"
2✔
98
      << "Compiled with " << System::getCompilerName() << " for " << System::getOSName();
1✔
99
    return s.str();
2✔
100
}
1✔
101

102
///////////////////////////////////////////////////////////////////////////////
103

104
#if defined _WIN32 && defined _DEBUG && defined _MSC_VER && defined RTTR_HWETRANS
105
/**
106
 *  Exception-Handler, wird bei einer C-Exception ausgeführt, falls dies mit RTTR_HWETRANS und
107
 *  im Projekt mit den Compilerflags (/EHa) aktiviert ist.
108
 *
109
 *  @param[in] exception_type    Typ der Exception (siehe GetExceptionCode)
110
 *  @param[in] exception_pointer Genaue Beschreibung der Exception (siehe GetExceptionInformation)
111
 */
112
void CExceptionHandler(unsigned exception_type, _EXCEPTION_POINTERS* exception_pointer)
113
{
114
    fatal_error("C-Exception caught\n");
115
}
116
#endif // _WIN32 && _DEBUG && RTTR_HWETRANS
117

118
bool askForDebugData()
×
119
{
120
#ifdef _WIN32
121
    std::string msg = gettext_noop("RttR crashed. Would you like to send debug information to RttR to help "
122
                                   "us avoiding this crash in the future? Thank you very much!");
123
    std::string errorTxt = gettext_noop("Error");
124
    try
125
    {
126
        msg = _(msg);
127
        errorTxt = _(errorTxt);
128
    } catch(...)
129
    {}
130
    std::wstring title = boost::nowide::widen(_(errorTxt));
131
    std::wstring text = boost::nowide::widen(_(msg));
132
    return (MessageBoxW(nullptr, text.c_str(), title.c_str(), MB_YESNO | MB_ICONERROR | MB_TASKMODAL | MB_SETFOREGROUND)
133
            == IDYES);
134
#else
135
    return false;
×
136
#endif
137
}
138
bool shouldSendDebugData()
×
139
{
140
    return (SETTINGS.global.submit_debug_data == 1) || askForDebugData();
×
141
}
142

143
void showCrashMessage()
×
144
{
145
    std::string text = gettext_noop("RttR crashed. Please restart the application!");
×
146
    std::string errorTxt = gettext_noop("Error");
×
147
    try
148
    {
149
        text = _(text);
×
150
        errorTxt = _(errorTxt);
×
151
    } catch(...)
×
152
    {}
×
153
#ifdef _WIN32
154
    MessageBoxW(nullptr, boost::nowide::widen(text).c_str(), boost::nowide::widen(errorTxt).c_str(),
155
                MB_OK | MB_ICONERROR | MB_TASKMODAL | MB_SETFOREGROUND);
156
#else
157
    RTTR_UNUSED(errorTxt);
158
    bnw::cerr << text << std::endl;
×
159
#endif
160
}
×
161

162
[[noreturn]] void terminateProgramm()
×
163
{
164
#ifdef _DEBUG
165
    abort();
166
#else
167
    throw RttrExitException(1);
×
168
#endif
169
}
170

171
void handleException(void* pCtx = nullptr) noexcept
×
172
{
173
    std::vector<void*> stacktrace = DebugInfo::GetStackTrace(pCtx);
×
174
    try
175
    {
176
        LogTarget target = (LOG.getFileWriter()) ? LogTarget::FileAndStderr : LogTarget::Stderr;
×
177
        LOG.write("RttR crashed. Backtrace:\n", target);
×
178
        // Don't let locale mess up addresses
179
        s25util::ClassicImbuedStream<std::stringstream> ss;
×
180
        for(void* p : stacktrace)
×
181
            ss << p << "\n";
×
182
        LOG.write("%1%", target) % ss.str();
×
183
        if(shouldSendDebugData())
×
184
        {
185
            DebugInfo di;
×
186
            di.SendReplay();
×
187
            di.SendStackTrace(stacktrace);
×
188
        }
×
189
    } catch(...)
×
190
    { //-V565
191
      // Could not write stacktrace or send debug data. Ignore errors
192
    }
×
193

194
    showCrashMessage();
×
195
}
×
196

197
#ifdef _MSC_VER
198
LONG WINAPI ExceptionHandler(LPEXCEPTION_POINTERS info)
199
{
200
    handleException(info->ContextRecord);
201
    terminateProgramm();
202
    return EXCEPTION_EXECUTE_HANDLER;
203
}
204
#else
205
[[noreturn]] void ExceptionHandler(int /*sig*/)
×
206
{
207
    handleException();
×
208
    terminateProgramm();
×
209
}
210
#endif
211

212
void InstallSignalHandlers()
×
213
{
214
#ifdef _WIN32
215
    SetConsoleCtrlHandler(ConsoleSignalHandler, TRUE);
216
#else
217
    struct sigaction sa;
218
    sa.sa_handler = ConsoleSignalHandler;
×
219
    sa.sa_flags = 0; // SA_RESTART would not allow to interrupt connect call;
×
220
    sigemptyset(&sa.sa_mask);
×
221

222
    sigaction(SIGINT, &sa, nullptr);
×
223
    sigaction(SIGPIPE, &sa, nullptr);
×
224
    sigaction(SIGALRM, &sa, nullptr);
×
225
#endif
226

227
#ifdef _MSC_VER
228
    SetUnhandledExceptionFilter(ExceptionHandler);
229
#    ifdef _DEBUG
230
#        ifdef RTTR_HWETRANS
231
    _set_se_translator(CExceptionHandler);
232
#        endif // RTTR_HWETRANS
233
#        ifdef RTTR_CRTDBG
234
    // Enable Memory-Leak-Detection
235
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF /*| _CRTDBG_CHECK_EVERY_1024_DF*/);
236
#        endif //  RTTR_CRTDBG
237
#    endif     // _DEBUG
238

239
#else
240
    signal(SIGSEGV, ExceptionHandler);
×
241
#endif // _MSC_VER
242
}
×
243

244
void UninstallSignalHandlers()
×
245
{
246
#ifdef _WIN32
247
    SetConsoleCtrlHandler(ConsoleSignalHandler, FALSE);
248
#else
249
    struct sigaction sa;
250
    sa.sa_handler = SIG_DFL;
×
251
    sa.sa_flags = 0; // SA_RESTART would not allow to interrupt connect call;
×
252
    sigemptyset(&sa.sa_mask);
×
253

254
    sigaction(SIGINT, &sa, nullptr);
×
255
    sigaction(SIGPIPE, &sa, nullptr);
×
256
    sigaction(SIGALRM, &sa, nullptr);
×
257
#endif // _WIN32
258

259
#ifdef _MSC_VER
260
    SetUnhandledExceptionFilter(nullptr);
261
#else
262
    signal(SIGSEGV, SIG_DFL);
×
263
#endif
264
}
×
265

266
/**
267
 *  Exit-Handler, wird bei @p exit ausgeführt.
268
 */
269
void ExitHandler()
×
270
{
271
    Socket::Shutdown();
×
272
    UninstallSignalHandlers();
×
273

274
#ifdef _DEBUG
275
    WaitForEnter();
276
#endif
277
}
×
278

279
void SetAppSymbol()
×
280
{
281
#ifdef _WIN32
282
    // set console window icon
283
    SendMessage(GetConsoleWindow(), WM_SETICON, ICON_BIG,
284
                (LPARAM)LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_SYMBOL)));
285
    SendMessage(GetConsoleWindow(), WM_SETICON, ICON_SMALL,
286
                (LPARAM)LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_SYMBOL)));
287
#endif // _WIN32
288
}
×
289

290
bool MigrateFilesAndDirectories()
×
291
{
292
    struct MigrationEntry
293
    {
294
        std::string oldName, newName;
295
        bool isFolder;
296
    };
297
    const std::string sharedLibext =
298
#ifdef _WIN32
299
      "dll";
300
#elif defined(__APPLE__)
301
      "dylib";
302
#else
303
      "so";
×
304
#endif
305

306
    // Mapping from old files or directories to new ones
307
    // Handled in order so multiple renamings are possible
308
    // Empty newName = Delete
309
    const std::vector<MigrationEntry> migrations = {
310
#ifdef _WIN32
311
      {"~/Siedler II.5 RttR", s25::folders::config, true},
312
#elif defined(__APPLE__)
313
      {"~/.s25rttr", s25::folders::config, true},
314
#endif
315
      {std::string(s25::folders::assetsUserOverrides).append("/SOUND.LST"), "", false},
×
316
      {std::string(s25::folders::driver).append("/video/libvideoSDL.").append(sharedLibext), "", false},
×
317
    };
×
318

319
    try
320
    {
321
        for(const MigrationEntry& entry : migrations)
×
322
        {
323
            const bfs::path oldName = RTTRCONFIG.ExpandPath(entry.oldName);
×
324
            if(!exists(oldName))
×
325
                continue;
×
326
            if(entry.isFolder && !is_directory(oldName))
×
327
                throw std::runtime_error(helpers::format("Expected folder but %1% is not a folder", oldName));
×
328
            else if(!entry.isFolder && !is_regular_file(oldName))
×
329
                throw std::runtime_error(helpers::format("Expected file but %1% is not a file", oldName));
×
330
            if(entry.newName.empty())
×
331
            {
332
                LOG.write("Removing %1% which is no longer needed\n", LogTarget::Stdout) % oldName;
×
333
                boost::system::error_code ec;
×
334
                if(!remove(oldName, ec))
×
335
                    throw std::runtime_error(helpers::format("Failed to delete %1%: %2%", oldName, ec.message()));
×
336
            } else
337
            {
338
                const bfs::path newName = RTTRCONFIG.ExpandPath(entry.newName);
×
339
                if(exists(newName))
×
340
                {
341
                    throw std::runtime_error(helpers::format(
×
342
                      "Old and new %1% found. Please delete the one you don't want to keep!\nOld: %2%\nNew: %3%",
343
                      is_directory(oldName) ? "directory" : "file", oldName, newName));
×
344
                }
345
                LOG.write("Filepath of %1% has changed to %2%. Renaming...\n", LogTarget::Stdout) % oldName % newName;
×
346
                boost::system::error_code ec;
×
347
                rename(oldName, newName, ec);
×
348
                if(ec)
×
349
                {
350
                    throw std::runtime_error(helpers::format("Renaming %1% to %2% failed\nError: %3%\nRename it "
×
351
                                                             "yourself and/or make sure the directory is writable!",
352
                                                             oldName, newName, ec.message()));
×
353
                }
354
            }
×
355
        }
×
356
    } catch(const std::exception& e)
×
357
    {
358
        LOG.write("ERROR: Migration of folders and files to new locations failed: %1%\n", LogTarget::Stderr) % e.what();
×
359
        return false;
×
360
    }
×
361
    return true;
×
362
}
×
363

364
bool InitDirectories()
×
365
{
366
    // Note: Do not use logger yet. Filepath may not exist
367
    const auto curPath = bfs::current_path();
×
368
    LOG.write("Starting in %1%\n", LogTarget::Stdout) % curPath;
×
369

370
    if(!MigrateFilesAndDirectories())
×
371
        return false;
×
372

373
    // Create all required/useful folders
374
    const std::array<std::string, 10> dirs = {
375
      {s25::folders::config, s25::folders::logs, s25::folders::mapsOwn, s25::folders::mapsPlayed, s25::folders::replays,
376
       s25::folders::save, s25::folders::assetsUserOverrides, s25::folders::screenshots, s25::folders::playlists}};
×
377

378
    for(const std::string& rawDir : dirs)
×
379
    {
380
        const bfs::path dir = RTTRCONFIG.ExpandPath(rawDir);
×
381
        boost::system::error_code ec;
×
382
        bfs::create_directories(dir, ec);
×
383
        if(ec != boost::system::errc::success)
×
384
        {
385
            // This writes to the log. If the log folder or file could not be created, an exception is thrown
386
            // Make sure we catch that
387
            try
388
            {
389
                s25util::error(std::string("Directory ") + dir.string() + " could not be created.");
×
390
                s25util::error("Failed to start the game");
×
391
            } catch(const std::runtime_error& error)
×
392
            {
393
                LOG.write("Additional error: %1%\n", LogTarget::Stderr) % error.what();
×
394
            }
×
395
            return false;
×
396
        }
397
    }
×
398
    LOG.write("Directory for user data (config etc.): %1%\n", LogTarget::Stdout)
×
399
      % RTTRCONFIG.ExpandPath(s25::folders::config);
×
400

401
    // Write this to file too, after folders are created
402
    LOG.setLogFilepath(RTTRCONFIG.ExpandPath(s25::folders::logs));
×
403
    try
404
    {
405
        LOG.open();
×
406
        LOG.write("%1%\n\n", LogTarget::File) % GetProgramDescription();
×
407
        LOG.write("Starting in %1%\n", LogTarget::File) % curPath;
×
408
    } catch(const std::exception& e)
×
409
    {
410
        LOG.write("Error initializing log: %1%\nSystem reports: %2%\n", LogTarget::Stderr) % e.what()
×
411
          % LOG.getLastError();
×
412
        return false;
×
413
    }
×
414
    return true;
×
415
}
×
416

417
bool InitGame(GameManager& gameManager)
×
418
{
419
    libsiedler2::setAllocator(new GlAllocator());
×
420

421
    // Socketzeug initialisieren
422
    if(!Socket::Initialize())
×
423
    {
424
        s25util::error("Could not init sockets!");
×
425
        s25util::error("Failed to start the game");
×
426
        return false;
×
427
    }
428

429
    // Spiel starten
430
    if(!gameManager.Start())
×
431
    {
432
        s25util::error("Failed to start the game");
×
433
        return false;
×
434
    }
435
    return true;
×
436
}
437

438
int RunProgram(po::variables_map& options)
×
439
{
440
    LOG.write("%1%\n\n", LogTarget::Stdout) % GetProgramDescription();
×
441
    if(!LocaleHelper::init())
×
442
        return 1;
×
443
    if(!RTTRCONFIG.Init())
×
444
        return 1;
×
445
    SetAppSymbol();
×
446
    InstallSignalHandlers();
×
447
    // Exit-Handler initialisieren
448
    atexit(&ExitHandler);
×
449
    if(!InitDirectories())
×
450
        return 1;
×
451

452
    // Zufallsgenerator initialisieren (Achtung: nur für Animations-Offsets interessant, für alles andere
453
    // (spielentscheidende) wird unser Generator verwendet)
454
    srand(static_cast<unsigned>(std::time(nullptr)));
×
455

456
    if(options.count("convert-sounds"))
×
457
    {
458
        try
459
        {
460
            convertAndSaveSounds(RTTRCONFIG, RTTRCONFIG.ExpandPath("<RTTR_USERDATA>/convertedSoundeffects"));
×
461
            return 0;
×
462
        } catch(const std::runtime_error& e)
×
463
        {
464
            bnw::cerr << "Error: " << e.what() << "\n";
×
465
            return 1;
×
466
        }
×
467
    }
468

469
    SetGlobalInstanceWrapper<GameManager> gameManager(setGlobalGameManager, LOG, SETTINGS, VIDEODRIVER, AUDIODRIVER,
470
                                                      WINDOWMANAGER);
×
471
    try
472
    {
473
        if(!InitGame(gameManager))
×
474
            return 2;
×
475

476
        if(options.count("map"))
×
477
        {
478
            std::vector<std::string> aiPlayers;
×
479
            if(options.count("ai"))
×
480
                aiPlayers = options["ai"].as<std::vector<std::string>>();
×
481

482
            if(!QuickStartGame(options["map"].as<std::string>(), aiPlayers))
×
483
                return 1;
×
484
        }
×
485

486
        // Hauptschleife
487

488
        while(gameManager.Run())
×
489
        {
490
#ifndef _WIN32
491
            killme = false;
×
492
#endif // !_WIN32
493
        }
494

495
        // Spiel beenden
496
        gameManager.Stop();
×
497
        libsiedler2::setAllocator(nullptr);
×
498
    } catch(RTTR_AssertError& error)
×
499
    {
500
        // Write to log file, but don't throw any errors if this fails too
501
        try
502
        {
503
            LOG.writeToFile(error.what());
×
504
        } catch(...)
×
505
        { //-V565
506
        }
×
507
        return 42;
×
508
    }
×
509
    return 0;
×
510
}
×
511
} // namespace
512

513
/**
514
 *  Hauptfunktion von Siedler II.5 Return to the Roots
515
 *
516
 *  @param[in] argc Anzahl übergebener Argumente
517
 *  @param[in] argv Array der übergebenen Argumente
518
 *
519
 *  @return Exit Status, 0 bei Erfolg, > 0 bei Fehler
520
 */
521
// Exceptions handled by registred global handlers
522
// NOLINTNEXTLINE(bugprone-exception-escape)
523
int main(int argc, char** argv)
3✔
524
{
525
    bnw::args _(argc, argv);
3✔
526

527
    po::options_description desc("Allowed options");
6✔
528
    // clang-format off
529
    desc.add_options()
3✔
530
        ("help,h", "Show help")
3✔
531
        ("map,m", po::value<std::string>(),"Map to load")
3✔
532
        ("ai", po::value<std::vector<std::string>>(),"AI player(s) to add")
3✔
533
        ("version", "Show version information and exit")
3✔
534
        ("convert-sounds", "Convert sounds and exit")
3✔
535
        ;
536
    // clang-format on
537
    po::positional_options_description positionalOptions;
3✔
538
    positionalOptions.add("map", 1);
3✔
539

540
    po::variables_map options;
3✔
541
    try
542
    {
543
        po::store(po::command_line_parser(argc, argv).options(desc).positional(positionalOptions).run(), options);
4✔
544
        // Catch the generic stdlib exception as hidden visibility messes up boost typeinfo on OSX
545
    } catch(const std::exception& e)
1✔
546
    {
547
        bnw::cerr << "Error: " << e.what() << "\n\n";
1✔
548
        bnw::cerr << desc << "\n";
1✔
549
        return 1;
1✔
550
    }
1✔
551
    po::notify(options);
2✔
552

553
    if(options.count("help"))
2✔
554
    {
555
        bnw::cout << desc << "\n";
1✔
556
        return 0;
1✔
557
    }
558
    if(options.count("version"))
1✔
559
    {
560
        bnw::cout << GetProgramDescription() << std::endl;
1✔
561
        return 0;
1✔
562
    }
563

564
    int result;
565
    try
566
    {
567
        result = RunProgram(options);
×
568
    } catch(const RttrExitException& e)
×
569
    {
570
        result = e.code;
×
571
    } catch(const std::exception& e)
×
572
    {
573
        bnw::cerr << "An exception occurred: " << e.what() << "\n\n";
×
574
        handleException(nullptr);
×
575
        result = 1;
×
576
    } catch(...)
×
577
    {
578
        bnw::cerr << "An unknown exception occurred\n";
×
579
        handleException(nullptr);
×
580
        result = 1;
×
581
    }
×
582
    if(result)
×
583
        WaitForEnter();
×
584

585
    return result;
×
586
}
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc