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
|