diff options
| author | xXJsonDeruloXx <danielhimebauch@gmail.com> | 2026-09-13 17:43:25 -0400 |
|---|---|---|
| committer | xXJsonDeruloXx <danielhimebauch@gmail.com> | 2026-09-13 17:43:25 -0400 |
| commit | 1295bc8ad4fc2a7e6ba311705b737e2c87165f8c (patch) | |
| tree | 2797f0c93a0299a91c8633f2fe978f6ed3a1b64d | |
| parent | 1ce63bca9c1d1aeaebbf6d2b6a0a5b7777969c54 (diff) | |
| download | decky-lsfg-vk-fix/reset-legacy-config.tar.gz decky-lsfg-vk-fix/reset-legacy-config.zip | |
fix: reset legacy LSFG-VK configs safelyfix/reset-legacy-config
| -rw-r--r-- | py_modules/lsfg_vk/config_schema.py | 19 | ||||
| -rw-r--r-- | py_modules/lsfg_vk/configuration.py | 19 | ||||
| -rw-r--r-- | py_modules/lsfg_vk/installation.py | 9 | ||||
| -rw-r--r-- | tests/test_configuration_profiles.py | 46 | ||||
| -rw-r--r-- | tests/test_installation_config.py | 63 |
5 files changed, 151 insertions, 5 deletions
diff --git a/py_modules/lsfg_vk/config_schema.py b/py_modules/lsfg_vk/config_schema.py index bf3e174..8b1f29c 100644 --- a/py_modules/lsfg_vk/config_schema.py +++ b/py_modules/lsfg_vk/config_schema.py @@ -12,6 +12,12 @@ class ProfileData(TypedDict): global_config: Dict[str, Any] +class UnsupportedConfigurationVersion(ValueError): + def __init__(self, version: Any): + self.version = version + super().__init__("unsupported lsfg-vk configuration version") + + PROFILE_DEFAULTS: Dict[str, Any] = { "active_in": [], "pacing_mode": "vsync", @@ -46,6 +52,14 @@ def _normalize_active_in(value: Any) -> list[str]: class ConfigurationManager: @staticmethod + def is_discardable_legacy_version(version: Any) -> bool: + return version is None or version == "1" or ( + isinstance(version, (int, float)) + and not isinstance(version, bool) + and version < 2 + ) + + @staticmethod def get_defaults() -> Dict[str, Any]: return {**GLOBAL_DEFAULTS, **PROFILE_DEFAULTS} @@ -99,8 +113,9 @@ class ConfigurationManager: @staticmethod def parse_toml_content_multi_profile(content: str) -> ProfileData: data = tomllib.loads(content) - if data.get("version") != 2: - raise ValueError("unsupported lsfg-vk configuration version") + version = data.get("version") + if version != 2: + raise UnsupportedConfigurationVersion(version) raw_global = data.get("global", {}) global_config = { "dll": str(raw_global.get("dll", "") or ""), diff --git a/py_modules/lsfg_vk/configuration.py b/py_modules/lsfg_vk/configuration.py index 3c65972..427aa3b 100644 --- a/py_modules/lsfg_vk/configuration.py +++ b/py_modules/lsfg_vk/configuration.py @@ -2,7 +2,7 @@ import re from typing import Any, Dict from .base_service import BaseService -from .config_schema import ConfigurationManager, ProfileData +from .config_schema import ConfigurationManager, ProfileData, UnsupportedConfigurationVersion from .runtime_service import RuntimeService @@ -16,10 +16,25 @@ class ConfigurationService(BaseService): def _default_data(self) -> ProfileData: return {"profiles": {}, "global_config": {"dll": "", "no_fp16": False}} + def _reset_legacy_config(self, version: Any) -> ProfileData: + data = self._default_data() + content = ConfigurationManager.generate_toml_content_multi_profile(data) + self._write_file(self.config_file_path, content) + self.log.warning( + f"Discarded legacy lsfg-vk configuration version {version!r}; reset conf.toml to v2 defaults" + ) + return data + def _get_profile_data(self) -> ProfileData: if not self.config_file_path.exists(): return self._default_data() - return ConfigurationManager.parse_toml_content_multi_profile(self.config_file_path.read_text(encoding="utf-8")) + content = self.config_file_path.read_text(encoding="utf-8") + try: + return ConfigurationManager.parse_toml_content_multi_profile(content) + except UnsupportedConfigurationVersion as error: + if not ConfigurationManager.is_discardable_legacy_version(error.version): + raise + return self._reset_legacy_config(error.version) def _save_profile_data(self, data: ProfileData) -> None: content = ConfigurationManager.generate_toml_content_multi_profile(data) diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py index 4003228..05ef4d0 100644 --- a/py_modules/lsfg_vk/installation.py +++ b/py_modules/lsfg_vk/installation.py @@ -7,7 +7,7 @@ from pathlib import Path from typing import Dict from .base_service import BaseService -from .config_schema import ConfigurationManager, ProfileData +from .config_schema import ConfigurationManager, ProfileData, UnsupportedConfigurationVersion from .constants import ( ARCHIVE_FILENAME, BIN_DIR, @@ -138,6 +138,13 @@ class InstallationService(BaseService): if self.config_file_path.exists() else self._default_config() ) + except UnsupportedConfigurationVersion as error: + if not ConfigurationManager.is_discardable_legacy_version(error.version): + raise + self.log.warning( + f"Discarding legacy lsfg-vk configuration version {error.version!r} during installation" + ) + profile_data = self._default_config() except ValueError: profile_data = self._default_config() self._resolve_dll_path(profile_data) diff --git a/tests/test_configuration_profiles.py b/tests/test_configuration_profiles.py index 898a9e7..9c72cd9 100644 --- a/tests/test_configuration_profiles.py +++ b/tests/test_configuration_profiles.py @@ -52,6 +52,52 @@ preserve_swapchain_image_count = false reparsed = ConfigurationManager.parse_toml_content_multi_profile(rendered) self.assertEqual(reparsed["profiles"]["flatpak:org.example.Game"]["multiplier"], 3) + def test_legacy_config_resets_to_v2_without_deleting_flatpak_state(self): + self.service.config_dir.mkdir(parents=True) + self.service.config_file_path.write_text( + 'version = 1\n\n[global]\nallow_fp16 = false\n', + encoding="utf-8", + ) + state_path = self.service.config_dir / "flatpak_state.json" + state_path.write_text('{"prepared_apps": {}}\n', encoding="utf-8") + backup_path = self.service.config_dir / "flatpak-overrides" / "com.example.Game.ini" + backup_path.parent.mkdir(parents=True) + backup_path.write_text("[Environment]\nKEEP=yes\n", encoding="utf-8") + + result = self.service.get_game_configs() + + self.assertTrue(result["success"]) + self.assertEqual(result["games"], []) + self.assertEqual(result["global_config"], {"dll": "", "no_fp16": False}) + self.assertTrue(self.service.config_file_path.read_text(encoding="utf-8").startswith("version = 2\n")) + self.assertEqual(self.service._get_profile_data(), self.service._default_data()) + self.assertEqual(state_path.read_text(encoding="utf-8"), '{"prepared_apps": {}}\n') + self.assertEqual(backup_path.read_text(encoding="utf-8"), "[Environment]\nKEEP=yes\n") + self.runtime.validate_config_content.assert_not_called() + + def test_unversioned_config_resets_to_v2_defaults(self): + self.service.config_dir.mkdir(parents=True) + self.service.config_file_path.write_text( + '[global]\nallow_fp16 = false\n', + encoding="utf-8", + ) + + data = self.service._get_profile_data() + + self.assertEqual(data, self.service._default_data()) + self.assertTrue(self.service.config_file_path.read_text(encoding="utf-8").startswith("version = 2\n")) + + def test_future_config_version_is_preserved(self): + self.service.config_dir.mkdir(parents=True) + original = "version = 3\n\n[global]\nallow_fp16 = true\n" + self.service.config_file_path.write_text(original, encoding="utf-8") + + result = self.service.get_game_configs() + + self.assertFalse(result["success"]) + self.assertEqual(result["error"], "unsupported lsfg-vk configuration version") + self.assertEqual(self.service.config_file_path.read_text(encoding="utf-8"), original) + def test_game_reset_all_preserves_flatpak_profiles(self): self.service.update_game_config("123", "Steam Game", {"multiplier": 2}) self.service.update_flatpak_config("org.example.Game", {"multiplier": 3}) diff --git a/tests/test_installation_config.py b/tests/test_installation_config.py new file mode 100644 index 0000000..9ee8b43 --- /dev/null +++ b/tests/test_installation_config.py @@ -0,0 +1,63 @@ +import sys +import tempfile +import types +import unittest +from pathlib import Path +from unittest.mock import Mock + + +sys.modules.setdefault( + "decky", + types.SimpleNamespace(DECKY_USER_HOME="/home/deck", logger=Mock()), +) +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "py_modules")) + +from lsfg_vk.config_schema import UnsupportedConfigurationVersion +from lsfg_vk.installation import InstallationService + + +class InstallationConfigTests(unittest.TestCase): + def setUp(self): + self.tempdir = tempfile.TemporaryDirectory() + self.home = Path(self.tempdir.name) / "home" / "deck" + self.home.mkdir(parents=True) + self.steam = Mock() + self.steam.find_lsfg_vk_dll.return_value = None + self.service = InstallationService( + logger=Mock(), + runtime_service=Mock(), + steam_service=self.steam, + ) + self.service.user_home = self.home + self.service.config_dir = self.home / ".config/lsfg-vk" + self.service.config_file_path = self.service.config_dir / "conf.toml" + + def tearDown(self): + self.tempdir.cleanup() + + def test_installation_discards_v1_profiles_instead_of_migrating_them(self): + self.service.config_dir.mkdir(parents=True) + self.service.config_file_path.write_text( + 'version = 1\n\n[[profile]]\nname = "Old Game"\n', + encoding="utf-8", + ) + + data = self.service._prepare_config() + + self.assertEqual(data["profiles"], {}) + self.assertEqual(data["global_config"], {"dll": "", "no_fp16": False}) + self.service.log.warning.assert_called_once() + + def test_installation_does_not_overwrite_a_future_config_version(self): + self.service.config_dir.mkdir(parents=True) + original = "version = 3\n\n[global]\nallow_fp16 = true\n" + self.service.config_file_path.write_text(original, encoding="utf-8") + + with self.assertRaises(UnsupportedConfigurationVersion): + self.service._prepare_config() + + self.assertEqual(self.service.config_file_path.read_text(encoding="utf-8"), original) + + +if __name__ == "__main__": + unittest.main() |
