mirror of
https://github.com/lutris/lutris
synced 2024-11-02 14:59:58 +00:00
Repair launch_config paths, especially for WINE games.
We're getting bogus paths in the launch_config options from GOG auto-generated installers. They have backslashes (boo!) and the "exe" entry is a partial path that is relative to the *games* working directory, but launch_config may have its own. Use both together and it all falls apart. This PR adds logic to fix Windows paths for the WINE runner only, replacing \ with / and fixing casing. It also detects when the "exe" is present relative to the game's directory but not the configs, and generates a new "exe" relative to the config's working directory if it can. Resolves #4662
This commit is contained in:
parent
e6efe2366d
commit
6e26f47b6f
2 changed files with 72 additions and 4 deletions
|
@ -255,8 +255,10 @@ class Runner: # pylint: disable=too-many-public-methods
|
|||
if a non-default config is chosen."""
|
||||
gameplay_info["command"] = self.get_launch_config_command(gameplay_info, launch_config)
|
||||
|
||||
if launch_config.get("working_dir"):
|
||||
gameplay_info["working_dir"] = launch_config["working_dir"]
|
||||
config_working_dir = self.get_launch_config_working_dir(launch_config)
|
||||
|
||||
if config_working_dir:
|
||||
gameplay_info["working_dir"] = config_working_dir
|
||||
|
||||
def get_launch_config_command(self, gameplay_info, launch_config):
|
||||
"""Generates a new command for the gameplay_info, to implement the launch_config.
|
||||
|
@ -277,14 +279,62 @@ class Runner: # pylint: disable=too-many-public-methods
|
|||
# The 'file' sort of gameplay_info cannot be made to use a configuration
|
||||
raise GameConfigError(_("The runner could not find a command to apply the configuration to."))
|
||||
|
||||
if "exe" in launch_config:
|
||||
command.append(launch_config["exe"])
|
||||
exe = self.get_launch_config_exe(launch_config)
|
||||
if exe:
|
||||
command.append(exe)
|
||||
|
||||
if launch_config.get("args"):
|
||||
command += strings.split_arguments(launch_config["args"])
|
||||
|
||||
return command
|
||||
|
||||
def get_launch_config_exe(self, launch_config):
|
||||
"""Locates the "exe" of the launch config. If it appears
|
||||
to be relative to the game's working_dir, this will try to
|
||||
adjust it to be relative to the config's instead.
|
||||
"""
|
||||
exe = launch_config.get("exe")
|
||||
config_working_dir = self.get_launch_config_working_dir(launch_config)
|
||||
|
||||
if exe and config_working_dir and not os.path.isabs(exe):
|
||||
exe_from_config = self.resolve_config_path(exe, config_working_dir)
|
||||
exe_from_game = self.resolve_config_path(exe)
|
||||
|
||||
if os.path.exists(exe_from_game) and not os.path.exists(exe_from_config):
|
||||
relative = os.path.relpath(exe_from_game, start=config_working_dir)
|
||||
if not relative.startswith("../"):
|
||||
return relative
|
||||
|
||||
return exe
|
||||
|
||||
def get_launch_config_working_dir(self, launch_config):
|
||||
"""Extracts the "working_dir" from the config, and resolves to relative
|
||||
to the game's working directory, so that an absolute path results.
|
||||
|
||||
This returns None if no working_dir is present, or if it found to be bogus.
|
||||
"""
|
||||
config_working_dir = launch_config.get("working_dir")
|
||||
if config_working_dir:
|
||||
config_working_dir = self.resolve_config_path(config_working_dir)
|
||||
|
||||
return config_working_dir
|
||||
|
||||
def resolve_config_path(self, path, relative_to=None):
|
||||
"""Interpret a path taken from the launch_config relative to
|
||||
a working directory, using the game's working_dir if that is omitted.
|
||||
|
||||
This is provided as a method so the WINE runner can try to convert
|
||||
Windows-style paths to usable paths.
|
||||
"""
|
||||
if not os.path.isabs(path):
|
||||
if not relative_to:
|
||||
relative_to = self.working_dir
|
||||
|
||||
if relative_to:
|
||||
return os.path.join(relative_to, path)
|
||||
|
||||
return path
|
||||
|
||||
def prelaunch(self):
|
||||
"""Run actions before running the game, override this method in runners; raise an
|
||||
exception if prelaunch fails, and it will be reported to the user, and
|
||||
|
|
|
@ -576,6 +576,24 @@ class wine(Runner):
|
|||
return self.runner_config.get("custom_wine_path", "")
|
||||
return os.path.join(WINE_DIR, version, "bin/wine")
|
||||
|
||||
def resolve_config_path(self, path, relative_to=None):
|
||||
# Resolve paths with tolerance for Windows-isms;
|
||||
# first try to fix mismatched casing, and then if that
|
||||
# finds no file or directory, try again after swapping in
|
||||
# slashes for backslashes.
|
||||
|
||||
resolved = super().resolve_config_path(path, relative_to)
|
||||
resolved = system.fix_path_case(resolved)
|
||||
|
||||
if not os.path.exists(resolved) and '\\' in path:
|
||||
fixed = path.replace('\\', '/')
|
||||
fixed_resolved = super().resolve_config_path(fixed, relative_to)
|
||||
fixed_resolved = system.fix_path_case(fixed_resolved)
|
||||
if fixed_resolved:
|
||||
return fixed_resolved
|
||||
|
||||
return resolved
|
||||
|
||||
def get_executable(self, version=None, fallback=True):
|
||||
"""Return the path to the Wine executable.
|
||||
A specific version can be specified if needed.
|
||||
|
|
Loading…
Reference in a new issue