summaryrefslogtreecommitdiff
path: root/main.py
diff options
context:
space:
mode:
Diffstat (limited to 'main.py')
-rw-r--r--main.py208
1 files changed, 208 insertions, 0 deletions
diff --git a/main.py b/main.py
index a22acca..881298a 100644
--- a/main.py
+++ b/main.py
@@ -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)