summaryrefslogtreecommitdiff
path: root/py_modules/lsfg_vk/dll_detection.py
blob: e547405c8897e635d2214464d261407730b7abb3 (plain)
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
"""
DLL detection service for Lossless Scaling.
"""

import os
import re
from pathlib import Path
from typing import Dict, Any, List

from .base_service import BaseService
from .constants import (
    ENV_LSFG_DLL_PATH, ENV_XDG_DATA_HOME, ENV_HOME,
    STEAM_COMMON_PATH, LOSSLESS_DLL_NAME
)
from .types import DllDetectionResponse


class DllDetectionService(BaseService):
    """Service for detecting Lossless Scaling DLL"""
    
    def check_lossless_scaling_dll(self) -> DllDetectionResponse:
        """Check if Lossless Scaling DLL is available at the expected paths
        
        Search order:
        1. LSFG_DLL_PATH environment variable
        2. XDG_DATA_HOME Steam directory
        3. HOME/.local/share Steam directory  
        4. All Steam library folders (including SD cards)
        
        Returns:
            DllDetectionResponse with detection status and path information
        """
        try:
            # Check environment variable first
            dll_path = self._check_env_dll_path()
            if dll_path:
                return dll_path
            
            # Check XDG_DATA_HOME path
            xdg_path = self._check_xdg_data_home()
            if xdg_path:
                return xdg_path
            
            # Check HOME/.local/share path
            home_path = self._check_home_local_share()
            if home_path:
                return home_path
            
            # Check all Steam library folders (including SD cards)
            steam_libraries_path = self._check_steam_library_folders()
            if steam_libraries_path:
                return steam_libraries_path
            
            # DLL not found in any expected location
            return {
                "detected": False,
                "path": None,
                "source": None,
                "message": "Lossless Scaling DLL not found in expected locations",
                "error": None
            }
            
        except Exception as e:
            error_msg = f"Error checking Lossless Scaling DLL: {str(e)}"
            self.log.error(error_msg)
            return {
                "detected": False,
                "path": None,
                "source": None,
                "message": None,
                "error": str(e)
            }
    
    def _check_env_dll_path(self) -> DllDetectionResponse | None:
        """Check LSFG_DLL_PATH environment variable
        
        Returns:
            DllDetectionResponse if found, None otherwise
        """
        dll_path = os.getenv(ENV_LSFG_DLL_PATH)
        if dll_path and dll_path.strip():
            dll_path_obj = Path(dll_path.strip())
            if dll_path_obj.exists():
                self.log.info(f"Found DLL via {ENV_LSFG_DLL_PATH}: {dll_path_obj}")
                return {
                    "detected": True,
                    "path": str(dll_path_obj),
                    "source": f"{ENV_LSFG_DLL_PATH} environment variable",
                    "message": None,
                    "error": None
                }
        return None
    
    def _check_xdg_data_home(self) -> DllDetectionResponse | None:
        """Check XDG_DATA_HOME Steam directory
        
        Returns:
            DllDetectionResponse if found, None otherwise
        """
        data_dir = os.getenv(ENV_XDG_DATA_HOME)
        if data_dir and data_dir.strip():
            dll_path = Path(data_dir.strip()) / "Steam" / STEAM_COMMON_PATH / LOSSLESS_DLL_NAME
            if dll_path.exists():
                self.log.info(f"Found DLL via {ENV_XDG_DATA_HOME}: {dll_path}")
                return {
                    "detected": True,
                    "path": str(dll_path),
                    "source": f"{ENV_XDG_DATA_HOME} Steam directory",
                    "message": None,
                    "error": None
                }
        return None
    
    def _check_home_local_share(self) -> DllDetectionResponse | None:
        """Check HOME/.local/share Steam directory
        
        Returns:
            DllDetectionResponse if found, None otherwise
        """
        home_dir = os.getenv(ENV_HOME)
        if home_dir and home_dir.strip():
            dll_path = Path(home_dir.strip()) / ".local" / "share" / "Steam" / STEAM_COMMON_PATH / LOSSLESS_DLL_NAME
            if dll_path.exists():
                self.log.info(f"Found DLL via {ENV_HOME}/.local/share: {dll_path}")
                return {
                    "detected": True,
                    "path": str(dll_path),
                    "source": f"{ENV_HOME}/.local/share Steam directory",
                    "message": None,
                    "error": None
                }
        return None

    def _check_steam_library_folders(self) -> DllDetectionResponse | None:
        """Check all Steam library folders for Lossless Scaling DLL
        
        This method parses Steam's libraryfolders.vdf file to find all
        Steam library locations and checks each one for the DLL.
        
        Returns:
            DllDetectionResponse if found, None otherwise
        """
        steam_libraries = self._get_steam_library_paths()
        
        for library_path in steam_libraries:
            dll_path = Path(library_path) / STEAM_COMMON_PATH / LOSSLESS_DLL_NAME
            if dll_path.exists():
                self.log.info(f"Found DLL in Steam library: {dll_path}")
                return {
                    "detected": True,
                    "path": str(dll_path),
                    "source": f"Steam library folder: {library_path}",
                    "message": None,
                    "error": None
                }
        
        return None
    
    def _get_steam_library_paths(self) -> List[str]:
        """Get all Steam library folder paths from libraryfolders.vdf
        
        Returns:
            List of Steam library folder paths
        """
        library_paths = []
        
        # Try different possible Steam installation locations
        steam_paths = []
        
        # XDG_DATA_HOME path
        data_dir = os.getenv(ENV_XDG_DATA_HOME)
        if data_dir and data_dir.strip():
            steam_paths.append(Path(data_dir.strip()) / "Steam")
        
        # HOME/.local/share path (most common on Steam Deck)
        home_dir = os.getenv(ENV_HOME)
        if home_dir and home_dir.strip():
            steam_paths.append(Path(home_dir.strip()) / ".local" / "share" / "Steam")
        
        for steam_path in steam_paths:
            if steam_path.exists():
                # Add the main Steam directory as a library
                library_paths.append(str(steam_path))
                
                # Parse libraryfolders.vdf for additional libraries
                vdf_path = steam_path / "steamapps" / "libraryfolders.vdf"
                if vdf_path.exists():
                    try:
                        additional_paths = self._parse_library_folders_vdf(vdf_path)
                        library_paths.extend(additional_paths)
                    except Exception as e:
                        self.log.warning(f"Failed to parse {vdf_path}: {str(e)}")
        
        # Remove duplicates while preserving order
        seen = set()
        unique_paths = []
        for path in library_paths:
            if path not in seen:
                seen.add(path)
                unique_paths.append(path)
        
        self.log.info(f"Found {len(unique_paths)} Steam library paths: {unique_paths}")
        return unique_paths
    
    def _parse_library_folders_vdf(self, vdf_path: Path) -> List[str]:
        """Parse Steam's libraryfolders.vdf file to extract library paths
        
        Args:
            vdf_path: Path to the libraryfolders.vdf file
            
        Returns:
            List of additional Steam library folder paths
        """
        library_paths = []
        
        try:
            with open(vdf_path, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()
            
            # Look for "path" entries in the VDF file
            # The format is typically: "path"		"/path/to/library"
            path_pattern = r'"path"\s*"([^"]+)"'
            matches = re.findall(path_pattern, content, re.IGNORECASE)
            
            for path_match in matches:
                # Convert Windows paths to Unix paths if needed
                path = path_match.replace('\\\\', '/').replace('\\', '/')
                library_path = Path(path)
                
                # Verify the library folder exists and has a steamapps directory
                if library_path.exists() and (library_path / "steamapps").exists():
                    library_paths.append(str(library_path))
                    self.log.info(f"Found additional Steam library: {library_path}")
        
        except Exception as e:
            self.log.error(f"Error parsing libraryfolders.vdf: {str(e)}")
        
        return library_paths