diff --git a/data/language/en-GB.txt b/data/language/en-GB.txt index 1f4b3466a3..8533df7a85 100644 --- a/data/language/en-GB.txt +++ b/data/language/en-GB.txt @@ -3651,6 +3651,15 @@ STR_6396 :Disable screensaver and monitor power saving STR_6397 :If checked, screensaver and other monitor power saving features will be inhibited while OpenRCT2 is running. STR_6398 :File contains unsupported ride types. Please update to a newer version of OpenRCT2. STR_6399 :OpenRCT2 needs files from the original RollerCoaster Tycoon 2 in order to work. Please set the “game_path” variable in config.ini to the directory where you installed RollerCoaster Tycoon 2, then restart OpenRCT2. +STR_6400 :I have the GOG installer for RollerCoaster Tycoon 2 downloaded, but it is not installed +STR_6401 :I have RollerCoaster Tycoon 2 installed already +STR_6402 :OpenRCT2 Data Setup +STR_6403 :Select which applies best to you +STR_6404 :Please select GOG RollerCoaster Tycoon 2 installer +STR_6405 :Select GOG Installer +STR_6406 :GOG RollerCoaster Tycoon 2 Installer +STR_6407 :This may take a few minutes +STR_6408 :Please install innoextract to extract GOG Installer ############# # Scenarios # diff --git a/debian/control b/debian/control index 9c3d0a27de..06cf51d293 100644 --- a/debian/control +++ b/debian/control @@ -12,6 +12,7 @@ Homepage: https://openrct2.io/ Vcs-Browser: https://github.com/OpenRCT2/OpenRCT2 Vcs-Git: https://github.com/OpenRCT2/OpenRCT2 Depends: ${shlibs:Depends}, ${misc:Depends} +Recommends: innoextract Description: An open source re-implementation of Roller Coaster Tycoon 2. An open source clone of RollerCoaster Tycoon 2 built by decompiling the original game one bit at a time. diff --git a/distribution/changelog.txt b/distribution/changelog.txt index bc8b39adff..0ad984c819 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -36,6 +36,7 @@ 0.3.2 (2020-11-01) ------------------------------------------------------------------------ +- Feature: [#12307] For Linux users, allow extraction of GOG installer via innoextract. - Feature: [#12110] Add Hybrid Coaster (Rocky Mountain Construction I-Box) track type. - Feature: [#12999] .sea (RCT Classic) scenarios are now listed in the “New Scenario” dialog. - Feature: [#13000] objective_options command for console. diff --git a/readme.md b/readme.md index bc2bb6ef11..27e3f5664b 100644 --- a/readme.md +++ b/readme.md @@ -116,6 +116,7 @@ The program can also be built as a command line program using CMake. This type o - gl (commonly provided by Mesa or GPU vendors; only for UI client, can be disabled) - duktape (unless scripting is disabled) - cmake +- innoextract (optional runtime dependency; used for GOG installer extraction during setup) Refer to https://github.com/OpenRCT2/OpenRCT2/wiki/Building-OpenRCT2-on-Linux#required-packages-general for more information about installing the packages. diff --git a/src/openrct2-ui/UiContext.Android.cpp b/src/openrct2-ui/UiContext.Android.cpp index c278f2dd80..4ba2a368fb 100644 --- a/src/openrct2-ui/UiContext.Android.cpp +++ b/src/openrct2-ui/UiContext.Android.cpp @@ -40,6 +40,17 @@ namespace OpenRCT2::Ui return false; } + bool HasMenuSupport() override + { + return false; + } + + int32_t ShowMenuDialog( + const std::vector& options, const std::string& title, const std::string& text) override + { + return -1; + } + void ShowMessageBox(SDL_Window* window, const std::string& message) override { log_verbose(message.c_str()); diff --git a/src/openrct2-ui/UiContext.Linux.cpp b/src/openrct2-ui/UiContext.Linux.cpp index 9b7b2e6c6d..3eacd67048 100644 --- a/src/openrct2-ui/UiContext.Linux.cpp +++ b/src/openrct2-ui/UiContext.Linux.cpp @@ -16,6 +16,7 @@ # include # include # include +# include # include # include # include @@ -256,6 +257,74 @@ namespace OpenRCT2::Ui return GetDialogApp(&dummy) != DIALOG_TYPE::NONE; } + bool HasMenuSupport() override + { + std::string executablePath; + DIALOG_TYPE dtype = GetDialogApp(&executablePath); + return dtype != DIALOG_TYPE::NONE; + } + + int32_t ShowMenuDialog( + const std::vector& options, const std::string& title, const std::string& text) override + { + std::string executablePath; + DIALOG_TYPE dtype = GetDialogApp(&executablePath); + + size_t longest_string = 0; + for (const auto& option : options) + { + if (option.size() > longest_string) + { + longest_string = option.size(); + } + } + + // zenity and kdialog don't support automatic scaling, this is an approximation + int width = (longest_string + 1) * 8; + int height = (options.size() + 1) * 8; + + switch (dtype) + { + case DIALOG_TYPE::ZENITY: + { + auto sb = StringBuilder(); + sb.Append(reinterpret_cast( + String::Format("zenity --list --column '' --width=%d --height=%d", width, height))); + for (const auto& option : options) + { + sb.Append(reinterpret_cast(String::Format(" '%s'", option.c_str()))); + } + sb.Append( + reinterpret_cast(String::Format(" --title '%s' --text '%s'", title.c_str(), text.c_str()))); + + std::string buff; + Platform::Execute(sb.GetBuffer(), &buff); + return std::find(options.begin(), options.end(), buff) - options.begin(); + } + case DIALOG_TYPE::KDIALOG: + { + auto sb = StringBuilder(); + sb.Append(reinterpret_cast( + String::Format("kdialog --geometry %dx%d --title '%s' --menu ", width, height, title.c_str()))); + sb.Append(reinterpret_cast(String::Format(" '%s'", text.c_str()))); + for (const auto& option : options) + { + sb.Append(reinterpret_cast(String::Format(" '%s' '%s'", option.c_str(), option.c_str()))); + } + + std::string buff; + Platform::Execute(sb.GetBuffer(), &buff); + return std::find(options.begin(), options.end(), buff) - options.begin(); + } + default: + { + break; + } + } + + return options.size(); + } + private: static DIALOG_TYPE GetDialogApp(std::string* executablePath) { diff --git a/src/openrct2-ui/UiContext.Win32.cpp b/src/openrct2-ui/UiContext.Win32.cpp index b5d3a11d16..9ffc58d766 100644 --- a/src/openrct2-ui/UiContext.Win32.cpp +++ b/src/openrct2-ui/UiContext.Win32.cpp @@ -95,6 +95,17 @@ namespace OpenRCT2::Ui MessageBoxW(hwnd, messageW.c_str(), L"OpenRCT2", MB_OK); } + bool HasMenuSupport() override + { + return false; + } + + int32_t ShowMenuDialog( + const std::vector& options, const std::string& title, const std::string& text) override + { + return -1; + } + void OpenFolder(const std::string& path) override { std::wstring pathW = String::ToWideChar(path); diff --git a/src/openrct2-ui/UiContext.cpp b/src/openrct2-ui/UiContext.cpp index b79b1754d2..c84ed7657c 100644 --- a/src/openrct2-ui/UiContext.cpp +++ b/src/openrct2-ui/UiContext.cpp @@ -604,6 +604,16 @@ public: _platformUiContext->ShowMessageBox(_window, message); } + bool HasMenuSupport() override + { + return _platformUiContext->HasMenuSupport(); + } + + int32_t ShowMenuDialog(const std::vector& options, const std::string& title, const std::string& text) override + { + return _platformUiContext->ShowMenuDialog(options, title, text); + } + void OpenFolder(const std::string& path) override { _platformUiContext->OpenFolder(path); diff --git a/src/openrct2-ui/UiContext.h b/src/openrct2-ui/UiContext.h index ed1904f0d9..cd2b9fb339 100644 --- a/src/openrct2-ui/UiContext.h +++ b/src/openrct2-ui/UiContext.h @@ -12,6 +12,7 @@ #include #include #include +#include struct SDL_Window; @@ -33,6 +34,9 @@ namespace OpenRCT2 virtual bool IsSteamOverlayAttached() abstract; virtual void ShowMessageBox(SDL_Window* window, const std::string& message) abstract; + virtual bool HasMenuSupport() abstract; + virtual int ShowMenuDialog( + const std::vector& options, const std::string& title, const std::string& text) abstract; virtual void OpenFolder(const std::string& path) abstract; virtual void OpenURL(const std::string& url) abstract; diff --git a/src/openrct2-ui/UiContext.macOS.mm b/src/openrct2-ui/UiContext.macOS.mm index cf84f450a9..c62158990c 100644 --- a/src/openrct2-ui/UiContext.macOS.mm +++ b/src/openrct2-ui/UiContext.macOS.mm @@ -64,6 +64,17 @@ namespace OpenRCT2::Ui } } + bool HasMenuSupport() override + { + return false; + } + + int32_t ShowMenuDialog( + const std::vector& options, const std::string& title, const std::string& text) override + { + return -1; + } + void OpenFolder(const std::string& path) override { @autoreleasepool diff --git a/src/openrct2/config/Config.cpp b/src/openrct2/config/Config.cpp index 7e60af7326..01dac05cad 100644 --- a/src/openrct2/config/Config.cpp +++ b/src/openrct2/config/Config.cpp @@ -11,6 +11,7 @@ #include "../Context.h" #include "../OpenRCT2.h" +#include "../PlatformEnvironment.h" #include "../core/Console.hpp" #include "../core/File.h" #include "../core/FileStream.h" @@ -721,6 +722,21 @@ namespace Config } return std::string(); } + + static bool ExtractGogInstaller(const utf8* installerPath, const utf8* targetPath) + { + std::string path; + std::string output; + + if (!Platform::FindApp("innoextract", &path)) + { + GetContext()->GetUiContext()->ShowMessageBox(format_string(STR_INSTALL_INNOEXTRACT, nullptr)); + return false; + } + int32_t exit_status = Platform::Execute( + String::Format("%s '%s' --exclude-temp --output-dir '%s'", path.c_str(), installerPath, targetPath), &output); + return exit_status == 0; + } } // namespace Config GeneralConfiguration gConfigGeneral; @@ -816,13 +832,71 @@ bool config_find_or_browse_install_directory() while (true) { uiContext->ShowMessageBox(format_string(STR_NEEDS_RCT2_FILES, nullptr)); + std::string gog = language_get_string(STR_OWN_ON_GOG); + std::string hdd = language_get_string(STR_INSTALLED_ON_HDD); - std::string installPath = uiContext->ShowDirectoryDialog(format_string(STR_PICK_RCT2_DIR, nullptr)); + std::vector options; + std::string chosenOption; + + if (uiContext->HasMenuSupport()) + { + options.push_back(hdd); + options.push_back(gog); + int optionIndex = uiContext->ShowMenuDialog( + options, language_get_string(STR_OPENRCT2_SETUP), language_get_string(STR_WHICH_APPLIES_BEST)); + if (optionIndex < 0 || static_cast(optionIndex) >= options.size()) + { + // graceful fallback if app errors or user exits out of window + chosenOption = hdd; + } + else + { + chosenOption = options[optionIndex]; + } + } + + chosenOption = hdd; + + std::string installPath; + if (chosenOption == hdd) + { + installPath = uiContext->ShowDirectoryDialog(language_get_string(STR_PICK_RCT2_DIR)); + } + else if (chosenOption == gog) + { + uiContext->ShowMessageBox(language_get_string(STR_PLEASE_SELECT_GOG_INSTALLER)); + utf8 gogPath[4096]; + std::string dest = Path::Combine( + GetContext()->GetPlatformEnvironment()->GetDirectoryPath(DIRBASE::CONFIG), "rct2"); + file_dialog_desc desc; + memset(&desc, 0, sizeof(desc)); + desc.type = FileDialogType::Open; + desc.title = language_get_string(STR_SELECT_GOG_INSTALLER); + desc.filters[0].name = language_get_string(STR_GOG_INSTALLER); + desc.filters[0].pattern = "*.exe"; + desc.filters[1].name = language_get_string(STR_ALL_FILES); + desc.filters[1].pattern = "*"; + desc.filters[2].name = nullptr; + + desc.initial_directory = Platform::GetFolderPath(SPECIAL_FOLDER::USER_HOME).c_str(); + + if (!platform_open_common_file_dialog(gogPath, &desc, 4096)) + { + return false; + } + + uiContext->ShowMessageBox(language_get_string(STR_THIS_WILL_TAKE_A_FEW_MINUTES)); + if (!Config::ExtractGogInstaller(gogPath, dest.c_str())) + { + return false; + } + + installPath = Path::Combine(dest, "app"); + } if (installPath.empty()) { return false; } - Memory::Free(gConfigGeneral.rct2_path); gConfigGeneral.rct2_path = String::Duplicate(installPath.c_str()); diff --git a/src/openrct2/localisation/StringIds.h b/src/openrct2/localisation/StringIds.h index 9d00e48a67..cd56a7ea6a 100644 --- a/src/openrct2/localisation/StringIds.h +++ b/src/openrct2/localisation/StringIds.h @@ -3905,6 +3905,16 @@ enum STR_NEEDS_RCT2_FILES_MANUAL = 6399, + STR_OWN_ON_GOG = 6400, + STR_INSTALLED_ON_HDD = 6401, + STR_OPENRCT2_SETUP = 6402, + STR_WHICH_APPLIES_BEST = 6403, + STR_PLEASE_SELECT_GOG_INSTALLER = 6404, + STR_SELECT_GOG_INSTALLER = 6405, + STR_GOG_INSTALLER = 6406, + STR_THIS_WILL_TAKE_A_FEW_MINUTES = 6407, + STR_INSTALL_INNOEXTRACT = 6408, + // Have to include resource strings (from scenarios and objects) for the time being now that language is partially working /* MAX_STR_COUNT = 32768 */ // MAX_STR_COUNT - upper limit for number of strings, not the current count strings }; diff --git a/src/openrct2/ui/DummyUiContext.cpp b/src/openrct2/ui/DummyUiContext.cpp index b636c8eef4..9f2a8915e7 100644 --- a/src/openrct2/ui/DummyUiContext.cpp +++ b/src/openrct2/ui/DummyUiContext.cpp @@ -90,6 +90,16 @@ namespace OpenRCT2::Ui void ShowMessageBox(const std::string& /*message*/) override { } + bool HasMenuSupport() override + { + return false; + } + + int32_t ShowMenuDialog( + const std::vector& options, const std::string& title, const std::string& text) override + { + return options.size(); + } void OpenFolder(const std::string& /*path*/) override { } diff --git a/src/openrct2/ui/UiContext.h b/src/openrct2/ui/UiContext.h index e169ccfd9b..2668e1ac9f 100644 --- a/src/openrct2/ui/UiContext.h +++ b/src/openrct2/ui/UiContext.h @@ -113,6 +113,11 @@ namespace OpenRCT2 virtual void TriggerResize() abstract; virtual void ShowMessageBox(const std::string& message) abstract; + + virtual bool HasMenuSupport() abstract; + // Creates a menu with a series of options, returns the index of the selected option + virtual int ShowMenuDialog( + const std::vector& options, const std::string& title, const std::string& text) abstract; virtual void OpenFolder(const std::string& path) abstract; virtual void OpenURL(const std::string& url) abstract; virtual std::string ShowFileDialog(const FileDialogDesc& desc) abstract;