diff options
Diffstat (limited to 'main.py')
| -rw-r--r-- | main.py | 208 |
1 files changed, 208 insertions, 0 deletions
@@ -10,6 +10,56 @@ from pathlib import Path # Set to False or comment out this constant to skip the overwrite by default. UPSCALER_OVERWRITE_ENABLED = True +INJECTOR_FILENAMES = [ + "dxgi.dll", + "winmm.dll", + "nvngx.dll", + "_nvngx.dll", + "nvngx-wrapper.dll", + "dlss-enabler.dll", + "OptiScaler.dll", +] + +ORIGINAL_DLL_BACKUPS = [ + "d3dcompiler_47.dll", + "amd_fidelityfx_dx12.dll", + "amd_fidelityfx_framegeneration_dx12.dll", + "amd_fidelityfx_upscaler_dx12.dll", + "amd_fidelityfx_vk.dll", +] + +SUPPORT_FILES = [ + "libxess.dll", + "libxess_dx11.dll", + "libxess_fg.dll", + "libxell.dll", + "amd_fidelityfx_dx12.dll", + "amd_fidelityfx_framegeneration_dx12.dll", + "amd_fidelityfx_upscaler_dx12.dll", + "amd_fidelityfx_vk.dll", + "nvngx.dll", + "dlssg_to_fsr3_amd_is_better.dll", + "fakenvapi.dll", + "fakenvapi.ini", +] + +LEGACY_FILES = [ + "dlssg_to_fsr3.ini", + "dlssg_to_fsr3.log", + "nvapi64.dll", + "nvapi64.dll.b", + "fakenvapi.log", + "dlss-enabler.dll", + "dlss-enabler-upscaler.dll", + "dlss-enabler.log", + "nvngx-wrapper.dll", + "_nvngx.dll", + "dlssg_to_fsr3_amd_is_better-3.0.dll", + "OptiScaler.asi", + "OptiScaler.ini", + "OptiScaler.log", +] + class Plugin: async def _main(self): decky.logger.info("Framegen plugin loaded") @@ -380,6 +430,146 @@ class Plugin: else: return {"exists": False} + def _resolve_target_directory(self, directory: str) -> Path: + target = Path(directory).expanduser() + if not target.exists(): + raise FileNotFoundError(f"Target directory does not exist: {directory}") + if not target.is_dir(): + raise NotADirectoryError(f"Target path is not a directory: {directory}") + if not os.access(target, os.W_OK | os.X_OK): + raise PermissionError(f"Insufficient permissions for {directory}") + return target + + def _manual_patch_directory_impl(self, directory: Path) -> dict: + fgmod_path = Path(decky.HOME) / "fgmod" + if not fgmod_path.exists(): + return { + "status": "error", + "message": "OptiScaler bundle not installed. Run Install first.", + } + + optiscaler_dll = fgmod_path / "OptiScaler.dll" + if not optiscaler_dll.exists(): + return { + "status": "error", + "message": "OptiScaler.dll not found in ~/fgmod. Reinstall OptiScaler.", + } + + dll_name = "dxgi.dll" + preserve_ini = True + + try: + decky.logger.info(f"Manual patch started for {directory}") + + for filename in INJECTOR_FILENAMES: + path = directory / filename + if path.exists(): + path.unlink() + + for dll in ORIGINAL_DLL_BACKUPS: + source = directory / dll + backup = directory / f"{dll}.b" + if source.exists() and not backup.exists(): + shutil.move(source, backup) + + for legacy in ["nvapi64.dll", "nvapi64.dll.b"]: + legacy_path = directory / legacy + if legacy_path.exists(): + legacy_path.unlink() + + renamed = fgmod_path / "renames" / dll_name + destination_dll = directory / dll_name + source_for_copy = renamed if renamed.exists() else optiscaler_dll + shutil.copy2(source_for_copy, destination_dll) + + target_ini = directory / "OptiScaler.ini" + source_ini = fgmod_path / "OptiScaler.ini" + if preserve_ini and target_ini.exists(): + decky.logger.info(f"Preserving existing OptiScaler.ini at {target_ini}") + elif source_ini.exists(): + shutil.copy2(source_ini, target_ini) + + plugins_src = fgmod_path / "plugins" + plugins_dest = directory / "plugins" + if plugins_src.exists(): + shutil.copytree(plugins_src, plugins_dest, dirs_exist_ok=True) + + for filename in SUPPORT_FILES: + source = fgmod_path / filename + if source.exists(): + shutil.copy2(source, directory / filename) + + decky.logger.info(f"Manual patch complete for {directory}") + return { + "status": "success", + "message": f"OptiScaler files copied to {directory}", + } + + except PermissionError as exc: + decky.logger.error(f"Manual patch permission error: {exc}") + return { + "status": "error", + "message": f"Permission error while patching: {exc}", + } + except Exception as exc: + decky.logger.error(f"Manual patch failed: {exc}") + return { + "status": "error", + "message": f"Manual patch failed: {exc}", + } + + def _manual_unpatch_directory_impl(self, directory: Path) -> dict: + try: + decky.logger.info(f"Manual unpatch started for {directory}") + + for filename in set(INJECTOR_FILENAMES + SUPPORT_FILES): + path = directory / filename + if path.exists(): + path.unlink() + + for legacy in LEGACY_FILES: + path = directory / legacy + if path.exists(): + try: + path.unlink() + except IsADirectoryError: + shutil.rmtree(path, ignore_errors=True) + + plugins_dir = directory / "plugins" + if plugins_dir.exists(): + shutil.rmtree(plugins_dir, ignore_errors=True) + + for dll in ORIGINAL_DLL_BACKUPS: + backup = directory / f"{dll}.b" + original = directory / dll + if backup.exists(): + if original.exists(): + original.unlink() + shutil.move(backup, original) + + uninstaller = directory / "fgmod-uninstaller.sh" + if uninstaller.exists(): + uninstaller.unlink() + + decky.logger.info(f"Manual unpatch complete for {directory}") + return { + "status": "success", + "message": f"OptiScaler files removed from {directory}", + } + + except PermissionError as exc: + decky.logger.error(f"Manual unpatch permission error: {exc}") + return { + "status": "error", + "message": f"Permission error while unpatching: {exc}", + } + except Exception as exc: + decky.logger.error(f"Manual unpatch failed: {exc}") + return { + "status": "error", + "message": f"Manual unpatch failed: {exc}", + } + async def list_installed_games(self) -> dict: try: steam_root = Path(decky.HOME) / ".steam" / "steam" @@ -444,3 +634,21 @@ class Plugin: async def log_error(self, error: str) -> None: decky.logger.error(f"FRONTEND: {error}") + + async def manual_patch_directory(self, directory: str) -> dict: + try: + target_dir = self._resolve_target_directory(directory) + except (FileNotFoundError, NotADirectoryError, PermissionError) as exc: + decky.logger.error(f"Manual patch validation failed: {exc}") + return {"status": "error", "message": str(exc)} + + return self._manual_patch_directory_impl(target_dir) + + async def manual_unpatch_directory(self, directory: str) -> dict: + try: + target_dir = self._resolve_target_directory(directory) + except (FileNotFoundError, NotADirectoryError, PermissionError) as exc: + decky.logger.error(f"Manual unpatch validation failed: {exc}") + return {"status": "error", "message": str(exc)} + + return self._manual_unpatch_directory_impl(target_dir) |
