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

Return-To-The-Roots / s25client / 24625571893

19 Apr 2026 09:10AM UTC coverage: 50.212% (-0.2%) from 50.371%
24625571893

Pull #1890

github

web-flow
Merge 8ad0e7354 into 7b5704a17
Pull Request #1890: Add support for borderless Windows

167 of 637 new or added lines in 44 files covered. (26.22%)

26 existing lines in 9 files now uncovered.

23082 of 45969 relevant lines covered (50.21%)

42862.09 hits per line

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

13.36
/libs/s25client/s25client.cpp
1
// Copyright (C) 2005 - 2026 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
#ifdef __ANDROID__
53
#    include <SDL.h> // For the sdl android entry point function (SDL_main)
54
#endif
55

56
namespace bfs = boost::filesystem;
57
namespace bnw = boost::nowide;
58
namespace po = boost::program_options;
59

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

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

77
// Throw this to terminate gracefully
78
struct RttrExitException : std::exception
79
{
80
    int code;
81
    RttrExitException(int code) : code(code) {}
×
82
};
83

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

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

105
///////////////////////////////////////////////////////////////////////////////
106

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

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

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

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

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

197
    showCrashMessage();
×
198
}
×
199

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

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

225
    sigaction(SIGINT, &sa, nullptr);
×
226
    sigaction(SIGPIPE, &sa, nullptr);
×
227
    sigaction(SIGALRM, &sa, nullptr);
×
228
#endif
229

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

242
#else
243
    signal(SIGSEGV, ExceptionHandler);
×
244
#endif // _MSC_VER
245
}
×
246

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

257
    sigaction(SIGINT, &sa, nullptr);
×
258
    sigaction(SIGPIPE, &sa, nullptr);
×
259
    sigaction(SIGALRM, &sa, nullptr);
×
260
#endif // _WIN32
261

262
#ifdef _MSC_VER
263
    SetUnhandledExceptionFilter(nullptr);
264
#else
265
    signal(SIGSEGV, SIG_DFL);
×
266
#endif
267
}
×
268

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

277
#ifdef _DEBUG
278
    WaitForEnter();
279
#endif
280
}
×
281

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

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

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

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

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

373
    if(!MigrateFilesAndDirectories())
×
374
        return false;
×
375

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

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

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

420
bool InitGame(GameManager& gameManager)
×
421
{
422
    libsiedler2::setAllocator(new GlAllocator());
×
423

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

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

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

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

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

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

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

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

489
        // Hauptschleife
490

491
        while(gameManager.Run())
×
492
        {
493
#ifndef _WIN32
494
            killme = false;
×
495
#endif // !_WIN32
496
        }
497

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

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

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

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

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

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

588
    return result;
×
589
}
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