diff --git a/procyon_ai/README.md b/procyon_ai/README.md new file mode 100644 index 0000000..1c6086c --- /dev/null +++ b/procyon_ai/README.md @@ -0,0 +1,21 @@ +# 3DMark + +Runs one of the 3DMark benchmark scenes and reads the Performance Graphics Score result from the output. + +## Prerequisites + +- Python 3.10+ +- 3DMark Professional Edition installed in default location and activated. +- Desired benchmarks are downloaded,. + +## Options + +- `--benchmark` Specifies the benchmark to run. + +## Output + +report.json +- `test`: The name of the selected benchmark +- `score`: 3DMark gpu score +- `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 \ No newline at end of file diff --git a/procyon_ai/config/ai_computer_vision_openvino_cpu.def b/procyon_ai/config/ai_computer_vision_openvino_cpu.def new file mode 100644 index 0000000..d85d5bf --- /dev/null +++ b/procyon_ai/config/ai_computer_vision_openvino_cpu.def @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + ai_device_type + CPU + + + ai_inference_precision + float32 + + + \ No newline at end of file diff --git a/procyon_ai/config/ai_computer_vision_openvino_gpu.def b/procyon_ai/config/ai_computer_vision_openvino_gpu.def new file mode 100644 index 0000000..170c84d --- /dev/null +++ b/procyon_ai/config/ai_computer_vision_openvino_gpu.def @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + ai_device_type + GPU + + + ai_inference_precision + float32 + + + \ No newline at end of file diff --git a/procyon_ai/config/ai_computer_vision_openvino_npu.def b/procyon_ai/config/ai_computer_vision_openvino_npu.def new file mode 100644 index 0000000..0362298 --- /dev/null +++ b/procyon_ai/config/ai_computer_vision_openvino_npu.def @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + ai_device_type + NPU + + + ai_inference_precision + float32 + + + \ No newline at end of file diff --git a/procyon_ai/config/ai_computer_vision_snpe.def b/procyon_ai/config/ai_computer_vision_snpe.def new file mode 100644 index 0000000..fcaedb2 --- /dev/null +++ b/procyon_ai/config/ai_computer_vision_snpe.def @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + ai_device_type + HTP + + + ai_inference_precision + integer + + + \ No newline at end of file diff --git a/procyon_ai/config/ai_computer_vision_tensorrt.def b/procyon_ai/config/ai_computer_vision_tensorrt.def new file mode 100644 index 0000000..4a0ad09 --- /dev/null +++ b/procyon_ai/config/ai_computer_vision_tensorrt.def @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + ai_inference_precision + float32 + + + ai_device_id + cuda:0 + + + \ No newline at end of file diff --git a/procyon_ai/config/ai_computer_vision_winml_cpu.def b/procyon_ai/config/ai_computer_vision_winml_cpu.def new file mode 100644 index 0000000..9422ba2 --- /dev/null +++ b/procyon_ai/config/ai_computer_vision_winml_cpu.def @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + ai_device_type + CPU + + + ai_inference_precision + float32 + + + ai_device_id + + + + \ No newline at end of file diff --git a/procyon_ai/config/ai_computer_vision_winml_gpu.def b/procyon_ai/config/ai_computer_vision_winml_gpu.def new file mode 100644 index 0000000..8d50b27 --- /dev/null +++ b/procyon_ai/config/ai_computer_vision_winml_gpu.def @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + ai_device_type + GPU + + + ai_inference_precision + float32 + + + ai_device_id + + + + \ No newline at end of file diff --git a/procyon_ai/manifest.yaml b/procyon_ai/manifest.yaml new file mode 100644 index 0000000..80ccc28 --- /dev/null +++ b/procyon_ai/manifest.yaml @@ -0,0 +1,17 @@ +friendly_name: "Procyon AI CV" +executable: "ulprocai.py" +process_name: "ProcyonCmd.exe" +disable_presentmon: true +output_dir: "run" +options: + - name: engine + type: select + values: + - "AMD_CPU" + - "AMD_GPU" + - "Intel_CPU" + - "Intel_GPU" + - "Intel_NPU" + - "NVIDIA_GPU" + - "Qualcomm_HTP" + tooltip: Select which configuration to run for Procyon \ No newline at end of file diff --git a/procyon_ai/ulprocai.py b/procyon_ai/ulprocai.py new file mode 100644 index 0000000..ee8b183 --- /dev/null +++ b/procyon_ai/ulprocai.py @@ -0,0 +1,154 @@ +"""3DMark test script""" +from argparse import ArgumentParser +import logging +from pathlib import Path +import subprocess +import sys +import time +import psutil +from utils import find_score_in_xml, is_process_running, get_install_path + +PARENT_DIR = str(Path(sys.path[0], "..")) +sys.path.append(PARENT_DIR) + +from harness_utils.output import ( + DEFAULT_DATE_FORMAT, + DEFAULT_LOGGING_FORMAT, + seconds_to_milliseconds, + setup_log_directory, + write_report_json +) + +##### +### Globals +##### +SCRIPT_DIR = Path(__file__).resolve().parent +LOG_DIR = SCRIPT_DIR / "run" +DIR_PROCYON = Path(get_install_path()) +EXECUTABLE = "ProcyonCmd.exe" +ABS_EXECUTABLE_PATH = DIR_PROCYON / EXECUTABLE +CONFIG_DIR = SCRIPT_DIR / "config" +BENCHMARK_CONFIG = { + "AMD_CPU": { + "config": f"\"{CONFIG_DIR}\\ai_computer_vision_winml_cpu.def\"", + "process_name": "WinML.exe", + "test_name": "WinML CPU (FLOAT32)" + }, + "AMD_GPU": { + "config": f"\"{CONFIG_DIR}\\ai_computer_vision_winml_gpu.def\"", + "process_name": "WinML.exe", + "test_name": "WinML GPU (FLOAT32)" + }, + "Intel_CPU": { + "config": f"\"{CONFIG_DIR}\\ai_computer_vision_openvino_cpu.def\"", + "process_name": "OpenVino.exe", + "test_name": "Intel OpenVINO CPU (FLOAT32)" + }, + "Intel_GPU": { + "config": f"\"{CONFIG_DIR}\\ai_computer_vision_openvino_gpu.def\"", + "process_name": "OpenVino.exe", + "test_name": "Intel OpenVINO GPU (FLOAT32)" + }, + "Intel_NPU": { + "config": f"\"{CONFIG_DIR}\\ai_computer_vision_openvino_npu.def\"", + "process_name": "OpenVino.exe", + "test_name": "Intel OpenVINO NPU (FLOAT32)" + }, + "NVIDIA_GPU": { + "config": f"\"{CONFIG_DIR}\\ai_computer_vision_tensorrt.def\"", + "process_name": "TensorRT.exe", + "test_name": "NVIDIA TensorRT (FLOAT32)" + }, + "Qualcomm_HTP": { + "config": f"\"{CONFIG_DIR}\\ai_computer_vision_snpe.def\"", + "process_name": "SNPE.exe", + "test_name": "Qualcomm SNPE (INTEGER)" + }, +} +RESULTS_FILENAME = "result.xml" +REPORT_PATH = LOG_DIR / RESULTS_FILENAME + +def setup_logging(): + """setup logging""" + setup_log_directory(LOG_DIR) + logging.basicConfig(filename=LOG_DIR / "harness.log", + format=DEFAULT_LOGGING_FORMAT, + datefmt=DEFAULT_DATE_FORMAT, + level=logging.DEBUG) + console = logging.StreamHandler() + formatter = logging.Formatter(DEFAULT_LOGGING_FORMAT) + console.setFormatter(formatter) + logging.getLogger('').addHandler(console) + + +def get_arguments(): + """get arguments""" + parser = ArgumentParser() + parser.add_argument( + "--engine", dest="engine", help="Engine test type", required=True, choices=BENCHMARK_CONFIG.keys()) + argies = parser.parse_args() + return argies + + +def create_procyon_command(test_option): + """create command string""" + command = f'\"{ABS_EXECUTABLE_PATH}\" --definition={test_option} --export=\"{REPORT_PATH}\"' + command = command.rstrip() + return command + + +def run_benchmark(process_name, command_to_run): + """run the benchmark""" + with subprocess.Popen(command_to_run, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) as proc: + logging.info("Procyon AI Computer Vision benchmark has started.") + start_time = time.time() + while True: + now = time.time() + elapsed = now - start_time + if elapsed >= 60: #seconds + raise ValueError("BenchMark subprocess did not start in time") + process = is_process_running(process_name) + if process is not None: + process.nice(psutil.HIGH_PRIORITY_CLASS) + break + time.sleep(0.2) + _, _ = proc.communicate() # blocks until 3dmark exits + return proc + +try: + setup_logging() + args = get_arguments() + option = BENCHMARK_CONFIG[args.engine]["config"] + cmd = create_procyon_command(option) + logging.info('Starting benchmark!') + logging.info(cmd) + start_time = time.time() + pr = run_benchmark(BENCHMARK_CONFIG[args.engine]["process_name"], cmd) + + if pr.returncode > 0: + logging.error("Procyon exited with return code %d", pr.returncode) + sys.exit(pr.returncode) + + score = find_score_in_xml() + if score is None: + logging.error("Could not find overall score!") + sys.exit(1) + + end_time = time.time() + elapsed_test_time = round(end_time - start_time, 2) + logging.info("Benchmark took %.2f seconds", elapsed_test_time) + logging.info("Score was %s", score) + + report = { + "test": BENCHMARK_CONFIG[args.engine]["test_name"], + "unit": "score", + "score": score, + "start_time": seconds_to_milliseconds(start_time), + "end_time": seconds_to_milliseconds(end_time) + } + + write_report_json(LOG_DIR, "report.json", report) +except Exception as e: + logging.error("Something went wrong running the benchmark!") + logging.exception(e) + sys.exit(1) diff --git a/procyon_ai/utils.py b/procyon_ai/utils.py new file mode 100644 index 0000000..72b6305 --- /dev/null +++ b/procyon_ai/utils.py @@ -0,0 +1,36 @@ +"""3dmark test utils""" +from pathlib import Path +import psutil +import xml.etree.ElementTree as ET +import winreg +import re + +SCRIPT_DIR = Path(__file__).resolve().parent +LOG_DIR = SCRIPT_DIR / "run" + +def is_process_running(process_name): + """check if given process is running""" + for process in psutil.process_iter(['pid', 'name']): + if process.info['name'] == process_name: + return process + return None + +def find_score_in_xml(): + """Reads score from local game log""" + score_pattern = re.compile(r"(\d+)") + cfg = f"{LOG_DIR}\\result.xml" + score_value = 0 + with open(cfg, encoding="utf-8") as file: + lines = file.readlines() + for line in lines: + score_match = score_pattern.search(line) + if score_match is not None: + score_value = score_match.group(1) + return score_value + +def get_install_path() -> str: + """Gets the path to the Steam installation directory from the SteamPath registry key""" + reg_path = r"Software\UL\Procyon" + reg_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, reg_path, 0, winreg.KEY_READ) + value, _ = winreg.QueryValueEx(reg_key, "InstallDir") + return value \ No newline at end of file diff --git a/procyon_ai_img_gen/README.md b/procyon_ai_img_gen/README.md new file mode 100644 index 0000000..1c6086c --- /dev/null +++ b/procyon_ai_img_gen/README.md @@ -0,0 +1,21 @@ +# 3DMark + +Runs one of the 3DMark benchmark scenes and reads the Performance Graphics Score result from the output. + +## Prerequisites + +- Python 3.10+ +- 3DMark Professional Edition installed in default location and activated. +- Desired benchmarks are downloaded,. + +## Options + +- `--benchmark` Specifies the benchmark to run. + +## Output + +report.json +- `test`: The name of the selected benchmark +- `score`: 3DMark gpu score +- `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 \ No newline at end of file diff --git a/procyon_ai_img_gen/config/ai_imagegeneration_sd15fp16_onnxruntime.def b/procyon_ai_img_gen/config/ai_imagegeneration_sd15fp16_onnxruntime.def new file mode 100644 index 0000000..eb110b6 --- /dev/null +++ b/procyon_ai_img_gen/config/ai_imagegeneration_sd15fp16_onnxruntime.def @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + ai_engine + ort-directml + + + ai_device_id + 0 + + + \ No newline at end of file diff --git a/procyon_ai_img_gen/config/ai_imagegeneration_sd15fp16_openvino.def b/procyon_ai_img_gen/config/ai_imagegeneration_sd15fp16_openvino.def new file mode 100644 index 0000000..d30e732 --- /dev/null +++ b/procyon_ai_img_gen/config/ai_imagegeneration_sd15fp16_openvino.def @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + ai_engine + openvino + + + ai_device_id + auto + + + \ No newline at end of file diff --git a/procyon_ai_img_gen/config/ai_imagegeneration_sd15fp16_tensorrt.def b/procyon_ai_img_gen/config/ai_imagegeneration_sd15fp16_tensorrt.def new file mode 100644 index 0000000..f49da59 --- /dev/null +++ b/procyon_ai_img_gen/config/ai_imagegeneration_sd15fp16_tensorrt.def @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + ai_engine + tensorrt + + + ai_device_id + cuda:0 + + + \ No newline at end of file diff --git a/procyon_ai_img_gen/config/ai_imagegeneration_sd15int8_openvino.def b/procyon_ai_img_gen/config/ai_imagegeneration_sd15int8_openvino.def new file mode 100644 index 0000000..d369ca4 --- /dev/null +++ b/procyon_ai_img_gen/config/ai_imagegeneration_sd15int8_openvino.def @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + ai_engine + openvino + + + ai_device_id + auto + + + split_unet + false + + + \ No newline at end of file diff --git a/procyon_ai_img_gen/config/ai_imagegeneration_sd15int8_tensorrt.def b/procyon_ai_img_gen/config/ai_imagegeneration_sd15int8_tensorrt.def new file mode 100644 index 0000000..157fafb --- /dev/null +++ b/procyon_ai_img_gen/config/ai_imagegeneration_sd15int8_tensorrt.def @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + ai_engine + tensorrt + + + ai_device_id + cuda:0 + + + split_unet + false + + + \ No newline at end of file diff --git a/procyon_ai_img_gen/config/ai_imagegeneration_sdxlfp16_onnxruntime.def b/procyon_ai_img_gen/config/ai_imagegeneration_sdxlfp16_onnxruntime.def new file mode 100644 index 0000000..17f72e8 --- /dev/null +++ b/procyon_ai_img_gen/config/ai_imagegeneration_sdxlfp16_onnxruntime.def @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + ai_engine + ort-directml + + + ai_device_id + 0 + + + \ No newline at end of file diff --git a/procyon_ai_img_gen/config/ai_imagegeneration_sdxlfp16_openvino.def b/procyon_ai_img_gen/config/ai_imagegeneration_sdxlfp16_openvino.def new file mode 100644 index 0000000..a925e1f --- /dev/null +++ b/procyon_ai_img_gen/config/ai_imagegeneration_sdxlfp16_openvino.def @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + ai_engine + openvino + + + ai_device_id + auto + + + \ No newline at end of file diff --git a/procyon_ai_img_gen/config/ai_imagegeneration_sdxlfp16_tensorrt.def b/procyon_ai_img_gen/config/ai_imagegeneration_sdxlfp16_tensorrt.def new file mode 100644 index 0000000..6e1f6d5 --- /dev/null +++ b/procyon_ai_img_gen/config/ai_imagegeneration_sdxlfp16_tensorrt.def @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + ai_engine + tensorrt + + + ai_device_id + cuda:0 + + + \ No newline at end of file diff --git a/procyon_ai_img_gen/manifest.yaml b/procyon_ai_img_gen/manifest.yaml new file mode 100644 index 0000000..2abe9c3 --- /dev/null +++ b/procyon_ai_img_gen/manifest.yaml @@ -0,0 +1,17 @@ +friendly_name: "Procyon AI Image Generation" +executable: "ulprocai_img_gen.py" +process_name: "ProcyonCmd.exe" +disable_presentmon: true +output_dir: "run" +options: + - name: engine + type: select + values: + - "AMD_CPU" + - "AMD_GPU" + - "Intel_CPU" + - "Intel_GPU" + - "Intel_NPU" + - "NVIDIA_GPU" + - "Qualcomm_HTP" + tooltip: Select which configuration to run for Procyon \ No newline at end of file diff --git a/procyon_ai_img_gen/ulprocai_img_gen.py b/procyon_ai_img_gen/ulprocai_img_gen.py new file mode 100644 index 0000000..59e22d1 --- /dev/null +++ b/procyon_ai_img_gen/ulprocai_img_gen.py @@ -0,0 +1,159 @@ +"""3DMark test script""" +from argparse import ArgumentParser +import logging +from pathlib import Path +import subprocess +import sys +import time +import psutil +from utils import find_score_in_xml, is_process_running, get_install_path + +PARENT_DIR = str(Path(sys.path[0], "..")) +sys.path.append(PARENT_DIR) + +from harness_utils.output import ( + DEFAULT_DATE_FORMAT, + DEFAULT_LOGGING_FORMAT, + seconds_to_milliseconds, + setup_log_directory, + write_report_json +) + +##### +### Globals +##### +SCRIPT_DIR = Path(__file__).resolve().parent +LOG_DIR = SCRIPT_DIR / "run" +DIR_PROCYON = Path(get_install_path()) +EXECUTABLE = "ProcyonCmd.exe" +ABS_EXECUTABLE_PATH = DIR_PROCYON / EXECUTABLE +CONFIG_DIR = SCRIPT_DIR / "config" +BENCHMARK_CONFIG = { + "AMD_GPU_FP16": { + "config": f"\"{CONFIG_DIR}\\ai_imagegeneration_sd15fp16_onnxruntime.def\"", + "process_name": "ort-directml.exe", + "test_name": "ONNX Stable Diffusion FP16" + }, + "AMD_GPU_XL_FP16": { + "config": f"\"{CONFIG_DIR}\\ai_imagegeneration_sdxlfp16_onnxruntime.def\"", + "process_name": "ort-directml.exe", + "test_name": "ONNX Stable Diffusion FP16 XL" + }, + "Intel_GPU_INT8": { + "config": f"\"{CONFIG_DIR}\\ai_imagegeneration_sd15int8_openvino.def\"", + "process_name": "OpenVino.exe", + "test_name": "Intel OpenVINO Stable Diffusion INT8" + }, + "Intel_GPU_FP16": { + "config": f"\"{CONFIG_DIR}\\ai_imagegeneration_sd15fp16_openvino.def\"", + "process_name": "OpenVino.exe", + "test_name": "Intel OpenVINO Stable Diffusion FP16" + }, + "Intel_GPU_XL_FP16": { + "config": f"\"{CONFIG_DIR}\\ai_imagegeneration_sdxlfp16_openvino.def\"", + "process_name": "OpenVino.exe", + "test_name": "Intel OpenVINO Stable Diffusion FP16 XL" + }, + "NVIDIA_GPU_INT8": { + "config": f"\"{CONFIG_DIR}\\ai_imagegeneration_sd15int8_tensorrt.def\"", + "process_name": "TensorRT.exe", + "test_name": "NVIDIA TensorRT Stable Diffusion INT8" + }, + "NVIDIA_GPU_FP16": { + "config": f"\"{CONFIG_DIR}\\ai_imagegeneration_sd15fp16_tensorrt.def\"", + "process_name": "TensorRT.exe", + "test_name": "NVIDIA TensorRT Stable Diffusion FP16" + }, + "NVIDIA_GPU_XL_FP16": { + "config": f"\"{CONFIG_DIR}\\ai_imagegeneration_sdxlfp16_tensorrt.def\"", + "process_name": "TensorRT.exe", + "test_name": "NVIDIA TensorRT Stable Diffusion FP16 XL" + } +} +RESULTS_FILENAME = "result.xml" +REPORT_PATH = LOG_DIR / RESULTS_FILENAME + +def setup_logging(): + """setup logging""" + setup_log_directory(LOG_DIR) + logging.basicConfig(filename=LOG_DIR / "harness.log", + format=DEFAULT_LOGGING_FORMAT, + datefmt=DEFAULT_DATE_FORMAT, + level=logging.DEBUG) + console = logging.StreamHandler() + formatter = logging.Formatter(DEFAULT_LOGGING_FORMAT) + console.setFormatter(formatter) + logging.getLogger('').addHandler(console) + + +def get_arguments(): + """get arguments""" + parser = ArgumentParser() + parser.add_argument( + "--engine", dest="engine", help="Engine test type", required=True, choices=BENCHMARK_CONFIG.keys()) + argies = parser.parse_args() + return argies + + +def create_procyon_command(test_option): + """create command string""" + command = f'\"{ABS_EXECUTABLE_PATH}\" --definition={test_option} --export=\"{REPORT_PATH}\"' + command = command.rstrip() + return command + + +def run_benchmark(process_name, command_to_run): + """run the benchmark""" + with subprocess.Popen(command_to_run, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) as proc: + logging.info("Procyon AI Image Generation benchmark has started.") + start_time = time.time() + while True: + now = time.time() + elapsed = now - start_time + if elapsed >= 60: #seconds + raise ValueError("BenchMark subprocess did not start in time") + process = is_process_running(process_name) + if process is not None: + process.nice(psutil.HIGH_PRIORITY_CLASS) + break + time.sleep(0.2) + _, _ = proc.communicate() # blocks until 3dmark exits + return proc + +try: + setup_logging() + args = get_arguments() + option = BENCHMARK_CONFIG[args.engine]["config"] + cmd = create_procyon_command(option) + logging.info('Starting benchmark!') + logging.info(cmd) + start_time = time.time() + pr = run_benchmark(BENCHMARK_CONFIG[args.engine]["process_name"], cmd) + + if pr.returncode > 0: + logging.error("Procyon exited with return code %d", pr.returncode) + sys.exit(pr.returncode) + + score = find_score_in_xml() + if score is None: + logging.error("Could not find overall score!") + sys.exit(1) + + end_time = time.time() + elapsed_test_time = round(end_time - start_time, 2) + logging.info("Benchmark took %.2f seconds", elapsed_test_time) + logging.info("Score was %s", score) + + report = { + "test": BENCHMARK_CONFIG[args.engine]["test_name"], + "unit": "score", + "score": score, + "start_time": seconds_to_milliseconds(start_time), + "end_time": seconds_to_milliseconds(end_time) + } + + write_report_json(LOG_DIR, "report.json", report) +except Exception as e: + logging.error("Something went wrong running the benchmark!") + logging.exception(e) + sys.exit(1) diff --git a/procyon_ai_img_gen/utils.py b/procyon_ai_img_gen/utils.py new file mode 100644 index 0000000..642fd19 --- /dev/null +++ b/procyon_ai_img_gen/utils.py @@ -0,0 +1,36 @@ +"""3dmark test utils""" +from pathlib import Path +import psutil +import xml.etree.ElementTree as ET +import winreg +import re + +SCRIPT_DIR = Path(__file__).resolve().parent +LOG_DIR = SCRIPT_DIR / "run" + +def is_process_running(process_name): + """check if given process is running""" + for process in psutil.process_iter(['pid', 'name']): + if process.info['name'] == process_name: + return process + return None + +def find_score_in_xml(): + """Reads score from local game log""" + score_pattern = re.compile(r"(\d+)") + cfg = f"{LOG_DIR}\\result.xml" + score_value = 0 + with open(cfg, encoding="utf-8") as file: + lines = file.readlines() + for line in lines: + score_match = score_pattern.search(line) + if score_match is not None: + score_value = score_match.group(1) + return score_value + +def get_install_path() -> str: + """Gets the path to the Steam installation directory from the SteamPath registry key""" + reg_path = r"Software\UL\Procyon" + reg_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, reg_path, 0, winreg.KEY_READ) + value, _ = winreg.QueryValueEx(reg_key, "InstallDir") + return value \ No newline at end of file