From 3135d6ee96d352d98dde126528eaa0349a6ac8ad Mon Sep 17 00:00:00 2001 From: derek-hirotsu <132305781+derek-hirotsu@users.noreply.github.com> Date: Fri, 22 Sep 2023 10:32:27 -0700 Subject: [PATCH] Audit Forza Horizon 5 (#42) * audit forza scripts * remove preset from report json * update readme * fix key error with keras results * remove commented out code * add rtss info to readme --- .gitignore | 2 + forza5/README.md | 13 ++- forza5/forza5.py | 187 ++++++++++++++++++----------------------- forza5/forza5_utils.py | 10 ++- 4 files changed, 99 insertions(+), 113 deletions(-) diff --git a/.gitignore b/.gitignore index c0aa47d..beb033e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ flac-1.4.3-win/ flac-1.4.3-win.zip y-cruncher v0.8.2.9522/ y-cruncher v0.8.2.9522.zip +csgo-benchmark-master/ +csgo-benchmark-master.zip # python __pycache__/ \ No newline at end of file diff --git a/forza5/README.md b/forza5/README.md index 4b82120..c9a8284 100644 --- a/forza5/README.md +++ b/forza5/README.md @@ -1,11 +1,20 @@ # Forza Horizon 5 -TODO +This script runs RTSS with the proivded profile config and navigates through in-game menus to the built in benchmark and runs it with the current settings. ## Prerequisites - Python 3.10+ -- Forza Horizon 5 installed via Steam. +- Forza Horizon 5 installed via Steam +- Keras OCR service +- RTSS installed - https://rivatuner.net/ + +## 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 ## Common Issues 1. "Login to Microsoft" modal pops up diff --git a/forza5/forza5.py b/forza5/forza5.py index f102e28..4b2cc7f 100644 --- a/forza5/forza5.py +++ b/forza5/forza5.py @@ -1,148 +1,121 @@ -import logging -import sys +"""Forza Horizon 5 test script""" from argparse import ArgumentParser +import logging import os -import pydirectinput as user +import time +import sys import pyautogui as gui -import time +import pydirectinput as user from forza5_utils import read_resolution sys.path.insert(1, os.path.join(sys.path[0], '..')) -from harness_utils.output import * +# pylint: disable=wrong-import-position +from harness_utils.output import ( + format_resolution, + seconds_to_milliseconds, + setup_log_directory, + write_report_json, + DEFAULT_LOGGING_FORMAT, + DEFAULT_DATE_FORMAT, +) from harness_utils.process import terminate_processes from harness_utils.rtss import start_rtss_process, copy_rtss_profile from harness_utils.steam import exec_steam_run_command from harness_utils.keras_service import KerasService +# pylint: enable=wrong-import-position STEAM_GAME_ID = 1551360 SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) LOG_DIRECTORY = os.path.join(SCRIPT_DIR, "run") APPDATALOCAL = os.getenv("LOCALAPPDATA") -CONFIG_LOCATION = f"{APPDATALOCAL}\\ForzaHorizon5\\User_SteamLocalStorageDirectory\\ConnectedStorage\\ForzaUserConfigSelections" +CONFIG_LOCATION = ( + f"{APPDATALOCAL}\\ForzaHorizon5\\User_SteamLocalStorageDirectory" + "\\ConnectedStorage\\ForzaUserConfigSelections" +) CONFIG_FILENAME = "UserConfigSelections" PROCESSES = ["ForzaHorizon5.exe", "RTSS.exe"] def start_rtss(): + """Sets up the RTSS process""" profile_path = os.path.join(SCRIPT_DIR, "ForzaHorizon5.exe.cfg") copy_rtss_profile(profile_path) return start_rtss_process() -def is_word_on_screen(word: str, attempts: int = 5, delay_seconds: int = 1) -> bool: - for _ in range(attempts): - result = kerasService.capture_screenshot_find_word(word) - if result != None: - return True - time.sleep(delay_seconds) - return False - -def is_word_clickable(word: str, attempts: int = 5, delay_seconds: int = 1) -> bool: - for _ in range(attempts): - result = kerasService.capture_screenshot_find_word(word) - if result != None: - return (result["x"], result["y"]) - time.sleep(delay_seconds) - - return None - -def accessibility() -> any: - return is_word_on_screen(word="start", attempts=10, delay_seconds=1) - -def graphics() -> any: - return is_word_clickable(word="graphics", attempts=10, delay_seconds=1) - -def benchmark() -> any: - return is_word_clickable(word="benchmark", attempts=10, delay_seconds=1) - -def results() -> any: - return is_word_on_screen(word="results", attempts=25, delay_seconds=1) - -def checkpoint() -> any: - return is_word_on_screen(word="checkpoint", attempts=10, delay_seconds=1) - - def run_benchmark(): + """Starts the benchmark""" start_rtss() # Give RTSS time to start time.sleep(10) exec_steam_run_command(STEAM_GAME_ID) - t1 = time.time() + setup_start_time = time.time() # Wait for menu to load time.sleep(30) - menu_screen_wait = time.time() - while (True): - accessibility() - if accessibility(): - start_time = time.time() - logging.info("Accessibilty found pressing X to continue.") - user.press("x") - time.sleep(2) - break - elif time.time()-menu_screen_wait > 60: - logging.info("Game didn't start.") - exit(1) - logging.info("Game hasn't started. Trying again in 10 seconds") - time.sleep(10) - graphics_wait = time.time() - while (True): - graphics() - if graphics(): - logging.info("Graphics found, clicking and continuing.") - graphics_click = graphics() - gui.moveTo(graphics_click[0], graphics_click[1]) - time.sleep(0.2) - gui.mouseDown() - time.sleep(0.2) - gui.mouseUp() - time.sleep(1) - break - elif time.time()-graphics_wait > 60: - logging.info("Game didn't load to the settings menu.") - exit(1) - logging.info("Menu hasn't loaded yet. Trying again in 10 seconds") - time.sleep(10) + logging.info("Waiting for start prompt...") + result = kerasService.wait_for_word("start", timeout=30) + if not result: + logging.info("Game didn't start.") + sys.exit(1) - if benchmark(): - logging.info("Benchmark found, clicking and starting benchmark.") - benchmark_click = benchmark() - gui.moveTo(benchmark_click[0], benchmark_click[1]) - time.sleep(0.2) - gui.mouseDown() - time.sleep(0.2) - gui.mouseUp() - time.sleep(1) - user.press("down") - time.sleep(0.2) - user.press("enter") - time.sleep(0.2) + logging.info("Accessibilty found pressing X to continue.") + user.press("x") + time.sleep(2) - loading_screen_start = time.time() - while (True): - checkpoint() - if checkpoint(): - break - elif time.time()-loading_screen_start > 360: - logging.info("Benchmark didn't start.") - exit(1) - logging.info("Benchmark hasn't started. Trying again in 10 seconds") - time.sleep(10) + result = kerasService.wait_for_word("graphics", timeout=30) + if not result: + logging.info("Game didn't load to the settings menu.") + sys.exit(1) - t2 = time.time() - logging.info(f"Harness setup took {round((t2 - t1), 2)} seconds") - start_time = time.time() + logging.info("Graphics found, clicking and continuing.") + gui.moveTo(result["x"], result["y"]) + time.sleep(0.2) + gui.mouseDown() + time.sleep(0.2) + gui.mouseUp() + time.sleep(1) + + result = kerasService.wait_for_word("benchmark", timeout=12) + if not result: + logging.info("Didn't find benchmark in settings.") + sys.exit(1) + + gui.mouseDown(result["x"], result["y"]) + time.sleep(0.2) + gui.mouseUp() + time.sleep(1) + user.press("down") + time.sleep(0.2) + user.press("enter") + time.sleep(0.2) + + result = kerasService.wait_for_word("checkpoint", timeout=360) + if not result: + logging.info("Benchmark didn't start.") + sys.exit(1) + + elapsed_setup_time = round((time.time() - setup_start_time), 2) + logging.info("Harness setup took %.2f seconds", elapsed_setup_time) + + test_start_time = time.time() time.sleep(95) # wait for benchmark to finish 95 seconds - if results(): - logging.info("Results screen found. Ending run.") - end_time = time.time() - logging.info(f"Benchmark took {round((end_time - start_time), 2)} seconds") + + result = kerasService.wait_for_word("results", timeout=25) + if not result: + logging.info("Results screen was not found!") + sys.exit(1) + + test_end_time = time.time() + elapsed_test_time = round((test_end_time - test_start_time), 2) + logging.info("Benchmark took %.2f seconds", elapsed_test_time) + terminate_processes(*PROCESSES) - return start_time, end_time + return test_start_time, test_end_time setup_log_directory(LOG_DIRECTORY) @@ -168,16 +141,16 @@ kerasService = KerasService(args.keras_host, args.keras_port, os.path.join( try: start_time, end_time = run_benchmark() width, height = read_resolution(f"{CONFIG_LOCATION}\\{CONFIG_FILENAME}") - result = { + report = { "resolution": format_resolution(width, height), - "graphics_preset": "current", "start_time": seconds_to_milliseconds(start_time), "end_time": seconds_to_milliseconds(end_time) } - 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(*PROCESSES) - exit(1) + sys.exit(1) diff --git a/forza5/forza5_utils.py b/forza5/forza5_utils.py index 6688e66..7e8513f 100644 --- a/forza5/forza5_utils.py +++ b/forza5/forza5_utils.py @@ -1,13 +1,15 @@ +"""Utility functions for Forza Horizon 5 test script""" import re -def read_resolution(config_path: str) -> tuple[int]: +def read_resolution(config_path: str) -> tuple[int]: + """Gets the resolution from local file""" height_pattern = re.compile(r"") width_pattern = re.compile(r"") width = 0 height = 0 - with open(config_path) as f: - lines = f.readlines() + with open(config_path, encoding="utf-8") as file: + lines = file.readlines() for line in lines: height_match = height_pattern.search(line) width_match = width_pattern.search(line) @@ -15,4 +17,4 @@ def read_resolution(config_path: str) -> tuple[int]: height = height_match.group(1) if width_match is not None: width = width_match.group(1) - return (width, height) \ No newline at end of file + return (width, height)