summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxXJsonDeruloXx <danielhimebauch@gmail.com>2026-09-13 17:43:25 -0400
committerxXJsonDeruloXx <danielhimebauch@gmail.com>2026-09-13 17:43:25 -0400
commit1295bc8ad4fc2a7e6ba311705b737e2c87165f8c (patch)
tree2797f0c93a0299a91c8633f2fe978f6ed3a1b64d
parent1ce63bca9c1d1aeaebbf6d2b6a0a5b7777969c54 (diff)
downloaddecky-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.py19
-rw-r--r--py_modules/lsfg_vk/configuration.py19
-rw-r--r--py_modules/lsfg_vk/installation.py9
-rw-r--r--tests/test_configuration_profiles.py46
-rw-r--r--tests/test_installation_config.py63
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()