Cyberpunk 2077 audit (#17)

* pep8 lint main script

* rename util script

* pep8 lint for utils file

* update readmes

* i waits
This commit is contained in:
Derek Hirotsu
2023-09-18 09:18:50 -07:00
committed by GitHub
parent 275455c0db
commit 35a6c2d918
5 changed files with 97 additions and 67 deletions

View File

@@ -1,5 +1,7 @@
# F1 22
This script navigates through in-game menus to the built in benchmark and runs it with the current settings. It then waits for a results screen, expecting the benchmark to be running 3 laps.
## Prerequisites
- Python 3.10+

View File

@@ -1,5 +1,7 @@
# F1 23
This script navigates through in-game menus to the built in benchmark and runs it with the current settings. It then waits for a results screen, expecting the benchmark to be running 3 laps.
## Prerequisites
- Python 3.10+

21
cyberpunk2077/README.md Normal file
View File

@@ -0,0 +1,21 @@
# Cyberpunk 2077
Navigates menus to in-game benchmark then runs it.
## Prerequisites
- Python 3.10+
- Cyberpunk 2077 installed
- Keras OCR service
## Options
- `kerasHost`: string representing the IP address of the Keras service. e.x. `0.0.0.0`
- `kerasPort`: string representing the port of the Keras serivce. e.x. `8080`
## Output
report.json
- `resolution`: string representing the resolution the test was run at, formatted as "[width]x[heigt]", e.x. `1920x1080`
- `start_time`: number representing a timestamp of the test's start time in milliseconds
- `end_time`: number representing a timestamp of the test's end time in milliseconds

View File

@@ -1,24 +1,22 @@
"""Cyberpunk 2077 test script"""
import time
import pydirectinput as user
import pyautogui as gui
import logging
from pywinauto import mouse
from win32con import SM_CXSCREEN, SM_CYSCREEN
import win32api
from subprocess import Popen
import sys
import os
import shutil
from pathlib import Path
from cyberpunk_utils import copy_no_intro_mod, get_args, read_current_resolution
import pyautogui as gui
import pydirectinput as user
sys.path.insert(1, os.path.join(sys.path[0], '..'))
#pylint: disable=wrong-import-position
from harness_utils.keras_service import KerasService
from harness_utils.logging import setup_log_directory, write_report_json, DEFAULT_LOGGING_FORMAT, DEFAULT_DATE_FORMAT
from harness_utils.logging import (
setup_log_directory, write_report_json, DEFAULT_LOGGING_FORMAT, DEFAULT_DATE_FORMAT)
from harness_utils.process import terminate_processes
from harness_utils.steam import get_run_game_id_command, DEFAULT_EXECUTABLE_PATH as STEAM_PATH
from utils import copy_no_intro_mod, get_args, read_current_resolution
from harness_utils.steam import DEFAULT_EXECUTABLE_PATH as STEAM_PATH
#pylint: enable=wrong-import-position
STEAM_GAME_ID = 1091500
STEAM_PATH = os.path.join(os.environ["ProgramFiles(x86)"], "steam")
@@ -28,8 +26,8 @@ LOG_DIRECTORY = os.path.join(SCRIPT_DIRECTORY, "run")
PROCESS_NAME = "cyberpunk2077.exe"
# Launch the game with no launcher or start screen
def start_game():
"""Launch the game with no launcher or start screen"""
cmd = os.path.join(STEAM_PATH, STEAM_EXECUTABLE)
cmd_array = [cmd, "-applaunch",
str(STEAM_GAME_ID), "--launcher-skip", "-skipStartScreen"]
@@ -38,36 +36,42 @@ def start_game():
def is_word_present(word: str, attempts: int = 5, delay_seconds: int = 1) -> bool:
"""Find given word on screen"""
for _ in range(attempts):
result = kerasService.capture_screenshot_find_word(word)
if result != None:
if result is not None:
return True
time.sleep(delay_seconds)
return False
def await_settings_menu() -> any:
"""Wait for word "new" on screen"""
return is_word_present(word="new", attempts=20, delay_seconds=3)
def await_results_screen() -> bool:
"""Wait for word "results" on screen"""
return is_word_present(word="results", attempts=10, delay_seconds=3)
def await_benchmark_start() -> bool:
"""Wait for word "fps" on screen"""
return is_word_present(word="fps", attempts=10, delay_seconds=2)
def is_continue_present() -> bool:
"""Wait for word "continue" on screen"""
return is_word_present(word="continue", attempts=10)
def navigate_main_menu() -> None:
"""Simulate inputs to navigate the main menu"""
logging.info("Navigating main menu")
continue_present = is_continue_present()
if not continue_present:
# an account with no save game has less menu options, so just press left and enter settings
user.press("left")
user.press("left")
time.sleep(0.5)
user.press("enter")
time.sleep(0.5)
@@ -83,70 +87,65 @@ def navigate_main_menu() -> None:
def run_benchmark():
"""Start the benchmark"""
copy_no_intro_mod()
"""
Start game via Steam and enter fullscreen mode
"""
t1 = time.time()
game_process = start_game()
# Start game via Steam and enter fullscreen mode
setup_start_time = time.time()
start_game()
time.sleep(10)
settings_menu_screen = await_settings_menu()
if not settings_menu_screen:
logging.info("Did not see settings menu option.")
exit(1)
sys.exit(1)
navigate_main_menu()
# """
# Start the benchmark!
# """
t2 = time.time()
logging.info(f"Harness setup took {round((t2 - t1), 2)} seconds")
setup_end_time = time.time()
elapsed_setup_time = round(setup_end_time - setup_start_time, 2)
logging.info("Harness setup took %f seconds", elapsed_setup_time)
global START_TIME
START_TIME = time.time()
test_start_time = time.time()
# Checking if loading screen is finished
benchmark_started = False
loading_screen_start = time.time()
logging.info(f"Looking for fps counter to indicate benchmark started")
while (not benchmark_started):
logging.info("Looking for fps counter to indicate benchmark started")
while not benchmark_started:
if time.time()-loading_screen_start > 60:
logging.info("Benchmark didn't start.")
exit(1)
sys.exit(1)
benchmark_started = await_benchmark_start()
logging.info("Benchmark started. Waiting for benchmark to complete.")
# """
# Wait for benchmark to complete
# """
time.sleep(70)
logging.info(f"Finished sleeping, waiting for results screen")
logging.info("Finished sleeping, waiting for results screen")
count = 0
results_screen_present = False
while (not results_screen_present):
results_screen_present = await_results_screen()
if results_screen_present:
break # break out early if we found it
if count >= 3: # we check 3 times every 40 minnutes because lower end cards take *forever* to finish
logging.info("Did not see results screen. Mark as DNF.")
exit(1)
logging.info(f"Benchmark not finished yet, continuing to wait for the {count} time")
time.sleep(40)
count += 1
while not results_screen_present:
results_screen_present = await_results_screen()
if results_screen_present:
break
# we check 3 times every 40 minutes because lower end cards take *forever* to finish
if count >= 3:
logging.info("Did not see results screen. Mark as DNF.")
sys.exit(1)
logging.info("Benchmark not finished yet, continuing to wait for the %d time", count)
time.sleep(40)
count += 1
global END_TIME
END_TIME = time.time()
logging.info(f"Benchmark took {round((END_TIME - START_TIME), 2)} seconds")
test_end_time = time.time()
elapsed_test_time = round((test_end_time - test_start_time), 2)
logging.info("Benchmark took %f seconds", elapsed_test_time)
gui.screenshot(os.path.join(LOG_DIRECTORY, "results.png"))
terminate_processes(PROCESS_NAME)
return START_TIME, END_TIME
return test_start_time, test_end_time
setup_log_directory(LOG_DIRECTORY)
@@ -167,16 +166,16 @@ kerasService = KerasService(args.keras_host, args.keras_port, os.path.join(
try:
start_time, endtime = run_benchmark()
resolution = read_current_resolution()
result = {
report = {
"resolution": f"{resolution}",
"graphics_preset": "current",
"start_time": round((start_time * 1000)), # seconds * 1000 = millis
"start_time": round((start_time * 1000)),
"end_time": round((endtime * 1000))
}
write_report_json(LOG_DIRECTORY, "report.json", result)
write_report_json(LOG_DIRECTORY, "report.json", report)
#pylint: disable=broad-exception-caught
except Exception as e:
logging.error("Something went wrong running the benchmark!")
logging.exception(e)
terminate_processes(PROCESS_NAME)
exit(1)
sys.exit(1)

View File

@@ -1,3 +1,4 @@
"""Utility functions for Cyberpunk 2077 test script"""
from argparse import ArgumentParser
import os
import logging
@@ -7,11 +8,12 @@ import shutil
SCRIPT_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
CYBERPUNK_INSTALL_DIR = os.path.join(
os.environ["ProgramFiles(x86)"], "Steam\steamapps\common\Cyberpunk 2077")
DEFAULT_NO_INTRO_PATH = os.path.join(CYBERPUNK_INSTALL_DIR, "archive\pc\mod")
os.environ["ProgramFiles(x86)"], "Steam\\steamapps\\common\\Cyberpunk 2077")
DEFAULT_NO_INTRO_PATH = os.path.join(CYBERPUNK_INSTALL_DIR, "archive\\pc\\mod")
def get_args() -> any:
"""Returns command line arg values"""
parser = ArgumentParser()
parser.add_argument("--kerasHost", dest="keras_host",
help="Host for Keras OCR service", required=True)
@@ -21,37 +23,41 @@ def get_args() -> any:
def copy_no_intro_mod() -> None:
"""Copies no intro mod file"""
src_file = os.path.join(
SCRIPT_DIRECTORY, "basegame_no_intro_videos.archive")
is_valid_no_intro = os.path.isfile(src_file)
if not is_valid_no_intro:
raise Exception(f"Can't find no intro: {src_file}")
raise OSError(f"Can't find no intro: {src_file}")
# Validate/create path to directory where we will copy profile to
dest_dir: str = DEFAULT_NO_INTRO_PATH
try:
Path(dest_dir).mkdir(parents=True, exist_ok=True)
except FileExistsError as e:
except FileExistsError as err:
logging.error(
"Could not create rtss profiles directory - likely due to non-directory file existing at path.")
raise e
"Could not create rtss profiles directory - " +
"likely due to non-directory file existing at path."
)
raise err
# Copy the profile over
destination_file = os.path.join(dest_dir, os.path.basename(src_file))
logging.info(F"Copying: {src_file} -> {destination_file}")
logging.info("Copying: %s -> %s", src_file, destination_file)
shutil.copy(src_file, destination_file)
def read_current_resolution():
APPDATA = os.getenv("LOCALAPPDATA")
CONFIG_LOCATION = f"{APPDATA}\\CD Projekt Red\\Cyberpunk 2077"
CONFIG_FILENAME = "UserSettings.json"
"""Get resolution from local game file"""
app_data = os.getenv("LOCALAPPDATA")
config_location = f"{app_data}\\CD Projekt Red\\Cyberpunk 2077"
config_filename = "UserSettings.json"
resolution_pattern = re.compile(r"\"value\"\: \"(\d+x\d+)\"\,")
cfg = f"{CONFIG_LOCATION}\\{CONFIG_FILENAME}"
cfg = f"{config_location}\\{config_filename}"
resolution = 0
with open(cfg) as f:
lines = f.readlines()
with open(cfg, encoding="utf-8") as file:
lines = file.readlines()
for line in lines:
resolution_match = resolution_pattern.search(line)
if resolution_match is not None: