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
This commit is contained in:
derek-hirotsu
2023-09-22 10:32:27 -07:00
committed by GitHub
parent fbb0100e3b
commit 3135d6ee96
4 changed files with 99 additions and 113 deletions

2
.gitignore vendored
View File

@@ -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__/

View File

@@ -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

View File

@@ -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 pyautogui as gui
import time
import sys
import pyautogui as gui
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)

View File

@@ -1,13 +1,15 @@
"""Utility functions for Forza Horizon 5 test script"""
import re
def read_resolution(config_path: str) -> tuple[int]:
"""Gets the resolution from local file"""
height_pattern = re.compile(r"<ResolutionHeight value=\"(\d+)\"/>")
width_pattern = re.compile(r"<ResolutionWidth value=\"(\d+)\"/>")
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)