From a3c5187d7e5e1a081043895c3261fcbc2abd12f4 Mon Sep 17 00:00:00 2001 From: Fabian Arndt Date: Mon, 8 Jan 2024 01:23:35 +0100 Subject: [PATCH 1/2] Refactored fixes that use WINE_CPU_TOPOLOGY Moved corresponding logic of gamefixes to util.py util.py: - Added functions to set the cpu topology - Added is_smt_enabled() - Added get_cpu_count() --- gamefixes/1659420.py | 8 ++--- gamefixes/20920.py | 5 +-- gamefixes/220240.py | 4 +-- gamefixes/298110.py | 4 +-- gamefixes/35130.py | 5 +-- gamefixes/45750.py | 16 +++------ gamefixes/55150.py | 6 ++-- util.py | 77 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 90 insertions(+), 35 deletions(-) diff --git a/gamefixes/1659420.py b/gamefixes/1659420.py index e57c5b04..ac493675 100755 --- a/gamefixes/1659420.py +++ b/gamefixes/1659420.py @@ -1,14 +1,12 @@ -""" Uncharted Collection +""" UNCHARTED: Legacy of Thieves Collection """ #pylint: disable=C0103 from protonfixes import util -import multiprocessing def main(): - """ Legacy Collection + """ The game chokes on more than 16 cores """ - if multiprocessing.cpu_count() > 16: - util.set_environment('WINE_CPU_TOPOLOGY', '16:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15') + util.set_cpu_topology_limit(16) diff --git a/gamefixes/20920.py b/gamefixes/20920.py index 71afa572..770f0e34 100755 --- a/gamefixes/20920.py +++ b/gamefixes/20920.py @@ -3,13 +3,10 @@ #pylint: disable=C0103 from protonfixes import util -import multiprocessing def main(): """ Witcher 2 chokes on more than 31 cores """ - if multiprocessing.cpu_count() > 24: - util.set_environment('WINE_CPU_TOPOLOGY', '31:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30') - + util.set_cpu_topology_limit(31) diff --git a/gamefixes/220240.py b/gamefixes/220240.py index 18149740..c37fcea0 100755 --- a/gamefixes/220240.py +++ b/gamefixes/220240.py @@ -3,12 +3,10 @@ #pylint: disable=C0103 from protonfixes import util -import multiprocessing def main(): """ FarCry 3 chokes on more than 24 cores """ - if multiprocessing.cpu_count() > 24: - util.set_environment('WINE_CPU_TOPOLOGY', '25:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24') + util.set_cpu_topology_limit(24) diff --git a/gamefixes/298110.py b/gamefixes/298110.py index 72b79f8e..b47bb515 100755 --- a/gamefixes/298110.py +++ b/gamefixes/298110.py @@ -3,7 +3,6 @@ #pylint: disable=C0103 from protonfixes import util -import multiprocessing def main(): @@ -12,5 +11,4 @@ def main(): util.protontricks('d3dcompiler_43') util.protontricks('d3dcompiler_47') - if multiprocessing.cpu_count() > 24: - util.set_environment('WINE_CPU_TOPOLOGY', '25:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24') + util.set_cpu_topology_limit(24) diff --git a/gamefixes/35130.py b/gamefixes/35130.py index b969eab6..8b370e7f 100755 --- a/gamefixes/35130.py +++ b/gamefixes/35130.py @@ -3,13 +3,10 @@ #pylint: disable=C0103 from protonfixes import util -import multiprocessing def main(): """ LCGoL chokes on more than 12 cores """ - if multiprocessing.cpu_count() > 24: - util.set_environment('WINE_CPU_TOPOLOGY', '12:0,1,2,3,4,5,6,7,8,9,10,11') - + util.set_cpu_topology_limit(12) diff --git a/gamefixes/45750.py b/gamefixes/45750.py index 13218728..90b1aa06 100644 --- a/gamefixes/45750.py +++ b/gamefixes/45750.py @@ -62,15 +62,7 @@ def main(): except: log("Unexpected exception when downloading mocked 'xlive.dll'") - # the core fix is only applied if the user has not provided its own topology mapping - if not os.getenv("WINE_CPU_TOPOLOGY"): - try: - cpu_cores = multiprocessing.cpu_count() - except: - cpu_cores = 0 - log("Could not retrieve the number of CPU cores") - - if cpu_cores > 12: - util.set_environment("WINE_CPU_TOPOLOGY", f"12:{','.join(str(n) for n in range(12))}") - - + # According to PCGW, no more than 6 physical cores work + # Nevertheless, the game was tested with 12 threads + # TODO: Test the game with SMT disabled / use set_cpu_topology_nosmt() + util.set_cpu_topology_limit(12) diff --git a/gamefixes/55150.py b/gamefixes/55150.py index 6c585929..2982b256 100755 --- a/gamefixes/55150.py +++ b/gamefixes/55150.py @@ -1,15 +1,13 @@ -""" Space Marine +""" Warhammer 40,000: Space Marine - Anniversary Edition """ #pylint: disable=C0103 from protonfixes import util -import multiprocessing def main(): """ Space Marine chokes on more than 24 cores """ - if multiprocessing.cpu_count() > 24: - util.set_environment('WINE_CPU_TOPOLOGY', '25:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24') + util.set_cpu_topology_limit(24) diff --git a/util.py b/util.py index a12c7e9d..6e884eef 100755 --- a/util.py +++ b/util.py @@ -661,3 +661,80 @@ def try_show_gui_error(text): subprocess.run(["notify-send", "protonfixes", text]) except: log.info("Failed to show error message with the following text: {}".format(text)) + +def is_smt_enabled() -> bool: + """ Returns whether SMT is enabled. + If the check has failed, False is returned. + """ + try: + with open('/sys/devices/system/cpu/smt/active') as smt_file: + return smt_file.read().strip() == "1" + except PermissionError: + log.warn('No permission to read SMT status') + except OSError as e: + log.warn(f'SMT status not supported by the kernel (errno: {e.errno})') + return False + +def get_cpu_count() -> int: + """ Returns the cpu core count, provided by the OS. + If the request failed, 0 is returned. + """ + cpu_cores = os.cpu_count() + if not cpu_cores or cpu_cores <= 0: + log.warn('Can not read count of logical cpu cores') + return 0 + return cpu_cores + +def set_cpu_topology(core_count: int, ignore_user_setting: bool = False) -> bool: + """ This sets the cpu topology to a fixed core count. + By default, a user provided topology is prioritized. + You can override this behavior by setting `ignore_user_setting`. + """ + + # Don't override the user's settings (except, if we override it) + user_topo = os.getenv('WINE_CPU_TOPOLOGY') + if user_topo and not ignore_user_setting: + log.info(f'Using WINE_CPU_TOPOLOGY set by the user: {user_topo}') + return False + + # Sanity check + if not core_count or core_count <= 0: + log.warn('Only positive core_counts can be used to set cpu topology') + return False + + # Format (example, 4 cores): 4:0,1,2,3 + cpu_topology = f'{core_count}:{",".join(map(str, range(core_count)))}' + set_environment('WINE_CPU_TOPOLOGY', cpu_topology) + log.info(f'Using WINE_CPU_TOPOLOGY: {cpu_topology}') + return True + + +def set_cpu_topology_nosmt(core_limit: int = 0, ignore_user_setting: bool = False, threads_per_core: int = 2) -> bool: + """ This sets the cpu topology to the count of physical cores. + If SMT is enabled, eg. a 4c8t cpu is limited to 4 logical cores. + You can limit the core count to the `core_limit` argument. + """ + + # Check first, if SMT is enabled + if is_smt_enabled() is False: + log.info('SMT is not active, skipping fix') + return False + + # Currently (2024) SMT allows 2 threads per core, this might change in the future + cpu_cores = get_cpu_count() // threads_per_core # Apply divider + cpu_cores = max(cpu_cores, min(cpu_cores, core_limit)) # Apply limit + return set_cpu_topology(cpu_cores, ignore_user_setting) + +def set_cpu_topology_limit(core_limit: int, ignore_user_setting: bool = False) -> bool: + """ This sets the cpu topology to a limited number of logical cores. + A limit that exceeds the available cores, will be ignored. + """ + + cpu_cores = get_cpu_count() + if core_limit >= cpu_cores: + log.info(f'The count of logical cores ({cpu_cores}) is lower than ' + f'or equal to the set limit ({core_limit}), skipping fix') + return False + + # Apply the limit + return set_cpu_topology(core_limit, ignore_user_setting) From f8348e469b2c7ef1d6db91d0d0df7a235fe0c208 Mon Sep 17 00:00:00 2001 From: Fabian Arndt Date: Mon, 8 Jan 2024 01:24:43 +0100 Subject: [PATCH 2/2] Game fix for The Forest --- gamefixes/242760.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 gamefixes/242760.py diff --git a/gamefixes/242760.py b/gamefixes/242760.py new file mode 100644 index 00000000..1feec2b8 --- /dev/null +++ b/gamefixes/242760.py @@ -0,0 +1,14 @@ +""" Game fix for The Forest +""" + +# pylint: disable=C0103 + +from protonfixes import util + + +def main(): + """ If SMT/HT is enabled, The Forest runs with extremely choppy. Just bad. + We can fix it by setting the topology to the physical cores / core count. + TODO: This fix was not tested with more than 10 physical cores yet. + """ + util.set_cpu_topology_nosmt()