Beispiel #1
0
    def _check_binary_dependencies(self):
        """Check if all required binaries are installed on the system.

        This reads a `require-binaries` entry in the script, parsed the same way as
        the `requires` entry.
        """
        binary_dependencies = unpack_dependencies(self.installer.script.get("require-binaries"))
        for dependency in binary_dependencies:
            if isinstance(dependency, tuple):
                installed_binaries = {
                    dependency_option: bool(system.find_executable(dependency_option))
                    for dependency_option in dependency
                }
                if not any(installed_binaries.values()):
                    raise ScriptingError("This installer requires %s on your system" % " or ".join(dependency))
            else:
                if not system.find_executable(dependency):
                    raise ScriptingError("This installer requires %s on your system" % dependency)
Beispiel #2
0
    def check_md5(self, data):
        """Checks compare the MD5 checksum of `file` and compare it to `value`"""
        self._check_required_params(['file', 'value'], data, 'check_md5')
        filename = self._substitute(data['file'])
        hash_string = self._killable_process(system.get_md5_hash, filename)

        if hash_string != data['value']:
            raise ScriptingError("MD5 checksum mismatch", data)
        self._iter_commands()
Beispiel #3
0
 def _monitor_task(self, command):
     if not command.is_running:
         logger.debug("Return code: %s", command.return_code)
         if command.return_code != "0":
             raise ScriptingError("Command exited with code %s",
                                  command.return_code)
         self._iter_commands()
         return False
     return True
Beispiel #4
0
 def validate_scripts(self):
     """Auto-fixes some script aspects and checks for mandatory fields"""
     for script in self.scripts:
         for item in ["description", "notes"]:
             script[item] = script.get(item) or ""
         for item in ["name", "runner", "version"]:
             if item not in script:
                 logger.error("Invalid script: %s", script)
                 raise ScriptingError('Missing field "%s" in install script' % item)
Beispiel #5
0
 def get_runner_class(self, runner_name):
     """Runner the runner class from its name"""
     try:
         runner = import_runner(runner_name)
     except InvalidRunner as err:
         GLib.idle_add(self.parent.cancel_button.set_sensitive, True)
         raise ScriptingError(
             _("Invalid runner provided %s") % runner_name) from err
     return runner
Beispiel #6
0
 def _check_required_params(params, command_data, command_name):
     """Verify presence of a list of parameters required by a command."""
     if isinstance(params, str):
         params = [params]
     for param in params:
         if isinstance(param, tuple):
             param_present = any(key in command_data for key in param)
             if not param_present:
                 raise ScriptingError(
                     "One of %s parameter is mandatory for the %s command" % (" or ".join(param), command_name),
                     command_data,
                 )
         else:
             if param not in command_data:
                 raise ScriptingError(
                     "The %s parameter is mandatory for the %s command" % (param, command_name),
                     command_data,
                 )
Beispiel #7
0
 def install_runner(self, runner):
     logger.debug('Installing %s', runner.name)
     try:
         runner.install(version=self._get_runner_version(),
                        downloader=self.parent.start_download,
                        callback=self.install_runners)
     except (NonInstallableRunnerError, RunnerInstallationError) as ex:
         logger.error(ex.message)
         raise ScriptingError(ex.message)
Beispiel #8
0
 def _check_required_params(self, params, command_data, command_name):
     """Verify presence of a list of parameters required by a command."""
     if type(params) is str:
         params = [params]
     for param in params:
         if param not in command_data:
             raise ScriptingError(
                 'The "%s" parameter is mandatory for '
                 'the %s command' % (param, command_name), command_data)
Beispiel #9
0
 def _map_command(self, command_data):
     """Map a directive from the `installer` section to an internal
     method."""
     command_name, command_params = self._get_command_name_and_params(
         command_data)
     if not hasattr(self, command_name):
         raise ScriptingError('The command "%s" does not exist.' %
                              command_name)
     return getattr(self, command_name), command_params
Beispiel #10
0
 def _append_steam_data_to_files(self, runner_class):
     data_path = self._get_steam_game_path(runner_class)
     if not data_path or not os.path.exists(data_path):
         raise ScriptingError("Unable to get Steam data for game")
     self.game_files[self.steam_data['file_id']] = os.path.abspath(
         os.path.join(
             data_path, self.steam_data['steam_rel_path']
         )
     )
     self.iter_game_files()
Beispiel #11
0
    def on_download_complete(self, _widget, _data, callback=None, callback_data=None):
        """Action called on a completed download."""
        if callback:
            try:
                callback(**callback_data)
            except Exception as ex:  # pylint: disable:broad-except
                raise ScriptingError(str(ex))

        self.interpreter.abort_current_task = None
        self.interpreter.iter_game_files()
Beispiel #12
0
    def check_hash(self, checksum, dest_file, dest_file_uri):
        """Checks the checksum of `file` and compare it to `value`

        Args:
            checksum (str): The checksum to look for (type:hash)
            dest_file (str): The path to the destination file
            dest_file_uri (str): The uri for the destination file
        """

        try:
            hash_type, expected_hash = checksum.split(':', 1)
        except ValueError:
            raise ScriptingError(
                "Invalid checksum, expected format (type:hash) ",
                dest_file_uri)

        if system.get_file_checksum(dest_file, hash_type) != expected_hash:
            raise ScriptingError(
                hash_type.capitalize() + " checksum mismatch ", dest_file_uri)
Beispiel #13
0
 def _check_required_params(params, command_data, command_name):
     """Verify presence of a list of parameters required by a command."""
     if isinstance(params, str):
         params = [params]
     for param in params:
         if isinstance(param, tuple):
             param_present = False
             for key in param:
                 if key in command_data:
                     param_present = True
             if not param_present:
                 raise ScriptingError(
                     'One of %s parameter is mandatory for the %s command' %
                     (' or '.join(param), command_name), command_data)
         else:
             if param not in command_data:
                 raise ScriptingError(
                     'The %s parameter is mandatory for the %s command' %
                     (param, command_name), command_data)
Beispiel #14
0
    def _download_file(self, game_file):
        """Download a file referenced in the installer script.

        Game files can be either a string, containing the location of the
        file to fetch or a dict with the following keys:
        - url : location of file, if not present, filename will be used
                this should be the case for local files.
        - filename : force destination filename when url is present or path
                     of local file.
        """
        # Setup file_id, file_uri and local filename
        file_id = list(game_file.keys())[0]
        if isinstance(game_file[file_id], dict):
            filename = game_file[file_id]['filename']
            file_uri = game_file[file_id]['url']
        else:
            file_uri = game_file[file_id]
            filename = os.path.basename(file_uri)
        if not filename:
            raise ScriptingError("No filename provided, please provide 'url' and 'filename' parameters in the script")
        if file_uri.startswith("/"):
            file_uri = "file://" + file_uri
        elif file_uri.startswith(("$WINESTEAM", "$STEAM")):
            # Download Steam data
            self._download_steam_data(file_uri, file_id)
            return

        # Check for file availability in PGA
        pga_uri = pga.check_for_file(self.game_slug, file_id)
        if pga_uri:
            file_uri = pga_uri

        # Setup destination path
        dest_file = os.path.join(self.cache_path, filename)

        logger.debug("Downloading [%s]: %s to %s", file_id, file_uri, dest_file)

        if file_uri.startswith("N/A"):
            # Ask the user where the file is located
            parts = file_uri.split(":", 1)
            if len(parts) == 2:
                message = parts[1]
            else:
                message = "Please select file '%s'" % file_id
            self.current_file_id = file_id
            self.parent.ask_user_for_file(message)
            return

        if os.path.exists(dest_file):
            os.remove(dest_file)

        # Change parent's status
        self.parent.set_status('')
        self.game_files[file_id] = dest_file
        self.parent.start_download(file_uri, dest_file)
Beispiel #15
0
    def execute(self, data):
        """Run an executable file."""
        args = []
        terminal = None
        working_dir = None
        if isinstance(data, dict):
            self._check_required_params('file', data, 'execute')
            file_ref = data['file']
            args_string = data.get('args', '')
            for arg in shlex.split(args_string):
                args.append(self._substitute(arg))
            terminal = data.get('terminal')
            working_dir = data.get('working_dir')
        else:
            file_ref = data

        # Determine whether 'file' value is a file id or a path
        exec_path = self._get_file(file_ref) or self._substitute(file_ref)
        if not exec_path:
            raise ScriptingError("Unable to find file %s" % file_ref, file_ref)
        if not os.path.exists(exec_path):
            raise ScriptingError("Unable to find required executable",
                                 exec_path)
        if not os.access(exec_path, os.X_OK):
            self.chmodx(exec_path)

        if terminal:
            terminal = system.get_default_terminal()

        if not working_dir or not os.path.exists(working_dir):
            working_dir = self.target_path

        command = [exec_path] + args
        logger.debug("Executing %s" % command)
        thread = LutrisThread(command,
                              env=runtime.get_env(),
                              term=terminal,
                              cwd=self.target_path,
                              watch=False)
        self.abort_current_task = thread.killall
        thread.run()
        self.abort_current_task = None
Beispiel #16
0
 def url(self):
     _url = ""
     if isinstance(self._file_meta, dict):
         if "url" not in self._file_meta:
             raise ScriptingError(_("missing field `url` for file `%s`") % self.id)
         _url = self._file_meta["url"]
     else:
         _url = self._file_meta
     if _url.startswith("/"):
         return "file://" + _url
     return _url
Beispiel #17
0
 def on_files_confirmed(self, _button, file_box):
     """Call this when the user confirms the install files
     This will start the downloads.
     """
     self.set_status("")
     self.continue_button.set_sensitive(False)
     try:
         file_box.start_all()
         self.continue_button.disconnect(self.continue_handler)
     except PermissionError as ex:
         self.continue_button.set_sensitive(True)
         raise ScriptingError("Unable to get files: %s" % ex)
Beispiel #18
0
 def install_runner(self, runner):
     """Install runner required by the install script"""
     logger.debug("Installing %s", runner.name)
     try:
         runner.install(
             version=self._get_runner_version(),
             downloader=simple_downloader,
             callback=self.install_runners,
         )
     except (NonInstallableRunnerError, RunnerInstallationError) as ex:
         logger.error(ex.message)
         raise ScriptingError(ex.message) from ex
Beispiel #19
0
    def rename(self, params):
        """Rename file or folder."""
        self._check_required_params(['src', 'dst'], params, 'rename')
        src, dst = self._get_move_paths(params)
        if not os.path.exists(src):
            raise ScriptingError(
                "Rename error, source path does not exist: %s" % src)
        if os.path.isdir(dst):
            os.rmdir(dst)  # Remove if empty
        if os.path.exists(dst):
            raise ScriptingError(
                "Rename error, destination already exists: %s" % src)
        dst_dir = os.path.dirname(dst)

        # Pre-move on dest filesystem to avoid error with
        # os.rename through different filesystems
        temp_dir = os.path.join(dst_dir, "lutris_rename_temp")
        os.makedirs(temp_dir)
        self._killable_process(shutil.move, src, temp_dir)
        src = os.path.join(temp_dir, os.path.basename(src))
        os.renames(src, dst)
Beispiel #20
0
 def filename(self):
     if isinstance(self._file_meta, dict):
         if "filename" not in self._file_meta:
             raise ScriptingError(_("missing field `filename` in file `%s`") % self.id)
         return self._file_meta["filename"]
     if self._file_meta.startswith("N/A"):
         if self.uses_pga_cache() and os.path.isdir(self.cache_path):
             return self.cached_filename
         return ""
     if self.url.startswith("$STEAM"):
         return self.url
     return os.path.basename(self._file_meta)
Beispiel #21
0
    def choose_installer(self):
        """Stage where we choose an install script."""
        self.title_label.set_markup('<b>Select which version to install</b>')
        self.installer_choice_box = Gtk.VBox()
        self.installer_choice = 0
        radio_group = None

        # Build list
        for index, script in enumerate(self.scripts):
            for item in ['description', 'notes']:
                script[item] = script.get(item) or ''
            for item in ['runner', 'version']:
                if item not in script:
                    raise ScriptingError('Missing field "%s" in install script' % item)

            runner = script['runner']
            version = script['version']
            label = "{} ({})".format(version, runner)
            btn = Gtk.RadioButton.new_with_label_from_widget(radio_group,
                                                             label)
            btn.connect('toggled', self.on_installer_toggled, index)
            self.installer_choice_box.pack_start(btn, False, False, 10)
            if not radio_group:
                radio_group = btn

        def _create_label(padding, text):
            label = Gtk.Label()
            label.set_max_width_chars(60)
            label.set_line_wrap(True)
            label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR)
            label.set_alignment(0, .5)
            label.set_margin_left(50)
            label.set_margin_right(50)
            label.set_markup(text)
            self.installer_choice_box.pack_start(label, True, True, padding)
            return label

        self.description_label = _create_label(
            10, "<i><b>{}</b></i>".format(self.scripts[0]['description'])
        )
        self.notes_label = _create_label(
            5, "<i>{}</i>".format(self.scripts[0]['notes'])
        )

        self.widget_box.pack_start(self.installer_choice_box, False, False, 10)
        self.installer_choice_box.show_all()

        self.continue_button.grab_focus()
        self.continue_button.show()
        self.continue_handler = self.continue_button.connect(
            'clicked', self.on_installer_selected
        )
Beispiel #22
0
 def _check_required_params(params, command_data, command_name):
     """Verify presence of a list of parameters required by a command."""
     if isinstance(params, str):
         params = [params]
     for param in params:
         if isinstance(param, tuple):
             param_present = False
             for key in param:
                 if key in command_data:
                     param_present = True
             if not param_present:
                 raise ScriptingError(
                     _("One of {params} parameter is mandatory for the {cmd} command").format(
                         params=_(" or ").join(param), cmd=command_name),
                     command_data,
                 )
         else:
             if param not in command_data:
                 raise ScriptingError(
                     _("The {param} parameter is mandatory for the {cmd} command").format(
                         param=param, cmd=command_name),
                     command_data,
                 )
Beispiel #23
0
 def create_game_folder(self):
     """Create the game folder if needed and store if is was created"""
     if (self.installer.files and self.target_path
             and not system.path_exists(self.target_path)
             and self.installer.creates_game_folder):
         try:
             logger.debug("Creating destination path %s", self.target_path)
             os.makedirs(self.target_path)
             self.game_dir_created = True
         except PermissionError:
             raise ScriptingError(
                 "Lutris does not have the necessary permissions to install to path:",
                 self.target_path,
             )
Beispiel #24
0
    def swap_gog_game_files(self):
        if not self.gogid:
            raise ScriptingError("The installer has no GOG ID!")
        links = self.get_gog_download_links()
        installer_file_id = None
        if links:
            for index, file in enumerate(self.files):
                file_id = list(file.keys())[0]
                file_meta = file[file_id]
                if ((isinstance(file_meta, str)
                     and file_meta.startswith("N/A"))
                        or (isinstance(file_meta, dict)
                            and file_meta.get('url', '').startswith('N/A'))):
                    logger.debug("Removing file %s", file_id)
                    self.files.pop(index)
                    installer_file_id = file_id
                    break

        if not installer_file_id:
            raise ScriptingError(
                "Could not match a GOG installer file in the files")

        for index, link in enumerate(links):

            filename = link.split("?")[0].split("/")[-1]

            if filename.lower().endswith((".exe", ".sh")):
                file_id = installer_file_id
            else:
                file_id = "gog_file_%s" % index

            logger.debug("Adding GOG file %s as %s", filename, file_id)

            self.files.append({file_id: {
                "url": link,
                "filename": filename,
            }})
Beispiel #25
0
 def _check_dependency(self):
     """When a game is a mod or an extension of another game, check that the base
     game is installed.
     If the game is available, install the game in the base game folder.
     The first game available listed in the dependencies is the one picked to base
     the installed on.
     """
     if self.extends:
         dependencies = [self.extends]
     else:
         dependencies = strings.unpack_dependencies(self.requires)
     error_message = "You need to install {} before"
     for index, dependency in enumerate(dependencies):
         if isinstance(dependency, tuple):
             dependency_choices = [
                 self._get_installed_dependency(dep) for dep in dependency
             ]
             installed_games = [
                 dep for dep in dependency_choices if dep
             ]
             if not installed_games:
                 raise ScriptingError(
                     error_message.format(' or '.join(dependency))
                 )
             if index == 0:
                 self.target_path = installed_games[0]['directory']
                 self.requires = installed_games[0]['installer_slug']
         else:
             game = self._get_installed_dependency(dependency)
             if not game:
                 raise ScriptingError(
                     error_message.format(dependency)
                 )
             if index == 0:
                 self.target_path = game['directory']
                 self.requires = game['installer_slug']
Beispiel #26
0
 def _substitute_config(self, script_config):
     """Substitute values such as $GAMEDIR in a config dict."""
     config = {}
     for key in script_config:
         if not isinstance(key, str):
             raise ScriptingError("Game config key must be a string", key)
         value = script_config[key]
         if isinstance(value, list):
             config[key] = [self._substitute(i) for i in value]
         elif isinstance(value, dict):
             config[key] = {k: self._substitute(v) for (k, v) in value.items()}
         elif isinstance(value, bool):
             config[key] = value
         else:
             config[key] = self._substitute(value)
     return config
Beispiel #27
0
    def on_runners_ready(self, _widget=None):
        """The runners are ready, proceed with file selection"""
        if self.interpreter.extras is None:
            extras = self.interpreter.get_extras()
            if extras:
                self.show_extras(extras)
                return
        try:
            self.interpreter.installer.prepare_game_files()
        except UnavailableGame as ex:
            raise ScriptingError(str(ex))

        if not self.interpreter.installer.files:
            logger.debug("Installer doesn't require files")
            self.interpreter.launch_installer_commands()
            return
        self.show_installer_files_screen()
Beispiel #28
0
    def write_file(self, params):
        """Write text to a file."""
        self._check_required_params(["file", "content"], params, "write_file")

        # Get file
        dest_file_path = self._get_file(params["file"])

        # Create dir if necessary
        basedir = os.path.dirname(dest_file_path)
        os.makedirs(basedir, exist_ok=True)

        mode = params.get("mode", "w")
        if not mode.startswith(("a", "w")):
            raise ScriptingError(_("Wrong value for write_file mode: '%s'") % mode)

        with open(dest_file_path, mode, encoding='utf-8') as dest_file:
            dest_file.write(self._substitute(params["content"]))
Beispiel #29
0
    def get_game_config(self):
        """Return the game configuration"""
        if self.requires:
            # Load the base game config
            required_game = get_game_by_field(self.requires,
                                              field="installer_slug")
            if not required_game:
                required_game = get_game_by_field(self.requires, field="slug")
            if not required_game:
                raise ValueError(
                    "No game matched '%s' on installer_slug or slug" %
                    self.requires)
            base_config = LutrisConfig(
                runner_slug=self.runner,
                game_config_id=required_game["configpath"])
            config = base_config.game_level
        else:
            config = {"game": {}}

        # Config update
        if "system" in self.script:
            config["system"] = self._substitute_config(self.script["system"])
        if self.runner in self.script and self.script[self.runner]:
            config[self.runner] = self._substitute_config(
                self.script[self.runner])
        launcher, launcher_config = self.get_game_launcher_config(
            self.interpreter.game_files)
        if launcher:
            config["game"][launcher] = launcher_config

        if "game" in self.script:
            try:
                config["game"].update(self.script["game"])
            except ValueError:
                raise ScriptingError("Invalid 'game' section",
                                     self.script["game"])
            config["game"] = self._substitute_config(config["game"])
            if AUTO_ELF_EXE in config["game"].get("exe", ""):
                config["game"]["exe"] = find_linux_game_executable(
                    self.interpreter.target_path, make_executable=True)
            elif AUTO_WIN32_EXE in config["game"].get("exe", ""):
                config["game"]["exe"] = find_windows_game_executable(
                    self.interpreter.target_path)

        return config
Beispiel #30
0
    def write_file(self, params):
        """Write text to a file."""
        self._check_required_params(['file', 'content'], params, 'write_file')

        # Get file
        dest_file_path = self._get_file(params['file'])

        # Create dir if necessary
        basedir = os.path.dirname(dest_file_path)
        if not os.path.exists(basedir):
            os.makedirs(basedir)

        mode = params.get('mode', 'w')
        if not mode.startswith(('a', 'w')):
            raise ScriptingError("Wrong value for write_file mode: '%s'" % mode)

        with open(dest_file_path, mode) as dest_file:
            dest_file.write(self._substitute(params['content']))