├── IC1a.qss ├── LauncherHelper.py ├── README.rst ├── SCFFx.png ├── SCFileTools.py ├── banner1.jpg ├── form.ui ├── requirements.txt └── settings.py /IC1a.qss: -------------------------------------------------------------------------------- 1 | 2 | QMainWindow{background-color: #0d2636;} 3 | 4 | /*-----QWidget-----*/ 5 | QWidget 6 | { 7 | /*background-color: #0d2636;*/ 8 | color: #ffffff; 9 | border-color: #1a587d; 10 | 11 | 12 | } 13 | 14 | 15 | QListWidget 16 | { 17 | background-color: #9B396BA7; 18 | color: #ffffff; 19 | 20 | } 21 | 22 | 23 | QMessageBox 24 | { 25 | 26 | background-color: #9B396BA7; 27 | color: #ffffff; 28 | } 29 | 30 | QTableWidget 31 | { 32 | background-color: #9B396BA7; 33 | /* 34 | background-color: transparent; 35 | background-color: transparent;*/ 36 | 37 | } 38 | 39 | QTableView { 40 | 41 | background-color: #9B396BA7; 42 | /* 43 | background-color: transparent; 44 | background-color: transparent;*/ 45 | 46 | 47 | 48 | } 49 | 50 | QHeaderView::section {background-color: transparent;} 51 | QHeaderView {background-color: transparent;} 52 | QTableCornerButton::section {background-color: transparent;} 53 | 54 | QProgressBar 55 | { 56 | 57 | border-radius: 2px; 58 | background-color: #1A5A7F; 59 | 60 | } 61 | 62 | QProgressBar::chunk 63 | { 64 | 65 | border-radius: 2px; 66 | background-color: #3785ae; 67 | } 68 | 69 | 70 | /*-----QMenuBar-----*/ 71 | QMenuBar 72 | { 73 | background-color: #6692fc; 74 | color: #000000; 75 | border-color: #181c21; 76 | 77 | } 78 | 79 | 80 | QMenuBar::item 81 | { 82 | background-color: transparent; 83 | 84 | } 85 | 86 | 87 | QMenuBar::item:selected 88 | { 89 | background-color: #389bff; 90 | 91 | } 92 | 93 | 94 | QMenuBar::item:pressed 95 | { 96 | background-color: #3a76b4; 97 | border: 1px solid #5ab8a0; 98 | margin-bottom: -1px; 99 | padding-bottom: 1px; 100 | 101 | } 102 | 103 | 104 | /*-----QMenu-----*/ 105 | QMenu 106 | { 107 | background-color: #e1f4ff; 108 | border: 1px solid; 109 | color: #000; 110 | 111 | } 112 | 113 | 114 | QMenu::separator 115 | { 116 | height: 1px; 117 | background-color: #000; 118 | color: #ffffff; 119 | padding-left: 4px; 120 | margin-left: 10px; 121 | margin-right: 5px; 122 | 123 | } 124 | 125 | 126 | QMenu::item 127 | { 128 | min-width : 150px; 129 | padding: 3px 20px 3px 20px; 130 | 131 | } 132 | 133 | 134 | QMenu::item:selected 135 | { 136 | background-color: #3a76b4; 137 | color: #ffffff; 138 | 139 | } 140 | 141 | 142 | QMenu::item:disabled 143 | { 144 | color: #262626; 145 | 146 | } 147 | 148 | 149 | /*-----QPushButton-----*/ 150 | QPushButton 151 | { 152 | background-color: #1A5A7F; 153 | 154 | color: #ffffff; 155 | /*border-style: solid;*/ 156 | border-color: #ffaaff; 157 | font-weight: bold; 158 | border-radius: 3px; 159 | font-size: 12px; 160 | font-family: Arial; 161 | 162 | } 163 | 164 | QPushButton:disabled { 165 | background-color: #727c8d; 166 | } 167 | 168 | QPushButton::hover 169 | { 170 | background-color: #188dd8; 171 | 172 | } 173 | 174 | 175 | QPushButton::pressed 176 | { 177 | background-color: #1a587d; 178 | 179 | } 180 | 181 | 182 | /*-----QLineEdit-----*/ 183 | QLineEdit 184 | { 185 | background-color: #12160c; 186 | color: #ffffff; 187 | border: 1px solid #6692fc; 188 | padding: 3px; 189 | 190 | } 191 | 192 | QTextEdit 193 | { 194 | background-color: #12160c; 195 | color: #ffffff; 196 | border: 1px solid #6692fc; 197 | padding: 3px 198 | 199 | } -------------------------------------------------------------------------------- /LauncherHelper.py: -------------------------------------------------------------------------------- 1 | import win32gui 2 | import win32con 3 | import psutil 4 | import subprocess 5 | import time 6 | import win32process 7 | import ctypes 8 | 9 | 10 | def launcher_properties(): 11 | window_title = 'RSI Launcher' 12 | # Find the handle of the program's main window 13 | window_handle = win32gui.FindWindow(None, window_title) 14 | 15 | if window_handle != 0: 16 | process_id = win32process.GetWindowThreadProcessId(window_handle)[1] 17 | process = psutil.Process(process_id) 18 | executable_path = process.exe() 19 | return window_handle, executable_path 20 | else: 21 | return None, None 22 | 23 | 24 | def close_launcher(window_handle): 25 | exe_name = 'RSI Launcher.exe' 26 | # Send a close request to the program's main window 27 | try: 28 | win32gui.PostMessage(window_handle, win32con.WM_CLOSE, 0, 0) 29 | time.sleep(1) 30 | except: 31 | # If the window is already closed, 32 | pass 33 | try: 34 | subprocess.call(['taskkill', '/f', '/im', exe_name], stderr=subprocess.DEVNULL) 35 | except: 36 | # If all remaining processes are killed 37 | pass 38 | 39 | 40 | def open_launcher(executable_location): 41 | if executable_location: 42 | subprocess.Popen(executable_location, shell=True, 43 | stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) 44 | 45 | 46 | def ctypes_input(): 47 | inputs = ctypes.c_buffer(2 * ctypes.sizeof(ctypes.c_ulong)) 48 | 49 | ctypes.windll.user32.keybd_event(0x11, 0, 0, 0) # CTRL key down 50 | ctypes.windll.user32.keybd_event(0x52, 0, 0, 0) # R key down and up 51 | ctypes.windll.user32.keybd_event(0x52, 0, win32con.KEYEVENTF_KEYUP, 0) 52 | ctypes.windll.user32.keybd_event(0x11, 0, win32con.KEYEVENTF_KEYUP, 0) # CTRL key up 53 | 54 | ctypes.windll.user32.SendInput(2, inputs, ctypes.sizeof(ctypes.c_ulong)) 55 | 56 | 57 | if __name__ == '__main__': 58 | window_handle, executable_path = launcher_properties() 59 | if window_handle: 60 | close_launcher(window_handle) 61 | open_launcher(executable_path) 62 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ================================================= 2 | SC File Fiddler ... *Fiddle around and find out!* 3 | ================================================= 4 | 5 | 6 | .. image:: https://media.discordapp.net/attachments/957406732525645855/1116365715142885476/image.png 7 | :align: center 8 | 9 | Wut? 10 | -------- 11 | 12 | **SC File Fiddler is a collection of simple tools for performing those standard 13 | yet tedious tasks when troubleshooting Star Citizen.** 14 | 15 | 16 | | Tired of remembering where your shaders have run off to? 17 | | Sick of losing your settings when you delete that *sweet* USER folder? 18 | | EAC holding your Finley for ransom? 19 | 20 | 21 | Well, *fear no more*! SC File Fiddler is here to help! 22 | 23 | 24 | Browse this documentation to see what you can do with SCFF, and be sure 25 | to look over the `Some Guidance <#some-guidance>`_ section to get a more detailed explanation of the functions. 26 | 27 | ____ 28 | 29 | Quick-Start 30 | ----------- 31 | *Because who has time to read words?* 32 | 33 | 1. Download `the latest portable version `_. 34 | 2. Run it. 35 | 36 | * Set your RSI directory. This is where your StarCitizen lives. 37 | 38 | * Shaders should already be set correctly, but change it if you need to. 39 | 40 | ``What you can do...`` 41 | 42 | * Shaders - Clear some or all. 43 | * USER Folder - Delete your USER folder for one or more environment. 44 | 45 | * Save your settings and bindings in place if you want to. 46 | 47 | * EAC - Delete EAC files in various locations. (bet you didn't know there were *that* many places!) 48 | * Misc. 49 | 50 | * Open the Game.log file 51 | * Create an 'external game console' using PowerShell! 52 | * Perform robust Launcher operations without having to play in Task Manager or remember key combos. 53 | 54 | ____ 55 | 56 | Features 57 | -------- 58 | 59 | - Clear your shaders - Either selectively or all at once! 60 | - Delete your USER folder, With options to back up your settings and keybindings! 61 | - NUKE EAC out of existence! (yeah it'll be back, but it feels good to watch it burn for a bit) 62 | - Quick access to the Star Citizen log files, including *console emulation* to view the logs in real-time! 63 | - ... *And much more!* 64 | 65 | Run it! 66 | ------- 67 | For cloning and running the python directly: 68 | 69 | Dependencies can be found in the `requirements.txt` file. 70 | 71 | *For the portable executable, just grab it and run it!* 72 | 73 | 74 | Fancy portable executable 75 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 76 | 77 | 78 | For Windows. What, you expected something else? 79 | 80 | **No dependencies or external files required!** 81 | 82 | 1. Installation: 83 | - Download the latest release (zipped bundle or standalone exe) executable from `where latest releases are born `_. 84 | 2. Run the Application: 85 | - Run the standalone executable, or unzip the bundle and run the executable. 86 | - That's it. You win. 87 | 88 | 89 | Git clone 90 | ~~~~~~~~~ 91 | 92 | Probably also exclusively for Windows. Otherwise, this is a rabbit hole for *you* to squeeze your way through. 93 | 94 | 1. Installation: 95 | - Clone the repository: `git clone https://github.com/AlpineFrostSC/SCFileTools.git` 96 | - Install the required dependencies (if needed): `pip install -r requirements.txt` 97 | 98 | 2. Run the Application: 99 | - Navigate to the project directory: `cd SCFileTools` 100 | - Launch the application: `python SCFileTools.py` 101 | - Failure to run the application from within its directory may result in broken file references. No big deal. 102 | 103 | Then just look at things with your eyeballs and identify what you'd like to accomplish. 104 | Alternatively, read some docs at [docLocationNotFoundSryNotSry] for more information. 105 | 106 | 107 | Some Guidance 108 | ------------ 109 | Let's take a look at the sections you see on the main window and the functions that those provide. 110 | 111 | First, please note the "Running as:" in the tile bar of the program. This indicates whether you have run the program as 112 | ADMIN or not. **This is an important point for at least one feature of the program.** But otherwise not a big deal. 113 | 114 | 1. **Folder Paths** 115 | - These couple of lines allow you to choose where your main RSI directory and Shaders folders exist. 116 | - *You'll need to set the RSI directory yourself* (this is where the StarCitizen folder is, containing your important game files), while the shaders directory is initially set to the default location. 117 | This assumes your game is installed inside the ``Roberts Space Industries`` folder somewhere, but you can set it to whatever directory your StarCitizen game folder exists in. Additionally, if you don't 118 | plan on fiddling with shaders, you can ignore the Shaders Directory line. 119 | 120 | 121 | 122 | 2. **Shaders** 123 | - This section is for clearing out your shaders. You can either clear them all at once, or selectively clear them. 124 | - The `Delete All` radio option will delete all the shader files in the Shaders directory, while the `Prompt for folder` 125 | option will allow you to select a specific Shader folder to clear upon clicking the `Delete Shaders` button. 126 | 127 | 3. **USER Folder** 128 | - This section is for deleting your USER folder. You can choose to retain your settings and keybindings **in place** before 129 | deleting the folder, and you can also choose to delete the user folders for more than one environment at a time. 130 | - `Keep exported keybinds` retains your exported binding files, `Keep current keybinds` preserves the ``actionmaps.xml``, and `Keep settings` preserves the ``attributes.xml`` file. All in-place. 131 | - Check one or more environments in which to delete the USER folder (while applying any of the aforementioned options). 132 | 4. **EAC** 133 | - This section is for deleting a bunch of EasyAntiCheat stuff. EAC issues are somewhat common sources of issues 134 | when attempting to launch the game, and there are ``three locations`` where sneaky EAC files reside. 135 | - `"In SC Folders"` will delete the EAC folder contents within any of the selected environments immediately below the button. 136 | - `"In AppData>Roaming"` will delete the EAC folder contents within the AppData\\Roaming directory. 137 | - `"In Program Files"` will delete the ``EasyAntiCheat_EOS.sys`` file within the relevant Program Files (x86) subdirectory. 138 | 139 | + **Note: This one will require elevated permissions. You will need to run the program as ADMIN to allow this.** 140 | 141 | - `"Nuke-It-All™"` will delete all of the above. **Note: This will respect any un-selected environments in the SC Folders section.** 142 | 143 | 5. **Misc.** 144 | - This section includes some tools for the Game.log file, as well as for some Launcher support. 145 | - `Open Game.log` will open the Game.log file in the default text editor. 146 | - `Run Game.log in Powershell...` will open the Game.log file in PowerShell, providing a real-time view of the log 147 | as the game makes changes to it. This effectively mirrors the functionality of the console in the game. 148 | - **Launcher stuff** 149 | 150 | + `Reset Launcher` will clear the contents of the Launcher folder in AppData\\Roaming. This is similar to a conventional 151 | launcher reset, though substantially more thorough. **Launcher will be automatically closed during this process and then restarted.** 152 | + `Fully Close Launcher` will close the launcher window, and exit all remaining ``RSI Launcher.exe`` processes. 153 | + `Re-Launch Launcher` will close the launcher (using the method above), and then reopen it. 154 | + `Refresh Launcher` sends a `Ctrl+R` to your launcher, to refresh it. This is a quick alternative to closing and reopening the launcher to see if a patch has dropped. 155 | 156 | + `"Patch-watch mode"` option will keep sending refresh commands to your launcher *until you un-check it*. Handy if you're *really* eager to grab that patch ASAP! 157 | 158 | Some Mild Caveats 159 | ~~~~~~~~~~~~~~~~~ 160 | 161 | Most of the functions provided by the Fiddler don't require any special permissions. That said, there are a few that do. 162 | At the moment, the one one that appears to require elevated permissions is deleting the EAC file within the 163 | Program Files directory. 164 | 165 | Generally, this program can be run without any consideration to permissions. But if you *really* wanna nuke EAC, you may 166 | want to run this as an administrator. 167 | 168 | .. tip:: This is recommended, since the EAC file tucked away in the Program Files location is one of the common causes of ``CreateFile 32`` errors! 169 | 170 | 171 | A Not-so-mild Disclaimer 172 | ~~~~~~~~~~~~~~~~~~~~~~~~ 173 | This program is provided as-is. I'm not responsible for any damage that may occur to your computer, your spaceships, 174 | your dog, or your Picos. Use at your own risk. 175 | 176 | With that out of the way, there shouldn't be much to lose sleep over. Most functions affect files and directories which 177 | are hard-coded (or nearly so), defined by the user, or determined by windows variables, meaning the worst that is likely to happen is the functions will do what you expect them 178 | to do! 179 | 180 | Of course, it's still your job to `back up your files, and back up your backups`! 181 | 182 | 183 | 184 | Contact 185 | ------- 186 | 187 | For any inquiries or support, you can reach me at GrimHEX on use1c 020. (or `Spectrum 188 | `_. @AlpineFrost) 189 | 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /SCFFx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlpineFrostSC/SCFileTools/d996a809cfcbd37c9b3f749432c5f3b3ea40e44b/SCFFx.png -------------------------------------------------------------------------------- /SCFileTools.py: -------------------------------------------------------------------------------- 1 | 2 | vn='0.3.2' 3 | import sys 4 | import os 5 | 6 | os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" 7 | os.environ["QT_SCREEN_SCALE_FACTORS"] = "1" 8 | os.environ["QT_SCALE_FACTOR"] = "1" 9 | 10 | from shutil import rmtree 11 | import ctypes 12 | import subprocess 13 | import win32gui 14 | from pyautogui import hotkey 15 | from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox 16 | from PyQt5.QtCore import Qt, QThreadPool, QRunnable, pyqtSignal 17 | from PyQt5.QtGui import QFont, QIcon 18 | 19 | from PyQt5.uic import loadUi 20 | import time 21 | 22 | import logging 23 | 24 | 25 | logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') 26 | o = logging.getLogger(__name__) 27 | 28 | import LauncherHelper 29 | import settings 30 | 31 | 32 | def exception_hook(type, value, traceback): 33 | """ 34 | Custom exception hook to handle and print exceptions. 35 | """ 36 | print("Exception Hook:") 37 | print(f"Type: {type}") 38 | print(f"Value: {value}") 39 | print(f"Traceback: {traceback}") 40 | 41 | sys.__excepthook__(type, value, traceback) 42 | 43 | 44 | sys.excepthook = exception_hook 45 | 46 | 47 | def scrub_folder(folder): 48 | """ 49 | Recursively delete files and directories within a folder. 50 | 51 | Args: 52 | folder (str): Path to the folder to be scrubbed. 53 | """ 54 | 55 | if not os.path.exists(folder): 56 | 57 | return 'path' 58 | 59 | for filename in os.listdir(folder): 60 | file_path = os.path.join(folder, filename) 61 | 62 | try: 63 | if os.path.isfile(file_path) or os.path.islink(file_path): 64 | os.unlink(file_path) 65 | 66 | elif os.path.isdir(file_path): 67 | rmtree(file_path) 68 | except Exception as e: 69 | print('Failed to delete %s. Reason: %s' % (file_path, e)) 70 | 71 | def format_path_qss(path): 72 | formatted_path = os.path.normpath(path).replace('\\', '/') 73 | return formatted_path 74 | def get_resource_path(relative_path): 75 | """ 76 | Get the absolute path to a resource, whether in development or PyInstaller bundle. 77 | """ 78 | if getattr(sys, 'frozen', False): 79 | # noinspection PyUnresolvedReferences 80 | base_path = sys._MEIPASS 81 | else: 82 | base_path = os.path.abspath(os.path.dirname(__file__)) 83 | 84 | return os.path.join(base_path, relative_path) 85 | 86 | 87 | def is_admin(): 88 | """ 89 | Check if the current user has admin privileges. 90 | 91 | Returns: 92 | bool: True if the user has admin privileges, False otherwise. 93 | """ 94 | try: 95 | return ctypes.windll.shell32.IsUserAnAdmin() 96 | except: 97 | return False 98 | 99 | 100 | def permission_level(): 101 | """ 102 | Get the permission level of the current user. 103 | 104 | Returns: 105 | str: Permission level, either "ADMIN" or "Non-Admin". 106 | """ 107 | if is_admin(): 108 | return "ADMIN" 109 | else: 110 | return "Non-Admin" 111 | 112 | class Task(QRunnable): 113 | def __init__(self, target_method, *args, **kwargs): 114 | super().__init__() 115 | self.target_method = target_method 116 | self.args = args 117 | self.kwargs = kwargs 118 | 119 | def run(self): 120 | self.target_method(*self.args, **self.kwargs) 121 | 122 | 123 | class SCFF(QMainWindow): 124 | info_signal = pyqtSignal(str, str) 125 | def __init__(self): 126 | super().__init__() 127 | thisid = u'alpine.space.incident.ff' 128 | ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(thisid) 129 | iconpath= get_resource_path("icon_ff.ico") 130 | self.setWindowIcon(QIcon(iconpath)) 131 | 132 | uipath = get_resource_path("form.ui") 133 | 134 | loadUi(uipath, self) 135 | 136 | values=settings.read() 137 | 138 | self.info_signal.connect(self.info) 139 | 140 | self.current_user = os.getenv('USERNAME') 141 | 142 | self.rctime = 0 143 | 144 | self.main_directory = values['rsi_path'] 145 | self.shaders_directory = values['shaders_path'] 146 | 147 | self.LIVE_directory = os.path.join(self.main_directory, "StarCitizen", "LIVE") 148 | self.PTU_directory = os.path.join(self.main_directory, "StarCitizen", "PTU") 149 | self.EPTU_directory = os.path.join(self.main_directory, "StarCitizen", "EPTU") 150 | 151 | # EAC directories 152 | self.eac_roaming = values['eac_roaming_path'] 153 | self.eac_programFiles = values['eac_programfiles_path'] 154 | 155 | self.launcher_roaming = values['launcher_roaming_path'] 156 | 157 | self.RSIPrefix = "RSI: " 158 | self.shadersPrefix = "Shaders: " 159 | 160 | self.misc_log_env = "LIVE" 161 | 162 | self.launcher_handle = None 163 | self.launcher_exe = None 164 | 165 | self.testing = True 166 | 167 | self.is_title_bar_hidden = False 168 | 169 | self.init_ui() 170 | 171 | def init_ui(self): 172 | """ 173 | Initialize the user interface and connect buttons to their respective functions. 174 | """ 175 | # Connect buttons to functions 176 | self.cb_top.clicked.connect(self.toggle_top) 177 | self.pb_setMainDir.clicked.connect(self.select_main_directory) 178 | self.pb_setShadersDir.clicked.connect(self.select_shaders_directory) 179 | self.pb_deleteShaders.clicked.connect(self.delete_shaders) 180 | self.pb_openShaders.clicked.connect(lambda: subprocess.Popen(f'explorer {self.shaders_directory}')) 181 | self.pb_deleteUser.clicked.connect(self.delete_user_folder) 182 | self.pb_EXIT.clicked.connect(self.close) 183 | self.pb_MINIMIZE.clicked.connect(self.showMinimized) 184 | self.pb_misc_launcher1.clicked.connect(lambda: self.reset_launcher(self.cb_launcher_deep.isChecked())) 185 | self.pb_misc_closeLauncher.clicked.connect(self.close_launcher) 186 | self.pb_misc_viewLog.clicked.connect(self.open_game_log) 187 | self.pb_misc_consoleLog.clicked.connect(self.powershell_log) 188 | self.pb_misc_recentErrorLog.clicked.connect(self.open_crash_log) 189 | self.pb_misc_relaunchLauncher.clicked.connect(self.relaunch_launcher) 190 | self.pb_misc_refreshLauncher.clicked.connect(self.refresh_launcher) 191 | 192 | self.pb_EAC_SC.clicked.connect(lambda: self.nuke_EAC(self.pb_EAC_SC)) 193 | self.pb_EAC_roaming.clicked.connect(lambda: self.nuke_EAC(self.pb_EAC_roaming)) 194 | self.pb_EAC_programFiles.clicked.connect(lambda: self.nuke_EAC(self.pb_EAC_programFiles)) 195 | self.pb_EAC_all.clicked.connect(lambda: self.nuke_EAC(self.pb_EAC_all)) 196 | 197 | self.rb_misc_LIVE.clicked.connect(lambda: setattr(self, "misc_log_env", "LIVE")) 198 | self.rb_misc_PTU.clicked.connect(lambda: setattr(self, "misc_log_env", "PTU")) 199 | self.rb_misc_EPTU.clicked.connect(lambda: setattr(self, "misc_log_env", "EPTU")) 200 | 201 | # Set the text of the labels to the current paths 202 | self.l_path_main.setText(self.RSIPrefix + self.main_directory) 203 | self.l_path_shaders.setText(self.shadersPrefix + self.shaders_directory.replace(self.current_user, "[user]")) 204 | 205 | self.l_permission.setText("Running as: " + permission_level()) 206 | 207 | self.chg_size(self.l_title, 14, f'{self.l_title.text()} - v{vn}') 208 | 209 | self.frame.mousePressEvent = self.mouse_press_event_frame 210 | self.frame.mouseMoveEvent = self.mouse_move_event 211 | self.frame.mouseReleaseEvent = self.mouse_release_event 212 | 213 | self.is_title_bar_hidden = False 214 | self.dragging_offset = None 215 | 216 | self.setStyleSheet("QGroupBox {font-size: 14px;}") 217 | self.info_style=QFont("Lucida Console", 10) 218 | 219 | self.l_info_1.setFont(self.info_style) 220 | self.l_info_2.setFont(self.info_style) 221 | 222 | 223 | 224 | self.adjustSize() 225 | 226 | self.toggle_title_bar() 227 | 228 | self.info(preset=0) 229 | 230 | def chg_size(self,element,size, txt=None): 231 | if txt is None: 232 | txt = element.text() 233 | element.setText(f'{txt}') 234 | 235 | def show_info_message(self, message): 236 | msg = QMessageBox() 237 | msg.setIcon(QMessageBox.Information) 238 | msg.setText(message) 239 | msg.setWindowTitle("Information") 240 | msg.exec_() 241 | 242 | def select_main_directory(self): 243 | """ 244 | Open a dialog to select the main directory and update the path accordingly. 245 | """ 246 | directory = QFileDialog.getExistingDirectory(self, "Select Main Directory") 247 | if directory: 248 | self.main_directory = directory 249 | settings.write_one('rsi_path', directory) 250 | self.l_path_main.setText(self.RSIPrefix + directory) 251 | 252 | 253 | def select_shaders_directory(self): 254 | """ 255 | Open a dialog to select the shaders directory and update the path accordingly. 256 | """ 257 | directory = QFileDialog.getExistingDirectory(self, "Select Shaders Directory") 258 | if directory: 259 | self.shaders_directory = directory 260 | settings.write_one('shaders_path', directory) 261 | username = os.path.split(os.path.expanduser("~"))[-1] 262 | display_directory = self.shaders_directory.replace(username, "[user]") 263 | self.l_path_shaders.setText(self.shadersPrefix + display_directory) 264 | 265 | def select_test_directory(self): 266 | """ 267 | Open a dialog to select the test directory and update the path accordingly. 268 | """ 269 | directory = QFileDialog.getExistingDirectory(self, "Select Test Directory") 270 | if directory: 271 | self.test_directory = directory 272 | self.l_testDir.setText(directory) 273 | 274 | def delete_user_folder(self): 275 | 276 | enviro_list = [] 277 | if self.cb_user_LIVE.isChecked(): 278 | enviro_list.append("LIVE") 279 | if self.cb_user_PTU.isChecked(): 280 | enviro_list.append("PTU") 281 | if self.cb_user_EPTU.isChecked(): 282 | enviro_list.append("EPTU") 283 | 284 | main_dir = os.path.join(self.main_directory, 'StarCitizen') 285 | 286 | fc = 0 287 | dc = 0 288 | kept = [] 289 | 290 | for env in enviro_list: 291 | folders_to_keep = [] 292 | files_to_keep = [] 293 | 294 | 295 | user_dir = os.path.join(main_dir, env, "USER") 296 | cli_0_dir = os.path.join(user_dir, "Client", "0") 297 | 298 | if self.cb_user_retainBindingsEx.isChecked(): 299 | folders_to_keep.append(os.path.join(cli_0_dir, "Controls", "Mappings")) 300 | kept.append("Exported Bindings") 301 | if self.cb_user_retainBindingsCur.isChecked(): 302 | files_to_keep.append(os.path.join(cli_0_dir, "Profiles", "default", "actionmaps.xml")) 303 | kept.append("Current Bindings") 304 | if self.cb_user_retainSettings.isChecked(): 305 | files_to_keep.append(os.path.join(cli_0_dir, "Profiles", "default", "attributes.xml")) 306 | kept.append("Settings") 307 | 308 | for root, dirs, files in os.walk(user_dir, topdown=False): 309 | for name in files: 310 | file_path = os.path.join(root, name) 311 | if file_path not in files_to_keep and root not in folders_to_keep: 312 | os.remove(file_path) 313 | fc += 1 314 | 315 | for name in dirs: 316 | dir_path = os.path.join(root, name) 317 | if not os.listdir(dir_path) and dir_path not in folders_to_keep: 318 | os.rmdir(dir_path) 319 | dc += 1 320 | self.info(clear_unused=True) 321 | if len(enviro_list) > 0: 322 | self.info(f"Deleted {fc} files and {dc} folders.") 323 | if len(kept) > 0: 324 | self.info(t2="Kept: " + ", ".join(kept)) 325 | else: 326 | self.info(t2="Kept nothing.") 327 | else: 328 | self.info("No environment selected.") 329 | 330 | def delete_shaders(self): 331 | """ 332 | Delete shaders based on the selected option. 333 | """ 334 | done = False 335 | if self.rb_s_All.isChecked(): 336 | folders = [f for f in os.listdir(self.shaders_directory) if 337 | os.path.isdir(os.path.join(self.shaders_directory, f))] 338 | for folder in folders: 339 | if folder.startswith("sc-alpha"): 340 | folder_path = os.path.join(self.shaders_directory, folder) 341 | rmtree(folder_path) 342 | 343 | done = True 344 | self.info(clear_unused=True) 345 | self.info(t1=f"Deleted all shaders!", t2=f"({len(folders)} folders)") 346 | elif self.rb_s_Pick.isChecked(): 347 | selected_folder = QFileDialog.getExistingDirectory(self, "Select Folder to Delete", self.shaders_directory) 348 | if selected_folder: 349 | rmtree(selected_folder) 350 | done = True 351 | self.info(clear_unused=True) 352 | self.info(t1=f"Deleted shaders in:", t2=f"{selected_folder}") 353 | 354 | def open_game_log(self): 355 | """ 356 | Open the game log file in the default program. 357 | """ 358 | 359 | envpath = os.path.join(self.main_directory, "StarCitizen", self.misc_log_env) 360 | logpath = os.path.join(envpath, "Game.log") 361 | self.info(clear_unused=True) 362 | if not os.path.isfile(logpath): 363 | self.info(f"Log file not found...") 364 | if not os.path.isdir(envpath): 365 | self.info(t2=f"Environment {self.misc_log_env} folder not found...") 366 | return 367 | else: 368 | try: 369 | os.startfile(logpath) 370 | self.info(f"Opened {logpath}") 371 | except: 372 | o.error("Failed to open log file.") 373 | def open_crash_log(self): 374 | """ 375 | Open the most recent crash log. 376 | """ 377 | 378 | logpath = os.path.join(self.shaders_directory, "Crashes") 379 | logfile = os.path.join(logpath,"Game.log") 380 | 381 | self.info(clear_unused=True) 382 | if not os.path.isfile(logfile): 383 | self.info(f"Log file not found...") 384 | if not os.path.isdir(logpath): 385 | self.info(t2=f"Crash folder not found.") 386 | return 387 | else: 388 | try: 389 | os.startfile(logfile) 390 | self.info(f"Opened {logfile}") 391 | except: 392 | o.error("Failed to open log file.") 393 | 394 | def powershell_log(self): 395 | """ 396 | Open PowerShell and display the game log in real-time. 397 | """ 398 | logpath = os.path.join(self.main_directory, "StarCitizen", self.misc_log_env, "Game.log") 399 | 400 | if not os.path.isfile(logpath): 401 | self.info(f"Log file not found...") 402 | if not os.path.isdir(self.misc_log_env): 403 | self.info(t2=f"Environment {self.misc_log_env} folder not found...") 404 | return 405 | 406 | file_path = logpath.replace("\\", "\\\\") 407 | pwershellpath = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" #Probably don't need the full path 408 | command = f'start {pwershellpath} Get-Content \'{file_path}\' -Wait' 409 | self.info(clear_unused=True) 410 | self.info(t1="Opening PowerShell...", t2=f"Log file: {logpath}") 411 | subprocess.call(command, shell=True) 412 | 413 | def nuke_EAC(self, sender): 414 | """ 415 | Nuke Easy Anti-Cheat (EAC) directories and files. 416 | 417 | Chosen environment(s) will be respected for the individual case of 418 | nuking the EAC folders in the SC directories. If the 'nuke all' button 419 | is pressed, these environments will also be included. Un-checked environments 420 | will be ignored in all cases. 421 | 422 | Args: 423 | sender (QPushButton): The button that triggered the function. 424 | This determines which nuke routines will run. If the 'nuke all' button 425 | is pressed, all nuke routines will run. 426 | """ 427 | sender_object = sender.objectName() 428 | enviro_list = [] 429 | if self.cb_EAC_LIVE.isChecked(): 430 | enviro_list.append("LIVE") 431 | if self.cb_EAC_PTU.isChecked(): 432 | enviro_list.append("PTU") 433 | if self.cb_EAC_EPTU.isChecked(): 434 | enviro_list.append("EPTU") 435 | checklist=[] 436 | 437 | if sender_object == 'pb_EAC_SC' and len(enviro_list) == 0: 438 | self.info("No environment selected.",'') 439 | return 440 | 441 | # Nuke the EAC folders in the SC directories 442 | if sender_object == 'pb_EAC_SC' or sender_object == 'pb_EAC_all': 443 | for env in enviro_list: 444 | 445 | print(f"Main directory currently set to: {self.main_directory}") 446 | _ = scrub_folder(os.path.join(self.main_directory, "StarCitizen", env, "EasyAntiCheat")) 447 | if _ == 'path': 448 | self.info(f"Problem finding EAC in {env}...", 'Check your RSI directory setting, or verify that the EAC folder exists.') 449 | if sender_object == 'pb_EAC_SC': 450 | return 451 | checklist.append(env) 452 | 453 | # Nuke the EAC folder in appdata roaming 454 | if sender_object == 'pb_EAC_roaming' or sender_object == 'pb_EAC_all': 455 | scrub_folder(self.eac_roaming) 456 | checklist.append('AppData\\Roaming') 457 | 458 | # Nuke the EasyAntiCheat_EOS.sys file in program files 459 | # This requires admin privileges 460 | if sender_object == 'pb_EAC_programFiles' or sender_object == 'pb_EAC_all': 461 | 462 | PATH = os.path.join(self.eac_programFiles, "EasyAntiCheat_EOS.sys") 463 | try: 464 | print("Attempting to delete EasyAntiCheat_EOS.sys") 465 | if ctypes.windll.shell32.IsUserAnAdmin(): 466 | if os.path.exists(PATH): 467 | os.remove(PATH) 468 | checklist.append('Program Files') 469 | else: 470 | self.info("Incorrect permissions. Please run the program as an administrator and try again.",'') 471 | msg = QMessageBox() 472 | msg.setIcon(QMessageBox.Critical) 473 | msg.setText("Error when attempting to nuke EAC in the Program Files (x86) folder...\n" 474 | "Incorrect permissions. Please run the program as an administrator and try again.") 475 | msg.setWindowTitle("Error") 476 | msg.exec_() 477 | return 478 | except: 479 | print("Failed to delete EasyAntiCheat_EOS.sys") 480 | msg = QMessageBox() 481 | msg.setIcon(QMessageBox.Critical) 482 | msg.setText("Error: Failed to delete EasyAntiCheat_EOS.sys\n" 483 | "--___--") 484 | msg.setWindowTitle("Error") 485 | msg.exec_() 486 | self.info(f'Nuking EAC is complete!',f'Locations liberated: {", ".join(checklist)}') 487 | 488 | 489 | def close_launcher_thread(self): 490 | LauncherHelper.close_launcher(self.launcher_handle) 491 | 492 | def open_launcher_thread(self): 493 | if self.launcher_exe: 494 | LauncherHelper.open_launcher(self.launcher_exe) 495 | else: 496 | o.error("Launcher exe not found. It may not have been running yet.") 497 | 498 | def close_launcher(self): 499 | """ 500 | Close the launcher using the LauncherHelper module. 501 | """ 502 | i_string="Closing launcher and all launcher processes." 503 | if self.l_info_2.text()==" ": 504 | self.info(t2=i_string) 505 | else: 506 | self.info(t1=i_string) 507 | QApplication.processEvents() 508 | 509 | thread = Task(self.close_launcher_thread) 510 | pool.start(thread) 511 | 512 | def relaunch_launcher(self): 513 | handle, exe = LauncherHelper.launcher_properties() 514 | if not handle: 515 | self.info("Launcher not running.",'') 516 | return 517 | 518 | self.launcher_handle = handle 519 | self.launcher_exe = exe 520 | 521 | self.info("Please wait for launcher to restart...", ' ') 522 | QApplication.processEvents() 523 | 524 | self.close_launcher() 525 | 526 | pool.waitForDone() 527 | 528 | thread2 = Task(self.open_launcher_thread) 529 | pool.start(thread2) 530 | 531 | pool.waitForDone() 532 | self.info(t1="Launcher restarted.",t2='') 533 | 534 | 535 | def relaunch_launcher_manager(self): 536 | """ 537 | Relaunch the launcher using the LauncherHelper module. 538 | """ 539 | 540 | thread1 = Task(self.close_launcher_thread) 541 | pool.start(thread1) 542 | 543 | pool.waitForDone() 544 | 545 | thread2 = Task(self.open_launcher_thread) 546 | pool.start(thread2) 547 | 548 | def reset_launcher(self, deep_clean): 549 | """ 550 | Reset the launcher by deleting selected items in the launcher directory. 551 | 552 | Args: 553 | deep_clean (bool): Whether to perform a deep clean or not. 554 | deep_clean will delete the entire contents of the roaming\rsilauncher directory. 555 | """ 556 | 557 | shallow_list = ['Session Storage', 'sentry', 'log.log'] 558 | launcher_roaming_dir = self.launcher_roaming 559 | launcher_handle, launcher_exe = LauncherHelper.launcher_properties() 560 | 561 | print('Closing Launcher...') 562 | LauncherHelper.close_launcher(launcher_handle) 563 | 564 | # For testing purposes, skip the deletion of the launcher items 565 | skip = False 566 | self.info(clear_unused=True) 567 | self.info(t1="Resetting and restarting launcher. Please wait...") 568 | if not skip: 569 | if deep_clean: 570 | # shutil.rmtree(launcher_roaming_dir) 571 | scrub_folder(launcher_roaming_dir) 572 | print(f"Deleted directory and all its contents: {launcher_roaming_dir}") 573 | else: 574 | for shallow_item in shallow_list: 575 | shallow_item_path = os.path.join(launcher_roaming_dir, shallow_item) 576 | if os.path.isdir(shallow_item_path): 577 | rmtree(shallow_item_path) 578 | print(f"Deleted directory and all its contents: {shallow_item_path}") 579 | elif os.path.isfile(shallow_item_path): 580 | os.remove(shallow_item_path) 581 | print(f"Deleted file: {shallow_item_path}") 582 | 583 | print("Reopening launcher") 584 | LauncherHelper.open_launcher(launcher_exe) 585 | self.info(t2="Done!") 586 | def refresh_task(self,handle, repeat=False): 587 | def primary(): 588 | print("So Fresh!") 589 | win32gui.SetForegroundWindow(handle) 590 | time.sleep(0.3) 591 | hotkey('ctrl', 'r') 592 | time.sleep(0.3) 593 | if repeat: 594 | while True: 595 | primary() 596 | if not self.cb_misc_refreshPatchWatch.isChecked(): 597 | return 598 | time.sleep(1) 599 | else: 600 | primary() 601 | 602 | def refresh_launcher(self): 603 | handle, _ = LauncherHelper.launcher_properties() 604 | if not handle: 605 | self.info("Launcher not running.",'') 606 | return 607 | 608 | self.info("Refreshing launcher...", ' ') 609 | QApplication.processEvents() 610 | 611 | if self.cb_misc_refreshPatchWatch.isChecked(): 612 | task = Task(self.refresh_task, handle, True) 613 | pool.start(task) 614 | 615 | 616 | else: 617 | self.refresh_task(handle) 618 | win32gui.SetForegroundWindow(int(self.winId())) 619 | 620 | def info(self, t1=None, t2=None, preset=None, clear_unused=False): 621 | 622 | def l1(txt): 623 | self.l_info_1.setText(str(txt)) 624 | 625 | def l2(txt): 626 | self.l_info_2.setText(str(txt)) 627 | 628 | if preset is not None: 629 | if preset == 0: 630 | l1("Welcome!") 631 | l2('') 632 | else: 633 | if t1 is not None: 634 | l1(t1) 635 | if clear_unused and t2 is None: 636 | l2('') 637 | if t2 is not None: 638 | l2(t2) 639 | if clear_unused and t1 is None: 640 | l1('') 641 | 642 | def mouse_press_event_frame(self, event): 643 | """ 644 | Handle the mouse press event on the frame (title bar). 645 | 646 | Args: 647 | event (QMouseEvent): Mouse press event. 648 | """ 649 | if event.button() == Qt.LeftButton: 650 | self.dragging_offset = event.globalPos() - self.frameGeometry().topLeft() 651 | event.accept() 652 | 653 | def mousePressEvent(self, event): 654 | """ 655 | Handle the mouse press event on the window. 656 | 657 | Args: 658 | event (QMouseEvent): Mouse press event. 659 | """ 660 | if event.button() == 2: 661 | thisclick = time.time() 662 | if thisclick - self.rctime < 0.15: 663 | 664 | self.toggle_title_bar() 665 | else: 666 | self.rctime = thisclick 667 | 668 | else: 669 | super().mousePressEvent(event) 670 | 671 | def mouse_move_event(self, event): 672 | """ 673 | Handle the mouse move event on the window. 674 | 675 | Args: 676 | event (QMouseEvent): Mouse move event. 677 | """ 678 | if event.buttons() & Qt.LeftButton and self.dragging_offset is not None: 679 | self.move(event.globalPos() - self.dragging_offset) 680 | event.accept() 681 | 682 | def mouse_release_event(self, event): 683 | """ 684 | Handle the mouse release event on the window. 685 | 686 | Args: 687 | event (QMouseEvent): Mouse release event. 688 | """ 689 | if event.button() == Qt.LeftButton: 690 | self.dragging_offset = None 691 | event.accept() 692 | 693 | def toggle_title_bar(self): 694 | """ 695 | Toggle the visibility of the title bar and testing elements. 696 | """ 697 | self.is_title_bar_hidden = not self.is_title_bar_hidden 698 | 699 | self.setWindowFlag(Qt.FramelessWindowHint, self.is_title_bar_hidden) 700 | self.show() 701 | def toggle_top(self): 702 | if self.windowFlags() & Qt.WindowStaysOnTopHint: #if top flag is set... 703 | self.setWindowFlags(self.windowFlags() & ~Qt.WindowStaysOnTopHint) #set new flags, excluding top flag 704 | self.show() 705 | else: 706 | self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint) #else set top flag 707 | self.show() 708 | 709 | def closeEvent(self, event): 710 | """ 711 | Handle the close event on the window. 712 | 713 | Args: 714 | event (QCloseEvent): Close event. 715 | """ 716 | pool.clear() 717 | event.accept() 718 | 719 | 720 | if __name__ == '__main__': 721 | pool=QThreadPool() 722 | app = QApplication(sys.argv) 723 | 724 | with open(get_resource_path("IC1a.qss"), "r") as qss_file: 725 | style_sheet = qss_file.read() 726 | ss2='QMainWindow {background-image: url(' + format_path_qss(get_resource_path("banner1.jpg")) +\ 727 | ');background-repeat: no-repeat;background-position: top left;}' 728 | app.setStyleSheet(style_sheet+ss2) 729 | 730 | SCFF_obj = SCFF() 731 | SCFF_obj.show() 732 | sys.exit(app.exec_()) 733 | -------------------------------------------------------------------------------- /banner1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlpineFrostSC/SCFileTools/d996a809cfcbd37c9b3f749432c5f3b3ea40e44b/banner1.jpg -------------------------------------------------------------------------------- /form.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 850 10 | 695 11 | 12 | 13 | 14 | SC File Fiddler 15 | 16 | 17 | 18 | 19 | 3 20 | 21 | 22 | 0 23 | 24 | 25 | 0 26 | 27 | 28 | 0 29 | 30 | 31 | 32 | 33 | 34 | 0 35 | 0 36 | 37 | 38 | 39 | 40 | 0 41 | 20 42 | 43 | 44 | 45 | 46 | 16777215 47 | 30 48 | 49 | 50 | 51 | QFrame::Box 52 | 53 | 54 | QFrame::Raised 55 | 56 | 57 | 58 | 1 59 | 60 | 61 | 1 62 | 63 | 64 | 1 65 | 66 | 67 | 1 68 | 69 | 70 | 71 | 72 | SC File Fiddler 73 | 74 | 75 | 76 | 77 | 78 | 79 | Qt::Horizontal 80 | 81 | 82 | 83 | 40 84 | 20 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | Running as: 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 25 101 | 0 102 | 103 | 104 | 105 | 106 | 25 107 | 16777215 108 | 109 | 110 | 111 | _ 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 25 120 | 0 121 | 122 | 123 | 124 | 125 | 25 126 | 16777215 127 | 128 | 129 | 130 | X 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 0 141 | 142 | 143 | 6 144 | 145 | 146 | 6 147 | 148 | 149 | 6 150 | 151 | 152 | 0 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 0 161 | 162 | 163 | 0 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | SCFFx.png 172 | 173 | 174 | 175 | 176 | 177 | 178 | 0 179 | 180 | 181 | 0 182 | 183 | 184 | 185 | 186 | 1 187 | 188 | 189 | 0 190 | 191 | 192 | 193 | 194 | Qt::Horizontal 195 | 196 | 197 | 198 | 40 199 | 20 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | Top 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | Qt::Vertical 217 | 218 | 219 | 220 | 20 221 | 12 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 0 230 | 231 | 232 | 233 | 234 | 235 | 200 236 | 0 237 | 238 | 239 | 240 | MainGameDirectory 241 | 242 | 243 | 244 | 245 | 246 | 247 | Qt::Horizontal 248 | 249 | 250 | 251 | 40 252 | 20 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 50 262 | 0 263 | 264 | 265 | 266 | 267 | 80 268 | 16777215 269 | 270 | 271 | 272 | Set 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 200 285 | 0 286 | 287 | 288 | 289 | TextLabel 290 | 291 | 292 | 293 | 294 | 295 | 296 | Qt::Horizontal 297 | 298 | 299 | 300 | 40 301 | 20 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 50 311 | 0 312 | 313 | 314 | 315 | 316 | 80 317 | 16777215 318 | 319 | 320 | 321 | Set 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | Qt::Vertical 331 | 332 | 333 | 334 | 20 335 | 12 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 12 348 | 349 | 350 | 5 351 | 352 | 353 | 0 354 | 355 | 356 | 0 357 | 358 | 359 | 360 | 361 | 0 362 | 363 | 364 | 0 365 | 366 | 367 | 368 | 369 | 370 | 0 371 | 0 372 | 373 | 374 | 375 | 376 | 9 377 | 378 | 379 | 380 | Shaders 381 | 382 | 383 | 384 | 6 385 | 386 | 387 | 1 388 | 389 | 390 | 1 391 | 392 | 393 | 1 394 | 395 | 396 | 1 397 | 398 | 399 | 400 | 401 | 22 402 | 403 | 404 | 405 | 406 | 6 407 | 408 | 409 | 3 410 | 411 | 412 | 22 413 | 414 | 415 | 3 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 200 424 | 16777215 425 | 426 | 427 | 428 | Delete Shaders 429 | 430 | 431 | 432 | 433 | 434 | 435 | Browse Shaders Folder 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | Delete All 445 | 446 | 447 | true 448 | 449 | 450 | bg_shaders 451 | 452 | 453 | 454 | 455 | 456 | 457 | Prompt for folder 458 | 459 | 460 | bg_shaders 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | true 475 | 476 | 477 | USER Folder 478 | 479 | 480 | 481 | 1 482 | 483 | 484 | 1 485 | 486 | 487 | 1 488 | 489 | 490 | 1 491 | 492 | 493 | 494 | 495 | 12 496 | 497 | 498 | 0 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 200 507 | 16777215 508 | 509 | 510 | 511 | Delete USER Folders 512 | 513 | 514 | 515 | 516 | 517 | 518 | Keep exported keybinds? 519 | 520 | 521 | 522 | 523 | 524 | 525 | Keep current keybinds? 526 | 527 | 528 | 529 | 530 | 531 | 532 | Keep settings? 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | LIVE 544 | 545 | 546 | 547 | 548 | 549 | 550 | PTU 551 | 552 | 553 | 554 | 555 | 556 | 557 | EPTU 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 6 574 | 575 | 576 | 0 577 | 578 | 579 | 580 | 581 | EAC Nuke! (delete EAC) 582 | 583 | 584 | 585 | 586 | 587 | 1 588 | 589 | 590 | 0 591 | 592 | 593 | 0 594 | 595 | 596 | 597 | 598 | 599 | 0 600 | 0 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 1 609 | 610 | 611 | 1 612 | 613 | 614 | 1 615 | 616 | 617 | 1 618 | 619 | 620 | 1 621 | 622 | 623 | 624 | 625 | In SC Folders 626 | 627 | 628 | 629 | 630 | 631 | 632 | 5 633 | 634 | 635 | 636 | 637 | EPTU 638 | 639 | 640 | 641 | 642 | 643 | 644 | PTU 645 | 646 | 647 | 648 | 649 | 650 | 651 | LIVE 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | In AppData>Roaming 664 | 665 | 666 | 667 | 668 | 669 | 670 | In Program Files 671 | 672 | 673 | 674 | 675 | 676 | 677 | Nuke-It-All™ 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | Misc. 690 | 691 | 692 | 693 | 0 694 | 695 | 696 | 0 697 | 698 | 699 | 0 700 | 701 | 702 | 0 703 | 704 | 705 | 706 | 707 | 0 708 | 709 | 710 | 0 711 | 712 | 713 | 714 | 715 | 2 716 | 717 | 718 | 0 719 | 720 | 721 | 0 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 1 731 | 732 | 733 | 1 734 | 735 | 736 | 1 737 | 738 | 739 | 1 740 | 741 | 742 | 1 743 | 744 | 745 | 746 | 747 | 2 748 | 749 | 750 | 0 751 | 752 | 753 | 754 | 755 | Open game.log 756 | 757 | 758 | 759 | 760 | 761 | 762 | Run game.log in Powershell (console emulation) 763 | 764 | 765 | 766 | 767 | 768 | 769 | true 770 | 771 | 772 | Open most recent crash log 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 2 782 | 783 | 784 | 0 785 | 786 | 787 | 788 | 789 | Game.log environment: 790 | 791 | 792 | 793 | 794 | 795 | 796 | LIVE 797 | 798 | 799 | true 800 | 801 | 802 | bg_misc_environment 803 | 804 | 805 | 806 | 807 | 808 | 809 | PTU 810 | 811 | 812 | bg_misc_environment 813 | 814 | 815 | 816 | 817 | 818 | 819 | EPTU 820 | 821 | 822 | bg_misc_environment 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 1 835 | 836 | 837 | 0 838 | 839 | 840 | 841 | 842 | 843 | 0 844 | 0 845 | 846 | 847 | 848 | 849 | 190 850 | 0 851 | 852 | 853 | 854 | Reset Launcher 855 | 856 | 857 | 858 | 859 | 860 | 861 | false 862 | 863 | 864 | Deep Clean 865 | 866 | 867 | true 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 1 877 | 878 | 879 | 0 880 | 881 | 882 | 0 883 | 884 | 885 | 886 | 887 | 888 | 0 889 | 0 890 | 891 | 892 | 893 | 894 | 190 895 | 0 896 | 897 | 898 | 899 | 900 | 190 901 | 16777215 902 | 903 | 904 | 905 | Fully Close Launcher 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 0 914 | 0 915 | 916 | 917 | 918 | 919 | 190 920 | 0 921 | 922 | 923 | 924 | 925 | 190 926 | 16777215 927 | 928 | 929 | 930 | Re-Launch Launcher 931 | 932 | 933 | 934 | 935 | 936 | 937 | Qt::Horizontal 938 | 939 | 940 | QSizePolicy::Preferred 941 | 942 | 943 | 944 | 3 945 | 20 946 | 947 | 948 | 949 | 950 | 951 | 952 | 953 | 954 | 955 | 1 956 | 957 | 958 | 2 959 | 960 | 961 | 962 | 963 | 964 | 0 965 | 0 966 | 967 | 968 | 969 | 970 | 190 971 | 16777215 972 | 973 | 974 | 975 | Refresh Launcher 976 | 977 | 978 | 979 | 980 | 981 | 982 | Patch-watch mode (uncheck to stop) 983 | 984 | 985 | 986 | 987 | 988 | 989 | 990 | 991 | 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999 | 1000 | 1001 | 1002 | 0 1003 | 1004 | 1005 | 0 1006 | 1007 | 1008 | 0 1009 | 1010 | 1011 | 1012 | 1013 | 1014 | 0 1015 | 40 1016 | 1017 | 1018 | 1019 | QFrame::WinPanel 1020 | 1021 | 1022 | QFrame::Raised 1023 | 1024 | 1025 | 1026 | 3 1027 | 1028 | 1029 | 1 1030 | 1031 | 1032 | 1 1033 | 1034 | 1035 | 0 1036 | 1037 | 1038 | 0 1039 | 1040 | 1041 | 1042 | 1043 | 12 1044 | 1045 | 1046 | 1047 | 1048 | 1049 | 12 1050 | 16777215 1051 | 1052 | 1053 | 1054 | > 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | Qt::Vertical 1062 | 1063 | 1064 | 1065 | 20 1066 | 3 1067 | 1068 | 1069 | 1070 | 1071 | 1072 | 1073 | 1074 | 1075 | 1076 | 1077 | 1078 | TextLabel 1079 | 1080 | 1081 | 1082 | 1083 | 1084 | 1085 | TextLabel 1086 | 1087 | 1088 | 1089 | 1090 | 1091 | 1092 | 1093 | 1094 | Qt::Horizontal 1095 | 1096 | 1097 | 1098 | 40 1099 | 20 1100 | 1101 | 1102 | 1103 | 1104 | 1105 | 1106 | 1107 | 1108 | 1109 | 1110 | 1111 | 1112 | 1113 | 1114 | Qt::Vertical 1115 | 1116 | 1117 | 1118 | 20 1119 | 1 1120 | 1121 | 1122 | 1123 | 1124 | 1125 | 1126 | 1127 | 1128 | 1129 | 0 1130 | 0 1131 | 850 1132 | 22 1133 | 1134 | 1135 | 1136 | 1137 | 1138 | 1139 | 1140 | 1141 | 1142 | 1143 | 1144 | 1145 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyQt5~=5.15.0 2 | configparser~=5.2.0 3 | PyAutoGUI~=0.9.54 4 | psutil~=5.8.0 5 | pywin32~=228 6 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import configparser 3 | 4 | 5 | def delete_ini(): 6 | 7 | if os.path.exists('SCFF.ini'): 8 | os.remove('SCFF.ini') 9 | print(f"SCFF.ini has been removed") 10 | else: 11 | print(f"SCFF.ini does not exist") 12 | 13 | def defaults(): 14 | shaders_path = os.path.join(os.getenv('LOCALAPPDATA'), "Star Citizen") 15 | eac_roaming_path = os.path.join(os.getenv('APPDATA'), "EasyAntiCheat") 16 | eac_programfiles_path = os.path.join(os.getenv('ProgramFiles(x86)'), "EasyAntiCheat_EOS") 17 | launcher_roaming_path = os.path.join(os.getenv('APPDATA'), "rsilauncher") 18 | defaults={ 19 | 'rsi_path': r"---NOT SET---", 20 | 'shaders_path': shaders_path, 21 | 'eac_roaming_path': eac_roaming_path, 22 | 'eac_programfiles_path': eac_programfiles_path, 23 | 'launcher_roaming_path': launcher_roaming_path, 24 | 25 | } 26 | 27 | return defaults 28 | 29 | def read(): 30 | if not os.path.isfile('SCFF.ini'): 31 | create_default_settings() 32 | verify_settings(populate=True) 33 | 34 | config = configparser.ConfigParser() 35 | config.read('SCFF.ini') 36 | 37 | settings = {} 38 | for key in config['Global']: 39 | settings[key] = config['Global'][key] 40 | 41 | return settings 42 | 43 | def read_one(setting, section='Global'): 44 | if not os.path.isfile('SCFF.ini'): 45 | create_default_settings() 46 | 47 | config = configparser.ConfigParser() 48 | config.read('SCFF.ini') 49 | 50 | if setting in config[section]: 51 | value = config[section][setting] 52 | else: 53 | value = None 54 | 55 | return value 56 | 57 | def write(settings): 58 | if not os.path.isfile('SCFF.ini'): 59 | create_default_settings() 60 | 61 | config = configparser.ConfigParser() 62 | 63 | if 'Global' not in config: 64 | config.add_section('Global') 65 | 66 | for key, value in settings.items(): 67 | config.set('Global', key, value) 68 | 69 | with open('SCFF.ini', 'w') as configfile: 70 | config.write(configfile) 71 | 72 | def verify_settings(required_keys=None, populate=False): 73 | if not os.path.isfile('SCFF.ini'): 74 | create_default_settings() 75 | 76 | if required_keys is None: 77 | required_keys = defaults().keys() 78 | 79 | config = configparser.ConfigParser() 80 | config.read('SCFF.ini') 81 | 82 | settings = {} 83 | 84 | if 'Global' not in config: 85 | print(f"Settings file does not contain required section: Global") 86 | if populate: 87 | print(f"Populating settings file with default values.") 88 | write(defaults()) 89 | return True 90 | else: 91 | return False 92 | 93 | for key in config['Global']: 94 | settings[key] = config['Global'][key] 95 | 96 | for key in required_keys: 97 | if key not in settings: 98 | print(f"Settings file does not contain required key: {key}") 99 | if populate: 100 | print(f"Populating settings file with default value.") 101 | write_one(key, defaults()[key]) 102 | else: 103 | return False 104 | 105 | return True 106 | 107 | 108 | def create_default_settings(leave_blank=False): 109 | config = configparser.ConfigParser() 110 | stype='Default' 111 | 112 | if not leave_blank: 113 | config['Global'] = defaults() 114 | else: 115 | stype='Blank' 116 | 117 | with open('SCFF.ini', 'w') as configfile: 118 | config.write(configfile) 119 | print(f"SCFF.ini has been created with {stype} settings") 120 | 121 | 122 | def write_one(setting, value, section='Global'): 123 | if setting == '' and value == '': 124 | return 125 | if not os.path.isfile('SCFF.ini'): 126 | create_default_settings() 127 | 128 | config = configparser.ConfigParser() 129 | config.read('SCFF.ini') 130 | 131 | if section not in config: 132 | config[section] = {} 133 | 134 | config[section][setting] = value 135 | 136 | with open('SCFF.ini', 'w') as configfile: 137 | config.write(configfile) 138 | 139 | if __name__ == "__main__": 140 | pass 141 | # delete_ini() 142 | # create_default_settings(leave_blank=False) 143 | # 144 | # # write(defaults()) 145 | # 146 | # write_one('setting23', '32') 147 | # # print(read_one('setting3')) 148 | # v=verify_settings(populate=False) 149 | # print(v) --------------------------------------------------------------------------------