Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Linux Support #13

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions csgo/csgo.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
#!/usr/bin/env python3

"""Counter Strike: Global Offensive test script"""
import logging
import os
import time
import sys
from argparse import ArgumentParser
import pyautogui as gui
import pydirectinput as user
try:
import pydirectinput as user
except ImportError:
# pyautogui provides the same API as pydirectinput,
# at least for the calls made in this harness
import pyautogui as user
from utils import (
get_resolution,
benchmark_folder_exists,
Expand Down Expand Up @@ -97,7 +104,7 @@ def run_benchmark():
logging.info("Benchmark took %.2f seconds", elapsed_test_time)

terminate_processes(PROCESS_NAME)
return start_time, end_time
return test_start_time, test_end_time


# Entry Point
Expand Down
8 changes: 7 additions & 1 deletion cyberpunk2077/cyberpunk2077.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
#!/usr/bin/env python3

"""Cyberpunk 2077 test script"""
import time
import logging
import sys
import os
import pyautogui as gui
import pydirectinput as user

try:
import pydirectinput as user
except ImportError:
import pyautogui as user
from cyberpunk_utils import copy_no_intro_mod, get_args, read_current_resolution


Expand Down
146 changes: 117 additions & 29 deletions harness_utils/steam.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
"""Utility functions related to using Steam for running games."""
import logging
import winreg
import os
import shutil
if os.name == 'nt':
import winreg
else:
# If we don't have a registry, then
# we need to parse Valve's data files
import vdf
from subprocess import Popen
from pathlib import Path

Expand All @@ -11,19 +18,56 @@ def get_run_game_id_command(game_id: int) -> str:


def get_steam_folder_path() -> str:
"""Gets the path to the Steam installation directory from the SteamPath registry key"""
reg_path = r"Software\Valve\Steam"
reg_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, reg_path, 0, winreg.KEY_READ)
value, _ = winreg.QueryValueEx(reg_key, "SteamPath")
return value
"""
Gets the path to the Steam installation directory from the SteamPath registry key
on Windows. On Linux, returns :code:`~/.steam/steam/` as that is the default
installation location.
"""
if os.name == 'nt':
reg_path = r"Software\Valve\Steam"
reg_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, reg_path, 0, winreg.KEY_READ)
value, _ = winreg.QueryValueEx(reg_key, "SteamPath")
return value
else:
return os.path.expanduser("~/.steam/steam")


def get_steam_exe_path() -> str:
"""Gets the path to the Steam executable from the SteamExe registry key"""
reg_path = r"Software\Valve\Steam"
reg_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, reg_path, 0, winreg.KEY_READ)
value, _ = winreg.QueryValueEx(reg_key, "SteamExe")
return value
def get_steam_exe() -> list[str]:
"""
On Windows, gets the path to the Steam executable from the SteamExe registry key.

On Linux, we first use :code:`which` to check if the system-wide version of Steam
is installed. If not, we then check if the flatpak is installed, and finally if the
snap is installed.

:return: The command to invoke Steam, as a :obj:`Popen`
style list.
"""
if os.name == 'nt':
reg_path = r"Software\Valve\Steam"
reg_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, reg_path, 0, winreg.KEY_READ)
value, _ = winreg.QueryValueEx(reg_key, "SteamExe")
return [value]
else:
# First check if steam is on the PATH
steam_path = shutil.which("steam")
if steam_path is not None:
return [steam_path]
else:
# Steam not on PATH, check if there is flatpak
flatpak_path = shutil.which("flatpak")
if flatpak_path is not None:
# Windows code assumes Steam is installed, so don't bother verifying
return [flatpak_path, "run", "com.valvesoftware.Steam"]
else:
# If not installed system-wide and there's no flatpak,
# then it must be installed through Snap, but we'll check
# for the existence of Snap anyway just in case
snap_path = shutil.which("snap")
if snap_path is not None:
return [snap_path, "run", "steam"]
else:
raise FileNotFoundError("Could not find a steam installation on your PATH")


def get_steamapps_common_path() -> str:
Expand All @@ -44,15 +88,60 @@ def get_registry_active_user() -> int:


def get_app_install_location(app_id: int) -> str:
"""Given the Steam App ID, Gets the install directory from the Windows Registry"""
reg_path = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App " + str(app_id)
registry_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, reg_path, 0, winreg.KEY_READ)
value, _ = winreg.QueryValueEx(registry_key, "InstallLocation")
winreg.CloseKey(registry_key)
return value
"""
Given the Steam App ID, Gets the install directory from the Windows Registry

On Linux, parses the libraryfolders.vdf file to find the app install location.
"""

def exec_steam_run_command(game_id: int, steam_path=None) -> Popen:
if os.name == 'nt':
Copy link

@rcmaehl rcmaehl Jan 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove this nt section entirely for get_app_install_location. Is I pointed out in #44, it can't be counted on reliably unlike the other method which should be OS-agnostic

reg_path = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App " + str(app_id)
registry_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, reg_path, 0, winreg.KEY_READ)
value, _ = winreg.QueryValueEx(registry_key, "InstallLocation")
winreg.CloseKey(registry_key)
return value
else:
# libraryfolders.vdf contains information on the different Steam
# libraries and what games / apps each library contains.
#
# The file format is VDF (Valve Data File). It is a key-value
# format similar to JSON. The VDF library parses this format
# and returns a dictionary-like object. VDF can have multiple
# entries with the same key, so the returned object is not
# a true dict.
#
# The libraryfolders.vdf file is structured in the following hierarchy:
#
# - "libraryfolders":
# - "<index>":
# "path": "<library base path>",
# "apps":
# "<app_id>": "<size in bytes>"
# ...
#
# There are many more keys, but these are the ones we are interested in.
# Inside each library, underneath the "steamapps" directory, there is an
# "appmanifest_<app_id>.acf" file for each install app. This file
# is also a VDF-formatted file, and we are interested in the following keys:
#
# - "AppState":
# - "installdir": "<directory_name>"
#
# The installdir is a directory path relative to the library's "steamapps/common" directory.
# Therefore, we combine all these pieces to return that absolute path.
with open(Path(get_steam_folder_path()) / "config" / "libraryfolders.vdf") as f:
database = vdf.load(f)
folders = database["libraryfolders"]
for index, data in folders.items():
apps = data["apps"]
if str(app_id) in apps.keys():
lib_folder = Path(data["path"])
with open(lib_folder / "steamapps" / f"appmanifest_{app_id}.acf") as app_acf:
app_database = vdf.load(app_acf)
return (lib_folder / "steamapps" / "common" / app_database["AppState"]["installdir"]).absolute()


def exec_steam_run_command(game_id: int, steam_command: list[str] = None) -> Popen:
"""Runs a game using the Steam browser protocol. The `steam_path` argument can be used to
specify a specifc path to the Steam executable instead of relying on finding the current
installation in the Window's registry.
Expand All @@ -61,21 +150,20 @@ def exec_steam_run_command(game_id: int, steam_path=None) -> Popen:
see the function `exec_steam_game`.
"""
steam_run_arg = "steam://rungameid/" + str(game_id)
if steam_path is None:
steam_path = get_steam_exe_path()
logging.info("%s %s", steam_path, steam_run_arg)
return Popen([steam_path, steam_run_arg])
if steam_command is None:
steam_command = get_steam_exe()
logging.info("%s %s", [" ".join(arg) for arg in steam_command], steam_run_arg)
steam_command.append(steam_run_arg)
return Popen(steam_command)


def exec_steam_game(game_id: int, steam_path=None, game_params=None) -> Popen:
def exec_steam_game(game_id: int, steam_command: list[str] = (), game_params: list[str] = ()) -> Popen:
"""Runs a game by providing steam executable with an array of parameters.
The `steam_path` argument can be used to specify a specifc path to the Steam executable
instead of relying on finding the current installation in the Window's registry.
"""
if steam_path is None:
steam_path = get_steam_exe_path()
if game_params is None:
game_params = []
command = [steam_path, "-applaunch", str(game_id)] + game_params
if len(steam_command) == 0:
steam_command = get_steam_exe()
command = steam_command + ["-applaunch", str(game_id)] + game_params
logging.info(", ".join(command))
return Popen(command)
7 changes: 4 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,23 @@ authors = []

[tool.poetry.dependencies]
python = "^3.10"
pywinauto = "0.6.8"
pywinauto = {version = "0.6.8", platform = "win32"}
pyyaml = "6.0"
python-dotenv = "0.20.0"
pyfiglet = "0.8.post1"
PyAutoGUI = "^0.9.53"
PyDirectInput = "^1.0.4"
PyDirectInput = {version = "^1.0.4", platform = "win32"}
opencv-python = "^4.5.5"
Pillow = "^9.1.1"
psutil = "^5.9.1"
pandas = "^1.4.2"
imutils = "^0.5.4"
matplotlib = "^3.5.2"
protobuf = "^4.21.2"
WMI = "^1.5.1"
WMI = {version = "^1.5.1", platform = "win32"}
mss = "^7.0.1"
requests = "^2.28.2"
vdf = {version = "^3.4", platform = "linux"}

[tool.poetry.dev-dependencies]
pytest = "^7.1.2"
Expand Down