コード例 #1
0
    def test_get_steam_apps_proton_precedence(self, custom_proton_factory,
                                              home_dir, steam_root, steam_dir,
                                              monkeypatch):
        """
        Create two Proton apps with the same name but located in
        different paths. Only one will be returned due to precedence
        in the directory paths
        """
        custom_compat_dir = home_dir / "CompatTools"

        monkeypatch.setenv("STEAM_EXTRA_COMPAT_TOOLS_PATHS",
                           str(custom_compat_dir))

        proton_app_a = custom_proton_factory(name="Fake Proton",
                                             compat_tool_dir=custom_compat_dir)

        steam_apps = get_steam_apps(steam_root=steam_root,
                                    steam_path=steam_dir,
                                    steam_lib_paths=[steam_dir])
        assert len(steam_apps) == 1
        assert str(steam_apps[0].install_path) == \
            str(proton_app_a.install_path)

        # Create a Proton app with the same name in the default directory;
        # this will override the former Proton app we created
        proton_app_b = custom_proton_factory(name="Fake Proton")

        steam_apps = get_steam_apps(steam_root=steam_root,
                                    steam_path=steam_dir,
                                    steam_lib_paths=[steam_dir])
        assert len(steam_apps) == 1
        assert str(steam_apps[0].install_path) == \
            str(proton_app_b.install_path)
コード例 #2
0
    def test_get_steam_apps_steamapps_case_warning(self, steam_app_factory,
                                                   steam_library_factory,
                                                   steam_root, steam_dir,
                                                   caplog):
        """
        Ensure a warning is logged if both 'steamapps' and 'SteamApps'
        directories exist at one of the Steam library directories
        """
        get_steam_apps(steam_root=steam_root,
                       steam_path=steam_dir,
                       steam_lib_paths=[steam_dir])

        # No log was created yet
        assert len([
            record for record in caplog.records
            if record.levelname == "WARNING"
        ]) == 0

        (steam_dir / "SteamApps").mkdir()

        get_steam_apps(steam_root=steam_root,
                       steam_path=steam_dir,
                       steam_lib_paths=[steam_dir])

        # Warning was logged due to two Steam app directories
        log = next(record for record in caplog.records
                   if record.levelname == "WARNING")
        assert ("directories were found at {}".format(str(steam_dir))
                in log.getMessage())
コード例 #3
0
ファイル: test_steam.py プロジェクト: Matoking/protontricks
    def test_get_steam_apps_missing_library_folder(self, steam_library_factory,
                                                   steam_dir, steam_root,
                                                   caplog):
        """
        Create multiple Steam library folders, delete one of them and ensure
        a warning is printed.

        This can happen if Protontricks is executed inside a Flatpak sandbox
        without the necessary filesystem permissions.
        """
        library_dir_a = steam_library_factory(name="LibraryA")
        library_dir_b = steam_library_factory(name="LibraryB")

        # Delete library B
        shutil.rmtree(str(library_dir_b))

        get_steam_apps(steam_root=steam_root,
                       steam_path=steam_dir,
                       steam_lib_paths=[library_dir_a, library_dir_b])

        warnings = [
            record for record in caplog.records
            if record.levelname == "WARNING"
        ]
        assert len(warnings) == 1

        warning = warnings[0]
        assert "{} not found.".format(str(library_dir_b)) in warning.message
コード例 #4
0
    def test_get_steam_apps_in_library_folder(self, default_proton,
                                              steam_library_factory,
                                              steam_app_factory, steam_dir,
                                              steam_root):
        """
        Create two games, one installed in the Steam installation directory
        and another in a Steam library folder
        """
        library_dir = steam_library_factory(name="GameDrive")
        steam_app_factory(name="Fake game 1", appid=10)
        steam_app_factory(name="Fake game 2",
                          appid=20,
                          library_dir=library_dir)

        steam_apps = get_steam_apps(
            steam_root=str(steam_root),
            steam_path=str(steam_dir),
            steam_lib_paths=[str(steam_dir), str(library_dir)])

        # Two games and the default Proton installation should be found
        assert len(steam_apps) == 3
        steam_app_a = next(app for app in steam_apps if app.appid == 10)
        steam_app_b = next(app for app in steam_apps if app.appid == 20)

        assert steam_app_a.install_path == \
            str(steam_dir / "steamapps" / "common" / "Fake game 1")
        assert steam_app_b.install_path == \
            str(library_dir / "steamapps" / "common" / "Fake game 2")
コード例 #5
0
ファイル: test_steam.py プロジェクト: Matoking/protontricks
    def test_get_steam_apps_custom_proton_corrupted_compatibilitytool(
            self, custom_proton_factory, steam_dir, steam_root, caplog):
        """
        Create a custom Proton installation with a corrupted
        compatibilitytool.vdf and ensure a warning is printed and the app
        is ignored
        """
        custom_proton = custom_proton_factory(name="Custom Proton")
        (custom_proton.install_path /
         "compatibilitytool.vdf").write_text("corrupted")

        steam_apps = get_steam_apps(steam_root=steam_root,
                                    steam_path=steam_dir,
                                    steam_lib_paths=[steam_dir])

        # Custom Proton is skipped due to empty tool manifest
        assert not any(app
                       for app in steam_apps if app.name == "Custom Proton")

        assert len([
            record for record in caplog.records
            if record.levelname == "WARNING"
        ]) == 1

        record = next(record for record in caplog.records
                      if record.levelname == "WARNING")

        assert record.getMessage().startswith(
            "Compatibility tool declaration at")
コード例 #6
0
ファイル: test_steam.py プロジェクト: Matoking/protontricks
    def test_get_steam_apps_steamapps_case_insensitive_fs(
            self, monkeypatch, steam_root, steam_dir, caplog):
        """
        Ensure that the "'steamapps' and 'SteamApps' both exist" warning
        is not printed if a case-insensitive file system is in use

        Regression test for https://github.com/Matoking/protontricks/issues/112
        """
        def _mock_is_dir(self):
            return self.name in ("steamapps", "SteamApps", "steam")

        # Mock the "existence" of both 'steamapps' and 'SteamApps' by
        # monkeypatching pathlib
        monkeypatch.setattr("pathlib.Path.is_dir", _mock_is_dir)

        get_steam_apps(steam_root=steam_root,
                       steam_path=steam_dir,
                       steam_lib_paths=[steam_dir])

        # No warning is printed
        assert len([
            record for record in caplog.records
            if record.levelname == "WARNING"
        ]) == 0
コード例 #7
0
    def test_get_steam_apps_custom_proton(self, default_proton,
                                          custom_proton_factory, steam_dir,
                                          steam_root):
        """
        Create a custom Proton installation and ensure
        'get_steam_apps' can find it
        """
        custom_proton = custom_proton_factory(name="Custom Proton")

        steam_apps = get_steam_apps(steam_root=str(steam_root),
                                    steam_path=str(steam_dir),
                                    steam_lib_paths=[str(steam_dir)])

        assert len(steam_apps) == 2

        found_custom_proton = next(app for app in steam_apps
                                   if app.name == "Custom Proton")
        assert found_custom_proton.install_path == \
            str(custom_proton.install_path)
コード例 #8
0
    def test_get_steam_apps_escape_chars(self, steam_app_factory,
                                         steam_library_factory, steam_root,
                                         steam_dir):
        """
        Create a Steam library directory with a name containing the
        character '[' and ensure it is found correctly.

        Regression test for https://github.com/Matoking/protontricks/issues/47
        """
        library_dir = steam_library_factory(name="[HDD-1] SteamLibrary")
        steam_app_factory(name="Test game", appid=10, library_dir=library_dir)

        steam_apps = get_steam_apps(steam_root=steam_root,
                                    steam_path=steam_dir,
                                    steam_lib_paths=[steam_dir, library_dir])

        assert len(steam_apps) == 1
        assert steam_apps[0].name == "Test game"
        assert str(steam_apps[0].install_path).startswith(str(library_dir))
コード例 #9
0
def main():
    """
    'protontricks' script entrypoint
    """
    parser = argparse.ArgumentParser(
        description=(
            "Wrapper for running Winetricks commands for "
            "Steam Play/Proton games.\n"
            "\n"
            "Usage:\n"
            "\n"
            "Run winetricks for game with APPID\n"
            "$ protontricks APPID COMMAND\n"
            "\n"
            "Search installed games to find the APPID\n"
            "$ protontricks -s GAME_NAME\n"
            "\n"
            "Launch the Protontricks GUI\n"
            "$ protontricks --gui\n"
            "\n"
            "Environment variables:\n"
            "\n"
            "PROTON_VERSION: name of the preferred Proton installation\n"
            "STEAM_DIR: path to custom Steam installation\n"
            "WINETRICKS: path to a custom 'winetricks' executable\n"
            "WINE: path to a custom 'wine' executable\n"
            "WINESERVER: path to a custom 'wineserver' executable"),
        formatter_class=argparse.RawTextHelpFormatter)
    parser.add_argument("--verbose",
                        "-v",
                        action="store_true",
                        help="Print debug information")
    parser.add_argument("-s",
                        "--search",
                        type=str,
                        dest="search",
                        nargs="+",
                        required=False,
                        help="Search for game(s) with the given name")
    parser.add_argument(
        "-c",
        "--command",
        type=str,
        dest="command",
        required=False,
        help="Run a command in the game's installation directory with "
        "Wine-related environment variables set. "
        "The command is passed to the shell as-is without being escaped.")
    parser.add_argument("--gui",
                        action="store_true",
                        help="Launch the Protontricks GUI.")
    parser.add_argument("appid", type=int, nargs="?", default=None)
    parser.add_argument("winetricks_command", nargs=argparse.REMAINDER)
    parser.add_argument("-V",
                        "--version",
                        action="version",
                        version="%(prog)s ({})".format(__version__))

    args = parser.parse_args()

    do_command = bool(args.command)
    do_search = bool(args.search)
    do_gui = bool(args.gui)
    do_winetricks = bool(args.appid and args.winetricks_command)

    if not do_command and not do_search and not do_gui and not do_winetricks:
        parser.print_help()
        return

    # Don't allow more than one action
    if sum([do_search, do_gui, do_winetricks, do_command]) != 1:
        print("Only one action can be performed at a time.")
        parser.print_help()
        return

    enable_logging(args.verbose)

    # 1. Find Steam path
    steam_path = find_steam_path()
    if not steam_path:
        print("Steam installation directory could not be found.")
        sys.exit(-1)

    # 2. Find Winetricks
    winetricks_path = get_winetricks_path()
    if not winetricks_path:
        print("Winetricks isn't installed, please install "
              "winetricks in order to use this script!")
        sys.exit(-1)

    # 3. Find any Steam library folders
    steam_lib_paths = get_steam_lib_paths(steam_path)

    # 4. Find any Steam apps
    steam_apps = get_steam_apps(steam_path, steam_lib_paths)

    # 5. Find active Proton version
    proton_app = find_proton_app(steam_path=steam_path,
                                 steam_apps=steam_apps,
                                 appid=args.appid)

    if not proton_app:
        print("Proton installation could not be found!")
        sys.exit(-1)

    # Run the GUI
    if args.gui:
        steam_app = select_steam_app_with_gui(steam_apps=steam_apps)
        run_command(steam_path=steam_path,
                    winetricks_path=winetricks_path,
                    proton_app=proton_app,
                    steam_app=steam_app,
                    command=[winetricks_path, "--gui"])
        return
    # Perform a search
    elif args.search:
        # Search for games
        search_query = " ".join(args.search)
        matching_apps = [
            app for app in steam_apps
            if app.prefix_path_exists and app.name_contains(search_query)
        ]

        if matching_apps:
            matching_games = "\n".join([
                "{} ({})".format(app.name, app.appid) for app in matching_apps
            ])
            print("Found the following games:" "\n{}\n".format(matching_games))
            print("To run protontricks for the chosen game, run:\n"
                  "$ protontricks APPID COMMAND")
        else:
            print("Found no games.")

        print(
            "\n"
            "NOTE: A game must be launched at least once before protontricks "
            "can find the game.")
        return

    # If neither search or GUI are set, do a normal Winetricks command
    # Find game by appid
    steam_appid = int(args.appid)
    try:
        steam_app = next(app for app in steam_apps
                         if not app.is_proton and app.appid == steam_appid
                         and app.prefix_path_exists)
    except StopIteration:
        print("Steam app with the given app ID could not be found. "
              "Is it installed, Proton compatible and have you launched it at "
              "least once? You can search for the app ID using the following "
              "command:\n"
              "$ protontricks -s <GAME NAME>")
        sys.exit(-1)

    if args.winetricks_command:
        run_command(steam_path=steam_path,
                    winetricks_path=winetricks_path,
                    proton_app=proton_app,
                    steam_app=steam_app,
                    command=[winetricks_path] + args.winetricks_command)
    elif args.command:
        run_command(
            steam_path=steam_path,
            winetricks_path=winetricks_path,
            proton_app=proton_app,
            steam_app=steam_app,
            command=args.command,
            # Pass the command directly into the shell *without*
            # escaping it
            cwd=steam_app.install_path,
            shell=True)