├── images ├── Hat.gif └── Hat.ico ├── Fonts ├── Segoe UI.ttf ├── Fonts │ ├── Segoe UI.ttf │ ├── CustomTkinter_shapes_font.otf │ └── notably_absent │ │ └── Notably Absent DEMO.ttf ├── CustomTkinter_shapes_font.otf └── notably_absent │ └── Notably Absent DEMO.ttf ├── Fsr3_auto_installer.py ├── README.md ├── theme.py ├── admin.py ├── guide.py ├── upscaler_updater.py ├── games_mods_config.py ├── helpers.py ├── cleanup.py ├── installer.py └── gui.py /images/Hat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/P4TOLINO06/FSR3.0-Mod-Setup-Utility/HEAD/images/Hat.gif -------------------------------------------------------------------------------- /images/Hat.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/P4TOLINO06/FSR3.0-Mod-Setup-Utility/HEAD/images/Hat.ico -------------------------------------------------------------------------------- /Fonts/Segoe UI.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/P4TOLINO06/FSR3.0-Mod-Setup-Utility/HEAD/Fonts/Segoe UI.ttf -------------------------------------------------------------------------------- /Fonts/Fonts/Segoe UI.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/P4TOLINO06/FSR3.0-Mod-Setup-Utility/HEAD/Fonts/Fonts/Segoe UI.ttf -------------------------------------------------------------------------------- /Fonts/CustomTkinter_shapes_font.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/P4TOLINO06/FSR3.0-Mod-Setup-Utility/HEAD/Fonts/CustomTkinter_shapes_font.otf -------------------------------------------------------------------------------- /Fonts/Fonts/CustomTkinter_shapes_font.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/P4TOLINO06/FSR3.0-Mod-Setup-Utility/HEAD/Fonts/Fonts/CustomTkinter_shapes_font.otf -------------------------------------------------------------------------------- /Fonts/notably_absent/Notably Absent DEMO.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/P4TOLINO06/FSR3.0-Mod-Setup-Utility/HEAD/Fonts/notably_absent/Notably Absent DEMO.ttf -------------------------------------------------------------------------------- /Fonts/Fonts/notably_absent/Notably Absent DEMO.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/P4TOLINO06/FSR3.0-Mod-Setup-Utility/HEAD/Fonts/Fonts/notably_absent/Notably Absent DEMO.ttf -------------------------------------------------------------------------------- /Fsr3_auto_installer.py: -------------------------------------------------------------------------------- 1 | from gui import * 2 | from admin import run_as_admin 3 | windll.shcore.SetProcessDpiAwareness(1) # Per-monitor DPI aware 4 | 5 | if __name__ == "__main__": 6 | run_as_admin() 7 | Gui().run() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FSR3 Mod Setup Utility 2 | ### Download the FSR3.0-Mod-Setup-Utility [Here](https://sharemods.com/u4p27vixjhyi/FSR3_v4.1.rar.html)
3 | 4 | ### Repository FSR3 Mod-Setup Utility Enhanced [Here](https://github.com/P4TOLINO06/FSR3-Mod-Setup-Utility-Enhanced) -------------------------------------------------------------------------------- /theme.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import tkinter.font as tkFont 3 | 4 | def get_font(): 5 | try: 6 | return ("Segoe UI", 13, 'bold') 7 | except tk.TclError: 8 | return tkFont.Font(family="Arial", size=10) 9 | 10 | def get_secondary_font(): 11 | try: 12 | return ("Segoe UI", 9) 13 | except tk.TclError: 14 | return tkFont.Font(family="Arial", size=9) 15 | 16 | COLORS = { 17 | "bg": "#1e1e2f", 18 | "fg": "#f0f0f0", 19 | "accent": "#4da6ff", 20 | "input_bg": "#2e2e3e", 21 | "text": "#C0C0C0", 22 | } 23 | -------------------------------------------------------------------------------- /admin.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import sys 3 | import os 4 | 5 | def uac(): 6 | try: 7 | return ctypes.windll.shell32.IsUserAnAdmin() 8 | except Exception: 9 | return False 10 | 11 | unlock_screen = True 12 | 13 | def run_as_admin(): 14 | global unlock_screen 15 | if uac(): 16 | unlock_screen = True 17 | else: 18 | unlock_screen = False 19 | try: 20 | ctypes.windll.shell32.ShellExecuteW( 21 | None, 22 | "runas", 23 | sys.executable, 24 | f'"{os.path.abspath(__file__)}"', 25 | None, 26 | 1 27 | ) 28 | sys.exit(0) 29 | except Exception as e: 30 | sys.exit(1) 31 | 32 | run_as_admin() -------------------------------------------------------------------------------- /guide.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from theme import * 3 | from customtkinter import * 4 | import re 5 | from helpers import load_or_create_json 6 | 7 | class FsrGuide: 8 | def __init__(self, screen, combobox_func=None): 9 | self.screen = screen 10 | self.font = get_font() 11 | 12 | self.screen_guide = None 13 | self.fsr_guide_label = None 14 | self.fsr_guide_var = tk.IntVar() 15 | self.combobox_func = combobox_func 16 | self.game_guides = [] 17 | self.load_data() 18 | 19 | def load_data(self): 20 | data = load_or_create_json("guides.json", { 21 | "templates": {}, 22 | "template_dlss_games": [], 23 | "template_dlssg_games": [], 24 | "template_dlss_dlssg_games": [], 25 | "custom_games": [] 26 | }) 27 | 28 | self.game_data = {} 29 | templates = data.get("templates", {}) 30 | 31 | def add_games(game_list, dimension, template_keys): 32 | if not game_list: 33 | return 34 | for game in game_list: 35 | guide_text = "" 36 | if "dlss_dlssg" in template_keys: 37 | guide_text = ( 38 | f"{templates.get('fsr_dlss_optiscaler_fg', '')}\n\n" 39 | f"{templates.get('dlssg_optiscaler', '')}\n\n" 40 | f"{templates.get('additional_info_fsr_dlss_fg', '')}" 41 | ) 42 | elif "dlssg" in template_keys: 43 | guide_text = templates.get("dlssg_optiscaler", "") 44 | else: 45 | guide_text = templates.get("fsr_dlss_optiscaler_fg", "") 46 | guide_text += f"\n\n{templates.get('additional_info_fsr_dlss_fg', '')}" 47 | 48 | self.game_data[game] = { 49 | "guide": guide_text.strip(), 50 | "dimension": dimension, 51 | } 52 | 53 | add_games(data.get("template_dlss_games"), data.get("dimension_dlss_games"), "dlss") 54 | add_games(data.get("template_dlssg_games"), data.get("dimension_dlssg_games"), "dlssg") 55 | add_games(data.get("template_dlss_dlssg_games"), data.get("dimension_dlss_dlssg_games"), "dlss_dlssg") 56 | 57 | for custom in data.get("custom_games", []): 58 | name = custom["name"] 59 | self.game_data[name] = { 60 | "guide": custom.get("extra_guides", ""), 61 | "dimension": custom.get("dimension", "800x560"), 62 | } 63 | 64 | self.game_data = dict(sorted( 65 | self.game_data.items(), 66 | key=lambda x: ( 67 | not x[0].lower().startswith("initial information"), 68 | x[0].lower() 69 | ) 70 | )) 71 | 72 | def toggle_guide(self): 73 | if self.fsr_guide_var.get() == 1: 74 | self.open_guide() 75 | elif self.screen_guide: 76 | self.screen_guide.destroy() 77 | self.screen_guide = None 78 | self.guide_label = None 79 | 80 | def open_guide(self): 81 | if self.screen_guide: 82 | self.screen_guide.deiconify() 83 | return 84 | 85 | self.screen_guide = CTkToplevel(self.screen) 86 | self.screen_guide.iconbitmap("images/Hat.ico") 87 | self.screen_guide.title("FSR GUIDE") 88 | self.screen_guide.geometry("500x360") 89 | self.screen_guide.configure(bg="#222223") 90 | self.screen_guide.resizable(0, 0) 91 | self.screen_guide.protocol("WM_DELETE_WINDOW", self.exit_fsr_guide) 92 | self.screen_guide.configure(fg_color="#222223") 93 | 94 | def apply_icon(): 95 | try: 96 | self.screen_guide.wm_iconbitmap("images/Hat.ico") 97 | except Exception as e: 98 | print("icon error:", e) 99 | 100 | self.screen_guide.after(350, apply_icon) 101 | 102 | self.create_guide_selector() 103 | 104 | self.guide_frame = CTkScrollableFrame( 105 | self.screen_guide, 106 | fg_color="#222223", 107 | bg_color="#222223", 108 | corner_radius=0, 109 | ) 110 | self.guide_frame.place(x=170, y=0, relwidth=1, relheight=1) 111 | 112 | def create_guide_selector(self): 113 | self.game_guides = list(self.game_data.keys()) 114 | 115 | if self.combobox_func is not None: 116 | self.guide_selected, self.combobox = self.combobox_func( 117 | self.screen_guide, 118 | self.game_guides, 119 | 2, 0, 120 | 165, 25, 121 | 0, 122 | 21, 123 | persistent=True 124 | ) 125 | self.guide_selected.trace_add("write", self.update_guide) 126 | 127 | 128 | def update_guide(self, *args): 129 | if not self.screen_guide or not self.guide_frame: 130 | return 131 | 132 | index = self.guide_selected.get() 133 | if not index: 134 | return 135 | 136 | info = self.game_data.get(index, {}) 137 | dimension = info.get("dimension", "520x360") 138 | guide_text = info.get("guide", "No guide available.") 139 | 140 | self.screen_guide.geometry(dimension) 141 | 142 | for widget in self.guide_frame.winfo_children(): 143 | widget.destroy() 144 | 145 | for line in guide_text.split("\n"): 146 | label = CTkLabel( 147 | self.guide_frame, 148 | text=line if line.strip() != "" else " ", 149 | font=("Segoe UI", 14, "bold" if self.is_title_line(line) else "normal"), 150 | text_color="white", 151 | anchor="w", 152 | justify="left", 153 | ) 154 | label.pack(fill="x", pady=0, padx=0, ipady=0) 155 | label._label.configure(pady=0) 156 | 157 | 158 | def exit_fsr_guide(self): 159 | if self.screen_guide: 160 | self.screen_guide.destroy() 161 | self.screen_guide = None 162 | self.guide_label = None 163 | self.fsr_guide_var.set(0) 164 | 165 | 166 | def is_title_line(self, line): 167 | stripped = line.strip() 168 | 169 | if stripped == "": 170 | return False 171 | 172 | whitelist = ["Additional Info FSR4/DLSS FG", "Red Dead Redemption 2 MIX", "RDR2 FG Custom", "Ghost of Tsushima FG DLSS", "FSR4/DLSS (Only Optiscaler) + AMD Anti Lag 2", "Indy FG (Only RTX)", "Unlock FPS"] 173 | if any(word.lower() in stripped.lower() for word in whitelist): 174 | return True 175 | 176 | if re.match(r"^\d+\.", stripped): 177 | return False 178 | 179 | if stripped.startswith("-"): 180 | return False 181 | 182 | blacklist = ["Output", "Enabled", "Disabled", "Select", "Chose", "Choose", "Mode"] 183 | if any(word.lower() in stripped.lower() for word in blacklist): 184 | return False 185 | 186 | title_starts = ["FSR", "DLSS", "DLSSG", "RTX"] 187 | if not any(stripped.startswith(prefix) for prefix in title_starts): 188 | return False 189 | 190 | if len(stripped.split()) > 5: 191 | return False 192 | 193 | if "." in stripped: 194 | return False 195 | 196 | return True -------------------------------------------------------------------------------- /upscaler_updater.py: -------------------------------------------------------------------------------- 1 | import os 2 | from helpers import copy_with_progress 3 | from games_mods_config import addons_files 4 | 5 | def update_upscalers(dest_path, copy_dlss = False, copy_dlss_dlssd = False, copy_dlss_fsr4 = False, copy_dlss_fsr3 = False,copy_dlss_xess = False, copy_dlss_dlssg = False,dlssg_path = None, progress_callback = None): 6 | update_fsr3 = addons_files["FSR3"]["addon_path"] 7 | update_fsr4 = addons_files["FSR4"]["addon_path"] 8 | update_dlss = addons_files["DLSS"]["addon_path"] 9 | update_dlssg = addons_files["DLSSG"]["addon_path"] 10 | update_xess = addons_files["XESS"]["addon_path"] 11 | update_dlssd = addons_files["DLSSD"]["addon_path"] 12 | 13 | try: 14 | # DLSS 15 | if copy_dlss: 16 | copy_with_progress(update_dlss, dest_path, progress_callback, True) 17 | 18 | # DLSSG 19 | if copy_dlss_dlssg: 20 | if dlssg_path and os.path.exists(os.path.dirname(dlssg_path)): 21 | copy_with_progress(update_dlssg, dlssg_path, progress_callback, True) 22 | else: 23 | copy_with_progress(update_dlssg, dest_path, progress_callback, True) 24 | 25 | # DLSSD 26 | if copy_dlss_dlssd: 27 | copy_with_progress(update_dlssd, dest_path, progress_callback, True) 28 | 29 | # XESS 30 | if copy_dlss_xess: 31 | copy_with_progress(update_xess, dest_path, progress_callback, True) 32 | 33 | # FSR 34 | if copy_dlss_fsr4: 35 | copy_with_progress(update_fsr4, dest_path, progress_callback, True) 36 | 37 | if copy_dlss_fsr3: 38 | copy_with_progress(update_fsr3, dest_path, progress_callback, True) 39 | 40 | if all([copy_dlss, copy_dlss_dlssg, copy_dlss_dlssd, copy_dlss_xess, copy_dlss_fsr4]): 41 | copy_with_progress(update_fsr4, dest_path, progress_callback, True) 42 | copy_with_progress(update_dlss, dest_path, progress_callback, True) 43 | copy_with_progress(update_dlssg, dest_path, progress_callback, True) 44 | copy_with_progress(update_xess, dest_path, progress_callback, True) 45 | copy_with_progress(update_dlssd, dest_path, progress_callback, True) 46 | 47 | except Exception as e: 48 | print(e) 49 | 50 | def games_to_update_upscalers(dest_path,game_selected,progress_callback=None,copy_dlss=False,copy_dlss_dlssg=False,copy_dlss_dlssd=False,copy_dlss_xess=False,copy_dlss_fsr4=False, copy_dlss_fsr3=False, absolute_path = False): 51 | if absolute_path: 52 | default_dlss_path = dest_path 53 | default_dlssg_path = dest_path 54 | else: 55 | default_dlss_path = os.path.abspath(os.path.join(dest_path, '..\\..\\..', 'Engine\\Plugins\\Runtime\\Nvidia\\DLSS\\Binaries\\ThirdParty\\Win64')) 56 | default_dlssg_path = os.path.abspath(os.path.join(dest_path, '..\\..\\..', 'Engine\\Plugins\\Runtime\\Nvidia\\Streamline\\Binaries\\ThirdParty\\Win64')) 57 | 58 | games_to_update_dlss = { 59 | 'Sifu': dest_path, 60 | 'Shadow of the Tomb Raider': dest_path, 61 | 'The Last of Us Part I' : dest_path, 62 | 'Steelrising' : dest_path, 63 | 'Final Fantasy XVI' : dest_path, 64 | 'Assetto Corsa Evo' : dest_path, 65 | 'Watch Dogs Legion' : dest_path, 66 | 'Alan Wake 2' : dest_path, 67 | 'GreedFall II: The Dying World' : dest_path, 68 | 'GTA V' : dest_path, 69 | 'Cities: Skylines 2' : dest_path, 70 | 'Crysis Remastered': dest_path, 71 | 'WILD HEARTS' : dest_path, 72 | 'Six Days in Fallujah' : default_dlss_path, 73 | 'FIST: Forged In Shadow Torch' : default_dlss_path, 74 | 'Hogwarts Legacy' : default_dlss_path, 75 | 'Gotham Knights' : default_dlss_path, 76 | 'Way Of The Hunter' : default_dlss_path, 77 | 'Evil West' : default_dlss_path, 78 | 'The First Berserker: Khazan' : default_dlss_path, 79 | 'Soulstice' : default_dlss_path, 80 | 'Alone in the Dark' : default_dlss_path, 81 | 'Choo-Choo Charles' : default_dlss_path, 82 | 'Five Nights at Freddy’s: Security Breach' : default_dlss_path, 83 | 'Kena: Bridge of Spirits' : default_dlss_path, 84 | 'Deliver Us The Moon' : default_dlss_path, 85 | 'Chernobylite' : default_dlss_path, 86 | 'Chorus' : default_dlss_path, 87 | 'The Outlast Trials' : default_dlss_path, 88 | 'South Of Midnight' : default_dlss_path, 89 | 'Elder Scrolls IV Oblivion Remaster': os.path.abspath(os.path.join(dest_path, '..\\..\\..', 'Engine\\Plugins\\Marketplace\\nvidia\\DLSS\\DLSS\\Binaries\\ThirdParty')), 90 | 'Clair Obscur Expedition 33' : os.path.abspath(os.path.join(dest_path, '..\\..', 'Plugins\\NVIDIA\\DLSS\\Binaries\\ThirdParty\\Win64')), 91 | 'Brothers: A Tale of Two Sons Remake' : os.path.abspath(os.path.join(dest_path, '..\\..', 'Brothers\\Plugins\\NVIDIA\\DLSS\\Binaries\\ThirdParty\\Win64')), 92 | 'Pacific Drive' : os.path.abspath(os.path.join(dest_path, '..\\..', 'Plugins\\DLSS\\Binaries\\ThirdParty\\Win64')), 93 | 'Bright Memory' : os.path.abspath(os.path.join(dest_path, '..\\..\\..', 'Engine\\Binaries\\ThirdParty\\NVIDIA\\NGX\\Win64')), 94 | 'Fobia – St. Dinfna Hotel' : os.path.abspath(os.path.join(dest_path, '..\\..', 'Plugins\\DLSS\\Binaries\\ThirdParty\\Win64')), 95 | 'Palworld' : os.path.abspath(os.path.join(dest_path, '..\\..', 'Plugins\\DLSS\\Binaries\\ThirdParty\\Win64')), 96 | 'Kingdom Come: Deliverance II' : os.path.abspath(os.path.join(dest_path, '..' , 'Win64Shared')), 97 | 'Lies of P' : os.path.abspath(os.path.join(dest_path, '..\\..\\..', 'Engine\\Plugins\\Marketplace\\DLSS\\Binaries\\ThirdParty\\Win64')), 98 | 'Final Fantasy VII Rebirth' : os.path.abspath(os.path.join(dest_path, '..\\..\\..', 'Engine\\Plugins\\DLSSSubset\\Binaries\\ThirdParty\\Win64')), 99 | 'Back 4 Blood' : os.path.abspath(os.path.join(dest_path, '..\\..\\..', 'Engine\\Binaries\\ThirdParty\\Nvidia\\NGX\\Win64')), 100 | 'Alone in the Dark' : os.path.abspath(os.path.join(dest_path, '..\\..', 'Plugins\\DLSS\\Binaries\\ThirdParty\\Win64')), 101 | 'Ghostrunner 2' : os.path.abspath(os.path.join(dest_path, '..\\..', 'Plugins\\DLSS\\Binaries\\ThirdParty\\Win64')), 102 | 'Remnant II' : os.path.abspath(os.path.join(dest_path,'..\\..', 'Plugins\\Shared\\DLSS\\Binaries\\ThirdParty\\Win64')), 103 | 'Mortal Shell': os.path.abspath(os.path.join(dest_path, '..\\..\\..', 'Engine\\Binaries\\ThirdParty\\NVIDIA\\NGX\\Win64')), 104 | 'Path of Exile II': os.path.join(dest_path, 'Streamline'), 105 | 'GTA Trilogy' : os.path.abspath(os.path.join(dest_path, '..\\..\\..', 'Engine\\Plugins\\Runtime\\Nvidia\\DLSS\\Binaries\\ThirdParty\\Win64')) 106 | } 107 | 108 | games_to_update_dlss_dlssg_dlssd = { 109 | 'Black Myth: Wukong': (default_dlss_path, default_dlssg_path), 110 | 'Indiana Jones and the Great Circle' : (os.path.join(dest_path, 'streamline'), os.path.join(dest_path, 'streamline')) 111 | } 112 | 113 | games_to_update_dlss_dlssg = { 114 | 'A Plague Tale Requiem' : dest_path, 115 | 'The Witcher 3' : dest_path, 116 | 'Dying Light 2' : dest_path, 117 | 'The Last of Us Part II' : dest_path, 118 | 'Hellblade 2': default_dlss_path, 119 | 'Assassin\'s Creed Shadows' : os.path.join(dest_path, "NVStreamline\\production"), 120 | 'Deliver Us Mars' : (default_dlss_path, default_dlssg_path), 121 | 'STAR WARS Jedi: Survivor' : (default_dlss_path, default_dlssg_path), 122 | 'Frostpunk 2' : (os.path.abspath(os.path.join(dest_path, '..\\..\\..', 'Engine\\Plugins\\Elb\\DLSS\\Binaries\\ThirdParty\\Win64')), os.path.abspath(os.path.join(dest_path, '..\\..\\..', 'Engine\\Plugins\\Elb\\Streamline\\Binaries\\ThirdParty\\Win64'))), 123 | 'Stalker 2' : (os.path.abspath(os.path.join(dest_path, '..\\..\\..', 'Engine\\Plugins\\Marketplace\\DLSS\\Binaries\\ThirdParty\\Win64')), os.path.abspath(os.path.join(dest_path, '..\\..\\..', 'Engine\\Plugins\\Marketplace\\Streamline\\Binaries\\ThirdParty\\Win64'))), 124 | 'Chernobylite 2: Exclusion Zone' : (os.path.abspath(os.path.join(dest_path, '..\\..\\..', 'Engine\\Plugins\\NVIDIA\\DLSS\\Binaries\\ThirdParty\\Win64')), os.path.abspath(os.path.join(dest_path, '..\\..\\..','Engine\\Plugins\\NVIDIA\\Streamline\\Binaries\\ThirdParty\\Win64'))) 125 | } 126 | 127 | games_to_update_all_upscalers = { 128 | 'Marvel\'s Spider-Man Miles Morales': dest_path, 129 | 'Marvel\'s Spider-Man Remastered': dest_path, 130 | 'Marvel\'s Spider-Man 2': dest_path, 131 | 'God of War Ragnarök' : dest_path 132 | } 133 | games_to_update_fsr4_dlss = { 134 | 'Control' : dest_path, 135 | 'Horizon Zero Dawn/Remastered' : dest_path, 136 | 'Hitman 3' : dest_path, 137 | 'Like a Dragon: Pirate Yakuza in Hawaii' : dest_path 138 | } 139 | games_to_update_dlss_xess = { 140 | 'Monster Hunter Wilds' : dest_path 141 | } 142 | 143 | try: 144 | all_paths = {} 145 | all_paths.update(games_to_update_dlss) 146 | all_paths.update(games_to_update_dlss_dlssg) 147 | all_paths.update(games_to_update_dlss_dlssg_dlssd) 148 | all_paths.update(games_to_update_dlss_xess) 149 | all_paths.update(games_to_update_fsr4_dlss) 150 | all_paths.update(games_to_update_all_upscalers) 151 | 152 | # Absoluth path -> addons_path (gui) 153 | if absolute_path: 154 | path_target = dest_path 155 | 156 | else: 157 | path_target = all_paths.get(game_selected) 158 | 159 | if isinstance(path_target, tuple): 160 | existing_paths = [ 161 | p for p in path_target 162 | if isinstance(p, str) and os.path.exists(p) 163 | ] 164 | path_target = existing_paths[0] if existing_paths else dest_path # dest_path -> same folder selected for mod installation (gui , Game folder) 165 | 166 | if not isinstance(path_target, str) or not os.path.exists(path_target): 167 | path_target = dest_path 168 | 169 | if not isinstance(path_target, str) or not os.path.exists(path_target): 170 | print(" No valid path found. (upscalers)") 171 | return 172 | 173 | update_upscalers( 174 | path_target, 175 | copy_dlss=copy_dlss, 176 | copy_dlss_dlssg=copy_dlss_dlssg, 177 | copy_dlss_dlssd=copy_dlss_dlssd, 178 | copy_dlss_xess=copy_dlss_xess, 179 | copy_dlss_fsr3=copy_dlss_fsr3, 180 | copy_dlss_fsr4=copy_dlss_fsr4, 181 | progress_callback=progress_callback, 182 | ) 183 | except Exception as e: 184 | print(f"[ERROR] update upscalers: {e}") -------------------------------------------------------------------------------- /games_mods_config.py: -------------------------------------------------------------------------------- 1 | game_config = ['Achilles Legends Untold','Alan Wake 2','Alan Wake Remastered','Alone in the Dark','A Plague Tale Requiem', 'A Quiet Place: The Road Ahead','Assassin\'s Creed Mirage','Assassin\'s Creed Shadows','Assassin\'s Creed Valhalla','Assetto Corsa Evo','Atomic Heart','AVOWED','Back 4 Blood','Baldur\'s Gate 3','Banishers: Ghosts of New Eden','Black Myth: Wukong','Blacktail','Bright Memory','Bright Memory: Infinite','Brothers a Tale of Two Sons','Brothers: A Tale of Two Sons Remake','Chernobylite','Chernobylite 2: Exclusion Zone','Choo-Choo Charles','Chorus','Cities: Skylines 2','COD MW3','Control','Clair Obscur Expedition 33','Crime Boss Rockay City', 'Crysis Remastered','Cyberpunk 2077','Dakar Desert Rally','Dead Island 2','Dead Rising Remaster','Deathloop','Death Stranding Director\'s Cut','Dead Space (2023)','Dragon Age: Veilguard','Dragons Dogma 2','Deliver Us Mars','Deliver Us The Moon','Dying Light 2','Dynasty Warriors: Origins','Elden Ring', 'Elden Ring Nightreign','Elder Scrolls IV Oblivion Remaster','Empire of the Ants','Everspace 2','Eternal Strands','Evil West','F1 2022','F1 2023','Final Fantasy VII Rebirth','Final Fantasy XVI','FIST: Forged In Shadow Torch','Five Nights at Freddy’s: Security Breach','Flintlock: The Siege of Dawn','Fobia – St. Dinfna Hotel','Fort Solis', 2 | 'Forza Horizon 5','Frostpunk 2','Ghost of Tsushima','Ghostrunner 2','Ghostwire: Tokyo','God Of War 4','God of War Ragnarök','Gotham Knights','GreedFall II: The Dying World','GTA Trilogy','GTA V','Hellblade: Senua\'s Sacrifice','Hellblade 2','High On Life','Hitman 3','Hogwarts Legacy','Horizon Zero Dawn/Remastered','Horizon Forbidden West','Hot Wheels Unleashed','Icarus','Indiana Jones and the Great Circle','Judgment','Jusant','Kingdom Come: Deliverance II','Kena: Bridge of Spirits','Layers of Fear','Lego Horizon Adventures','Lies of P','Like a Dragon: Pirate Yakuza in Hawaii','Lords of the Fallen','Loopmancer','Lost Records Bloom And Rage','Manor Lords','Martha Is Dead','Marvel\'s Avengers','Marvel\'s Guardians of the Galaxy','Marvel\'s Spider-Man Remastered','Marvel\'s Spider-Man 2','Marvel\'s Spider-Man Miles Morales','Marvel\'s Midnight Suns','Metro Exodus Enhanced Edition','Microsoft Flight Simulator 2024','Monster Hunter Rise','Monster Hunter Wilds','Mortal Shell','MOTO GP 24','Nightingale','Ninja Gaiden 2 Black','Nobody Wants To Die','Orcs Must Die! Deathtrap','Outpost: Infinity Siege','Pacific Drive','Palworld','Path of Exile II','Ratchet & Clank - Rift Apart', 3 | 'Red Dead Redemption','Red Dead Redemption 2','Ready or Not','Remnant II','Resident Evil 4 Remake','Returnal','Rise of The Tomb Raider','Ripout','RoboCop: Rogue City','Saints Row','Satisfactory','Sackboy: A Big Adventure','Scorn','Sengoku Dynasty','Shadow Warrior 3','Shadow of the Tomb Raider','Sifu','Silent Hill 2','Smalland','Soulslinger Envoy of Death','Soulstice','South Of Midnight','S.T.A.L.K.E.R. 2','Starfield','STAR WARS Jedi: Survivor','Star Wars Outlaws','Steelrising','Suicide Squad: Kill the Justice League','Tainted Grail Fall of Avalon','The Alters','TEKKEN 8','Test Drive Ultimate Solar Crown','The Ascent','The Callisto Protocol','The Casting Of Frank Stone','The Chant','The First Berserker: Khazan','The Invincible','The Last of Us Part I','The Last of Us Part II','The Medium','The Outer Worlds: Spacer\'s Choice Edition','The Outlast Trials','The Talos Principle 2','The Thaumaturge','Thymesia','The Witcher 3','Uncharted Legacy of Thieves Collection','Unknown 9: Awakening','Until Dawn','Wanted: Dead','Warhammer: Space Marine 2', 'Watch Dogs Legion', 'Way Of The Hunter','Wayfinder','WILD HEARTS'] 4 | 5 | fsr_31_dlss_mods = [ 6 | 'FSR4/DLSS FG (Only Optiscaler)', 7 | 'FSR4/DLSSG FG (Only Optiscaler)' 8 | ] 9 | 10 | addons_files = { 11 | "XESS" : { 12 | "target": ["libxess.dll", "libxess_fg.dll", "libxell.dll", "libxess_dx11.dll"], 13 | "addon_path": [ 14 | r"mods\Temp\Upscalers\Intel\libxess.dll", 15 | r"mods\Temp\Upscalers\Intel\libxess_fg.dll", 16 | r"mods\Temp\Upscalers\Intel\libxell.dll", 17 | r"mods\Temp\Upscalers\Intel\libxess_dx11.dll" 18 | ] 19 | 20 | }, 21 | "DLSS":{ 22 | "target": "nvngx_dlss.dll", 23 | "addon_path": r"mods\Temp\Upscalers\Nvidia\\Dlss\nvngx_dlss.dll" 24 | }, 25 | "DLSSD":{ 26 | "target": "nvngx_dlssd.dll", 27 | "addon_path": r"mods\Temp\Upscalers\Nvidia\\Dlssd\nvngx_dlssd.dll" 28 | }, 29 | "DLSSG":{ 30 | "target": "nvngx_dlssg.dll", 31 | "addon_path": r"mods\Temp\Upscalers\Nvidia\\Dlssg\nvngx_dlssg.dll" 32 | }, 33 | "FSR4":{ 34 | "target": ["amd_fidelityfx_framegeneration_dx12.dll", "amd_fidelityfx_upscaler_dx12.dll", "amd_fidelityfx_vk.dll", "amd_fidelityfx_dx12.dll"], 35 | "addon_path": [ 36 | r"mods\Temp\Upscalers\\AMD\FSR4\amd_fidelityfx_framegeneration_dx12.dll", 37 | r"mods\Temp\Upscalers\\AMD\FSR4\amd_fidelityfx_upscaler_dx12.dll", 38 | r"mods\Temp\Upscalers\\AMD\FSR4\amd_fidelityfx_vk.dll", 39 | r"mods\Temp\Upscalers\\AMD\FSR4\amd_fidelityfx_dx12.dll", 40 | ] 41 | }, 42 | "FSR3":{ 43 | "target": ["amd_fidelityfx_vk.dll", "amd_fidelityfx_dx12.dll"], 44 | "addon_path":[ 45 | r"mods\Temp\Upscalers\AMD\FSR3\amd_fidelityfx_vk.dll", 46 | r"mods\Temp\Upscalers\AMD\FSR3\amd_fidelityfx_dx12.dll" 47 | ] 48 | } 49 | } 50 | 51 | addons_presets = { 52 | "DLSS": [ 53 | "Sifu", "Shadow of the Tomb Raider", "The Last of Us Part I", "Steelrising", "Final Fantasy XVI", 54 | "Assetto Corsa Evo", "Watch Dogs Legion", "Alan Wake 2", "GreedFall II: The Dying World", "GTA V", 55 | "Cities: Skylines 2", "Crysis Remastered", "WILD HEARTS", "Six Days in Fallujah", 56 | "FIST: Forged In Shadow Torch", "Hogwarts Legacy", "Gotham Knights", "Way Of The Hunter", "Path of Exile II" 57 | "Evil West", "The First Berserker: Khazan", "Soulstice", "GTA Trilogy", "Atomic Heart", "Choo-Choo Charles", 58 | "Five Nights at Freddy’s: Security Breach", "Kena: Bridge of Spirits", "Deliver Us The Moon", "Chernobylite", "Chorus", 59 | "The Outlast Trials", "South Of Midnight", "Elder Scrolls IV Oblivion Remaster", "Clair Obscur Expedition 33", "Brothers: A Tale of Two Sons Remake", 60 | "Pacific Drive", "Bright Memory", "Fobia – St. Dinfna Hotel", "Palworld", "Kingdom Come: Deliverance II", 61 | "Lies of P", "Final Fantasy VII Rebirth", "Back 4 Blood", "Alone in the Dark", "Ghostrunner 2", 62 | "Remnant II", "Mortal Shell" 63 | ], 64 | 65 | "DLSS_DLSSG":[ 66 | "A Plague Tale Requiem", "The Witcher 3", "Dying Light 2", "The Last of Us Part II", "Assassin\'s Creed Shadows", 67 | "Deliver Us Mars", "STAR WARS Jedi: Survivor", "Frostpunk 2", "Lost Records Bloom And Rage", "Stalker 2", 68 | "Chernobylite 2: Exclusion Zone", "Hellblade 2" 69 | ], 70 | 71 | "DLSS_DLSSG_DLSSD":[ 72 | "Black Myth: Wukong", "Indiana Jones and the Great Circle" 73 | ], 74 | 75 | "DLSS_FSR4": [ 76 | "Control", "Horizon Zero Dawn/Remastered", "Hitman 3", "Like a Dragon: Pirate Yakuza in Hawaii" 77 | ], 78 | 79 | "DLSS_XESS":[ 80 | "Monster Hunter Wilds" 81 | ], 82 | 83 | "DLSS_DLSSG_DLSSD_FSR_XESS": ["Marvel\'s Spider-Man Miles Morales", "Marvel\'s Spider-Man Remastered", "Marvel\'s Spider-Man 2", "God of War Ragnarök"] 84 | } 85 | 86 | game_mods_config = { 87 | 'A Plague Tale Requiem': [*fsr_31_dlss_mods], 88 | 'A Quiet Place: The Road Ahead': [*fsr_31_dlss_mods], 89 | 'Alan Wake 2': ['Alan Wake 2 FG RTX', *fsr_31_dlss_mods], 90 | 'Alan Wake Remastered': [*fsr_31_dlss_mods], 91 | 'Alone in the Dark' : [*fsr_31_dlss_mods], 92 | 'Assassin\'s Creed Mirage': [*fsr_31_dlss_mods], 93 | 'Assassin\'s Creed Shadows' : [*fsr_31_dlss_mods], 94 | 'Assassin\'s Creed Valhalla': ['Ac Valhalla DLSS3 (Only RTX)', 'Ac Valhalla FSR3 All GPU'], 95 | 'Assetto Corsa EVO': [*fsr_31_dlss_mods], 96 | 'Atomic Heart' : [*fsr_31_dlss_mods], 97 | 'Back 4 Blood' : [*fsr_31_dlss_mods], 98 | 'Baldur\'s Gate 3': ['Baldur\'s Gate 3 FSR3', 'Baldur\'s Gate 3 FSR3 V2', 'Baldur\'s Gate 3 FSR3 V3'], 99 | 'Black Myth: Wukong': [*fsr_31_dlss_mods], 100 | 'Bright Memory' : [*fsr_31_dlss_mods], 101 | 'Brothers: A Tale of Two Sons Remake' : [*fsr_31_dlss_mods ], 102 | 'Cities: Skylines 2' : [*fsr_31_dlss_mods], 103 | 'Chernobylite' : [*fsr_31_dlss_mods ], 104 | 'Chernobylite 2: Exclusion Zone' : [*fsr_31_dlss_mods], 105 | 'Choo-Choo Charles' : [*fsr_31_dlss_mods], 106 | 'Chorus' : [*fsr_31_dlss_mods], 107 | 'Clair Obscur Expedition 33' : [*fsr_31_dlss_mods], 108 | 'Crysis Remastered' : [*fsr_31_dlss_mods], 109 | 'Lost Records Bloom And Rage' : [*fsr_31_dlss_mods], 110 | 'COD MW3': ['COD MW3 FSR3'], 111 | 'Control': [*fsr_31_dlss_mods], 112 | 'Cyberpunk 2077': [*fsr_31_dlss_mods,'RTX DLSS FG'], 113 | 'Dead Rising Remaster': ['Dinput8 DRR','DDR FG'], 114 | 'Dragon Age: Veilguard' : ['FSR4/DLSS DG Veil',*fsr_31_dlss_mods], 115 | 'Dragons Dogma 2': ['Dinput8 DD2', 'DD2 FG'], 116 | 'Deliver Us Mars' : [*fsr_31_dlss_mods], 117 | 'Deliver Us The Moon' : [*fsr_31_dlss_mods], 118 | 'Dying Light 2': [*fsr_31_dlss_mods], 119 | 'Elden Ring': ['Disable_Anti-Cheat', 'Elden_Ring_FSR3', 'Elden_Ring_FSR3 V2', 'FSR4/DLSS FG Custom Elden', 'Unlock FPS Elden'], 120 | 'Elden Ring Nightreign': ['FSR4/DLSS Nightreign RTX'], 121 | 'Elder Scrolls IV Oblivion Remaster': [*fsr_31_dlss_mods], 122 | 'Evil West': [*fsr_31_dlss_mods], 123 | 'Final Fantasy VII Rebirth': [*fsr_31_dlss_mods], 124 | 'Final Fantasy XVI': ['FFXVI DLSS RTX',*fsr_31_dlss_mods], 125 | 'Flintlock: The Siege of Dawn': [*fsr_31_dlss_mods], 126 | 'FIST: Forged In Shadow Torch': [*fsr_31_dlss_mods], 127 | 'Five Nights at Freddy’s: Security Breach' : [*fsr_31_dlss_mods], 128 | 'Fobia – St. Dinfna Hotel': [*fsr_31_dlss_mods], 129 | 'Forza Horizon 5': ['Forza Horizon 5 FSR3'], 130 | 'Frostpunk 2': [*fsr_31_dlss_mods], 131 | 'Ghost of Tsushima': ['Ghost of Tsushima FG DLSS', *fsr_31_dlss_mods], 132 | 'Ghostrunner 2' : [*fsr_31_dlss_mods], 133 | 'Gotham Knights': [*fsr_31_dlss_mods], 134 | 'GreedFall II: The Dying World' : [*fsr_31_dlss_mods], 135 | 'GTA Trilogy' : [*fsr_31_dlss_mods], 136 | 'GTA V': ['FSR4/DLSS FG (Only Optiscaler)'], 137 | 'God Of War 4': ['FSR4/DLSS Gow4'], 138 | 'God of War Ragnarök': [*fsr_31_dlss_mods ], 139 | 'Hellblade 2': ['Others Mods HB2', 'Hellblade 2 FSR3 (Only RTX)', *fsr_31_dlss_mods], 140 | 'Hitman 3' : [*fsr_31_dlss_mods ], 141 | 'Hogwarts Legacy': [*fsr_31_dlss_mods], 142 | 'Horizon Forbidden West': [*fsr_31_dlss_mods], 143 | 'Horizon Zero Dawn/Remastered': [*fsr_31_dlss_mods], 144 | 'Icarus': [*fsr_31_dlss_mods, 'Icarus DLSSG RTX', 'Icarus FSR3 AMD/GTX'], 145 | 'Indiana Jones and the Great Circle' : ['FSR4/DLSS FG (Only Optiscaler Indy)','Indy FG (Only RTX)'], 146 | 'Kingdom Come: Deliverance II' : [*fsr_31_dlss_mods], 147 | 'Kena: Bridge of Spirits': [*fsr_31_dlss_mods], 148 | 'Lego Horizon Adventures': [*fsr_31_dlss_mods], 149 | 'Lies of P': [*fsr_31_dlss_mods], 150 | 'Like a Dragon: Pirate Yakuza in Hawaii': ['DLSSG Yakuza', *fsr_31_dlss_mods], 151 | 'Lords of the Fallen': ['DLSS FG LOTF2 (RTX)'], 152 | 'Marvel\'s Spider-Man Miles Morales': [*fsr_31_dlss_mods], 153 | 'Marvel\'s Spider-Man Remastered': [*fsr_31_dlss_mods], 154 | 'Marvel\'s Spider-Man 2': [*fsr_31_dlss_mods], 155 | 'Marvel\'s Midnight Suns' : [*fsr_31_dlss_mods], 156 | 'Metro Exodus Enhanced Edition': [*fsr_31_dlss_mods], 157 | 'Microsoft Flight Simulator 2024': ['FSR 3.1 Custom MSFS', *fsr_31_dlss_mods], 158 | 'Monster Hunter Wilds' : [*fsr_31_dlss_mods, 'DLSSG Wilds (Only RTX)'], 159 | 'Mortal Shell': [*fsr_31_dlss_mods], 160 | 'Pacific Drive' : [*fsr_31_dlss_mods], 161 | 'Palworld': [*fsr_31_dlss_mods,'Palworld Build03'], 162 | 'Path of Exile II': [*fsr_31_dlss_mods], 163 | 'Red Dead Redemption' : [*fsr_31_dlss_mods], 164 | 'Red Dead Redemption 2': ['FSR4/DLSS FG (Only Optiscaler RDR2)', 'RDR2 Mix', 'RDR2 FG Custom', *fsr_31_dlss_mods], 165 | 'Resident Evil 4 Remake': ['FSR4/DLSS RE4'], 166 | 'Returnal': [*fsr_31_dlss_mods], 167 | 'Saints Row': [*fsr_31_dlss_mods], 168 | 'Satisfactory': [*fsr_31_dlss_mods], 169 | 'Soulstice': [*fsr_31_dlss_mods], 170 | 'South Of Midnight' : [*fsr_31_dlss_mods], 171 | 'Shadow of the Tomb Raider' : [*fsr_31_dlss_mods], 172 | 'Sifu': [*fsr_31_dlss_mods], 173 | 'Silent Hill 2': [*fsr_31_dlss_mods, 'DLSS FG RTX'], 174 | 'Six Days in Fallujah' : [*fsr_31_dlss_mods], 175 | 'STAR WARS Jedi: Survivor': [*fsr_31_dlss_mods], 176 | 'Star Wars Outlaws': ['Outlaws DLSS RTX', *fsr_31_dlss_mods], 177 | 'S.T.A.L.K.E.R. 2': [*fsr_31_dlss_mods, 'DLSS FG (Only RTX)'], 178 | 'Steelrising' : [*fsr_31_dlss_mods], 179 | 'Suicide Squad: Kill the Justice League' : [*fsr_31_dlss_mods], 180 | 'Tainted Grail Fall of Avalon': [*fsr_31_dlss_mods], 181 | 'TEKKEN 8': ['Unlock Fps Tekken 8'], 182 | 'The Alters': [*fsr_31_dlss_mods], 183 | 'The Callisto Protocol': ['FSR4/DLSS FG (Only Optiscaler)'], 184 | 'The Casting Of Frank Stone': [*fsr_31_dlss_mods], 185 | 'The First Berserker: Khazan' : [*fsr_31_dlss_mods], 186 | 'The Last of Us Part I': [*fsr_31_dlss_mods], 187 | 'The Last of Us Part II': [*fsr_31_dlss_mods], 188 | 'The Outlast Trials':[*fsr_31_dlss_mods], 189 | 'The Witcher 3': [*fsr_31_dlss_mods], 190 | 'Until Dawn': [*fsr_31_dlss_mods], 191 | 'Warhammer: Space Marine 2': [*fsr_31_dlss_mods], 192 | 'Watch Dogs Legion': [*fsr_31_dlss_mods], 193 | 'Way Of The Hunter' : [*fsr_31_dlss_mods], 194 | 'WILD HEARTS' : [*fsr_31_dlss_mods ], 195 | } -------------------------------------------------------------------------------- /helpers.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import json 4 | from tkinter import messagebox 5 | import subprocess 6 | import psutil 7 | import tkinter as tk 8 | import os, json, shutil 9 | import sys 10 | 11 | # TOOLTIP 12 | def show_tip(obj, text=None): 13 | widget = getattr(obj, "widget", obj) 14 | 15 | if text is None: 16 | text = getattr(widget, "_tooltip_text", "") 17 | 18 | if not text: 19 | return 20 | 21 | if hasattr(widget, "_tipwindow") and widget._tipwindow: 22 | try: 23 | widget._tipwindow.destroy() 24 | except Exception: 25 | pass 26 | widget._tipwindow = None 27 | 28 | x = widget.winfo_rootx() 29 | y = widget.winfo_rooty() + widget.winfo_height() 30 | widget._tipwindow = tw = tk.Toplevel(widget) 31 | tw.wm_overrideredirect(True) 32 | tw.wm_geometry(f"+{x}+{y}") 33 | label = tk.Label( 34 | tw, text=text, justify=tk.LEFT, font=("Segoe UI", 10), 35 | background="#FFFFFF", relief=tk.SOLID, borderwidth=0 36 | ) 37 | label.pack(ipadx=3, ipady=1) 38 | 39 | 40 | def hide_tip(obj): 41 | widget = getattr(obj, "widget", obj) 42 | 43 | if hasattr(widget, "_tipwindow") and widget._tipwindow: 44 | try: 45 | widget._tipwindow.destroy() 46 | except Exception: 47 | pass 48 | widget._tipwindow = None 49 | 50 | def bind_tooltip(widget, text, limit=33): 51 | ul= getattr(widget, "widget", widget) 52 | try: 53 | ul._tooltip_text = text 54 | except Exception: 55 | setattr(widget, "_tooltip_text", text) 56 | 57 | hide_tip(widget) 58 | 59 | if len(text) > limit: 60 | widget.bind("", lambda e, w=widget: show_tip(w)) 61 | widget.bind("", lambda e, w=widget: hide_tip(w)) 62 | else: 63 | widget.unbind("") 64 | widget.unbind("") 65 | hide_tip(widget) 66 | 67 | def run_dis_anti_c(dest_path): 68 | var_anti_c = messagebox.askyesno('Disable Anti Cheat','Do you want to disable the anticheat? (only for Steam users)') 69 | 70 | del_anti_c_path = os.path.join(dest_path,'toggle_anti_cheat.exe') 71 | if var_anti_c: 72 | subprocess.call(del_anti_c_path) 73 | 74 | def copy_with_progress(src, dst, progress_callback=None, safe_mode=False): 75 | 76 | if safe_mode and not os.path.exists(dst): 77 | print(f"safe_mode active, destination does not exist: {dst}") 78 | return 79 | 80 | if isinstance(src, (list, tuple)): 81 | for item in src: 82 | copy_with_progress(item, dst, progress_callback, safe_mode) 83 | return 84 | 85 | # 1: file -> file 86 | if os.path.isfile(src) and (dst.lower().endswith(".dll") or os.path.splitext(dst)[1] != ""): 87 | if not safe_mode: 88 | os.makedirs(os.path.dirname(dst), exist_ok=True) 89 | shutil.copy2(src, dst) 90 | if progress_callback: 91 | progress_callback() 92 | return 93 | 94 | # 2: file -> folder 95 | if os.path.isfile(src): 96 | if not os.path.exists(dst): 97 | if safe_mode: 98 | print(f"[SKIP] Pasta destino não existe: {dst}") 99 | return 100 | os.makedirs(dst, exist_ok=True) 101 | shutil.copy2(src, os.path.join(dst, os.path.basename(src))) 102 | if progress_callback: 103 | progress_callback() 104 | return 105 | 106 | # 3: folder -> folder 107 | if os.path.isdir(src): 108 | for root, dirs, files in os.walk(src): 109 | for d in dirs: 110 | dest_dir = os.path.join(dst, os.path.relpath(os.path.join(root, d), src)) 111 | if not safe_mode: 112 | os.makedirs(dest_dir, exist_ok=True) 113 | 114 | for f in files: 115 | src_file = os.path.join(root, f) 116 | dest_file = os.path.join(dst, os.path.relpath(src_file, src)) 117 | if safe_mode and not os.path.isdir(os.path.dirname(dest_file)): 118 | continue 119 | os.makedirs(os.path.dirname(dest_file), exist_ok=True) 120 | shutil.copy2(src_file, dest_file) 121 | if progress_callback: 122 | progress_callback() 123 | 124 | def handle_prompt(window_title, window_message,action_func=None): 125 | user_choice = messagebox.askyesno(window_title, window_message) 126 | 127 | if user_choice and action_func: 128 | action_func(user_choice) 129 | 130 | return user_choice 131 | 132 | def runReg(path_reg): 133 | reg_path = ['regedit.exe', '/s', path_reg] 134 | 135 | subprocess.run(reg_path,check=True) 136 | 137 | def dlss_to_fsr(dest_path, progress_callback): 138 | path_dlss_to_fsr = 'mods\\DLSS_TO_FSR' 139 | dlss_to_fsr_reg = "mods\\Temp\\NvidiaChecks\\DisableNvidiaSignatureChecks.reg" 140 | 141 | copy_with_progress(path_dlss_to_fsr, dest_path, progress_callback) 142 | 143 | runReg(dlss_to_fsr_reg) 144 | 145 | def global_dlss(dest_path, progress_callback = None): 146 | path_dlss_rtx = 'mods\\DLSS_Global\\RTX' 147 | path_dlss_rtx_reshade = 'mods\\DLSS_Global\\RTX_Reshade' 148 | path_dlss_amd = 'mods\\DLSS_Global\\AMD' 149 | path_dlss_amd_reshade = 'mods\\DLSS_Global\\AMD_Reshade' 150 | dlss_global_reg = "mods\\Temp\\NvidiaChecks\\DisableNvidiaSignatureChecks.reg" 151 | 152 | if os.path.exists(os.path.join(dest_path, 'reshade-shaders')): 153 | var_gpu_copy(dest_path, path_dlss_amd_reshade, path_dlss_rtx_reshade, progress_callback) 154 | else: 155 | var_gpu_copy(dest_path, path_dlss_amd, path_dlss_rtx, progress_callback) 156 | 157 | runReg(dlss_global_reg) 158 | 159 | def search_un(): # Search for all available storage drives 160 | local_disk_names = [] 161 | 162 | for partition in psutil.disk_partitions(all=False): 163 | if 'cdrom' not in partition.opts and partition.fstype != '': 164 | local_disk_names.append(partition.device) 165 | 166 | return local_disk_names 167 | 168 | def get_active_gpu(): 169 | try: 170 | # Use PowerShell to identify GPUs in Windows with details about the driver and memory 171 | result = subprocess.check_output( 172 | ['powershell', '-Command', 173 | 'Get-CimInstance -ClassName Win32_VideoController | Select-Object Caption, DriverDate, DriverVersion, AdapterRAM'], 174 | stderr=subprocess.STDOUT, 175 | creationflags=subprocess.CREATE_NO_WINDOW 176 | ) 177 | 178 | gpu_info = result.decode('utf-8').strip().split("\n") 179 | 180 | # Filter out empty results and split GPU data 181 | user_gpus = [gpu.strip() for gpu in gpu_info[2:] if gpu.strip()] # Ignore header lines 182 | 183 | if not user_gpus: 184 | return None 185 | 186 | # If there's only one GPU, it's automatically the primary one 187 | if len(user_gpus) == 1: 188 | return user_gpus[0].lower() # Return the GPU name in lowercase 189 | 190 | # If there are more than one GPU, try to identify which one is currently in use 191 | utilization_result = subprocess.check_output( 192 | ['powershell', '-Command', 193 | 'Get-CimInstance -ClassName Win32_PerfFormattedData_Counters_GPUUsage | Select-Object GPUTime'], 194 | stderr=subprocess.STDOUT, 195 | creationflags=subprocess.CREATE_NO_WINDOW 196 | ) 197 | utilization_info = utilization_result.decode('utf-8').strip().split("\n") 198 | 199 | # Filter the usage data (considering the first line is a header) 200 | gpu_usages = [int(usage.strip()) for usage in utilization_info[2:] if usage.strip()] 201 | 202 | # If there are more than one GPU, select the one with the highest usage 203 | if len(user_gpus) == len(gpu_usages): 204 | primary_gpu = user_gpus[gpu_usages.index(max(gpu_usages))] # GPU with the highest usage will be considered the primary one 205 | 206 | return primary_gpu.lower() # Return the primary GPU 207 | 208 | except subprocess.CalledProcessError: 209 | return None 210 | 211 | def var_gpu_copy(dest_path, path_amd, path_rtx, progress_callback=None): 212 | gpu_name = get_active_gpu() 213 | 214 | print(gpu_name) 215 | 216 | if 'nvidia' in gpu_name: 217 | copy_with_progress(path_rtx, dest_path, progress_callback) 218 | elif 'amd' in gpu_name or 'intel' in gpu_name: 219 | copy_with_progress(path_amd, dest_path, progress_callback) 220 | elif gpu_name is None: 221 | src = path_rtx if messagebox.askyesno('GPU', 'Do you have an Nvidia GPU?') else path_amd 222 | copy_with_progress(src, dest_path, progress_callback) 223 | 224 | def load_or_create_json(filename, default_data): 225 | 226 | if getattr(sys, "frozen", False): 227 | exe_dir = os.path.dirname(sys.executable) 228 | else: 229 | exe_dir = os.getcwd() 230 | 231 | exe_file = os.path.join(exe_dir, "J", filename) 232 | print("J/Json file:", exe_file) 233 | 234 | # app data 235 | appdir = os.path.join(os.getenv("LOCALAPPDATA"), "FSR-Mod-Utility") 236 | os.makedirs(appdir, exist_ok=True) 237 | appdata_file = os.path.join(appdir, filename) 238 | 239 | def hide_only(path): 240 | """ J/Json hidden. """ 241 | if os.path.exists(path) and os.name == "nt": 242 | subprocess.call(["attrib", "+h", path], 243 | stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 244 | 245 | def hide_and_protect_appdata(path): 246 | """ Hidden + Read only appdata json """ 247 | if os.path.exists(path) and os.name == "nt": 248 | subprocess.call(["attrib", "+h", "+r", path], 249 | stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 250 | 251 | def make_writable(path): 252 | """ disable read only """ 253 | if os.path.exists(path) and os.name == "nt": 254 | subprocess.call(["attrib", "-h", "-r", path], 255 | stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 256 | 257 | # J/Json load 258 | exe_json_valid = False 259 | exe_json_data = None 260 | 261 | if os.path.exists(exe_file) and os.path.getsize(exe_file) > 2: 262 | try: 263 | with open(exe_file, "r", encoding="utf-8") as f: 264 | exe_json_data = json.load(f) 265 | exe_json_valid = True 266 | hide_only(exe_file) # J/Json hidden 267 | except: 268 | exe_json_valid = False 269 | 270 | if not os.path.exists(appdata_file): 271 | 272 | if exe_json_valid: 273 | shutil.copy2(exe_file, appdata_file) 274 | hide_and_protect_appdata(appdata_file) 275 | return exe_json_data 276 | 277 | with open(appdata_file, "w", encoding="utf-8") as f: 278 | json.dump(default_data, f, indent=4) 279 | hide_and_protect_appdata(appdata_file) 280 | return default_data 281 | 282 | try: 283 | with open(appdata_file, "r", encoding="utf-8") as f: 284 | data = json.load(f) 285 | hide_and_protect_appdata(appdata_file) 286 | return data 287 | 288 | except: 289 | make_writable(appdata_file) 290 | 291 | if exe_json_valid: 292 | shutil.copy2(exe_file, appdata_file) 293 | hide_and_protect_appdata(appdata_file) 294 | return exe_json_data 295 | 296 | with open(appdata_file, "w", encoding="utf-8") as f: 297 | json.dump(default_data, f, indent=4) 298 | hide_and_protect_appdata(appdata_file) 299 | return default_data 300 | 301 | def config_json(path_json, values_json,ini_message=None): 302 | var_config_json = False 303 | 304 | if os.path.exists(path_json): 305 | try: 306 | while not var_config_json: 307 | 308 | if os.path.exists(path_json): 309 | var_config_json = True 310 | else: 311 | var_config_json = False 312 | 313 | if var_config_json: 314 | 315 | with open(path_json, 'r', encoding='utf8') as file: 316 | data = json.load(file) 317 | 318 | for key, new_value in values_json.items(): 319 | if key in data: 320 | data[key] = new_value 321 | 322 | with open(path_json, 'w', encoding='utf8') as file: 323 | json.dump(data, file, indent=4) 324 | 325 | messagebox.showinfo('Sucess',ini_message) 326 | except Exception as e: 327 | messagebox.showinfo("Error","An error occurred in the Utility. Try closing and reopening it") 328 | else: 329 | messagebox.showinfo('Not Found', 'File not found, the installation could not be completed') 330 | 331 | def Remove_ini_effect(game_selected, key_ini, value_ini, path_ini, message_path_not_found, message_hb2 = None): 332 | 333 | if not os.path.exists(path_ini): 334 | messagebox.showinfo('Path Not Found', message_path_not_found) 335 | return 336 | 337 | if game_selected == 'Hellblade 2': 338 | dest_path = os.path.dirname(path_ini) 339 | 340 | with open(path_ini, 'r') as configfile: 341 | lines = configfile.readlines() 342 | 343 | section_exists = False 344 | section_start = None 345 | section_end = None 346 | 347 | for idx, line in enumerate(lines): 348 | if line.strip() == f"[{key_ini}]": 349 | section_exists = True 350 | section_start = idx 351 | elif line.startswith('[') and section_start is not None: 352 | section_end = idx 353 | break 354 | 355 | if section_exists: 356 | updated_section = lines[section_start:(section_end if section_end is not None else len(lines))] 357 | 358 | for key, new_value in value_ini.items(): 359 | for idx, line in enumerate(updated_section): 360 | if line.startswith(f"{key}="): 361 | updated_section[idx] = f"{key}={new_value}\n" 362 | break 363 | else: 364 | updated_section.append(f"{key}={new_value}\n") 365 | 366 | lines[section_start:(section_end if section_end is not None else len(lines))] = updated_section 367 | else: 368 | lines.append("\n") 369 | lines.append(f"\n[{key_ini}]\n") 370 | lines.extend(f"{key}={value}\n" for key, value in value_ini.items()) 371 | 372 | with open(path_ini, 'w') as configfile: 373 | configfile.writelines(lines) 374 | 375 | if message_hb2 != None: 376 | messagebox.showinfo('Success', message_hb2) 377 | 378 | def remove_post_processing_effects_hell2(dest_path): 379 | path_inihb2 = os.getenv("LOCALAPPDATA") + '\\Hellblade2\\Saved\\Config\\Windows\\Engine.ini' 380 | alt_path_hb2 = os.getenv("LOCALAPPDATA") + '\\Hellblade2\\Saved\\Config\\WinGDK\\Engine.ini' 381 | 382 | value_remove_black_bars = {'r.NT.EnableConstrainAspectRatio' :'0'} 383 | value_remove_black_bars_alt = { 384 | 'r.NT.AllowAspectRatioHorizontalExtension': '0', 385 | 'r.NT.EnableConstrainAspectRatio': '0' 386 | } 387 | 388 | value_remove_all_pos_processing = { 389 | 'r.DefaultFeature.MotionBlur':'0', 390 | 'r.MotionBlurQuality':'0', 391 | 'r.NT.Lens.ChromaticAberration.Intensity':'0', 392 | 'r.Tonemapper.GrainQuantization':'0', 393 | 'r.DepthOfFieldQuality':'0', 394 | 'r.FilmGrain':'0', 395 | 'r.NT.DOF.NTBokehTransform':'0', 396 | 'r.NT.Lens.Distortion.Stretch':'0', 397 | 'r.NT.Lens.Distortion.Intensity':'0', 398 | 'r.SceneColorFringeQuality':'0', 399 | 'r.NT.DOF.RotationalBokeh':'0', 400 | 'r.NT.AllowAspectRatioHorizontalExtension':'0', 401 | 'r.Tonemapper.Quality':'0', 402 | 'r.NT.EnableConstrainAspectRatio':'0' 403 | } 404 | 405 | key_remove_post_processing = 'SystemSettings' 406 | message_path_not_found_hb2 = 'Path not found, please select manually. The path to the Engine.ini file is something like this: C:\\Users\\YourName\\AppData\\Local\\Hellblade2\\Saved\\Config\\Windows or WinGDK. If you need further instructions, refer to the Hellblade 2 FSR Guide' 407 | path_final = "" 408 | 409 | if os.path.exists(os.path.join(path_inihb2)): 410 | path_final = path_inihb2 411 | 412 | elif os.path.exists(os.path.join(alt_path_hb2)): 413 | path_final = alt_path_hb2 414 | 415 | elif dest_path is None: 416 | messagebox.showinfo('Path Not Found','Path not found, please select manually. The path to the Engine.ini file is something like this: C:\\Users\\YourName\\AppData\\Local\\Hellblade2\\Saved\\Config\\Windows or WinGDK and then select the option again. If you need further instructions, refer to the Hellblade 2 FSR Guide') 417 | return 418 | else: 419 | manually_folder_ini = os.path.join(dest_path,'Engine.ini') 420 | if os.path.exists(manually_folder_ini): 421 | path_final = manually_folder_ini 422 | 423 | if path_final != "": 424 | 425 | # Remove Black Bars 426 | handle_prompt( 427 | 'Remove Black Bars', 428 | 'Do you want to remove the Black Bars?', 429 | lambda _: ( 430 | Remove_ini_effect("Hellblade 2",key_remove_post_processing,value_remove_black_bars,path_final,message_path_not_found_hb2) 431 | ) 432 | ) 433 | 434 | # Remove Black Bars Alt 435 | handle_prompt( 436 | 'Remove Black Bars Alt', 437 | 'Do you want to remove the Black Bars? Select this option if the previous option did not work, the removal of the black bars will be\nautomatically performed if the Engine.ini file is found. If it is not found, you need to select the path\nin \'Select Folder\' and press \'Install\'.\n\n', 438 | lambda _: ( 439 | Remove_ini_effect("Hellblade 2",key_remove_post_processing,value_remove_black_bars_alt,path_final,message_path_not_found_hb2) 440 | ) 441 | ) 442 | 443 | # Remove Post Processing Effects 444 | handle_prompt( 445 | 'Remove Post Processing Effects', 446 | 'Do you want to remove the main post-processing effects? (Lens Distortion, Black Bars, and Chromatic Aberration will be removed). If you want to remove all post-processing effects, select "No" and then select "Yes" in the next window.', 447 | lambda _: ( 448 | Remove_ini_effect("Hellblade 2",key_remove_post_processing,value_remove_black_bars_alt,path_final,message_path_not_found_hb2) 449 | ) 450 | ) 451 | 452 | # Remove All Post Processing Effects 453 | handle_prompt( 454 | 'Remove All Post Processing Effects', 455 | 'Do you want to remove all post-processing effects?', 456 | lambda _: ( 457 | Remove_ini_effect("Hellblade 2",key_remove_post_processing,value_remove_all_pos_processing,path_final,message_path_not_found_hb2) 458 | ) 459 | ) 460 | 461 | handle_prompt( 462 | 'Restore Post Processing', 463 | 'Do you want to revert to the post-processing options? (Black bars, film grain, etc.)', 464 | lambda _: ( 465 | path_replace_ini := 'mods\\FSR3_HB2\\Replace_ini\\Engine.ini', 466 | 467 | os.path.exists(os.path.join(path_inihb2)) and shutil.copy2(path_replace_ini, path_inihb2) or 468 | os.path.exists(os.path.join(alt_path_hb2)) and shutil.copy2(path_replace_ini, alt_path_hb2) or 469 | dest_path is None or ( 470 | replace_ini_path := os.path.join(dest_path, 'Engine.ini'), 471 | os.path.exists(replace_ini_path) and shutil.copy2(path_replace_ini, os.path.dirname(replace_ini_path)) 472 | ) 473 | ) 474 | ) 475 | else: 476 | messagebox.showinfo('Not Found', 'Engine.ini not found, please check if it exists. The path is something like C:\\Users\\YourName\\AppData\\Local\\Hellblade2\\Saved\\Config\\Windows or WinGDK. If it\'s not there, open the game to have the file created.') -------------------------------------------------------------------------------- /cleanup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | from tkinter import messagebox 4 | from helpers import runReg, handle_prompt, get_active_gpu 5 | 6 | del_uni = ['winmm.ini','winmm.dll','uniscaler.config.toml','Uniscaler.asi','nvngx.dll'] 7 | 8 | del_elden_fsr3 =['_steam_appid.txt','_winhttp.dll','anti_cheat_toggler_config.ini','anti_cheat_toggler_mod_list.txt', 9 | 'start_game_in_offline_mode.exe','toggle_anti_cheat.exe','ReShade.ini','EldenRingUpscalerPreset.ini', 10 | 'dxgi.dll','d3dcompiler_47.dll'] 11 | 12 | del_elden_fsr3_v3 = ['ERSS2.dll','dxgi.dll','ERSS-FG.dll'] 13 | 14 | del_elden_nightreign = ['dinput8.dll','mod_loader.ini','d3d12.dll','NRSS.dll','steam_appid.txt'] 15 | 16 | del_bdg_fsr3 = ['nvngx.dll','version.dll','version.org'] 17 | 18 | del_sh2_dlss = ['dxgi.dll','ReShade.ini','SH2UpscalerPreset.ini'] 19 | 20 | del_fl4_fsr3 = ['f4se_whatsnew.txt','f4se_steam_loader.dll','f4se_readme.txt','f4se_loader.exe','f4se_1_10_163.dll', 21 | 'CustomControlMap.txt'] 22 | 23 | del_fh_fsr3 = ['DisableNvidiaSignatureChecks.reg','dlssg_to_fsr3_amd_is_better.dll','nvngx.dll','RestoreNvidiaSignatureChecks.reg', 24 | 'dinput8.dll','dlssg_to_fsr3.asi','nvapi64.asi','winmm.dll','winmm.ini'] 25 | 26 | del_rdr2_fsr3 = ['ReShade.ini','RDR2UpscalerPreset.ini','d3dcompiler_47.dll','d3d12.dll','dinput8.dll','ScriptHookRDR2.dll','NVNGX_Loader.asi', 27 | 'd3dcompiler_47.dll','nvngx.dll','winmm.ini','winmm.dll','fsr2fsr3.config.toml','FSR2FSR3.asi','fsr2fsr3.log'] 28 | 29 | del_icarus_otgpu_fsr3 = ['nvngx.dll', 'FSR2FSR3.asi','fsr2fsr3.config.toml','winmm.dll','winmm.ini'] 30 | del_icarus_rtx_fsr3 = ['RestoreNvidiaSignatureChecks.reg','dlssg_to_fsr3_amd_is_better.dll','DisableNvidiaSignatureChecks.reg'] 31 | 32 | del_tekken_fsr3 = ['TekkenOverlay.exe','Tekken8OverlaySettings.yaml','Tekken8Overlay.dll','Tekken7Overlay.dll'] 33 | 34 | del_gtav_fsr3 = ['GTAVUpscaler.org.asi','GTAVUpscaler.asi','d3d12.dll','dxgi.asi','GTAVUpscaler.dll','GTAVUpscaler.org.dll','dinput8.dll'] 35 | 36 | del_lotf_fsr3 = ['version.dll','RestoreNvidiaSignatureChecks.reg','nvngx.dll','launch.bat','dlssg_to_fsr3_amd_is_better.dll','DisableNvidiaSignatureChecks.reg', 37 | 'Uniscaler.asi','DisableEasyAntiCheat.bat','winmm.dll','winmm.ini'] 38 | del_cb2077 = ['nvngx.dll','RestoreNvidiaSignatureChecks.reg','DisableNvidiaSignatureChecks.reg','dlssg_to_fsr3_amd_is_better.dll'] 39 | 40 | del_got = ['version.dll','RestoreNvidiaSignatureChecks.reg','dxgi.dll','dlssg_to_fsr3_amd_is_better.dll','DisableNvidiaSignatureChecks.reg','d3d12core.dll','d3d12.dll','no-filmgrain.reg'] 41 | 42 | del_valhalla_fsr3 = ['ReShade.ini','dxgi.dll','ACVUpscalerPreset.ini'] 43 | 44 | del_hb2 = ['version.dll','RestoreNvidiaSignatureChecks.reg','DisableNvidiaSignatureChecks.reg','dlssg_to_fsr3_amd_is_better.dll'] 45 | 46 | del_aw2_rtx = ['nvngx.dll','RestoreNvidiaSignatureChecks.reg','DisableNvidiaSignatureChecks.reg','dlssg_to_fsr3_amd_is_better.dll','amd_fidelityfx_vk.dll','amd_fidelityfx_dx12.dll'] 47 | 48 | del_dd2 = ['dinput8.dll','dlssg_to_fsr3_amd_is_better.dll', 'version.dll'] 49 | 50 | del_drr_dlss_to_fg_custom = ['dlssg_to_fsr3_amd_is_better.dll', 'version.dll'] 51 | 52 | del_optiscaler = ['OptiScaler.ini','nvngx.dll','libxess.dll','winmm.dll', 'nvapi64.dll','fakenvapi.ini', 'fakenvapi.dll','dlssg_to_fsr3_amd_is_better.dll','nvngx.ini'] 53 | 54 | del_dlss_rtx = [ 55 | 'dlss-enabler-upscaler.dll', 'dlss-enabler.dll', 'dlss-enabler.log', 'dlssg_to_fsr3.log', 56 | 'dlssg_to_fsr3_amd_is_better-3.0.dll', 'dlssg_to_fsr3_amd_is_better.dll', 'dxgi.dll', 'fakenvapi.log', 'nvngx-wrapper.dll', 57 | 'nvngx.ini', 'unins000.dat','nvapi64-proxy.dll','winmm.dll' 58 | ] 59 | 60 | del_dlss_amd = [ 61 | 'nvapi64-proxy.dll','dlss-enabler-upscaler.dll', 'dlss-enabler.dll', 'dlss-enabler.log', 'dlssg_to_fsr3.log', 62 | 'dlssg_to_fsr3_amd_is_better-3.0.dll', 'dlssg_to_fsr3_amd_is_better.dll', 'dxgi.dll', 'fakenvapi.ini', 'fakenvapi.log', 63 | 'nvapi64.dll', 'nvngx-wrapper.dll', 'nvngx.dll', 'nvngx.ini', 'unins000.dat', 'fakenvapi.ini', 'nvapi64.dll', 'winmm.dll' 64 | ] 65 | 66 | del_dlss_to_fg = ['dlssg_to_fsr3_amd_is_better.dll','version.dll'] 67 | 68 | def get_cleanup_registry(game_selected, mod_selected, dest_path): 69 | return { 70 | ("", ""): { 71 | "condition": lambda: mod_selected in [ 72 | 'FSR4/DLSS FG (Only Optiscaler)', 73 | 'FSR4/DLSSG FG (Only Optiscaler)', 74 | 'FSR4/DLSS Gow4', 75 | 'FSR4/DLSS Nightreign RTX' 76 | ], 77 | "actions": [ 78 | {"type": "run_del_optiscaler", "def": True, "base_path": dest_path, "mod": mod_selected}, 79 | {"type": "reg", "path": "mods\\Addons_mods\\DLSS Preset Overlay\\Disable Overlay.reg", 80 | "remove_reg": os.path.join(dest_path,"Enable Overlay.reg"), "base_path": dest_path,"show_message": True} 81 | ] 82 | }, 83 | 84 | ("Elden Ring", ""): { 85 | "condition": lambda: game_selected == "Elden Ring", 86 | "actions": [ 87 | *( 88 | [ 89 | {"type": "remove_list", "files": del_elden_fsr3, "base_path": dest_path}, 90 | {"type": "delete_folder", "path": os.path.join(dest_path, "mods")}, 91 | {"type": "delete_folder", "path": os.path.join(dest_path, "reshade-shaders")} 92 | ] if mod_selected not in ["FSR4/DLSS FG Custom Elden", "Unlock FPS Elden"] else [] 93 | ), 94 | *( 95 | [{"type": "remove_list", "files": del_elden_fsr3_v3, "base_path": dest_path}] 96 | if mod_selected == "FSR4/DLSS FG Custom Elden" else [] 97 | ), 98 | *( 99 | [ 100 | {"type": "delete_folder", "path": os.path.join(dest_path, "mods\\UnlockTheFps")}, 101 | {"type": "delete_file", "file": os.path.join(dest_path, "mods\\UnlockTheFps.dll")}, 102 | {"type": "delete_file", "file": os.path.join(dest_path, "UnlockFps.txt")} 103 | ] if mod_selected == "Unlock FPS Elden" else [] 104 | ) 105 | ] 106 | }, 107 | 108 | ("Elden Ring Nightreign", ""): { 109 | "condition": lambda: game_selected == "Elden Ring Nightreign", 110 | "actions": [ 111 | {"type": "remove_list", "files": del_elden_nightreign, "base_path": dest_path}, 112 | {"type": "delete_folder", "path": os.path.join(dest_path, "NRSS")}, 113 | {"type": "delete_file", "file": os.path.join(dest_path, 'mods\\RemoveChromaticAberration.dll')}, 114 | {"type": "delete_file", "file": os.path.join(dest_path, 'mods\\RemoveVignette.dll')}, 115 | {"type": "run_del_optiscaler", "def": True} 116 | ] 117 | }, 118 | 119 | ("Dragons Dogma 2", ""): { 120 | "condition": lambda: game_selected == "Dragons Dogma 2", 121 | "actions": [ 122 | {"type": "remove_list", "files": del_dd2, "base_path": dest_path} 123 | ] 124 | }, 125 | 126 | ("Baldur's Gate 3", ""): { 127 | "condition": lambda: game_selected == "Baldur's Gate 3", 128 | "actions": [ 129 | {"type": "remove_list", "files": del_bdg_fsr3, "base_path": dest_path}, 130 | {"type": "delete_folder", "path": os.path.join(dest_path,'mods')} 131 | ] 132 | }, 133 | 134 | ("Cyberpunk 2077", "RTX DLSS FG"): { 135 | "condition": lambda: game_selected == "Cyberpunk 2077" and mod_selected == "RTX DLSS FG", 136 | "actions": [ 137 | {"type": "remove_list", "files": del_cb2077, "base_path": dest_path}, 138 | {"type": "reg", "path": "mods\\FSR3_CYBER2077\\dlssg-to-fsr3-0.90_universal\\RestoreNvidiaSignatureChecks.reg"} 139 | ] 140 | }, 141 | 142 | ("Red Dead Redemption 2", ""): { 143 | "condition": lambda: game_selected == "Red Dead Redemption 2", 144 | "actions": [ 145 | *( 146 | [ 147 | {"type": "remove_list", "files": del_optiscaler, "base_path": dest_path}, 148 | {"type": "remove_file", "file": os.path.join(dest_path, "winmm.dll")} 149 | ] if mod_selected == "FSR4/DLSS FG (Only Optiscaler RDR2)" else [] 150 | ), 151 | *( 152 | [ 153 | {"type": "remove_list", "files": del_rdr2_fsr3, "base_path": dest_path}, 154 | {"type": "delete_folder", "path": os.path.join(dest_path, "mods")}, 155 | {"type": "delete_folder", "path": os.path.join(dest_path,'reshade-shaders')} 156 | ] if mod_selected in ["RDR2 FG Custom", "RDR2 Mix"] else [] 157 | ) 158 | ] 159 | }, 160 | 161 | ("Palworld", ""): { 162 | "condition": lambda: game_selected == "Palworld", 163 | "actions": [ 164 | {"type": "remove_list", "files": del_rdr2_fsr3, "base_path": dest_path}, 165 | {"type": "delete_folder", "path": os.path.join(dest_path, "mods")}, 166 | {"type": "delete_folder", "path": os.path.join(dest_path,'reshade-shaders')}, 167 | {"type": "delete_file", "file": os.path.join(dest_path,'PalworldUpscalerPreset.ini')}, 168 | {"type": "copy_file", "file": os.path.abspath(os.path.join(os.getenv("LOCALAPPDATA"), r"Pal\\Saved\\Config\\WinGDK\\..\\Engine.ini")), 169 | "dest": os.path.join(os.getenv("LOCALAPPDATA"), r"Pal\\Saved\\Config\\WinGDK")} 170 | ] 171 | }, 172 | 173 | ("Forza Horizon 5", ""): { 174 | "condition": lambda: game_selected == "Forza Horizon 5", 175 | "actions": [ 176 | {"type": "remove_list", "files": del_fh_fsr3, "base_path": dest_path}, 177 | {"type": "reg", "path": "mods\\FSR3_FH\\RTX\\RestoreNvidiaSignatureChecks.reg"} 178 | ] 179 | }, 180 | 181 | ("Dragon Age: Veilguard", ""): { 182 | "condition": lambda: game_selected == "Dragon Age: Veilguard" and mod_selected == "FSR4/DLSS DG Veil", 183 | "actions": [ 184 | {"type": "del_fsr_dlss_mods", "rtx": del_dlss_rtx, "amd": del_dlss_amd,"base_path": dest_path}, 185 | {"type": "reg", "path": "mods\\Optiscaler FSR 3.1 Custom\\RestoreNvidiaSignatureChecks.reg"} 186 | ] 187 | }, 188 | 189 | ("Like a Dragon: Pirate Yakuza in Hawaii", "DLSSG Yakuza"): { 190 | "condition": lambda: game_selected == "Like a Dragon: Pirate Yakuza in Hawaii" and mod_selected == "DLSSG Yakuza", 191 | "actions": [ 192 | {"type": "del_fsr_dlss_mods", "rtx": del_dlss_rtx, "amd": del_dlss_amd,"base_path": dest_path}, 193 | {"type": "reg", "path": "mods\\Temp\\NvidiaChecks\\RestoreNvidiaSignatureChecks.reg"} 194 | ] 195 | }, 196 | 197 | ("Warhammer: Space Marine 2", "FSR4/DLSS FG Marine"): { 198 | "condition": lambda: game_selected == "Warhammer: Space Marine 2" and mod_selected == "FSR4/DLSS FG Marine", 199 | "actions": [ 200 | {"type": "del_fsr_dlss_mods", "rtx": del_dlss_rtx, "amd": del_dlss_amd,"base_path": dest_path}, 201 | {"type": "reg", "path": "mods\\Temp\\disable signature override\\DisableSignatureOverride.reg"} 202 | ] 203 | }, 204 | 205 | ("Icarus", ""): { 206 | "condition": lambda: game_selected == "Icarus", 207 | "actions": lambda: [ 208 | *( 209 | [ 210 | {"type": "remove_list", "files": del_icarus_otgpu_fsr3, "base_path": dest_path}, 211 | {"type": "reg", "path": "mods\\FSR3_FH\\RTX\\RestoreNvidiaSignatureChecks.reg"} 212 | ] if mod_selected == "Icarus FSR3 AMD/GTX" else [] 213 | ), 214 | *( 215 | [ 216 | {"type": "remove_list", "files": del_icarus_rtx_fsr3} 217 | ] if mod_selected == "Icarus FSR3 RTX" else [] 218 | ) 219 | ] 220 | }, 221 | 222 | ("TEKKEN 8", "Unlock Fps Tekken 8"): { 223 | "condition": lambda: game_selected == "TEKKEN 8" and mod_selected == "Unlock Fps Tekken 8", 224 | "actions": [ 225 | {"type": "remove_list", "files": del_tekken_fsr3, "base_path": dest_path}, 226 | {"type": "delete_folder", "path": os.path.join(dest_path, "assets")} 227 | ] 228 | }, 229 | 230 | ("Indiana Jones and the Great Circle", ""): { 231 | "condition": lambda: game_selected == "Indiana Jones and the Great Circle", 232 | "actions": [ 233 | *( 234 | [ 235 | {"type": "remove_list", "files": del_tekken_fsr3, "base_path": dest_path}, 236 | {"type": "delete_folder", "path": os.path.join(dest_path, "assets")} 237 | ] if mod_selected == "FSR4/DLSS FG (Only Optiscaler Indy)" else [] 238 | ), 239 | *( 240 | [ 241 | {"type": "remove_list", "files": del_dlss_to_fg, "base_path": dest_path}, 242 | {"type": "remove_file", "file": os.path.join(os.environ['USERPROFILE'], 'Saved Games\\MachineGames\\TheGreatCircle\\base\\TheGreatCircleConfig.local')}, 243 | { 244 | "type": "rename", 245 | "old": os.path.join( 246 | os.environ['USERPROFILE'], 247 | 'Saved Games\\MachineGames\\TheGreatCircle\\base\\TheGreatCircleConfig.txt' 248 | ), 249 | "new": os.path.join( 250 | os.environ['USERPROFILE'], 251 | 'Saved Games\\MachineGames\\TheGreatCircle\\base\\TheGreatCircleConfig.local' 252 | ) 253 | } 254 | ] if mod_selected == "Indy FG (Only RTX)" else [] 255 | ) 256 | ] 257 | }, 258 | 259 | ("Monster Hunter Wilds", "DLSSG Wilds (Only RTX)"): { 260 | "condition": lambda: game_selected == "Monster Hunter Wilds" and mod_selected == "DLSSG Wilds (Only RTX)", 261 | "actions": [ 262 | {"type": "remove_list", "files": del_dlss_to_fg, "base_path": dest_path}, 263 | {"type": "remove_file", "file": os.path.join(dest_path, "dinput8.dll")} 264 | ] 265 | }, 266 | 267 | ("Dead Rising Remaster", "FSR 3.1 FG DRR"): { 268 | "condition": lambda: game_selected == "Dead Rising Remaster" and mod_selected == "FSR 3.1 FG DRR", 269 | "actions": [ 270 | {"type": "remove_list", "files": del_drr_dlss_to_fg_custom, "base_path": os.path.join(dest_path, 'reframework\\plugins')}, 271 | {"type": "remove_file", "file": os.path.join(dest_path, "dinput8.dll")} 272 | ] 273 | }, 274 | 275 | ("GTA V", ""): { 276 | "condition": lambda: game_selected == "GTA V" and mod_selected not in ['FSR4/DLSS FG (Only Optiscaler)'], 277 | "actions": [ 278 | {"type": "rename", 279 | "old": os.path.join(dest_path,'dxgi.dll'), 280 | "new": os.path.join(dest_path,'dxgi.asi')}, 281 | {"type": "delete_folder", "file": os.path.join(dest_path, "mods\\Shaders")} 282 | ] 283 | }, 284 | 285 | ("Lords of the Fallen", "DLSS FG LOTF2 (RTX)"): { 286 | "condition": lambda: game_selected == "Lords of the Fallen" and mod_selected == "DLSS FG LOTF2 (RTX)", 287 | "actions": [ 288 | {"type": "remove_list", "files": del_dlss_to_fg, "base_path": dest_path}, 289 | {"type": "remove_file", "file": os.path.join(dest_path, "rungame.bat")} 290 | ] 291 | }, 292 | 293 | ("The Callisto Protocol", ""): { 294 | "condition": lambda: game_selected == "The Callisto Protocol", 295 | "actions": [ 296 | *( 297 | [ 298 | {"type": "remove_list", "files": del_uni, "base_path": dest_path}, 299 | {"type": "delete_folder", "file": os.path.join(dest_path, "uniscale")} 300 | ] if mod_selected == "The Callisto Protocol FSR3" else [] 301 | ), 302 | *( 303 | [ 304 | {"type": "remove_list", "files": del_optiscaler, "base_path": dest_path} 305 | ] if mod_selected == "FSR4/DLSS Custom Callisto" else [] 306 | ) 307 | ] 308 | }, 309 | 310 | ("Resident Evil 4 Remake", ""):{ 311 | "condition": lambda: game_selected == "Resident Evil 4 Remake", 312 | "actions": [ 313 | {"type": "delete_folder", "path": os.path.join(dest_path,'reframework')}, 314 | {"type": "remove_file", "file": os.path.join(dest_path, "dinput8.dll")}, 315 | ] 316 | }, 317 | 318 | ("Silent Hill 2", ""):{ 319 | "condition": lambda: game_selected == "Silent Hill 2" and mod_selected == "DLSS FG RTX", 320 | "actions": [ 321 | {"type": "remove_list", "files": del_sh2_dlss, "base_path": dest_path}, 322 | {"type": "delete_folder", "path": os.path.join(dest_path,'mods')}, 323 | {"type": "delete_folder", "path": os.path.join(dest_path,'reshade-shaders')}, 324 | ] 325 | }, 326 | 327 | ("Suicide Squad: Kill the Justice League", ""):{ 328 | "condition": lambda: game_selected == "Suicide Squad: Kill the Justice League", 329 | "actions": [ 330 | {"type": "remove_copy", "dest":os.path.abspath(os.path.join(dest_path, '..\\..\\..')), 331 | "folder": os.path.abspath(os.path.join(dest_path, '..\\..\\..\\EasyAntiCheat')), 332 | "folder_copy": os.path.abspath(os.path.join(dest_path, '..\\..\\..\\Backup EAC')), 333 | "del_folder": os.path.abspath(os.path.join(dest_path, '..\\..\\..\\Backup EAC'))}, 334 | ] 335 | }, 336 | 337 | ("God Of War 4", ""):{ 338 | "condition": lambda: game_selected == "God Of War 4", 339 | "actions": [ 340 | {"type": "remove_list", "files": del_optiscaler, "base_path": dest_path}, 341 | {"type": "reg", "path": "mods\\Temp\\disable signature override\\DisableSignatureOverride.reg"} 342 | ] 343 | }, 344 | 345 | ("Ghost of Tsushima", "Ghost of Tsushima FG DLSS"):{ 346 | "condition": lambda: game_selected == "Ghost of Tsushima" and mod_selected == "Ghost of Tsushima FG DLSS", 347 | "actions": [ 348 | {"type": "remove_list", "files": del_got, "base_path": dest_path}, 349 | {"type": "reg", "path": "mods\\Temp\\NvidiaChecks\\RestoreNvidiaSignatureChecks.reg"} 350 | ] 351 | }, 352 | 353 | ("Hellblade 2", "Hellblade 2 FSR3 (Only RTX)"):{ 354 | "condition": lambda: game_selected == "Hellblade 2" and mod_selected == "Hellblade 2 FSR3 (Only RTX)", 355 | "actions": [ 356 | {"type": "remove_list", "files": del_hb2, "base_path": dest_path}, 357 | {"type": "reg", "path": "mods\\Temp\\NvidiaChecks\\RestoreNvidiaSignatureChecks.reg"} 358 | ] 359 | }, 360 | 361 | ("Assassin's Creed Valhalla", ""):{ 362 | "condition": lambda: game_selected == "Assassin's Creed Valhalla", 363 | "actions": [ 364 | {"type": "remove_list", "files": del_valhalla_fsr3, "base_path": dest_path}, 365 | {"type": "delete_folder", "path": os.path.join(dest_path,'reshade-shaders')}, 366 | {"type": "delete_folder", "path": os.path.join(dest_path,'mods')}, 367 | ] 368 | }, 369 | 370 | ("Alan Wake 2", "Alan Wake 2 FG RTX"):{ 371 | "condition": lambda: game_selected == "Alan Wake 2" and mod_selected == "Alan Wake 2 FG RTX", 372 | "actions": [ 373 | {"type": "remove_list", "files": del_aw2_rtx, "base_path": dest_path}, 374 | ] 375 | }, 376 | 377 | ("Black Myth: Wukong", "DLSS FG (ALL GPUs) Wukong"):{ 378 | "condition": lambda: game_selected == "Black Myth: Wukong" and mod_selected == "DLSS FG (ALL GPUs) Wukong", 379 | "actions": [ 380 | {"type": "remove_list", "files": del_dlss_to_fg, "base_path": dest_path}, 381 | {"type": "reg", "path": "mods\\Addons_mods\\OptiScaler\\EnableSignatureOverride.reg"} 382 | ] 383 | }, 384 | 385 | ("Final Fantasy XVI", "FFXVI DLSS RTX"):{ 386 | "condition": lambda: game_selected == "Final Fantasy XVI" and mod_selected == "FFXVI DLSS RTX", 387 | "actions": [ 388 | {"type": "remove_list", "files": del_dlss_to_fg, "base_path": dest_path}, 389 | {"type": "del_fsr_dlss_mods", "rtx": del_dlss_rtx, "amd": del_dlss_amd,"base_path": dest_path}, 390 | ] 391 | } 392 | } 393 | 394 | def count_cleanup_items(dest_path, game_selected, mod_selected=None): 395 | registry = get_cleanup_registry(game_selected, mod_selected, dest_path) 396 | total = 0 397 | for (game, mod), data in registry.items(): 398 | try: 399 | if not data["condition"](): 400 | continue 401 | actions = data["actions"] 402 | if callable(actions): 403 | actions = actions() 404 | for action in actions: 405 | t = action.get("type") 406 | if t == "remove_list": 407 | base = action.get("base_path", dest_path) 408 | for f in action.get("files", []): 409 | if os.path.exists(os.path.join(base, f)): 410 | total += 1 411 | elif t == "delete_folder": 412 | if os.path.exists(action.get("path")): 413 | total += 1 414 | elif t == "delete_file": 415 | if os.path.exists(action.get("file")): 416 | total += 1 417 | elif t == "copy_file": 418 | if os.path.exists(action.get("file")): 419 | total += 1 420 | elif t == "del_fsr_dlss_mods": 421 | gpu_name = get_active_gpu() 422 | target = action.get("rtx") if "nvidia" in gpu_name.lower() else action.get("amd", []) 423 | for fn in target: 424 | if os.path.exists(os.path.join(action.get("base_path", dest_path), fn)): 425 | total += 1 426 | elif t == "run_del_optiscaler": 427 | base = action.get("base_path", dest_path) 428 | found = False 429 | for fn in del_optiscaler: 430 | if os.path.exists(os.path.join(base, fn)): 431 | found = True 432 | break 433 | if found: 434 | total += 1 435 | elif t == "remove_copy": 436 | if os.path.exists(action.get("folder")): 437 | total += 1 438 | elif t == "rename": 439 | if os.path.exists(action.get("old")): 440 | total += 1 441 | except Exception: 442 | pass 443 | return total 444 | 445 | def del_all_mods_optiscaler(dest_path, mod_list, remove_dxgi=False, search_folder_name=None, progress_callback=None): 446 | try: 447 | nvngx = os.path.join(dest_path, 'nvngx.dll') 448 | nvngx_dlss = os.path.join(dest_path, 'nvngx_dlss.dll') 449 | if os.path.exists(nvngx): 450 | try: 451 | os.replace(nvngx, nvngx_dlss) 452 | if progress_callback: progress_callback() 453 | except Exception: 454 | pass 455 | 456 | if remove_dxgi: 457 | dxgi = os.path.join(dest_path, 'dxgi.dll') 458 | if os.path.exists(dxgi): 459 | os.remove(dxgi) 460 | if progress_callback: progress_callback() 461 | 462 | for item in mod_list: 463 | full = os.path.join(dest_path, item) 464 | if os.path.exists(full): 465 | os.remove(full) 466 | if progress_callback: progress_callback() 467 | 468 | if search_folder_name is not None: 469 | mods_path = os.path.join(dest_path, search_folder_name) 470 | if os.path.exists(mods_path): 471 | shutil.rmtree(mods_path) 472 | if progress_callback: progress_callback() 473 | return True 474 | 475 | except Exception as e: 476 | messagebox.showinfo('Error','Please close the game or any other folders related to the game.') 477 | print(e) 478 | return False 479 | 480 | def perform_cleanup_action(action, progress_callback=None): 481 | try: 482 | action_type = action.get("type") 483 | if action_type == "remove_list": 484 | base = action.get("base_path", "") 485 | for file in action.get("files", []): 486 | full_path = os.path.join(base, file) 487 | if os.path.exists(full_path): 488 | os.remove(full_path) 489 | if progress_callback: 490 | progress_callback() 491 | 492 | elif action_type == "del_fsr_dlss_mods": 493 | gpu_name = get_active_gpu() 494 | target_list = action.get("rtx") if "nvidia" in gpu_name.lower() else action.get("amd", []) 495 | base = action.get("base_path", "") 496 | for file_name in target_list: 497 | full_path = os.path.join(base, file_name) 498 | if os.path.exists(full_path): 499 | os.remove(full_path) 500 | if progress_callback: 501 | progress_callback() 502 | 503 | elif action_type == "reg": 504 | path_reg = action.get("path") 505 | base = action.get("base_path", "") 506 | if path_reg and os.path.exists(os.path.join(base, "Enable Overlay.reg")): 507 | if action.get("show_message"): 508 | handle_prompt( 509 | 'DLSS Overlay', 510 | 'Do you want to disable the DLSS Overlay?', 511 | lambda _: ( 512 | runReg(action["path"]), 513 | os.remove(action.get("remove_reg")) if action.get("remove_reg") else None, 514 | progress_callback() if progress_callback else None 515 | ) 516 | ) 517 | else: 518 | runReg(action["path"]) 519 | if progress_callback: 520 | progress_callback() 521 | 522 | elif action_type == "delete_folder": 523 | p = action.get("path") 524 | if os.path.exists(p): 525 | shutil.rmtree(p) 526 | if progress_callback: 527 | progress_callback() 528 | 529 | elif action_type == "remove_copy": 530 | if os.path.exists(action.get("folder")): 531 | if action.get("handle_message") is not None: 532 | handle_prompt( 533 | 'File', 534 | action["handle_message"], 535 | lambda _: ( 536 | shutil.rmtree(action["folder"]), 537 | shutil.copytree(action["folder_copy"], action["dest"], dirs_exist_ok=True), 538 | progress_callback() if progress_callback else None 539 | ) 540 | ) 541 | if action.get("del_folder") == True: 542 | shutil.rmtree(action["folder_copy"]) 543 | if progress_callback: 544 | progress_callback() 545 | 546 | elif action_type == "delete_file": 547 | if os.path.exists(action.get("file")): 548 | os.remove(action.get("file")) 549 | if progress_callback: 550 | progress_callback() 551 | 552 | elif action_type == "copy_file": 553 | if os.path.exists(action.get("file")): 554 | shutil.copy2(action.get("file"), action.get("dest")) 555 | os.remove(action.get("file")) 556 | if progress_callback: 557 | progress_callback() 558 | 559 | elif action_type == "run_del_optiscaler": 560 | if action.get("def") == True: 561 | del_all_mods_optiscaler( 562 | action["base_path"], 563 | del_optiscaler, 564 | remove_dxgi=True, 565 | search_folder_name=None, 566 | progress_callback=progress_callback 567 | ) 568 | 569 | elif action_type == "rename": 570 | if os.path.exists(action.get("old")): 571 | os.rename(action.get("old"), action.get("new")) 572 | if progress_callback: 573 | progress_callback() 574 | 575 | except Exception as e: 576 | print(e) 577 | 578 | def setup_cleanup(dest_path, game_selected, mod_selected=None, progress_callback=None): 579 | registry = get_cleanup_registry(game_selected, mod_selected, dest_path) 580 | 581 | for (game, mod), data in registry.items(): 582 | try: 583 | cond = data["condition"]() 584 | if cond: 585 | actions = data["actions"] 586 | if callable(actions): 587 | actions = actions() 588 | for action in actions: 589 | perform_cleanup_action(action, progress_callback=progress_callback) 590 | except Exception as e: 591 | print(e) -------------------------------------------------------------------------------- /installer.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | from tkinter import messagebox 4 | import asyncio 5 | from helpers import * 6 | import re 7 | 8 | from games_mods_config import addons_files 9 | 10 | class ModInstaller: 11 | def __init__(self, dest_path, game_selected, mod_selected, progress_callback=None): 12 | self.dest_path = dest_path 13 | self.game_selected = game_selected 14 | self.mod_selected = mod_selected 15 | self.progress_callback = progress_callback 16 | self.gpu_name = get_active_gpu() 17 | 18 | self.game_handlers = { 19 | "Elden Ring": self.install_elden_ring, 20 | "Elden Ring Nightreign": self.install_elden_nightreign, 21 | "Forza Horizon 5": self.install_forza_horizon_5, 22 | "Icarus": self.install_icarus, 23 | "Lords of the Fallen": self.install_lords_of_the_fallen, 24 | "Cod Black Ops Cold War": self.install_cod_black_ops_cold_war, 25 | "COD MW3": self.install_cod_mw3, 26 | "Dead Rising Remaster": self.install_dead_rising_remaster, 27 | "Cyberpunk 2077": self.install_cyberpunk_2077, 28 | "Black Myth Wukong": self.install_black_myth_wukong, 29 | "Like a Dragon: Pirate Yakuza in Hawaii": self.install_like_a_dragon_pirate_yakuza, 30 | "Baldur's Gate 3": self.install_baldurs_gate_3, 31 | "The Callisto Protocol": self.install_callisto, 32 | "Palworld": self.install_palworld, 33 | "Silent Hill 2": self.install_silent_hill_2, 34 | "Suicide Squad Kill the Justice League": self.install_sskjl, 35 | "Resident Evil 4 Remake": self.install_re4_remake, 36 | "Dead Islans 2": self.install_di2, 37 | "Returnal": self.install_returnal, 38 | "Stalker 2": self.install_stalker2, 39 | "Red Dead Redemption 2": self.install_rdr2, 40 | "Monster Hunter Wilds": self.install_mhw, 41 | "Dragons Dogma 2": self.install_dd2, 42 | "Dragon Age: Veilguard": self.install_dg_veil, 43 | "A Quiet Place: The Road Ahead": self.install_quiet_place, 44 | "Indiana Jones and the Great Circle": self.install_indy, 45 | "Red Dead Redemption": self.install_rdr, 46 | "Warhammer: Space Marine 2": self.install_warhammer_space_marine, 47 | "Hellblade 2": self.install_hellblade_2, 48 | "Assassin\'s Creed Valhalla": self.install_ac_valhalla, 49 | "MOTO GP 24": self.install_moto_gp_24, 50 | "Final Fantasy XVI": self.install_ff16, 51 | "Ghost of Tsushima": self.install_ghost_of_tsushima, 52 | "Alan Wake 2": self.install_alan_wake_2, 53 | "TEKKEN 8": self.install_unlock_fps_tekken_8, 54 | } 55 | 56 | mod_groups = { 57 | self.install_optiscaler_fsr_dlss: { 58 | 'FSR4/DLSS FG (Only Optiscaler)', 59 | 'FSR4/DLSSG FG (Only Optiscaler)', 60 | 'FSR4/DLSS Gow4', 61 | 'FSR4/DLSS Nightreign RTX', 62 | }, 63 | } 64 | self.mods_handlers = {} 65 | 66 | for handler, mods_set in mod_groups.items(): 67 | for mod in mods_set: 68 | self.mods_handlers[mod] = lambda h=handler: h(self.dest_path, self.game_selected, self.mod_selected) 69 | 70 | def verify_mod_selected(self, mod_selected): 71 | if not mod_selected: 72 | messagebox.showwarning("Error", "No mod selected. Please select a mod to install.") 73 | return False 74 | return True 75 | 76 | def copy_folder(self, src, dst): 77 | try: 78 | for root, dirs, files in os.walk(src): 79 | relative_path = os.path.relpath(root, src) 80 | dest_path = os.path.join(dst, relative_path) 81 | os.makedirs(dest_path, exist_ok=True) 82 | for file in files: 83 | shutil.copy2(os.path.join(root, file), os.path.join(dest_path, file)) 84 | except Exception as e: 85 | messagebox.showerror("Error", f"Failed to copy folder: {e}") 86 | 87 | def copy_progress(self, src, dst): 88 | copy_with_progress(src, dst, self.progress_callback) 89 | 90 | def install_mod(self, game_path, mod_selected): 91 | if not self.verify_mod_selected(mod_selected): 92 | return 93 | 94 | mod_path = os.path.join(mod_path, mod_selected) 95 | 96 | try: 97 | self.copy_folder(mod_path, game_path) 98 | except Exception as e: 99 | messagebox.showerror("Error", f"Failed to install mod: {e}") 100 | return 101 | 102 | # Game-specific installation methods 103 | 104 | # FSR4/DLSS FG (Only Optiscaler) (Default) 105 | def install_optiscaler_fsr_dlss(self, dest_path, game_selected, mod_selected, copy_dlss = True, copy_nvapi = True): # Default Optiscaler is used for games that don't work with Custom Optiscaler or other mods 106 | path_optiscaler = 'mods\\Addons_mods\\OptiScaler' 107 | path_optiscaler_dlss = 'mods\\Addons_mods\\Optiscaler DLSS' 108 | path_optiscaler_dlssg = 'mods\\Addons_mods\\Optiscaler DLSSG\\OptiScaler.ini' 109 | path_dlss_to_fsr = 'mods\\Addons_mods\\Optiscaler DLSSG\\dlssg_to_fsr3_amd_is_better.dll' 110 | path_ini_only_upscalers = 'mods\\Addons_mods\\Optiscaler Only Upscalers\\OptiScaler.ini' 111 | nvapi_amd = 'mods\\Addons_mods\\Nvapi AMD\\Nvapi' 112 | nvapi_ini = 'mods\\Addons_mods\\Nvapi AMD\\Nvapi Ini\\OptiScaler.ini' 113 | nvapi_ini_dlssg = 'mods\\Addons_mods\\Nvapi AMD\\DLSSG Nvapi Ini\\OptiScaler.ini' 114 | fake_nvapi = 'mods\\Addons_mods\\Fake_Nvapi' 115 | games_to_install_nvapi_amd = ['Microsoft Flight Simulator 2024', 'Death Stranding Director\'s Cut', 'Shadow of the Tomb Raider', 'Rise of The Tomb Raider', 'The Witcher 3', 'Uncharted Legacy of Thieves Collection', 'Suicide Squad: Kill the Justice League','Sifu', 'Mortal Shell', 'FIST: Forged In Shadow Torch', 'Ghostrunner 2', 'Final Fantasy XVI', 'Sengoku Dynasty', 'Red Dead Redemption 2', 'S.T.A.L.K.E.R. 2', 'Monster Hunter Wilds', 'AVOWED', 'Frostpunk 2', 'STAR WARS Jedi: Survivor', 'Deliver Us Mars', 'GTA V', 'Chernobylite 2: Exclusion Zone', 'South Of Midnight', 'Assassin\'s Creed Shadows', 'Star Wars Outlaws', 'Elder Scrolls IV Oblivion Remaster', 'The Alters', 'Satisfactory'] 116 | games_to_use_anti_lag_2 = ['God of War Ragnarök', 'God Of War 4', 'Path of Exile II', 'Hitman 3', 'Marvel\'s Midnight Suns', 'Hogwarts Legacy', 'The First Berserker: Khazan'] 117 | games_only_upscalers = ['The Last of Us Part I'] 118 | games_with_dlssg = ['The First Berserker: Khazan', 'Atomic Heart','Marvel\'s Spider-Man Remastered', 'Marvel\'s Spider-Man Miles Morales', 'Marvel\'s Spider-Man 2', 'Alan Wake 2', 'S.T.A.L.K.E.R. 2', 'Eternal Strands', 'Monster Hunter Wilds', 'AVOWED', 'Frostpunk 2', 'God of War Ragnarök', 'STAR WARS Jedi: Survivor', 'Deliver Us Mars', 'Chernobylite 2: Exclusion Zone', 'Assassin\'s Creed Shadows', 'The Last of Us Part II', 'Star Wars Outlaws', 'Elder Scrolls IV Oblivion Remaster', 'The Alters', 'Satisfactory' ] 119 | games_with_anti_cheat = ['Back 4 Blood', 'GTA V'] 120 | games_no_nvngx = ['Red Dead Redemption 2', 'Marvel\'s Spider-Man Remastered', 'Marvel\'s Spider-Man Miles Morales', 'Marvel\'s Spider-Man 2'] # Games that don't need the file nvngx_dlss.dll renamed to nvngx.dll (Only RTX) 121 | games_with_d3d12 = ['GTA Trilogy'] # Some games (especially Rockstar) do not work with the nvngx.dll file renamed to dxgi.dll; it must be renamed to d3d12.dll instead 122 | 123 | print(self.gpu_name) 124 | 125 | try: 126 | # Rename the dxgi.dll file from ReShade to d3d12.dll 127 | if os.path.exists(os.path.join(dest_path, 'dxgi.dll')) and os.path.exists(os.path.join(dest_path, 'reshade-shaders')): 128 | os.replace(os.path.join(dest_path, 'dxgi.dll'), os.path.join(dest_path, 'd3d12.dll')) 129 | 130 | # Rename the DLSS file (nvngx_dlss.dll) to nvngx.dll. 131 | if os.path.exists(os.path.join(dest_path, 'nvngx_dlss.dll')) and copy_dlss: 132 | self.copy_progress(path_optiscaler, self.dest_path) 133 | os.replace(os.path.join(dest_path, 'nvngx.dll'), os.path.join(dest_path, 'dxgi.dll')) 134 | 135 | if not game_selected in games_no_nvngx or self.gpu_name in ['amd', 'rx', 'intel', 'arc', 'gtx']: 136 | self.copy_progress(os.path.join(dest_path, 'nvngx_dlss.dll'), os.path.join(dest_path, 'nvngx.dll')) 137 | else: 138 | self.copy_progress(path_optiscaler_dlss, dest_path) 139 | 140 | if game_selected in games_no_nvngx and 'rtx' in self.gpu_name and os.path.exists(os.path.join(dest_path, 'nvngx.dll')): 141 | os.replace(os.path.join(dest_path, 'nvngx.dll'), os.path.join(dest_path, 'nvngx_dlss.dll')) 142 | 143 | # Rename the DLSS file (nvngx_dlss.dll) to d3d12.dll 144 | if os.path.exists(os.path.join(dest_path, 'dxgi.dll')) and game_selected in games_with_d3d12: 145 | os.replace(os.path.join(dest_path, 'dxgi.dll'), os.path.join(dest_path, 'd3d12.dll')) 146 | 147 | if mod_selected == 'FSR4/DLSSG FG (Only Optiscaler)': 148 | self.copy_progress(path_optiscaler_dlssg, dest_path) 149 | 150 | # Install the dlss_to_fsr file if the mod does not work with the default files 151 | if not re.search(r"rtx\s*(40|50)\d{2}", self.gpu_name) and game_selected not in games_only_upscalers and messagebox.askyesno('DLSS/FSR','Do you want to install the dlssg_to_fsr3_amd_is_better.dll file? It is recommended to install this only if you are unable to enable the game\'s DLSS Frame Generation (this mod does not have its own FG; the game\'s DLSS FG is used).' or game_selected in games_with_dlssg ): 152 | self.copy_progress(path_dlss_to_fsr, dest_path) 153 | 154 | if game_selected in games_only_upscalers: 155 | self.copy_progress(path_ini_only_upscalers, dest_path) 156 | 157 | # AMD Anti Lag 2 158 | if game_selected in games_to_use_anti_lag_2 and messagebox.askyesno('Anti Lag 2', f'Do you want to use AMD Anti Lag 2? Check the {game_selected} guide in FSR Guide to see how to enable it.'): 159 | self.copy_progress(nvapi_amd, dest_path) 160 | 161 | nvapi_ini_file = nvapi_ini_dlssg if mod_selected == 'FSR4/DLSSG FG (Only Optiscaler)' else nvapi_ini 162 | self.copy_progress(nvapi_ini_file, dest_path) 163 | 164 | # Nvapi for non-RTX users 165 | elif copy_nvapi and any(gpus in self.gpu_name for gpus in ['amd', 'rx', 'intel', 'arc', 'gtx']) and game_selected in games_to_install_nvapi_amd and messagebox.askyesno('Nvapi', 'Do you want to install Nvapi? Only select "Yes" if the mod doesn\'t work with the default files.'): 166 | self.copy_progress(nvapi_amd, dest_path) 167 | 168 | nvapi_ini_file = nvapi_ini_dlssg if mod_selected == 'FSR4/DLSSG FG (Only Optiscaler)' else nvapi_ini 169 | self.copy_progress(nvapi_ini_file, dest_path) 170 | 171 | if game_selected in games_with_anti_cheat: 172 | messagebox.showinfo('Anti Cheat','Do not use the mod in Online mode, or you may be banned') 173 | 174 | # Fake Nvapi for non-RTX 40/50x users 175 | if not re.search(r"rtx\s*(40|50)\d{2}", self.gpu_name) and mod_selected in ["FSR4/DLSS FG (Only Optiscaler)", "FSR4/DLSSG FG (Only Optiscaler)"]: 176 | self.copy_progress(fake_nvapi, dest_path) 177 | 178 | except Exception as e: 179 | print(e) 180 | 181 | # Elden Ring & Nightreign 182 | def install_elden_ring(self): 183 | er_origins = {'Disable_Anti-Cheat':'mods\\Elden_Ring_FSR3\\ToggleAntiCheat', 184 | 'Elden_Ring_FSR3':'mods\\Elden_Ring_FSR3\\EldenRing_FSR3', 185 | 'Elden_Ring_FSR3 V2':'mods\\Elden_Ring_FSR3\\EldenRing_FSR3 v2', 186 | 'FSR4/DLSS FG Custom Elden':'mods\\Elden_Ring_FSR3\\EldenRing_FSR3 v3', 187 | } 188 | 189 | if self.game_selected == 'Elden Ring': 190 | update_fsr_elden = 'mods\\Temp\\Upscalers\\AMD' 191 | update_dlss_elden = 'mods\\Temp\\Upscalers\\Nvidia\\nvngx_dlss.dll' 192 | 193 | if self.mod_selected in er_origins: 194 | elden_folder = er_origins[self.mod_selected] 195 | 196 | if self.mod_selected in er_origins: 197 | self.copy_progress(elden_folder,self.dest_path) 198 | 199 | if self.mod_selected == 'Unlock FPS Elden': 200 | 201 | self.copy_progress('mods\\Elden_Ring_FSR3\\Unlock_Fps', self.dest_path) 202 | 203 | if os.path.exists(os.path.join(self.dest_path, 'toggle_anti_cheat.exe')): 204 | run_dis_anti_c(self.dest_path) 205 | 206 | if self.mod_selected == 'FSR4/DLSS FG Custom Elden' and os.path.exists(os.path.join(self.dest_path,'ERSS2\\bin')): 207 | self.copy_progress(update_fsr_elden, os.path.join(self.dest_path,'ERSS2\\bin')) 208 | self.copy_progress(update_dlss_elden,os.path.join(self.dest_path,'ERSS2\\bin')) 209 | 210 | # Elden Ring Nightreign 211 | def install_elden_nightreign(self): 212 | if self.game_selected == 'Elden Ring Nightreign': 213 | disable_eac_nightreign = 'mods\\Elden_Ring_FSR3\\Nightreign FSR3\\Disable EAC' 214 | nrss_nightreign = 'mods\\Elden_Ring_FSR3\\Nightreign FSR3\\NRSS' 215 | remove_vignette_aberration = 'mods\\Elden_Ring_FSR3\\Nightreign FSR3\\Remove Vignette & Aberration' 216 | 217 | if self.mod_selected == 'FSR4/DLSS Nightreign RTX': 218 | self.copy_progress(disable_eac_nightreign, self.dest_path) 219 | self.copy_progress(nrss_nightreign, self.dest_path) 220 | self.copy_progress(remove_vignette_aberration, self.dest_path) 221 | messagebox.showinfo('Guide', 'It is recommended to check the guide in FSR Guide to complete the installation (it includes additional steps, such as disabling the Anti-Cheat).') 222 | 223 | # Forza Horizon 5 224 | def install_forza_horizon_5(self): 225 | path_rtx = 'mods\\FSR3_FH\\RTX' 226 | path_ot_gpu = 'mods\\FSR3_FH\\Ot_Gpu' 227 | en_rtx_reg = "mods\\FSR3_FH\\RTX\\DisableNvidiaSignatureChecks.reg" 228 | 229 | if 'rtx' in self.gpu_name: 230 | self.copy_progress(path_rtx, self.dest_path) 231 | runReg(en_rtx_reg) 232 | else: 233 | self.copy_progress(path_ot_gpu, self.dest_path) 234 | 235 | # Palworld 236 | def install_palworld(self): 237 | path_pw_mod = 'mods\\FSR3_PW\\FG' 238 | path_ini_fg_rtx = 'mods\\FSR3_PW\\Ini FG RTX\\PalworldUpscaler.ini' 239 | appdata_pw = os.getenv("LOCALAPPDATA") 240 | path_ini_pw = os.path.join(appdata_pw, 'Pal\\Saved\\Config\\WinGDK') 241 | dx12_ini_pw = 'mods\\FSR3_PW\\Dx12\\Engine.ini' 242 | 243 | if self.mod_selected == 'Palworld Build03': 244 | self.copy_progress(path_pw_mod, self.dest_path) 245 | 246 | if 'rtx' in self.gpu_name and os.path.exists(os.path.join(self.dest_path, 'mods')): 247 | self.copy_progress(path_ini_fg_rtx, os.path.join(self.dest_path, 'mods')) 248 | 249 | try: 250 | if os.path.exists(os.path.join(self.dest_path, 'Palworld-WinGDK-Shipping.exe')): 251 | 252 | if os.path.exists(path_ini_pw): 253 | if not os.path.exists(os.path.abspath(os.path.join(path_ini_pw, '..', 'Engine.ini'))): 254 | self.copy_progress(os.path.join(path_ini_pw, 'Engine.ini'), os.path.abspath(os.path.join(path_ini_pw, '..'))) # Engine.ini Backup 255 | 256 | self.copy_progress(dx12_ini_pw, path_ini_pw) 257 | else: 258 | self.copy_progress(dx12_ini_pw, self.dest_path) 259 | messagebox.showinfo('Error', 'Unable to activate DX12 (it is required for the mod to work). Try reinstalling or copy the Engine.ini file, which was installed in the selected folder in Utility, to the following path:"C:\\Users\\YourName\\AppData\\Local\\Pal\\Saved\\Config\\WinGDK".') 260 | 261 | elif os.path.exists(os.path.join(self.dest_path, 'Palworld-Win64-Shipping.exe')): 262 | messagebox.showinfo('DX12', 'Check the "Palworld" guide in FSR Guide on how to enable DX12 (it is required for the mod to work).') 263 | except: 264 | self.copy_progress(dx12_ini_pw, self.dest_path) 265 | messagebox.showinfo('Error', 'Unable to activate DX12 (it is required for the mod to work). Try reinstalling or copy the Engine.ini file, which was installed in the selected folder in Utility, to the following path:"C:\\Users\\YourName\\AppData\\Local\\Pal\\Saved\\Config\\WinGDK".') 266 | 267 | # Tekken 8 268 | def install_unlock_fps_tekken_8(self): 269 | path_tekken = 'mods\\Unlock_fps_Tekken' 270 | 271 | if self.mod_selected == 'Unlock Fps Tekken 8': 272 | self.copy_progress(path_tekken, self.dest_path) 273 | 274 | messagebox.showinfo('Run Overlay','Run TekkenOverlay.exe for the mod to work, refer to the FSR GUIDE if needed.') 275 | 276 | # Icarus 277 | def install_icarus(self): 278 | icr_rtx = 'mods\\FSR3_ICR\\ICARUS_DLSS_3_FOR_RTX' 279 | icr_ot_gpu = 'mods\\FSR3_ICR\\ICARUS_FSR_3_FOR_AMD_GTX' 280 | icr_rtx_reg = "mods\\FSR3_ICR\\ICARUS_DLSS_3_FOR_RTX\\DisableNvidiaSignatureChecks.reg" 281 | 282 | if self.mod_selected == 'Icarus DLSSG RTX': 283 | self.copy_progress(icr_rtx, self.dest_path) 284 | act_dlss = messagebox.askyesno('DLSS','Do you want to run DisableNvidiaSignatureChecks.reg? It\'s necessary for the mod to work') 285 | 286 | if act_dlss: 287 | runReg(icr_rtx_reg) 288 | 289 | elif self.mod_selected == 'Icarus FSR3 AMD/GTX': 290 | self.copy_progress(icr_ot_gpu, self.dest_path) 291 | 292 | # Monster Hunter Wilds 293 | def install_mhw(self): 294 | dlssg_rtx_mhw = 'mods\\FSR3_Wilds\\DLSSG RTX' 295 | 296 | if self.mod_selected == 'DLSSG Wilds (Only RTX)': 297 | self.copy_progress(dlssg_rtx_mhw, self.dest_path) 298 | 299 | # Lords of the Fallen 300 | def install_lords_of_the_fallen(self): 301 | disableEacLotf2 = """\ 302 | set SteamAppId=1501750 303 | set SteamGameId=1501750 304 | start LOTF2-Win64-Shipping.exe -DLSSFG 305 | """ # Disable the Anti-Cheat for the mod to work. You need to launch the game using the .bat file 306 | 307 | if self.mod_selected == 'DLSS FG LOTF2 (RTX)': 308 | dlss_to_fsr(self.dest_path, self.progress_callback) 309 | 310 | with open(os.path.join(self.dest_path, "rungame.bat"), "w") as file: 311 | file.write(disableEacLotf2) 312 | 313 | messagebox.showinfo('Guide', 'Run the game using the "rungame.bat" file that was created in the mod installation folder. It is recommended to check the guide if you need more information') 314 | 315 | # Cod Black Ops Cold War 316 | def install_cod_black_ops_cold_war(self): 317 | messagebox.showinfo('Ban','Do not use the mod in multiplayer, otherwise you may be banned. We are not responsible for any bans') 318 | 319 | # Cod MW3 320 | def install_cod_mw3(self): 321 | global_dlss(self.dest_path, self.progress_callback) 322 | 323 | # Dragon Age Veilguard 324 | def install_dg_veil(self): 325 | amd_dg_veil = 'mods\\DLSS_Global\\For games that have native FG\\AMD' 326 | rtx_dg_veil = 'mods\\DLSS_Global\\For games that have native FG\\RTX' 327 | 328 | if self.mod_selected == 'FSR4/DLSS DG Veil': 329 | var_gpu_copy(self.dest_path,amd_dg_veil, rtx_dg_veil, self.progress_callback) 330 | 331 | # Dragons Dogma 2 332 | def install_dd2(self): 333 | dinput_dd2 = 'mods\\FSR3_DD2\\DD2_Framework\\dinput8.dll' 334 | 335 | if self.mod_selected == 'Dinput8 DD2': 336 | self.copy_progress(dinput_dd2, self.dest_path) 337 | elif not os.path.exists(os.path.join(self.dest_path, 'dinput8.dll')): 338 | messagebox.showinfo('Dinput8','If you haven\'t installed the dinput8.dll file, check the DD2 guide in the FSR Guide for installation instructions. It is required for the mod to work') 339 | return 340 | 341 | if self.mod_selected == 'DD2 FG': 342 | dlss_to_fsr(self.dest_path, self.progress_callback) 343 | 344 | if os.path.exists(os.path.join(self.dest_path,'shader.cache2')): 345 | if messagebox.showinfo('Do you want to remove the sharder_cache2? It is necessary for the mod to work'): 346 | os.remove(os.path.join(self.dest_path,'shader.cache2')) 347 | 348 | # Alan Wake 2 349 | def install_alan_wake_2(self): 350 | path_rtx = 'mods\\FSR3_AW2\\RTX' 351 | path_dlss = 'mods\\Temp\\Upscalers\\Nvidia\\nvngx_dlss.dll' 352 | 353 | if self.mod_selected == 'Alan Wake 2 FG RTX': 354 | self.copy_progress(path_rtx, self.dest_path) 355 | self.copy_progress(path_dlss, self.dest_path) 356 | 357 | # Moto GP 24 358 | def install_moto_gp_24(self): 359 | if self.dest_path == 'MOTO GP 24': 360 | path_uni = os.path.join(self.dest_path,'uniscaler') 361 | 362 | if os.path.exists(path_uni): 363 | shutil.rmtree(path_uni) 364 | 365 | # Ghost of Tsushima 366 | def install_ghost_of_tsushima(self): 367 | path_dlss_got = 'mods\\FSR3_GOT\\DLSS FG' 368 | got_reg = "mods\\FSR3_GOT\\DLSS FG\\DisableNvidiaSignatureChecks.reg" 369 | 370 | if self.mod_selected == 'Ghost of Tsushima FG DLSS': 371 | shutil.copytree(path_dlss_got,self.dest_path,dirs_exist_ok=True) 372 | 373 | runReg(got_reg) 374 | 375 | # Yakuza Like a Dragon: Pirate 376 | def install_like_a_dragon_pirate_yakuza(self): 377 | nvidia_checks = 'mods\\Temp\\NvidiaChecks\\DisableNvidiaSignatureChecks.reg' 378 | 379 | if self.mod_selected == 'DLSSG Yakuza': 380 | dlss_to_fsr(self.dest_path, self.progress_callback) 381 | runReg(nvidia_checks) 382 | 383 | # Assassin's Creed Valhalla 384 | def install_ac_valhalla(self): 385 | path_dlss = 'mods\\Ac_Valhalla_DLSS' 386 | path_dlss2 = 'mods\\Ac_Valhalla_DLSS2' 387 | 388 | if self.mod_selected == "Ac Valhalla DLSS3 (Only RTX)": 389 | self.copy_progress(path_dlss, self.dest_path) 390 | elif self.mod_selected == "Ac Valhalla FSR3 All GPU": 391 | self.copy_progress(path_dlss2, self.dest_path) 392 | 393 | # Final Fantasy XVI 394 | def install_ff16(self): 395 | if self.mod_selected == 'FFXVI DLSS RTX': 396 | dlss_to_fsr(self.progress_callback, self.dest_path) 397 | 398 | # Warhammer: Space Marine 2 399 | def install_warhammer_space_marine(self): 400 | path_dxgi = os.path.join(self.dest_path, 'dxgi.dll') 401 | 402 | if self.mod_selected == 'FSR4/DLSS FG Marine': 403 | global_dlss(self.dest_path, self.progress_callback) 404 | 405 | if os.path.exists(path_dxgi): 406 | backup_folder_marine = os.path.join(self.dest_path, 'Backup_DXGI') 407 | os.makedirs(backup_folder_marine, exist_ok=True) 408 | 409 | self.copy_progress(path_dxgi, backup_folder_marine) 410 | 411 | os.rename(path_dxgi, os.path.join(self.dest_path, 'd3d12.dll')) 412 | 413 | # Black Myth Wukong 414 | def install_black_myth_wukong(self): 415 | cache_wukong = os.path.join(os.getenv('USERPROFILE'), 'AppData') 416 | 417 | try: 418 | if self.mod_selected == 'FSR4/DLSS FG (Only Optiscaler)' or self.mod_selected == 'FSR4/DLSSG FG (Only Optiscaler)': 419 | if os.path.exists(os.path.join(cache_wukong, 'Local\\b1\\Saved')) and messagebox.askyesno('Cache','Do you want to clear the game cache? (it may prevent possible texture errors caused by the mod)'): 420 | for wukong_cache in os.listdir(os.path.join(cache_wukong, 'Local\\b1\\Saved')): 421 | if wukong_cache.endswith(".ushaderprecache"): 422 | path_cache_wukong = os.path.join(os.path.join(cache_wukong, 'Local\\b1\\Saved'), wukong_cache) 423 | if os.path.isfile(path_cache_wukong): 424 | os.remove(path_cache_wukong) 425 | except Exception as e: 426 | print(e) 427 | 428 | # Hellblade 2 429 | def install_hellblade_2(self): 430 | path_dlss_hb2 = 'mods\\FSR3_GOT\\DLSS FG' 431 | hb2_reg = "mods\\FSR3_GOT\\DLSS FG\\DisableNvidiaSignatureChecks.reg" 432 | 433 | if self.mod_selected == 'Hellblade 2 FSR3 (Only RTX)': 434 | self.copy_progress(path_dlss_hb2, self.dest_path,) 435 | 436 | runReg(hb2_reg) 437 | 438 | if self.mod_selected == 'Others Mods HB2': 439 | remove_post_processing_effects_hell2(self.dest_path) 440 | 441 | # Silent Hill 2 442 | def install_silent_hill_2(self): 443 | rtx_fg_sh2 = 'mods\\FSR3_SH2\\RTX_FG' 444 | 445 | if self.mod_selected == 'DLSS FG RTX': 446 | self.copy_folder(self.dest_path, rtx_fg_sh2) 447 | 448 | # Red Dead Redemption 449 | def install_rdr(self): 450 | if self.mod_selected == 'FSR4/DLSS FG (Only Optiscaler)': 451 | 452 | runReg('mods\\Temp\\NvidiaChecks\\DisableNvidiaSignatureChecks.reg') 453 | 454 | if messagebox.askyesno('Enable', 'Do you want to enable Nvidia Signature Checks? Select "Yes" only if the mod does not work'): 455 | runReg('mods\\Temp\\NvidiaChecks\\RestoreNvidiaSignatureChecks.reg') 456 | 457 | # Red Dead Redemption 2 458 | def install_rdr2(self): 459 | rdr2_mix = 'mods\\FSR3_RDR2\\RDR2_FSR3_mix' 460 | rdr2_fg_custom = 'mods\\FSR3_RDR2\\RDR2 FG Custom' 461 | rdr2_amd_ini = 'mods\\FSR3_RDR2\\RDR2 FG Custom\\Amd Ini\\RDR2Upscaler.ini' 462 | rdr2_optiscaler = 'mods\\FSR3_RDR2\\Optiscaler_fsr_dlss' 463 | 464 | if self.mod_selected == 'FSR4/DLSS FG (Only Optiscaler RDR2)': 465 | self.copy_progress(rdr2_optiscaler, self.dest_path) 466 | 467 | if self.mod_selected == 'RDR2 Mix': 468 | self.copy_progress(rdr2_mix, self.dest_path) 469 | 470 | if self.mod_selected == 'RDR2 FG Custom': 471 | self.copy_progress(rdr2_fg_custom, self.dest_path) 472 | 473 | if 'amd' in self.gpu_name and os.path.exists(os.path.join(self.dest_path, 'mods')): 474 | self.copy_progress(rdr2_amd_ini, os.path.join(self.dest_path, 'mods')) 475 | 476 | # Stalker 2 477 | def install_stalker2(self): 478 | dlss_fg_stalker = 'mods\\FSR3_Stalker2\\FG DLSS' 479 | 480 | if self.mod_selected == 'DLSS FG (Only RTX)': 481 | self.copy_progress(dlss_fg_stalker, self.dest_path) 482 | runReg('mods\\Temp\\NvidiaChecks\\DisableNvidiaSignatureChecks.reg') 483 | 484 | # Dead Rising Remasrter 485 | def install_dead_rising_remaster(self): 486 | dlss_to_fg_drr = 'mods\\FSR3_DRR\\FSR3FG\\Dlss_to_Fsr' 487 | dinput_drr = 'mods\\FSR3_DRR\\FSR3FG\\Dinput' 488 | dlss_drr = 'mods\\Temp\\Upscalers\\Nvidia\\nvngx_dlss.dll' 489 | 490 | if self.mod_selected == 'Dinput8 DRR': 491 | self.copy_progress(dinput_drr, self.dest_path) 492 | 493 | if self.mod_selected == 'DDR FG': 494 | if os.path.exists(os.path.join(self.dest_path,'reframework\\plugins')) and os.path.exists(os.path.join(self.dest_path,'dinput8.dll')): 495 | self.copy_progress(dlss_to_fg_drr, os.path.join(self.dest_path,'reframework\\plugins')) 496 | self.copy_progress(dlss_drr, self.dest_path) 497 | else: 498 | messagebox.showinfo('Not Found', 'First, install the "Dinput8 DRR" before installing the main mod. See the DRR guide in the FSR Guide to learn how to install the mod.') 499 | return False 500 | return True 501 | 502 | # Dead Island 2 503 | def install_di2(self): 504 | path_tcp_di2 = 'mods\\FSR3_DI2\\TCP' 505 | 506 | if self.mod_selected == 'FSR4/DLSS FG (Only Optiscaler)': 507 | self.copy_progress(path_tcp_di2, self.dest_path) 508 | runReg('mods\\FSR3_DI2\\TCP\\EnableNvidiaSigOverride.reg') 509 | 510 | # Resident Evil 4 Remake 511 | def install_re4_remake(self): 512 | fsr_dlss_re4 = 'mods\\FSR3_RE4Remake\\FSR_DLSS' 513 | 514 | if self.mod_selected == 'FSR4/DLSS RE4': 515 | self.copy_progress(fsr_dlss_re4, self.dest_path) 516 | 517 | # Suicide Squad: Kill the Justice League 518 | def install_sskjl(self): 519 | root_path_sskjl = os.path.abspath(os.path.join(self.dest_path, '..\\..\\..')) 520 | path_dxgi_sskjl = os.path.join(self.dest_path, 'dxgi.dll') 521 | path_nvngx_sskjl = os.path.join(self.dest_path, 'nvngx.dll') 522 | path_disable_eac = 'mods\\FSR3_SSKJL\\Disable_EAC\\EAC Bypass' 523 | 524 | if self.mod_selected == 'FSR4/DLSS FG (Only Optiscaler)': 525 | if os.path.exists(path_dxgi_sskjl): 526 | os.replace(path_dxgi_sskjl, os.path.join(self.dest_path, 'winmm.dll')) ## Necessary to rename the file so it won't be replaced by the Disable EAC files. 527 | 528 | if os.path.exists(path_nvngx_sskjl) and 'rtx' in self.gpu_name: 529 | os.remove(path_nvngx_sskjl) 530 | 531 | ## Backup EAC 532 | if os.path.exists(os.path.join(root_path_sskjl,'EasyAntiCheat')): 533 | self.copy_progress(os.path.join(root_path_sskjl,'EasyAntiCheat'), os.path.join(root_path_sskjl, 'Backup EAC\\EasyAntiCheat')) 534 | self.copy_progress(root_path_sskjl, 'start_protected_game.exe'), os.path.join(root_path_sskjl, 'Backup EAC') 535 | 536 | ## Disable EAC 537 | self.copy_progress(path_disable_eac, root_path_sskjl) 538 | 539 | # Returnal 540 | def install_returnal(self): 541 | root_path_returnal = os.path.abspath(os.path.join(self.dest_path, '..\\..\\..')) 542 | path_default_folder_dlss_returnal = os.path.join(root_path_returnal, 'Engine\\Binaries\\ThirdParty\\NVIDIA\\NGX\\Win64') 543 | path_nvapi_returnal = 'mods\\FSR3_Flight_Simulator24\\Amd' 544 | 545 | try: 546 | if os.path.exists(path_default_folder_dlss_returnal): 547 | 548 | if self.mod_selected == 'FSR4/DLSS FG (Only Optiscaler)': 549 | self.copy_progress(os.path.join(path_default_folder_dlss_returnal, 'nvngx_dlss.dll'), os.path.join(self.dest_path, 'nvngx.dll')) 550 | 551 | if self.mod_selected == 'FSR4/DLSS FG (Only Optiscaler)' and messagebox.askyesno('Nvapi', 'Do you want to install nvapi.dll? Select "Yes" only if you are an AMD user and DLSS does not appear in the game after installing the mod.'): 552 | self.copy_progress(path_nvapi_returnal, self.dest_path) 553 | 554 | except Exception as e: 555 | print(e) 556 | 557 | # Indiana Jones and the Great Circle 558 | def install_indy(self): 559 | optiscaler_indy = 'mods\\FSR3_Indy\\Optiscaler Indy' 560 | fg_indy = 'mods\\FSR3_Indy\\FG\\Mod' 561 | config_file_path_indy = os.path.join(os.environ['USERPROFILE'], 'Saved Games\\MachineGames\\TheGreatCircle\\base') 562 | config_file_indy = 'mods\\FSR3_Indy\\FG\\Config File\\TheGreatCircleConfig.local' 563 | old_config_file_indy = os.path.join(config_file_path_indy,'TheGreatCircleConfig.local') 564 | 565 | if self.mod_selected == 'FSR4/DLSS FG (Only Optiscaler Indy)': 566 | self.copy_progress(optiscaler_indy, self.dest_path) 567 | 568 | if self.mod_selected == 'Indy FG (Only RTX)': 569 | self.copy_progress(fg_indy, self.dest_path) 570 | 571 | if os.path.exists(old_config_file_indy): 572 | os.rename(old_config_file_indy, os.path.join(config_file_path_indy,'TheGreatCircleConfig.txt')) 573 | self.copy_progress(config_file_indy, config_file_path_indy) 574 | else: 575 | self.copy_progress(config_file_indy, self.dest_path) 576 | messagebox.showinfo('Not Found','The file TheGreatCircleConfig.local was not found. Please check if it exists (C:\\Users\\YourName\\Saved Games\\MachineGames\\TheGreatCircle\\base). If it doesn\'t exist, open the game to have the file created. You can also manually copy the file to this path. The TheGreatCircleConfig.local file is in the folder selected in the Utility.') 577 | 578 | # A Quiet Place 579 | def install_quiet_place(self): 580 | optiscaler_quiet_place = 'mods\\Addons_mods\\OptiScaler' 581 | 582 | if self.mod_selected == 'FSR4/DLSS Quiet Place': 583 | self.copy_progress(optiscaler_quiet_place, self.dest_path) 584 | runReg('mods\\Temp\\enable signature override\\EnableSignatureOverride.reg') 585 | 586 | # Cyberpunk 2077 587 | async def install_cyberpunk_2077(self): 588 | path_rtx_dlss = "mods\\FSR3_CYBER2077\\dlssg-to-fsr3-0.90_universal" 589 | cb2077_reg = "mods\\FSR3_CYBER2077\\dlssg-to-fsr3-0.90_universal\\DisableNvidiaSignatureChecks.reg" 590 | 591 | if self.mod_selected == "RTX DLSS FG": 592 | await asyncio.to_thread(self.copy_progress(path_rtx_dlss, self.dest_path)) 593 | await asyncio.to_thread(runReg(cb2077_reg)) 594 | 595 | # The Callisto Protocol 596 | def install_callisto(self): 597 | tcp_callisto = 'mods\\FSR3_Callisto\\TCP' 598 | 599 | self.copy_progress(self.dest_path, tcp_callisto) 600 | 601 | # Baldur's Gate 3 602 | def install_baldurs_gate_3(self): 603 | base_bdg = "mods\\FSR3_BDG\\FSR_BDG" 604 | v2_bdg = "mods\\FSR3_BDG\\FSR3_BDG_2" 605 | v3_ini = "mods\\FSR3_BDG\\FSR3_BDG_3\\BG3Upscaler.ini" 606 | 607 | self.copy_progress(base_bdg, self.dest_path) 608 | 609 | if self.mod_selected == "Baldur's Gate 3 FSR3 V2": 610 | self.copy_progress(v2_bdg, self.dest_path) 611 | 612 | elif self.mod_selected == "Baldur's Gate 3 FSR3 V3": 613 | self.copy_progress(v2_bdg, self.dest_path) 614 | 615 | path_mods_bdg = os.path.join(self.dest_path, "mods") 616 | if os.path.exists(path_mods_bdg): 617 | self.copy_progress(v3_ini, path_mods_bdg) 618 | 619 | def install_addons(self, var_addons, dest_path, selected_addons): 620 | if not var_addons or not selected_addons: 621 | return 622 | 623 | for addon_name in selected_addons: 624 | config = addons_files.get(addon_name) 625 | 626 | if not config: 627 | messagebox.showerror("Error", f"Addon '{addon_name}' not found.") 628 | continue 629 | 630 | addon_path = config.get("addon_path") 631 | target = config.get("target") 632 | 633 | if not addon_path or not target: 634 | messagebox.showwarning("Error", f" Addon '{addon_name}' Missing path.") 635 | continue 636 | 637 | try: 638 | self.copy_progress(addon_path, dest_path) 639 | print(f"Addon '{addon_name}' installer sucessfully {dest_path}") 640 | 641 | except Exception as e: 642 | print("Error", f"Error installing the addon'{addon_name}': {e}") 643 | 644 | 645 | 646 | 647 | -------------------------------------------------------------------------------- /gui.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import asyncio 3 | import inspect 4 | from customtkinter import * 5 | from tkinter import filedialog,messagebox 6 | import os 7 | import sys 8 | from tkinter import font as tkFont 9 | import threading 10 | import json 11 | import requests 12 | import webbrowser 13 | import subprocess 14 | 15 | from theme import * 16 | from cleanup import * 17 | from games_mods_config import game_mods_config, game_config, addons_presets 18 | from installer import ModInstaller 19 | from helpers import runReg, bind_tooltip 20 | from guide import FsrGuide 21 | from upscaler_updater import games_to_update_upscalers 22 | from helpers import load_or_create_json 23 | from ctypes import windll 24 | windll.shcore.SetProcessDpiAwareness(1) # Per-monitor DPI aware 25 | 26 | UPDATE_STATE_FILE = os.path.join(os.getenv('LOCALAPPDATA'), "FSR-Mod-Utility", "update_state.json") 27 | GITHUB_API_URL = "https://api.github.com/repos/P4TOLINO06/FSR3.0-Mod-Setup-Utility/releases/latest" 28 | CURRENT_VERSION = "4.1v" 29 | 30 | class Gui: 31 | def __init__(self): 32 | self.root = CTk() 33 | icon = tk.PhotoImage(file="images/Hat.gif") 34 | self.root.wm_iconphoto(True, icon) 35 | self.root.title("FSR3.0 Mod Setup Utility - 4.1v") 36 | self.root.configure(bg='') 37 | self.font = get_font() 38 | self.root.geometry("450x360") 39 | self.root.resizable(0,0) 40 | self.root.configure(bg="#222223") 41 | 42 | self.update_available = False 43 | self.update_version = None 44 | self.update_state = self.load_update_state() 45 | self.check_for_update() 46 | self.game_selected = None 47 | self.dest_folder = None 48 | self.addons_dest_folder = None 49 | self.mod_options = ['FSR4/DLSS FG (Only Optiscaler)','FSR4/DLSSG FG (Only Optiscaler)'] 50 | self.mod_selected = None 51 | self.addons_listbox_visible = False 52 | self.selected_addon = None 53 | self.game_options_listbox_visible = False 54 | self.disable_sigover_var = IntVar(value=0) 55 | self.enable_sigover_var = IntVar(value=0) 56 | self.enable_dlss_overlay_var = IntVar(value=0) 57 | self._active_dropdown = None 58 | self._active_menu_btn = None 59 | self.gpu_name = get_active_gpu() 60 | self.total_steps_progress = 0 61 | self.completed_steps_progress = 0 62 | self.progress_finished = False 63 | 64 | self.build_ui() 65 | 66 | def build_ui(self): 67 | self.game_selection() 68 | self.folder_selection() 69 | self.mod_selection() 70 | self.addons_selection() 71 | self.enable_signature_override() 72 | self.disable_signature_override() 73 | self.enable_dlss_overlay() 74 | self.guide() 75 | self.toopTip() 76 | self.install_gui() 77 | self.exit() 78 | self.cleanup_mod() 79 | self.root.iconbitmap("images\\Hat.ico") 80 | 81 | def run(self): 82 | self.root.mainloop() 83 | 84 | def load_update_state(self): 85 | os.makedirs(os.path.dirname(UPDATE_STATE_FILE), exist_ok=True) 86 | return load_or_create_json("update_state.json", {"hidden": False}) 87 | 88 | def save_update_state(self): 89 | try: 90 | # Remove all atributtes 91 | subprocess.call(["attrib", "-h", "-r", UPDATE_STATE_FILE], 92 | stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 93 | 94 | with open(UPDATE_STATE_FILE, "w", encoding="utf-8") as f: 95 | json.dump(self.update_state, f, indent=4) 96 | 97 | # Hidden + ReadOnly 98 | subprocess.call(["attrib", "+h", "+r", UPDATE_STATE_FILE], 99 | stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 100 | 101 | except Exception as e: 102 | print("Error saving update_state:", e) 103 | 104 | def check_for_update(self): 105 | def fetch_latest(): 106 | try: 107 | response = requests.get(GITHUB_API_URL, timeout=5) 108 | if response.status_code == 200: 109 | data = response.json() 110 | tag = data.get("tag_name") or data.get("name") or "" 111 | if tag and tag.replace("v", "") > CURRENT_VERSION.replace("v", ""): 112 | self.update_available = True 113 | self.update_version = tag 114 | self.root.after(0, self.show_update_button) 115 | except Exception as e: 116 | print("Error checking for update:", e) 117 | 118 | threading.Thread(target=fetch_latest, daemon=True).start() 119 | 120 | def show_update_button(self): 121 | if self.update_state.get("hidden"): 122 | self.show_discreet_update_icon() 123 | return 124 | 125 | if not self.update_available: 126 | return 127 | 128 | # Update btn 129 | self.update_btn = CTkButton( 130 | self.root, 131 | text="Update", 132 | width=70, 133 | height=25, 134 | corner_radius=8, 135 | fg_color="#ff9933", 136 | hover_color="#ffb366", 137 | text_color="white", 138 | command=self.open_latest_release 139 | ) 140 | self.update_btn.place(x=170, y=227) 141 | 142 | bind_tooltip(self.update_btn, f"Update available ({self.update_version})", 0) 143 | 144 | # Close btn 145 | self.close_update_btn = CTkButton( 146 | self.root, 147 | text="X", 148 | width=25, 149 | height=25, 150 | corner_radius=8, 151 | fg_color="#555a64", 152 | hover_color="#777", 153 | text_color="white", 154 | command=self.hide_update_button 155 | ) 156 | self.close_update_btn.place(x=245, y=227) 157 | 158 | bind_tooltip(self.close_update_btn, "Close", 0) 159 | 160 | def hide_update_button(self): 161 | if hasattr(self, "update_btn"): 162 | self.update_btn.destroy() 163 | if hasattr(self, "close_update_btn"): 164 | self.close_update_btn.destroy() 165 | 166 | self.update_state["hidden"] = True 167 | self.save_update_state() 168 | self.show_discreet_update_icon() 169 | 170 | def show_discreet_update_icon(self): 171 | if hasattr(self, "update_icon_btn"): 172 | return 173 | 174 | self.update_icon_btn = CTkButton( 175 | self.root, 176 | text="⬇", 177 | width=25, 178 | height=25, 179 | corner_radius=50, 180 | fg_color="#3a3f4b", 181 | hover_color="#5b6373", 182 | text_color="white", 183 | command=self.open_latest_release 184 | ) 185 | self.update_icon_btn.place(x=415, y=330) 186 | bind_tooltip(self.update_icon_btn, f"Update available ({self.update_version})", 0) 187 | 188 | def open_latest_release(self): 189 | webbrowser.open("https://github.com/P4TOLINO06/FSR3.0-Mod-Setup-Utility/releases/latest") 190 | 191 | def main_screen(self): 192 | # ICON 193 | icon_image = tk.PhotoImage(file="images\\Hat.gif") 194 | self.root.iconphoto(True, icon_image) 195 | 196 | # FONT 197 | self.change_text = False 198 | 199 | try: 200 | self.font_selected = (self.font, 11, 'bold') 201 | 202 | self.var_font = tk.Label(self.root,text=".", font=self.font,fg=COLORS['fg'], bg=COLORS['bg']) 203 | 204 | except tk.TclError: 205 | self.font_selected = tkFont.Font(family="Arial",size=10) 206 | self.change_text = True 207 | 208 | second_tittle = tk.Label(self.root, text="FSR4/DLSS FG Mods", font=("Arial", 11, "bold"), fg="#778899", bg=COLORS['bg']) 209 | second_tittle.pack(anchor='w',pady=0) 210 | 211 | # GAME SELECTION 212 | def game_selection(self): 213 | self.game_selected_label = CTkLabel( 214 | self.root, text="Game Select -", 215 | font=(self.font[0], 17, 'bold'), 216 | text_color=COLORS["text"] 217 | ) 218 | self.game_selected_label.place(x=0,y=1) 219 | 220 | self.game_selected, self.game_options_menu = self._Combobox(self.root,game_config,115,4) 221 | 222 | self.game_selected.trace_add("write", self.on_game_selected) 223 | 224 | def on_game_selected(self, *_): 225 | game = self.game_selected.get() 226 | if game: 227 | self.update_mod_list(game, game_mods_config) 228 | 229 | if hasattr(self, "sub_addons"): 230 | self.update_addons_for_game() 231 | 232 | print(game) 233 | 234 | # FOLDER SELECTION 235 | def folder_selection(self): 236 | self.selected_folder = None 237 | 238 | self.game_folder_label = CTkLabel( 239 | self.root, text="Game folder -", 240 | font=(self.font[0], 17, 'bold'), 241 | text_color=COLORS["text"] 242 | ) 243 | self.game_folder_label.place(x=0,y=35) 244 | 245 | self.text_selected_game_folder,self.selected_game_folder_canvas = self._CanvasLabel(self.root, self.dest_folder, 115,38, text_color="white") 246 | 247 | self.btn_selected_folder = self._Button( 248 | self.root, 249 | "Browser", 250 | 359, 39, 251 | lambda: self.open_explorer("dest_folder", self.text_selected_game_folder, self.selected_game_folder_canvas), 252 | fg_color="#555a64", 253 | hover_color="#6b7180" 254 | ) 255 | 256 | # OPEN EXPLORER 257 | def open_explorer(self, attr_name, text_var=None, widget=None, limited_text=29): 258 | folder = filedialog.askdirectory() 259 | 260 | setattr(self, attr_name, folder if folder else "") 261 | 262 | if folder: 263 | if text_var: 264 | 265 | text_var.set(folder if len (folder) <= limited_text else folder[:limited_text] + "…") 266 | 267 | if widget: 268 | bind_tooltip(widget, folder, 0) 269 | else: 270 | if text_var: 271 | text_var.set("") 272 | if widget: 273 | bind_tooltip(widget, "", 0) 274 | 275 | # MOD SELECTION 276 | def mod_selection(self): 277 | self.mod_version_label = CTkLabel( 278 | self.root, text="Mod version -", 279 | font=(self.font[0], 17, 'bold'), 280 | text_color=COLORS["text"] 281 | ) 282 | self.mod_version_label.place(x=0,y=69) 283 | 284 | self.mod_selected, self.mod_version_menu = self._Combobox(self.root, self.mod_options, 115, 72, max_visible_items=4) 285 | 286 | def update_mod_list(self, game_selected, game_mods_config): 287 | 288 | if game_selected in game_mods_config: 289 | self.mod_options = game_mods_config[game_selected] 290 | else: 291 | self.mod_options = ['FSR4/DLSS FG (Only Optiscaler)','FSR4/DLSSG FG (Only Optiscaler)'] 292 | 293 | self.mod_selected, self.mod_version_menu = self._Combobox(self.root, self.mod_options, 115, 72, max_visible_items=4) 294 | 295 | print(self.mod_selected.get()) 296 | 297 | def on_mod_selected(self, event=None): 298 | self.mod_selected = self.mod_version_menu.get() 299 | self.mod_version_menu.place_forget() 300 | 301 | # ADDONS 302 | def addons_selection(self): 303 | # Addons Cbox 304 | self.addons_var = IntVar() 305 | self.addons_cbox = CTkCheckBox( 306 | self.root, text="Addons", 307 | font=self.font, 308 | fg_color=COLORS["accent"], 309 | hover_color="#66b2ff", 310 | text_color=COLORS["text"], 311 | variable=self.addons_var, 312 | corner_radius=7, 313 | checkbox_width=20, 314 | checkbox_height=20, 315 | command=self.on_addons_toggle 316 | ) 317 | self.addons_cbox.place(x=3, y=108) 318 | 319 | # Addons Btn 320 | self.addons_selected = tk.StringVar(value="") 321 | 322 | self.addons_canvas_btn = CTkButton( 323 | self.root, 324 | textvariable=self.addons_selected, 325 | width=238, 326 | height=25, 327 | corner_radius=6, 328 | fg_color="#2e3a5a", 329 | hover_color="#6a5aff", 330 | text_color="white", 331 | font=self.font, 332 | command=self.toggle_addons_menu 333 | ) 334 | self.addons_canvas_btn.place(x=115, y=108) 335 | 336 | # Browser 337 | self.addons_selected_btn = self._Button( 338 | self.root, 339 | "Browser", 340 | 359, 109, 341 | command=lambda: self.open_explorer("addons_dest_folder", None, self.addons_selected_btn, 0), 342 | fg_color="#555a64", 343 | hover_color="#6b7180" 344 | ) 345 | 346 | addons_opt = ["FSR4", "FSR3", "DLSS", "DLSSG", "DLSSD", "XESS"] 347 | self.sub_addons = {n: IntVar() for n in addons_opt} 348 | 349 | self._addons_dropdown = None 350 | 351 | def on_addons_toggle(self): 352 | if not self.addons_var.get(): 353 | if self._addons_dropdown and self._addons_dropdown.winfo_exists(): 354 | self._addons_dropdown.destroy() 355 | self._addons_dropdown = None 356 | self.addons_selected.set("") 357 | [v.set(0) for v in self.sub_addons.values()] 358 | bind_tooltip(self.addons_canvas_btn, "") 359 | else: 360 | self.update_addons_for_game() 361 | 362 | def toggle_addons_menu(self): 363 | if not self.addons_var.get(): 364 | return 365 | 366 | if self._addons_dropdown and self._addons_dropdown.winfo_exists(): 367 | self._addons_dropdown.destroy() 368 | self._addons_dropdown = None 369 | self.root.unbind_all("") 370 | return 371 | 372 | dropdown = tk.Toplevel(self.root) 373 | dropdown.wm_overrideredirect(True) 374 | dropdown.configure(bg="#353535") 375 | dropdown.pack_propagate(False) 376 | 377 | self._addons_dropdown = dropdown 378 | 379 | x = self.addons_canvas_btn.winfo_rootx() 380 | y = self.addons_canvas_btn.winfo_rooty() + self.addons_canvas_btn.winfo_height() 381 | 382 | # DPI/Height 383 | dpi_scale = self.root.winfo_fpixels('1i') / 96 384 | item_height = int(30 * dpi_scale) 385 | 386 | visible_count = len(self.sub_addons) 387 | 388 | # Submenu limit 389 | min_items = 2 390 | max_items = 3 391 | 392 | dropdown_items = max(min_items, min(visible_count, max_items)) 393 | height = dropdown_items * item_height 394 | 395 | # Width 396 | self.addons_canvas_btn.update_idletasks() 397 | button_width = self.addons_canvas_btn.winfo_width() 398 | 399 | scroll_width = 25 400 | canvas_width = button_width - scroll_width 401 | 402 | width_total = button_width 403 | 404 | self.root.bind( 405 | "", 406 | lambda e, w=dropdown, b=self.addons_canvas_btn, wt=width_total, ht=height: 407 | self.follow_root(w, b, wt, ht), 408 | add="+" 409 | ) 410 | 411 | dropdown.geometry(f"{width_total}x{height}+{x}+{y}") 412 | 413 | # Canvas 414 | canvas = tk.Canvas(dropdown, bg="#353535", highlightthickness=0, bd=0, 415 | width=canvas_width, height=height) 416 | canvas.pack(side="left", fill="y") 417 | 418 | # Scrollbar 419 | scroll = tk.Scrollbar(dropdown, command=canvas.yview) 420 | scroll.pack(side="right", fill="y") 421 | canvas.configure(yscrollcommand=scroll.set) 422 | 423 | # Frame Cboxes 424 | inner = tk.Frame(canvas, bg="#353535") 425 | canvas.create_window((0, 0), window=inner, anchor="nw", width=canvas_width) 426 | 427 | canvas.bind("", lambda e, c=canvas: 428 | self.root.bind_all("", lambda ev: self.on_mouse_wheel(ev, c))) 429 | canvas.bind("", lambda e: self.root.unbind_all("")) 430 | 431 | # Upscalers cbox 432 | for name, var in self.sub_addons.items(): 433 | CTkCheckBox(inner, text=name, variable=var, font=self.font, 434 | text_color="white", fg_color="#555a64", hover_color="#6b7180", 435 | corner_radius=5, checkbox_width=18, checkbox_height=18, 436 | command=self.update_addons_button_text).pack(anchor="w", padx=10, pady=3) 437 | 438 | inner.configure(bg="#353535") 439 | inner.update_idletasks() 440 | canvas.configure(scrollregion=canvas.bbox("all")) 441 | 442 | self.root.bind("", lambda e: ( 443 | dropdown.destroy(), 444 | setattr(self, "_addons_dropdown", None), 445 | self.root.unbind_all(""), 446 | self.root.unbind("") 447 | ) if dropdown and not dropdown.winfo_containing(e.x_root, e.y_root) else None, add="+") 448 | 449 | def update_addons_button_text(self): 450 | selected = [name for name, var in self.sub_addons.items() if var.get()] 451 | text = ", ".join(selected) if selected else "" 452 | 453 | self.addons_selected.set(text[:33]) 454 | 455 | bind_tooltip(self.addons_canvas_btn, text) 456 | 457 | def update_addons_for_game(self): 458 | # Marks the upscaler preset according to the selected game 459 | game = self.game_selected.get() if self.game_selected else None 460 | if not game: 461 | return 462 | 463 | for v in self.sub_addons.values(): 464 | v.set(0) 465 | 466 | for combo, games in addons_presets.items(): 467 | if game in games: 468 | for name in combo.split("_"): 469 | if name in self.sub_addons: 470 | self.sub_addons[name].set(1) 471 | 472 | if self.addons_var.get() == 1: 473 | self.update_addons_button_text() 474 | 475 | def get_selected_upscalers(self): 476 | return {k: v.get() for k, v in self.sub_addons.items()} 477 | 478 | # ENABLE SIGNATURE OVERRIDE 479 | def enable_signature_override(self): 480 | self._Checkbox( 481 | "Enable Signature", 3, 147, "enable_sigover_var", 482 | command=lambda: runReg("mods\\Temp\\enable signature override\\EnableSignatureOverride.reg") 483 | if self.enable_sigover_var.get() == 1 else None 484 | ) 485 | 486 | # DISABLE SIGNATURE OVERRIDE 487 | def disable_signature_override(self): 488 | self._Checkbox( 489 | "Disable Signature", 163, 147, "disable_sigover_var", 490 | command=lambda: runReg("mods\\Temp\\disable signature override\\DisableSignatureOverride.reg") 491 | if self.disable_sigover_var.get() == 1 else None 492 | ) 493 | 494 | # DLSS Overlay (Only RTX) 495 | def enable_dlss_overlay(self): 496 | visible = 'rtx' in self.gpu_name 497 | self.dlss_overlay_cbox = self._Checkbox( 498 | "DLSS Overlay", 323, 147, "enable_dlss_overlay_var", 499 | command=lambda: runReg( 500 | "mods\\Addons_mods\\DLSS Preset Overlay\\Enable Overlay.reg" 501 | if self.enable_dlss_overlay_var.get() == 1 else 502 | "mods\\Addons_mods\\DLSS Preset Overlay\\Disable Overlay.reg" 503 | ), 504 | visible=visible 505 | ) 506 | 507 | def guide(self): 508 | self.fsr_guide = FsrGuide(self.root, self._Combobox) 509 | 510 | self.guide_cbox = self._Checkbox( 511 | "GUIDE", 163, 186, "fsr_guide_var", command=self.fsr_guide.toggle_guide, variable=self.fsr_guide.fsr_guide_var 512 | ) 513 | 514 | # Cleanup Mods 515 | def cleanup_mod(self): 516 | self.cleanup_var = IntVar() 517 | self.cleanup_cbox = self._Checkbox( 518 | "Cleanup Mod", 3, 186, "cleanup_var", command=self.cbox_cleanup 519 | ) 520 | 521 | def cbox_cleanup(self): 522 | if self.cleanup_var.get() == 1: 523 | try: 524 | if self.dest_folder is None: 525 | messagebox.showinfo('Select Folder','Please select the destination folder') 526 | self.cleanup_cbox.deselect() 527 | return 528 | 529 | if not messagebox.askyesno('Uninstall','Would you like to proceed with the uninstallation of the mod?'): 530 | self.cleanup_cbox.deselect() 531 | return 532 | 533 | total = count_cleanup_items(self.dest_folder, self.game_selected.get() if self.game_selected else None, self.mod_selected.get() if self.mod_selected else None) 534 | self.cleanup_total_steps = total 535 | self.cleanup_completed_steps = 0 536 | self.cleanup_finished = False 537 | self.cleanup_progress.set(0) 538 | 539 | if total == 0: 540 | messagebox.showinfo('Info','No files were found for removal.') 541 | self.cleanup_cbox.after(400, self.cleanup_cbox.deselect) 542 | return 543 | 544 | threading.Thread(target=self._run_cleanup_thread, daemon=True).start() 545 | 546 | except Exception as e: 547 | print(e) 548 | 549 | def toopTip(self): 550 | # Game selection tooltip 551 | bind_tooltip(self.game_selected_label, "Select a game to install the mod.", 0) 552 | 553 | # Game folder tooltip 554 | bind_tooltip(self.game_folder_label, "Select the game folder where you want to install the mod.", 0) 555 | 556 | # Mod version tooltip 557 | bind_tooltip(self.mod_version_label, "Select the version of the mod you want to install\n(it is recommended to check the FSR Guide before installing any mod).", 0) 558 | 559 | # Guide tooltip 560 | bind_tooltip(self.guide_cbox, "It includes installation guides for most games\n(it is highly recommended to check the guides before performing any installation).", 0) 561 | 562 | # DLSS Overlay tooltip 563 | bind_tooltip(self.dlss_overlay_cbox, "Displays the version and preset of DLSS being used,\nas well as the version of DLSSG currently active\n(Check or uncheck the box to enable/disable)", 0) 564 | 565 | # INSTALL 566 | def install_gui(self, event=None): 567 | self.install_button = CTkButton( 568 | self.root, text="Install", 569 | fg_color=COLORS["accent"], 570 | text_color="white", 571 | corner_radius=8, 572 | hover_color="#66b2ff", 573 | width=70, 574 | height=25, 575 | command=self.start_install_thread 576 | ) 577 | self.install_button.place(x=90, y=227) 578 | 579 | # Installation progress 580 | self.progress = CTkProgressBar( 581 | self.root, 582 | width=153, 583 | height=15, 584 | corner_radius=8 585 | ) 586 | self.progress.set(0) 587 | 588 | # Cleanup progress 589 | self.cleanup_progress = CTkProgressBar( 590 | self.root, 591 | width=153, 592 | height=15, 593 | corner_radius=8 594 | ) 595 | self.cleanup_progress.set(0) 596 | 597 | self.cleanup_total_steps = 0 598 | self.cleanup_completed_steps = 0 599 | self.cleanup_finished = False 600 | 601 | def install(self, event=None): 602 | self.progress_finished = False 603 | 604 | fields = { 605 | "Game": self.game_selected.get() if self.game_selected else "", 606 | "Destination Folder": self.dest_folder.strip() if self.dest_folder else "", 607 | "Mod": self.mod_selected.get() if self.mod_selected else "" 608 | } 609 | 610 | self.total_steps_progress = 0 611 | self.completed_steps_progress = 0 612 | self.progress.set(0) 613 | 614 | # Total Files 615 | selected_upscalers = self.get_selected_upscalers() 616 | 617 | self.total_steps_progress = self.files_to_install_progress(self.dest_folder, selected_upscalers) 618 | print(f"Total mods files to install: {self.total_steps_progress}") 619 | 620 | try: 621 | installer = ModInstaller( 622 | dest_path=self.dest_folder, 623 | game_selected=self.game_selected.get() if self.game_selected else None, 624 | mod_selected=self.mod_selected.get() if self.mod_selected else None, 625 | progress_callback=self.update_progress 626 | ) 627 | 628 | if self.missing_fields(fields, True): 629 | handler = installer.game_handlers.get(self.game_selected.get()) 630 | mod_handler = installer.mods_handlers.get(self.mod_selected.get()) 631 | 632 | def install_mod(): 633 | if handler: 634 | self.run_handler(handler) 635 | if mod_handler: 636 | self.run_handler(mod_handler) 637 | 638 | mod_thread = threading.Thread(target=install_mod) 639 | mod_thread.start() 640 | mod_thread.join() 641 | 642 | if self.addons_var.get(): 643 | def install_addons(): 644 | try: 645 | dest_path = self.addons_dest_folder or self.dest_folder 646 | games_to_update_upscalers( 647 | dest_path, 648 | self.game_selected.get(), 649 | copy_dlss=bool(selected_upscalers["DLSS"]), 650 | copy_dlss_dlssg=bool(selected_upscalers["DLSSG"]), 651 | copy_dlss_dlssd=bool(selected_upscalers["DLSSD"]), 652 | copy_dlss_xess=bool(selected_upscalers["XESS"]), 653 | copy_dlss_fsr3=bool(selected_upscalers["FSR3"]), 654 | copy_dlss_fsr4=bool(selected_upscalers["FSR4"]), 655 | progress_callback=self.update_progress, 656 | absolute_path = True if dest_path == self.addons_dest_folder else False 657 | ) 658 | 659 | print(dest_path) 660 | except Exception as e: 661 | print(e) 662 | finally: 663 | self.root.after(200, self.finish_progress) 664 | 665 | addons_thread = threading.Thread(target=install_addons) 666 | addons_thread.start() 667 | addons_thread.join() 668 | else: 669 | self.root.after(200, self.finish_progress) 670 | 671 | except Exception as e: 672 | messagebox.showwarning('Error', f'Installation error {self.addons_selected.get()}') 673 | print(f"Error: {e}") 674 | 675 | def files_to_install_progress(self, dest_path, selected_upscalers): 676 | total = 0 677 | 678 | # Mods 679 | mod_dirs = [] 680 | if dest_path and os.path.exists(dest_path): 681 | mod_dirs.append(dest_path) 682 | 683 | # Addons 684 | upscaler_paths = { 685 | "DLSS": r'mods\Temp\Upscalers\Nvidia\Dlss', 686 | "DLSSG": r'mods\Temp\Upscalers\Nvidia\Dlssg', 687 | "DLSSD": r'mods\Temp\Upscalers\Nvidia\Dlssd', 688 | "XESS": r'mods\Temp\Upscalers\Intel', 689 | "FSR3": r'mods\Temp\Upscalers\AMD\FSR3', 690 | "FSR4": r'mods\Temp\Upscalers\AMD\FSR4' 691 | } 692 | 693 | 694 | # Only marked addons 695 | if selected_upscalers.get("DLSS"): 696 | mod_dirs.append(upscaler_paths["DLSS"]) 697 | 698 | if selected_upscalers.get("DLSSG"): 699 | mod_dirs.append(upscaler_paths["DLSSG"]) 700 | 701 | if selected_upscalers.get("DLSSD"): 702 | mod_dirs.append(upscaler_paths["DLSSD"]) 703 | 704 | if selected_upscalers.get("XESS"): 705 | mod_dirs.append(upscaler_paths["XESS"]) 706 | 707 | if selected_upscalers.get("FSR3"): 708 | mod_dirs.append(upscaler_paths["FSR3"]) 709 | 710 | if selected_upscalers.get("FSR4"): 711 | mod_dirs.append(upscaler_paths["FSR4"]) 712 | 713 | # Total Files 714 | for path in mod_dirs: 715 | if os.path.isfile(path): 716 | total += 1 717 | elif os.path.isdir(path): 718 | for _, _, files in os.walk(path): 719 | total += len(files) 720 | 721 | return total 722 | 723 | def start_install_thread(self): 724 | threading.Thread(target=self.install, daemon=True).start() 725 | 726 | def finish_progress(self): 727 | if self.progress_finished: 728 | return 729 | self.progress_finished = True 730 | 731 | self.progress.place_forget() 732 | self.sucess = CTkLabel( 733 | self.root, text="Successful installation!", 734 | font=(self.font[0], 15, 'bold'), 735 | fg_color="#222223", 736 | text_color="#A8B0C0" 737 | ) 738 | self.sucess.place(x=5, y=260) 739 | self.root.after(3000, self.sucess.destroy) 740 | 741 | def update_progress(self): 742 | if self.progress_finished or not self.total_steps_progress: 743 | return 744 | 745 | def safe_update(): 746 | if self.progress_finished: 747 | return 748 | 749 | self.completed_steps_progress += 1 750 | value = min(self.completed_steps_progress / self.total_steps_progress, 1.0) 751 | self.progress.set(value) 752 | 753 | if not self.progress.winfo_ismapped(): 754 | self.progress.place(x=5, y=265) 755 | 756 | self.root.after(0, safe_update) 757 | 758 | def _run_cleanup_thread(self): 759 | try: 760 | if not self.cleanup_progress.winfo_ismapped(): 761 | self.cleanup_progress.place(x=0, y=300) 762 | 763 | setup_cleanup( 764 | self.dest_folder, 765 | self.game_selected.get() if self.game_selected else None, 766 | self.mod_selected.get() if self.mod_selected else None, 767 | progress_callback=self.update_cleanup_progress 768 | ) 769 | 770 | self.root.after(200, self._finish_cleanup) 771 | 772 | except Exception as e: 773 | print("Cleanup error:", e) 774 | self.root.after(200, lambda: messagebox.showwarning('Error', f'Cleanup error, please try again')) 775 | self.root.after(400, self.cleanup_cbox.deselect) 776 | 777 | def update_cleanup_progress(self): 778 | def safe_update(): 779 | if self.cleanup_finished or not self.cleanup_total_steps: 780 | return 781 | self.cleanup_completed_steps += 1 782 | value = min(self.cleanup_completed_steps / self.cleanup_total_steps, 1.0) 783 | self.cleanup_progress.set(value) 784 | self.root.after(0, safe_update) 785 | 786 | def _finish_cleanup(self): 787 | if self.cleanup_finished: 788 | return 789 | self.cleanup_finished = True 790 | self.cleanup_progress.place_forget() 791 | self.cleanup_success = CTkLabel( 792 | self.root, text="Successful cleanup!", 793 | font=(self.font[0], 15, 'bold'), 794 | fg_color="#222223", 795 | text_color="#A8B0C0" 796 | ) 797 | self.cleanup_success.place(x=5, y=300) 798 | self.root.after(3000, self.cleanup_success.destroy) 799 | self.cleanup_cbox.after(400, self.cleanup_cbox.deselect) 800 | 801 | # EXIT 802 | def exit(self): 803 | self._Button(self.root, "Exit", 3, 227, sys.exit, width=70, height=25, fg_color=COLORS["accent"], hover_color="#66b2ff") 804 | 805 | def run_handler(self,handler): 806 | if inspect.iscoroutinefunction(handler): 807 | asyncio.run(handler()) 808 | else: 809 | handler() 810 | 811 | def missing_fields(self, fields: dict, message = False): 812 | 813 | missing = [name for name, value in fields.items() if not value] 814 | 815 | if missing: 816 | if message: 817 | messagebox.showinfo( 818 | "Missing Information", 819 | "Please fill out the following field(s): " + ", ".join(missing) 820 | ) 821 | return False 822 | 823 | return True 824 | 825 | def on_mouse_wheel(self, event, canvas): 826 | if canvas.winfo_exists(): 827 | canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") 828 | 829 | def _Combobox( 830 | self, root, options, place_x, place_y, 831 | width=238, height=25, width_menu=0, limit_chars=32, max_visible_items=5, 832 | fg_color="#2e3a5a", hover_color="#6a5aff", cbox_var=None, persistent=False): 833 | 834 | selected = tk.StringVar(value="") 835 | 836 | menu_btn = CTkButton( 837 | root, text="", 838 | width=width, height=height, 839 | corner_radius=6, font=self.font, 840 | fg_color=fg_color, hover_color=hover_color, text_color="white" 841 | ) 842 | menu_btn._persistent = persistent 843 | menu_btn.place(x=place_x, y=place_y) 844 | 845 | def close_active_dropdown(ignore_persistent: bool = False): 846 | ad = getattr(self, "_active_dropdown", None) 847 | amb = getattr(self, "_active_menu_btn", None) 848 | 849 | if amb and getattr(amb, "_persistent", False) and not ignore_persistent: 850 | return 851 | 852 | closed_something = False 853 | if ad and ad.winfo_exists(): 854 | try: 855 | ad.destroy() 856 | closed_something = True 857 | except Exception: 858 | pass 859 | 860 | if closed_something: 861 | self.root.unbind_all("") 862 | 863 | if closed_something or ignore_persistent: 864 | self._active_dropdown = None 865 | self._active_menu_btn = None 866 | 867 | def toggle_menu(): 868 | if hasattr(menu_btn, "_dropdown") and menu_btn._dropdown and menu_btn._dropdown.winfo_exists(): 869 | try: menu_btn._dropdown.destroy() 870 | except: pass 871 | menu_btn._dropdown = None 872 | root.unbind_all("") 873 | return 874 | 875 | if (getattr(self, "_active_menu_btn", None) is menu_btn and 876 | getattr(self, "_active_dropdown", None) and self._active_dropdown.winfo_exists()): 877 | close_active_dropdown() 878 | return 879 | 880 | close_active_dropdown() 881 | 882 | if cbox_var is not None and not bool(cbox_var.get()): 883 | return 884 | 885 | x = menu_btn.winfo_rootx() 886 | y = menu_btn.winfo_rooty() + menu_btn.winfo_height() 887 | 888 | menu_btn.update_idletasks() 889 | button_width = menu_btn.winfo_width() 890 | 891 | menu_width = max(button_width, width_menu) if width_menu > 0 else button_width 892 | 893 | item_height = int(30 * self.root.winfo_fpixels('1i') / 96) 894 | 895 | visible_count = min(len(options), max_visible_items) 896 | 897 | min_items = 2 898 | max_items = 6 899 | 900 | dropdown_height = max( 901 | min_items * item_height, 902 | min(visible_count, max_items) * item_height + 10 903 | ) 904 | 905 | dropdown = tk.Toplevel(root) 906 | dropdown.wm_overrideredirect(True) 907 | 908 | self._active_dropdown = dropdown 909 | self._active_menu_btn = menu_btn 910 | menu_btn._dropdown = dropdown 911 | 912 | dropdown.geometry(f"{menu_width}x{dropdown_height}+{x}+{y}") 913 | 914 | root.bind( 915 | "", 916 | lambda e, w=dropdown, b=menu_btn: self.follow_root(w, b, menu_width, dropdown_height), 917 | add="+" 918 | ) 919 | 920 | frame = tk.Frame(dropdown, bg="#353535") 921 | frame.pack(fill="both", expand=True) 922 | 923 | needs_scroll = len(options) > max_visible_items 924 | scrollbar_width = 15 if needs_scroll else 0 925 | canvas_width = menu_width - scrollbar_width 926 | 927 | canvas = tk.Canvas( 928 | frame, 929 | bg="#353535", 930 | highlightthickness=0, 931 | bd=0, 932 | width=canvas_width, 933 | height=dropdown_height, 934 | ) 935 | scrollbar = None 936 | 937 | if needs_scroll: 938 | scrollbar = tk.Scrollbar( 939 | frame, orient="vertical", 940 | command=canvas.yview, bd=0, highlightthickness=0 941 | ) 942 | canvas.configure(yscrollcommand=scrollbar.set) 943 | scrollbar.pack(side="right", fill="y") 944 | 945 | canvas.pack(side="left", fill="both", expand=True) 946 | 947 | inner = tk.Frame(canvas, bg="#353535") 948 | inner_id = canvas.create_window((0, 0), window=inner, anchor="nw", width=canvas_width) 949 | 950 | canvas.bind("", lambda e: self.root.bind_all("", lambda ev: self.on_mouse_wheel(ev, canvas))) 951 | canvas.bind("", lambda e: self.root.unbind_all("")) 952 | 953 | for opt in options: 954 | lbl = tk.Label( 955 | inner, 956 | text=opt, 957 | anchor="w", 958 | bg="#353535", 959 | fg="white", 960 | padx=15, 961 | pady=7, 962 | cursor="hand2", 963 | wraplength=menu_width - 30, 964 | font=(self.font[0], 10) 965 | ) 966 | lbl.pack(fill="x") 967 | 968 | lbl.bind("", lambda e, w=lbl: w.configure(bg="#444444")) 969 | lbl.bind("", lambda e, w=lbl: w.configure(bg="#353535")) 970 | 971 | def on_label_click(v=opt): 972 | selected.set(v) 973 | menu_btn.configure(text=(v if len(v) <= limit_chars else v[:limit_chars] + "…")) 974 | bind_tooltip(menu_btn, "", 0) 975 | bind_tooltip(menu_btn, v, limit_chars) 976 | 977 | if not persistent: 978 | close_active_dropdown() 979 | 980 | lbl.bind("", lambda e, v=opt: on_label_click(v)) 981 | 982 | inner.update_idletasks() 983 | canvas.configure(scrollregion=canvas.bbox("all")) 984 | canvas.itemconfig(inner_id, width=canvas_width) 985 | 986 | if not persistent: 987 | dropdown.bind("", lambda e: close_active_dropdown()) 988 | 989 | menu_btn.configure(command=toggle_menu) 990 | return selected, menu_btn 991 | 992 | 993 | def _CanvasLabel(self, root, text, place_x, place_y, width=238, height=25,fg_color="#2e3a5a", text_color="white", corner_radius=6): 994 | 995 | selected = StringVar(value=text) 996 | 997 | label_btn = CTkButton( 998 | root, 999 | textvariable=selected, 1000 | width=width, 1001 | height=height, 1002 | corner_radius=corner_radius, 1003 | fg_color=fg_color, 1004 | text_color=text_color, 1005 | font=self.font, 1006 | hover=False, 1007 | command=None 1008 | ) 1009 | 1010 | label_btn.place(x=place_x, y=place_y) 1011 | 1012 | return selected, label_btn 1013 | 1014 | def _Button(self, root, text, place_x, place_y, command, width=50, height=19, fg_color="#2e3a5a", hover_color="#6a5aff", text_color="white", corner_radius=6, font=None): 1015 | 1016 | btn = CTkButton( 1017 | root, 1018 | text=text, 1019 | command=command, 1020 | width=width, 1021 | height=height, 1022 | corner_radius=corner_radius, 1023 | fg_color=fg_color, 1024 | hover_color=hover_color, 1025 | text_color=text_color, 1026 | font=font if font else self.font 1027 | ) 1028 | 1029 | btn.place(x=place_x, y=place_y) 1030 | return btn 1031 | 1032 | def _Checkbox(self, text, x, y, var_name, command=None, visible=True, variable=None): 1033 | var = variable or IntVar() 1034 | 1035 | checkbox = CTkCheckBox( 1036 | self.root, text=text, font=self.font, 1037 | fg_color=COLORS["accent"], hover_color="#66b2ff", 1038 | text_color=COLORS["text"], 1039 | variable=var, corner_radius=7, 1040 | checkbox_width=20, checkbox_height=20, 1041 | command=command 1042 | ) 1043 | if visible: 1044 | checkbox.place(x=x, y=y) 1045 | 1046 | setattr(self, var_name, var) 1047 | return checkbox 1048 | 1049 | 1050 | def follow_root(self, window, button, width, height): 1051 | if window and window.winfo_exists(): 1052 | x = button.winfo_rootx() 1053 | y = button.winfo_rooty() + button.winfo_height() 1054 | window.geometry(f"{width}x{height}+{x}+{y}") --------------------------------------------------------------------------------