main.cpp 19.9 KB
Newer Older
1 2 3 4 5 6
//EmulationStation, a graphical front-end for ROM browsing. Created by Alec "Aloshi" Lofquist.
//http://www.aloshi.com

#include <SDL.h>
#include <iostream>
#include <iomanip>
Bkg2k's avatar
Bkg2k committed
7 8 9 10 11
#include <sstream>
#include <fstream>

#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
12
#include <RootFolders.h>
OyyoDams's avatar
OyyoDams committed
13
#include <VideoEngine.h>
Bkg2k's avatar
Bkg2k committed
14 15 16 17 18 19 20 21 22 23 24

#include "AudioManager.h"
#include "CommandThread.h"
#include "EmulationStation.h"
#include "FileSorts.h"
#include "Locale.h"
#include "Log.h"
#include "NetPlayThread.h"
#include "NetworkThread.h"
#include "platform.h"
#include "RecalboxConf.h"
25
#include "Renderer.h"
Bkg2k's avatar
Bkg2k committed
26 27 28
#include "resources/Font.h"
#include "ScraperCmdLine.h"
#include "Settings.h"
29
#include "SystemData.h"
Bkg2k's avatar
Bkg2k committed
30 31 32
#include "views/ViewController.h"
#include "VolumeControl.h"
#include "Window.h"
Bkg2k's avatar
Bkg2k committed
33
#include "DemoMode.h"
34
#include "guis/GuiDetectDevice.h"
Bkg2k's avatar
Bkg2k committed
35
#include "guis/GuiInfoPopup.h"
36
#include "guis/GuiMsgBox.h"
37
#include "guis/GuiMsgBoxScroll.h"
38
#include "recalbox/RecalboxSystem.h"
Bkg2k's avatar
Bkg2k committed
39
#include "recalbox/RecalboxUpgrade.h"
40
#include "recalbox/RecalboxSystem.h"
41
#include "datetime/SystemDateTimeInterface.h"
42

43
#ifdef WIN32
44
  #include <Windows.h>
45 46
#endif

47 48 49 50
namespace fs = boost::filesystem;

bool scrape_cmdline = false;

Bkg2k's avatar
Bkg2k committed
51 52 53 54 55 56 57 58 59 60
void playSound(const std::string& name)
{
  std::string selectedTheme = Settings::getInstance()->getString("ThemeSet");
  std::string loadingMusic = RootFolders::DataRootFolder + "/system/.emulationstation/themes/" + selectedTheme +
                             "/fx/" + name + ".ogg";
  if (boost::filesystem::exists(loadingMusic))
  {
    Music::get(loadingMusic)->play(false, nullptr);
  }
}
61

62 63
bool parseArgs(int argc, char* argv[], unsigned int* width, unsigned int* height)
{
64 65 66 67 68 69 70 71 72 73
  for (int i = 1; i < argc; i++)
  {
    if (strcmp(argv[i], "--resolution") == 0)
    {
      if (i >= argc - 2)
      {
        std::cerr << "Invalid resolution supplied.";
        return false;
      }

Bkg2k's avatar
Bkg2k committed
74 75 76
      char* err;
      *width = (unsigned int)strtol(argv[i + 1], &err, 10);
      *height = (unsigned int)strtol(argv[i + 2], &err, 10);
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
      i += 2; // skip the argument value
    }
    else if (strcmp(argv[i], "--ignore-gamelist") == 0)
    {
      Settings::getInstance()->setBool("IgnoreGamelist", true);
    }
    else if (strcmp(argv[i], "--draw-framerate") == 0)
    {
      Settings::getInstance()->setBool("DrawFramerate", true);
    }
    else if (strcmp(argv[i], "--no-exit") == 0)
    {
      Settings::getInstance()->setBool("ShowExit", false);
    }
    else if (strcmp(argv[i], "--debug") == 0)
    {
      Settings::getInstance()->setBool("Debug", true);
      Settings::getInstance()->setBool("HideConsole", false);
95
      Log::setReportingLevel(LogLevel::LogDebug);
96 97 98 99 100 101 102
    }
    else if (strcmp(argv[i], "--windowed") == 0)
    {
      Settings::getInstance()->setBool("Windowed", true);
    }
    else if (strcmp(argv[i], "--vsync") == 0)
    {
Bkg2k's avatar
Bkg2k committed
103
      bool vsync = (strcmp(argv[i + 1], "on") == 0 || strcmp(argv[i + 1], "1") == 0);
104 105 106 107 108 109 110 111 112
      Settings::getInstance()->setBool("VSync", vsync);
      i++; // skip vsync value
    }
    else if (strcmp(argv[i], "--scrape") == 0)
    {
      scrape_cmdline = true;
    }
    else if (strcmp(argv[i], "--max-vram") == 0)
    {
Bkg2k's avatar
Bkg2k committed
113 114
      char* err;
      int maxVRAM = (int)strtol(argv[i + 1], &err, 10);
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
      Settings::getInstance()->setInt("MaxVRAM", maxVRAM);
    }
    else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0)
    {
      #ifdef WIN32
      // This is a bit of a hack, but otherwise output will go to nowhere
      // when the application is compiled with the "WINDOWS" subsystem (which we usually are).
      // If you're an experienced Windows programmer and know how to do this
      // the right way, please submit a pull request!
      AttachConsole(ATTACH_PARENT_PROCESS);
      freopen("CONOUT$", "wb", stdout);
      #endif
      std::cout << "EmulationStation, a graphical front-end for ROM browsing.\n"
                   "Written by Alec \"Aloshi\" Lofquist.\n"
                   "Version " << PROGRAM_VERSION_STRING << ", built " << PROGRAM_BUILT_STRING << "\n\n"
                                                                                                 "Command line arguments:\n"
                                                                                                 "--resolution [width] [height]	try and force a particular resolution\n"
                                                                                                 "--gamelist-only			skip automatic game search, only read from gamelist.xml\n"
                                                                                                 "--ignore-gamelist		ignore the gamelist (useful for troubleshooting)\n"
                                                                                                 "--draw-framerate		display the framerate\n"
                                                                                                 "--no-exit			don't show the exit option in the menu\n"
                                                                                                 "--hide-systemview		show only gamelist view, no system view\n"
                                                                                                 "--debug				more logging, show console on Windows\n"
                                                                                                 "--scrape			scrape using command line interface\n"
                                                                                                 "--windowed			not fullscreen, should be used with --resolution\n"
                                                                                                 "--vsync [1/on or 0/off]		turn vsync on or off (default is on)\n"
                                                                                                 "--max-vram [size]		Max VRAM to use in Mb before swapping. 0 for unlimited\n"
                                                                                                 "--help, -h			summon a sentient, angry tuba\n\n"
                                                                                                 "More information available in README.md.\n";
      return false; //exit after printing help
    }
  }

  return true;
149 150 151 152
}

bool verifyHomeFolderExists()
{
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
  //make sure the config directory exists
  std::string home = RootFolders::DataRootFolder;
  std::string configDir = home + "/system/.emulationstation";
  if (!fs::exists(configDir))
  {
    std::cout << "Creating config directory \"" << configDir << "\"\n";
    fs::create_directory(configDir);
    if (!fs::exists(configDir))
    {
      std::cerr << "Config directory could not be created!\n";
      return false;
    }
  }

  return true;
168 169 170 171 172
}

// Returns true if everything is OK, 
bool loadSystemConfigFile(const char** errorString)
{
Bkg2k's avatar
Bkg2k committed
173
  *errorString = nullptr;
174 175 176 177 178 179 180 181 182

  if (!SystemData::loadConfig())
  {
    LOG(LogError) << "Error while parsing systems configuration file!";
    *errorString = "IT LOOKS LIKE YOUR SYSTEMS CONFIGURATION FILE HAS NOT BEEN SET UP OR IS INVALID. YOU'LL NEED TO DO THIS BY HAND, UNFORTUNATELY.\n\n"
                   "VISIT EMULATIONSTATION.ORG FOR MORE INFORMATION.";
    return false;
  }

Bkg2k's avatar
Bkg2k committed
183
  if (SystemData::sSystemVector.empty())
184 185 186 187 188 189 190 191 192 193 194 195
  {
    LOG(LogError)
      << "No systems found! Does at least one system have a game present? (check that extensions match!)\n(Also, make sure you've updated your es_systems.cfg for XML!)";
    *errorString = "WE CAN'T FIND ANY SYSTEMS!\n"
                   "CHECK THAT YOUR PATHS ARE CORRECT IN THE SYSTEMS CONFIGURATION FILE, AND "
                   "YOUR GAME DIRECTORY HAS AT LEAST ONE GAME WITH THE CORRECT EXTENSION.\n"
                   "\n"
                   "VISIT RECALBOX.FR FOR MORE INFORMATION.";
    return false;
  }

  return true;
196 197 198 199 200
}

//called on exit, assuming we get far enough to have the log initialized
void onExit()
{
201
  Log::close();
202 203
}

204
int setLocale(char* argv1)
205
{
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
  char path_save[PATH_MAX];
  char abs_exe_path[PATH_MAX];
  char* p;

  bool error = false;
  if (!(p = strrchr(argv1, '/')))
  {
    char* res = getcwd(abs_exe_path, sizeof(abs_exe_path));
    error = (res == nullptr);
  }
  else
  {
    *p = '\0';
    if (getcwd(path_save, sizeof(path_save)) == nullptr)
      error = true;
    if (chdir(argv1) != 0)
      error = true;
    if (getcwd(abs_exe_path, sizeof(abs_exe_path)) == nullptr)
      error = true;
    if (chdir(path_save) != 0)
      error = true;
  }
  if (error)
  {
    LOG(LogError) << "Error getting path";
  }

  boost::locale::localization_backend_manager my = boost::locale::localization_backend_manager::global();
  // Get global backend

  my.select("std");
  boost::locale::localization_backend_manager::global(my);
  // set this backend globally

  boost::locale::generator gen;

  std::string localeDir = abs_exe_path;
  localeDir += "/locale/lang";
  LOG(LogInfo) << "Setting local directory to " << localeDir;
  // Specify location of dictionaries
  gen.add_messages_path(localeDir);
  gen.add_messages_path("/usr/share/locale");
  gen.add_messages_domain("emulationstation2");

  // Generate locales and imbue them to iostream
  std::locale::global(gen(""));
  std::cout.imbue(std::locale());
  LOG(LogInfo) << "Locals set...";
  return 0;
255 256
}

257 258
int main(int argc, char* argv[])
{
259 260
  unsigned int width = 0;
  unsigned int height = 0;
261

262 263
  if (!parseArgs(argc, argv, &width, &height))
    return 0;
264

265 266 267 268 269 270 271
  // start the logger
  Log::open();
  LOG(LogInfo) << "EmulationStation - v" << PROGRAM_VERSION_STRING << ", built " << PROGRAM_BUILT_STRING;

  //always close the log on exit
  atexit(&onExit);

272 273
  // Initialize system datetime interface
  SystemDateTimeImplementation::Initialize();
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307

  // only show the console on Windows if HideConsole is false
  #ifdef WIN32
  // MSVC has a "SubSystem" option, with two primary options: "WINDOWS" and "CONSOLE".
  // In "WINDOWS" mode, no console is automatically created for us.  This is good,
  // because we can choose to only create the console window if the user explicitly
  // asks for it, preventing it from flashing open and then closing.
  // In "CONSOLE" mode, a console is always automatically created for us before we
  // enter main. In this case, we can only hide the console after the fact, which
  // will leave a brief flash.
  // TL;DR: You should compile ES under the "WINDOWS" subsystem.
  // I have no idea how this works with non-MSVC compilers.
  if(!Settings::getInstance()->getBool("HideConsole"))
  {
    // we want to show the console
    // if we're compiled in "CONSOLE" mode, this is already done.
    // if we're compiled in "WINDOWS" mode, no console is created for us automatically;
    // the user asked for one, so make one and then hook stdin/stdout/sterr up to it
    if(AllocConsole()) // should only pass in "WINDOWS" mode
    {
      freopen("CONIN$", "r", stdin);
      freopen("CONOUT$", "wb", stdout);
      freopen("CONOUT$", "wb", stderr);
    }
  }else{
    // we want to hide the console
    // if we're compiled with the "WINDOWS" subsystem, this is already done.
    // if we're compiled with the "CONSOLE" subsystem, a console is already created;
    // it'll flash open, but we hide it nearly immediately
    if(GetConsoleWindow()) // should only pass in "CONSOLE" mode
      ShowWindow(GetConsoleWindow(), SW_HIDE);
  }
  #endif

Bkg2k's avatar
Bkg2k committed
308 309 310
  // Randomize
  std::srand(std::time(nullptr));

311
  try
312
  {
313 314
    //if ~/.emulationstation doesn't exist and cannot be created, bail
    if (!verifyHomeFolderExists())
315
      return 1;
316

317 318
    // Set locale
    setLocale(argv[0]);
319

320 321 322 323
    // other init
    FileSorts::init(); // require locale
    Settings::getInstance()->setBool("ThemeChanged", false);
    Settings::getInstance()->setBool("ThemeHasMenuView", false);
324

325 326 327 328 329 330
    std::string arch;
    std::ifstream archFile;
    archFile.open("/recalbox/recalbox.arch");
    std::getline(archFile, arch);
    archFile.close();
    Settings::getInstance()->setString("Arch", arch);
331

332 333 334 335
    Renderer::init(width, height);
    Window window;
    ViewController::init(&window);
    window.pushGui(ViewController::get());
336

337
    if (!scrape_cmdline)
Filipe Azevedo's avatar
Filipe Azevedo committed
338
    {
339 340 341 342 343 344 345 346 347 348 349 350
      if (!window.init(width, height, false))
      {
        LOG(LogError) << "Window failed to initialize!";
        return 1;
      }

      std::string glExts = (const char*) glGetString(GL_EXTENSIONS);
      LOG(LogInfo) << "Checking available OpenGL extensions...";
      LOG(LogInfo) << " ARB_texture_non_power_of_two: "
                   << (glExts.find("ARB_texture_non_power_of_two") != std::string::npos ? "OK" : "MISSING");

      window.renderLoadingScreen();
351
    }
352

353 354 355 356 357 358
    // Initialize audio manager
    VolumeControl::getInstance()->init();
    AudioManager::getInstance()->init();

    playSound("loading");

Bkg2k's avatar
Bkg2k committed
359
    const char* errorMsg = nullptr;
360
    if (!loadSystemConfigFile(&errorMsg))
361
    {
362
      // something went terribly wrong
Bkg2k's avatar
Bkg2k committed
363
      if (errorMsg == nullptr)
364 365 366 367 368 369
      {
        LOG(LogError) << "Unknown error occured while parsing system config file.";
        if (!scrape_cmdline)
          Renderer::deinit();
        return 1;
      }
370

371 372 373
      // we can't handle es_systems.cfg file problems inside ES itself, so display the error message then quit
      window.pushGui(new GuiMsgBox(&window, errorMsg, _("QUIT"), []
      {
Bkg2k's avatar
Bkg2k committed
374 375
        SDL_Event* quit = nullptr;
        quit = new SDL_Event();
376 377 378 379 380 381 382
        quit->type = SDL_QUIT;
        SDL_PushEvent(quit);
      }));
    }

    RecalboxConf* recalboxConf = RecalboxConf::getInstance();
    if (recalboxConf->get("kodi.enabled") == "1" && recalboxConf->get("kodi.atstartup") == "1")
383
    {
384 385 386 387 388
      RecalboxSystem::getInstance()->launchKodi(&window);
    }
    RecalboxSystem::getInstance()->getIpAdress();
    // UPDATED VERSION MESSAGE
    std::string changelog = RecalboxUpgrade::getInstance()->getChangelog();
Bkg2k's avatar
Bkg2k committed
389
    if (!changelog.empty())
390 391 392 393 394 395 396
    {
      std::string message = "Changes :\n" + changelog;
      window.pushGui(new GuiMsgBoxScroll(&window, _("THE SYSTEM IS UP TO DATE"), message, _("OK"), []
      {
        RecalboxUpgrade::getInstance()->updateLastChangelogFile();
      }, "", nullptr, "", nullptr, ALIGN_LEFT));
    }
397

398 399
    // UPDATE CHECK THREAD
    NetworkThread networkThread(&window);
400

401 402
    // Start the socket server
    CommandThread commandThread(&window);
403

OyyoDams's avatar
OyyoDams committed
404 405 406
    // Starts Video engine
    VideoEngine::This().StartEngine();

407 408 409 410
    // Allocate custom event types
    unsigned int NetPlayPopupEvent = SDL_RegisterEvents(2);
    unsigned int MusicStartEvent = NetPlayPopupEvent + 1;
    AudioManager::getInstance()->SetMusicStartEvent(&window, MusicStartEvent);
Supernature2k's avatar
Supernature2k committed
411

412 413 414 415 416 417 418
    NetPlayThread netPlayThread(&window, NetPlayPopupEvent);
    /*if (RecalboxConf::getInstance()->get("global.netplay") == "1")
    {
      auto s = std::make_shared<GuiInfoPopup>(&window, "", 0, 20);
      window.setInfoPopup(s);
      netPlayThread.Start();
    }*/
Supernature2k's avatar
Supernature2k committed
419 420


421 422 423 424 425
    //run the command line scraper then quit
    if (scrape_cmdline)
    {
      return run_scraper_cmdline();
    }
426

427 428
    //dont generate joystick events while we're loading (hopefully fixes "automatically started emulator" bug)
    SDL_JoystickEventState(SDL_DISABLE);
429 430 431



432 433 434
    // preload what we can right away instead of waiting for the user to select it
    // this makes for no delays when accessing content, but a longer startup time
    //ViewController::get()->preload();
435

436
    //choose which GUI to open depending on if an input configuration already exists
Bkg2k's avatar
Bkg2k committed
437
    if (errorMsg == nullptr)
438
    {
439 440 441 442 443 444 445 446 447
      if (fs::exists(InputManager::getConfigPath()) && InputManager::getInstance()->getNumConfiguredDevices() > 0)
      {
        ViewController::get()->goToStart();
      }
      else
      {
        window.pushGui(new GuiDetectDevice(&window, true, []
        { ViewController::get()->goToStart(); }));
      }
448
    }
449

450 451 452 453 454 455
    // Create a flag in  temporary directory to signal READY state
    fs::path ready_path = fs::temp_directory_path();
    ready_path /= "emulationstation.ready";
    FILE* ready_file = fopen(ready_path.c_str(), "w");
    if (ready_file)
      fclose(ready_file);
456

457 458
    //generate joystick events since we're done loading
    SDL_JoystickEventState(SDL_ENABLE);
459

460 461 462 463 464
    int popupDuration = Settings::getInstance()->getInt("MusicPopupTime");
    int lastTime = SDL_GetTicks();
    bool running = true;
    bool doReboot = false;
    bool doShutdown = false;
465

Bkg2k's avatar
Bkg2k committed
466 467
    DemoMode demoMode(window);

468
    while (running)
469
    {
470 471
      SDL_Event event;
      while (SDL_PollEvent(&event))
472
      {
473
        switch (event.type)
474
        {
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508
          case SDL_JOYHATMOTION:
          case SDL_JOYBUTTONDOWN:
          case SDL_JOYBUTTONUP:
          case SDL_KEYDOWN:
          case SDL_KEYUP:
          case SDL_JOYAXISMOTION:
          case SDL_TEXTINPUT:
          case SDL_TEXTEDITING:
          case SDL_JOYDEVICEADDED:
          case SDL_JOYDEVICEREMOVED:
            InputManager::getInstance()->parseEvent(event, &window);
            break;
          case SDL_QUIT:
            running = false;
            break;
          case RecalboxSystem::SDL_FAST_QUIT | RecalboxSystem::SDL_RB_REBOOT:
            running = false;
            doReboot = true;
            Settings::getInstance()->setBool("IgnoreGamelist", true);
            break;
          case RecalboxSystem::SDL_FAST_QUIT | RecalboxSystem::SDL_RB_SHUTDOWN:
            running = false;
            doShutdown = true;
            Settings::getInstance()->setBool("IgnoreGamelist", true);
            break;
          case SDL_QUIT | RecalboxSystem::SDL_RB_REBOOT:
            running = false;
            doReboot = true;
            break;
          case SDL_QUIT | RecalboxSystem::SDL_RB_SHUTDOWN:
            running = false;
            doShutdown = true;
            break;
          default:
509
          {
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
            if (event.type == NetPlayPopupEvent)
            {
              std::shared_ptr<GuiInfoPopup> popup = std::make_shared<GuiInfoPopup>(&window,
                                                                                   netPlayThread.GetLastPopupText(),
                                                                                   popupDuration, 20);
              window.setInfoPopup(popup);
            }
            else if (event.type == MusicStartEvent)
            {
              std::shared_ptr<GuiInfoPopup> popup = std::make_shared<GuiInfoPopup>(&window,
                                                                                   AudioManager::getInstance()->GetLastPopupText(),
                                                                                   popupDuration, 10);
              window.setInfoPopup(popup);
            }
            break;
525 526 527 528
          }
        }
      }

529 530
      if (window.isSleeping())
      {
Bkg2k's avatar
Bkg2k committed
531 532 533 534 535
        if (demoMode.hasDemoMode())
        {
          demoMode.runDemo();
        }

536
        lastTime = SDL_GetTicks();
Bkg2k's avatar
Bkg2k committed
537 538
        // this doesn't need to be accurate, we're just giving up our CPU time until something wakes us up
        SDL_Delay(1);
539 540
        continue;
      }
541

542 543 544
      int curTime = SDL_GetTicks();
      int deltaTime = curTime - lastTime;
      lastTime = curTime;
545

546 547 548
      // cap deltaTime at 1000
      if (deltaTime > 1000 || deltaTime < 0)
        deltaTime = 1000;
549

550 551 552
      window.update(deltaTime);
      window.render();
      Renderer::swapBuffers();
553

554 555
      Log::flush();
    }
556

557 558 559
    // Clean ready flag
    if (fs::exists(ready_path))
      fs::remove(ready_path);
560

561 562
    while (window.peekGui() != ViewController::get())
      delete window.peekGui();
563

564 565 566 567 568
    window.renderShutdownScreen();
    SystemData::deleteSystems();
    window.deinit();
    LOG(LogInfo) << "EmulationStation cleanly shutting down.";
    if (doReboot)
Bkg2k's avatar
Bkg2k committed
569
    {
570 571 572 573 574 575 576
      LOG(LogInfo) << "Rebooting system";
      int res1 = system("touch /tmp/reboot.please");
      int res2 = system("shutdown -r now");
      if ((res1 | res2) != 0)
      {
        LOG(LogError) << "Error rebooting system";
      }
Bkg2k's avatar
Bkg2k committed
577
    }
578
    else if (doShutdown)
Bkg2k's avatar
Bkg2k committed
579
    {
580 581 582 583 584 585 586
      LOG(LogInfo) << "Shutting system down";
      int res1 = system("touch /tmp/shutdown.please");
      int res2 = system("shutdown -h now");
      if ((res1 | res2) != 0)
      {
        LOG(LogError) << "Error shutting system down";
      }
Bkg2k's avatar
Bkg2k committed
587
    }
588
  }
589 590 591 592 593
  catch(std::exception& ex)
  {
    LOG(LogError) << "Main thread crashed.";
    LOG(LogError) << "Exception: " << ex.what();
  }
594

595
  return 0;
596
}
597