diff --git a/robocop_ng/helpers/ryujinx_log_analyser.py b/robocop_ng/helpers/ryujinx_log_analyser.py index bfbe34c..4c1d2f7 100644 --- a/robocop_ng/helpers/ryujinx_log_analyser.py +++ b/robocop_ng/helpers/ryujinx_log_analyser.py @@ -3,9 +3,10 @@ from enum import IntEnum, auto from typing import Optional, Union from robocop_ng.helpers.disabled_ids import is_build_id_valid +from robocop_ng.helpers.size import Size -class CommonErrors(IntEnum): +class CommonError(IntEnum): SHADER_CACHE_COLLISION = auto() DUMP_HASH = auto() SHADER_CACHE_CORRUPTION = auto() @@ -209,24 +210,29 @@ class LogAnalyser: self._hardware_info[setting] = cpu_match.group(1).rstrip() case "ram": + sizes = "|".join(Size.names()) ram_match = re.search( - r"RAM: Total ([\d.]+) (MiB|GB) ; Available ([\d.]+) (MiB|GB)", + rf"RAM: Total ([\d.]+) ({sizes}) ; Available ([\d.]+) ({sizes})", self._log_text, re.MULTILINE, ) if ram_match is not None: try: + dest_unit = Size.MiB + ram_available = float(ram_match.group(3)) - if ram_match.group(4) == "GB": - ram_available *= 1024 + ram_available = Size.from_name(ram_match.group(4)).convert( + ram_available, dest_unit + ) ram_total = float(ram_match.group(1)) - if ram_match.group(2) == "GB": - ram_total *= 1024 + ram_total = Size.from_name(ram_match.group(2)).convert( + ram_total, dest_unit + ) self._hardware_info[ setting - ] = f"{ram_available}/{ram_total} MiB" + ] = f"{ram_available:.0f}/{ram_total:.0f} {dest_unit.name}" except ValueError: # ram_match.group(1) or ram_match.group(3) couldn't be parsed as a float. self._hardware_info[setting] = "Error" @@ -500,36 +506,36 @@ class LogAnalyser: def __get_notes(self): for common_error in self.get_common_errors(): match common_error: - case CommonErrors.SHADER_CACHE_COLLISION: + case CommonError.SHADER_CACHE_COLLISION: self._notes.append( "⚠️ Cache collision detected. Investigate possible shader cache issues" ) - case CommonErrors.SHADER_CACHE_CORRUPTION: + case CommonError.SHADER_CACHE_CORRUPTION: self._notes.append( "⚠️ Cache corruption detected. Investigate possible shader cache issues" ) - case CommonErrors.DUMP_HASH: + case CommonError.DUMP_HASH: self._notes.append( "⚠️ Dump error detected. Investigate possible bad game/firmware dump issues" ) - case CommonErrors.UPDATE_KEYS: + case CommonError.UPDATE_KEYS: self._notes.append( "⚠️ Keys or firmware out of date, consider updating them" ) - case CommonErrors.FILE_PERMISSIONS: + case CommonError.FILE_PERMISSIONS: self._notes.append( "⚠️ File permission error. Consider deleting save directory and allowing Ryujinx to make a new one" ) - case CommonErrors.FILE_NOT_FOUND: + case CommonError.FILE_NOT_FOUND: self._notes.append( "⚠️ Save not found error. Consider starting game without a save file or using a new save file⚠️ Save not found error. Consider starting game without a save file or using a new save file" ) - case CommonErrors.MISSING_SERVICES: + case CommonError.MISSING_SERVICES: if self._settings["ignore_missing_services"] == "False": self._notes.append( "⚠️ Consider enabling `Ignore Missing Services` in Ryujinx settings" ) - case CommonErrors.VULKAN_OUT_OF_MEMORY: + case CommonError.VULKAN_OUT_OF_MEMORY: if self._settings["texture_recompression"] == "Disabled": self._notes.append( "⚠️ Consider enabling `Texture Recompression` in Ryujinx settings" @@ -591,11 +597,11 @@ class LogAnalyser: def get_last_error(self) -> Optional[list[str]]: return self._log_errors[-1] if len(self._log_errors) > 0 else None - def get_common_errors(self) -> list[CommonErrors]: + def get_common_errors(self) -> list[CommonError]: errors = [] if self.contains_errors(["Cache collision found"], self._log_errors): - errors.append(CommonErrors.SHADER_CACHE_COLLISION) + errors.append(CommonError.SHADER_CACHE_COLLISION) if self.contains_errors( [ "ResultFsInvalidIvfcHash", @@ -603,7 +609,7 @@ class LogAnalyser: ], self._log_errors, ): - errors.append(CommonErrors.DUMP_HASH) + errors.append(CommonError.DUMP_HASH) if self.contains_errors( [ "Ryujinx.Graphics.Gpu.Shader.ShaderCache.Initialize()", @@ -612,17 +618,17 @@ class LogAnalyser: ], self._log_errors, ): - errors.append(CommonErrors.SHADER_CACHE_CORRUPTION) + errors.append(CommonError.SHADER_CACHE_CORRUPTION) if self.contains_errors(["MissingKeyException"], self._log_errors): - errors.append(CommonErrors.UPDATE_KEYS) + errors.append(CommonError.UPDATE_KEYS) if self.contains_errors(["ResultFsPermissionDenied"], self._log_errors): - errors.append(CommonErrors.FILE_PERMISSIONS) + errors.append(CommonError.FILE_PERMISSIONS) if self.contains_errors(["ResultFsTargetNotFound"], self._log_errors): - errors.append(CommonErrors.FILE_NOT_FOUND) + errors.append(CommonError.FILE_NOT_FOUND) if self.contains_errors(["ServiceNotImplementedException"], self._log_errors): - errors.append(CommonErrors.MISSING_SERVICES) + errors.append(CommonError.MISSING_SERVICES) if self.contains_errors(["ErrorOutOfDeviceMemory"], self._log_errors): - errors.append(CommonErrors.VULKAN_OUT_OF_MEMORY) + errors.append(CommonError.VULKAN_OUT_OF_MEMORY) return errors diff --git a/robocop_ng/helpers/size.py b/robocop_ng/helpers/size.py new file mode 100644 index 0000000..4e815bc --- /dev/null +++ b/robocop_ng/helpers/size.py @@ -0,0 +1,53 @@ +from enum import IntEnum, auto +from typing import Self + + +class Size(IntEnum): + KB = auto() + KiB = auto() + MB = auto() + MiB = auto() + GB = auto() + GiB = auto() + + @classmethod + def names(cls) -> list[str]: + return [size.name for size in cls] + + @classmethod + def from_name(cls, name: str) -> Self: + for size in cls: + if size.name.lower() == name.lower(): + return size + raise ValueError(f"No matching member found for: {name}") + + @property + def _is_si_unit(self) -> bool: + return self.value % 2 != 0 + + @property + def _unit_value(self) -> int: + return self.value // 2 + (1 if self._is_si_unit else 0) + + @property + def _base_factor(self) -> int: + return 10**3 if self._is_si_unit else 2**10 + + @property + def _byte_factor(self) -> int: + return ( + 10 ** (3 * self._unit_value) + if self._is_si_unit + else 2 ** (10 * self._unit_value) + ) + + def convert(self, value: float, fmt: Self) -> float: + if self == fmt: + return value + if self._is_si_unit == fmt._is_si_unit: + if self < fmt: + return value / self._base_factor ** (fmt._unit_value - self._unit_value) + else: + return value * self._base_factor ** (self._unit_value - fmt._unit_value) + else: + return value * (self._byte_factor / fmt._byte_factor)