├── 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}")
--------------------------------------------------------------------------------