Update to latest version

This commit is contained in:
Ahrimdon 2024-02-24 23:32:30 -05:00
parent 38f74dadc3
commit 1a4f0c1292
9 changed files with 756 additions and 434 deletions

7
.gitignore vendored
View File

@ -162,5 +162,8 @@ cython_debug/
# Other # Other
*.ini *.ini
*.conf *.conf
dist/ .vscode
.vscode test/
steamcmd
t7xwd_library.json
*.exe

119
README.md
View File

@ -1,90 +1,45 @@
# T7x Workshop Downloader (T7xWD) # T7xWD
- A Feature-rich GUI Steam Workshop downloader for BO3 ([T7x client](https://github.com/Ezz-lol/T7x-free)) built using CustomTkinter <br> - A Feature-rich GUI Steam Workshop downloader for [Call of Duty®: Black Ops III](https://store.steampowered.com/app/311210/Call_of_Duty_Black_Ops_III/) built using CustomTkinter <br>
<div style="display: flex; justify-content: space-between;"> <table>
<!-- Left Side --> <tr>
<div style="flex: 1; margin-right: 5px;"> <td align="center">
<img src="https://github.com/faroukbmiled/BOIIIWD/assets/51106560/0aa8295f-ba07-4778-8140-200021df4ba9" width="400" /> <img src="https://github.com/faroukbmiled/BOIIIWD/assets/51106560/4d199e21-c9a0-4dfc-b831-866fbff1d1a1" max-width="400" />
<img src="https://github.com/faroukbmiled/BOIIIWD/assets/51106560/b4f27fe1-88f2-4158-b7ba-c8aec57b9968" width="400" /> </td>
</div> <td align="center">
<!-- Right Side --> <img src="https://github.com/faroukbmiled/BOIIIWD/assets/51106560/25174889-4524-455f-9836-f4ea5240e07f" max-width="400" />
<div style="flex: 1; margin-left: 5px;"> </td>
<img src="https://github.com/faroukbmiled/BOIIIWD/assets/51106560/86c07cf2-b04b-42d0-ae06-8526bffafb34" width="400" /> <td align="center">
<img src="https://github.com/faroukbmiled/BOIIIWD/assets/51106560/4c5877eb-81a7-4ae7-99db-3096ab57b12b" width="400" /> <img src="https://github.com/faroukbmiled/BOIIIWD/assets/51106560/df54a0d7-f9ab-4061-b8b7-06d9e5992c90" max-width="400" />
</div> </td>
</div> </tr>
</table>
## Usage (exe): ## Usage:
- Run [T7xWD.exe](https://git.rimmyscorner.com/Rim/T7x-Workshop-Downloader/releases/download/latest/T7xWD.exe) and use it (it'll ask you to download steamcmd within the app if not found) - Run [T7xWD.exe](https://github.com/faroukbmiled/T7xWD/releases/latest/download/Release.zip) ([VirusTotal Scan](https://www.virustotal.com/gui/file/5ca1367a82893a1f412b59a52431e9ac4219a67a50c294ee86a7d41473826b14/detection))
- That's it slap in your workshop item link or just the id then hit Download and wait for it to finish, when it does just launch your game (Please check [Notes](#notes) before you ask anything) - [Optional] Run as script:```python T7xwd_package\T7xwd.py```
- If the exe is getting flagged as a virus by your ac it is obviously a false positive, if you still do not trust it you can [compile/freeze](#freezing) it yourself ([VirusTotal Scan](https://www.virustotal.com/gui/file/9df159098638ab8a8bec7205eeb271cb5891c19cdbb81bcd5368dfc1ef213f76/detection))
## Usage (script):
- ```pip install -r requirements.txt``` -> use my modified [CTkToolTip](./CTkToolTip) and [CTkListbox](./CTkListbox) for v0.2.8 and up
- ```python T7xwd_package\T7xwd.py```
- Slap in your workshop item link for example: "https://steamcommunity.com/sharedfiles/filedetails/?id=3011930738" or just the id 3011930738
## Features: ## Features:
- Improves steamcmd's stability while downloading - Improved download stability
- Auto installs mods and maps to T7x - Auto installs mods and maps
- Queue -> download items in queue - Queue -> download items in queue
- Library tab -> lists your downloaded items - Library tab -> lists your downloaded items
- Item updater -> Checks your items for updates (redownloads them,no way for now to "update" them only) -> Under Library tab - Item updater -> checks your items for updates
- Steam to T7x -> Item mover (moves items (mods,maps) from steam to T7x client) -> Under settings tab - Workshop Transfer -> copy/move items from the workshop folder into the game directory
- Themes -> Under settings tab - Custom Themes
- Bunch of useful settings -> Under settings tab
<a name="freezing"></a> ## Notes:
## Freezing into an exe (pyinstaller): - Steamcmd will be downloaded if it is not installed <br>
- ```pip install -r requirements.txt``` -> use my modified [CTkToolTip](./CTkToolTip) and [CTkListbox](./CTkListbox) for v0.2.8 and up. - Initializing SteamCMD for the first time could take some time depending on your internet speed <br>
#### Mouse Bindings:
Library Tab:
* Mouse1 -> copy id
* Ctrl + Mouse1 -> append to clipboard
* Mouse2 (scroll wheel button) -> open item path in file explorer
* Mouse3 (Right click) -> copy path
## Building from Source:
- ```pip install -r requirements.txt``` -> use my modified [CTkToolTip](./CTkToolTip) and [CTkListbox](./CTkListbox)
- ```python build.py``` - ```python build.py```
## Queue tab (beta)
- added Queue tab which has a text field that you can slap in workshop ids/links in 2 formats, for example:<br>
```3010399939,2976006537,2118338989```
or <br>
```3010399939
2976006537
2118338989
2113146805
```
## Hidden Config Options:
- Added a way to update invalid Items (in details) -> add ```update_invalid = yes``` to config.ini
- Added a way to download Beta items that normally will throw invalid item warning -> add ```skip_invalid = no``` to config.ini
<a name="notes"></a>
### Notes:
* It saves your input except for workshop id <br>
* If you do not know where to find your map in-game check this [video](https://youtu.be/XIQjfXXlgQs?t=260) out ,for mods find "mods" in the game's main menu <br>
* Initializing SteamCMD for the first time could take some time depending on your internet speed <br>
* New item update window: Right click on an item (mouse3) -> will open the item in the browser (Steam Workshop) <br>
* T7xWD requires having "T7xwd_library.json" in the app's directory for the new features to work -> clicking on the library tab will generate the JSON file (please don't touch it) <br>
* v0.3.1 and up program will use windows registry to save window coordinates (height , width ,x_pos, y_pos)
* For Item Updater to recognize your Item as a valid one is to have it's folder named either "FolderName ,"PublisherID" or "FolderName_PublisherID" (taken from workshop.json from each item) -> Invalid items will have a warning icon in library and will not be checked for updates -> Downloading items from T7xwd will be valid by default
* Invalid items will still work in game but will not be checked for updates
#### PS (Library tab): <br>
* Mouse1 -> copy id <br>
* Mouse2 (scroll wheel btn) -> Open item path in explorer <br>
* Mouse3 -> Copy path <br>
* Ctrl + Mouse 1 (after Mouse1) -> Append to clipboard
### Known bugs: <br>
* Rare UI bug => instead of showing a warning message, its window goes invisible and leads to the whole ui becoming unclickable (end the task from task manager) <br>
* Possible logic bugs related to the progress bar , sometimes it carries on progressing when you pressed stop => please raise an issue if this happens often <br>
* If the exe is getting flagged as a virus by your ac it is obviously a false positive, if you still do not trust it you can [compile/freeze](#freezing) it yourself <br>
* [VirusTotal](https://www.virustotal.com/gui/file/9df159098638ab8a8bec7205eeb271cb5891c19cdbb81bcd5368dfc1ef213f76/detection) <br>
### todos:
- [x] add a menu that shows you current installed mods/maps
- [x] fix the progress bar => progress bar logic based on an estimation
- [x] other improvements regarding the download (steamcmd likes to fail sometimes for no reason) => added a way to keep looping when steamcmd crashes and it will eventually finishes
- [x] add a queue window that you can slap in a bunch of items to download sequentially and or simultaneously
- [ ] add an option to login with your account => delayed (do we really need it?)
### Themes:
- If you choose "custom" theme a file called T7xwd_theme.json will be created in the current folder (where the exe at) , Don't add anything or edit any keyes just modify the colours, If you mess up something you can just rename the file and it'll go back to the default theme (you can always press custom again and the file will be created again, based on which theme you choose before)

View File

@ -1,5 +1,4 @@
import os import os
import shutil
import PyInstaller.__main__ import PyInstaller.__main__
from distutils.sysconfig import get_python_lib from distutils.sysconfig import get_python_lib
@ -29,5 +28,7 @@ PyInstaller.__main__.run([
"--add-data", f"{site_packages_path}/CTkToolTip;CTkToolTip", "--add-data", f"{site_packages_path}/CTkToolTip;CTkToolTip",
]) ])
current_directory = os.path.dirname(__file__) # create symbolic hardlink to main directory
shutil.copy2(os.path.join(current_directory, "dist", "T7xWD.exe"), current_directory) if os.path.exists("T7xWD.exe"):
os.remove("T7xWD.exe")
os.link('dist/T7xWD.exe', 'T7xWD.exe')

Binary file not shown.

View File

@ -3,12 +3,13 @@ from src.imports import *
# Start helper functions # Start helper functions
#testing app offline # testing app offline
# import socket # import socket
# def guard(*args, **kwargs): # def guard(*args, **kwargs):
# pass # pass
# socket.socket = guard # socket.socket = guard
def check_config(name, fallback=None): def check_config(name, fallback=None):
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.read(CONFIG_FILE_PATH) config.read(CONFIG_FILE_PATH)
@ -16,6 +17,7 @@ def check_config(name, fallback=None):
return config.get("Settings", name, fallback=fallback) return config.get("Settings", name, fallback=fallback)
return config.get("Settings", name, fallback="") return config.get("Settings", name, fallback="")
def save_config(name, value): def save_config(name, value):
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.read(CONFIG_FILE_PATH) config.read(CONFIG_FILE_PATH)
@ -24,25 +26,32 @@ def save_config(name, value):
with open(CONFIG_FILE_PATH, "w") as config_file: with open(CONFIG_FILE_PATH, "w") as config_file:
config.write(config_file) config.write(config_file)
def check_custom_theme(theme_name): def check_custom_theme(theme_name):
if os.path.exists(os.path.join(APPLICATION_PATH, theme_name)): if os.path.exists(os.path.join(APPLICATION_PATH, theme_name)):
return os.path.join(APPLICATION_PATH, theme_name) return os.path.join(APPLICATION_PATH, theme_name)
else: else:
try: return os.path.join(RESOURCES_DIR, theme_name) try:
except: return os.path.join(RESOURCES_DIR, "T7xwd_theme.json") return os.path.join(RESOURCES_DIR, theme_name)
except:
return os.path.join(RESOURCES_DIR, "T7xwd_theme.json")
# theme initialization # theme initialization
ctk.set_appearance_mode(check_config("appearance", "Dark")) # Modes: "System" (standard), "Dark", "Light" # Modes: "System" (standard), "Dark", "Light"
ctk.set_appearance_mode(check_config("appearance", "Dark"))
try: try:
ctk.set_default_color_theme(check_custom_theme(check_config("theme", fallback="T7xwd_theme.json"))) ctk.set_default_color_theme(check_custom_theme(
check_config("theme", fallback="T7xwd_theme.json")))
except: except:
save_config("theme", "T7xwd_theme.json") save_config("theme", "T7xwd_theme.json")
ctk.set_default_color_theme(os.path.join(RESOURCES_DIR, "T7xwd_theme.json")) ctk.set_default_color_theme(os.path.join(
RESOURCES_DIR, "T7xwd_theme.json"))
def get_latest_release_version(): def get_latest_release_version():
try: try:
release_api_url = f"https://git.rimmyscorner.com/api/v1/repos/{GITHUB_REPO}/releases/latest" release_api_url = f"https://api.github.com/repos/{GITHUB_REPO}/releases/latest"
response = requests.get(release_api_url) response = requests.get(release_api_url)
response.raise_for_status() response.raise_for_status()
data = response.json() data = response.json()
@ -51,6 +60,7 @@ def get_latest_release_version():
show_message("Warning", f"Error while checking for updates: \n{e}") show_message("Warning", f"Error while checking for updates: \n{e}")
return None return None
def create_update_script(current_exe, new_exe, updater_folder, program_name): def create_update_script(current_exe, new_exe, updater_folder, program_name):
script_content = f""" script_content = f"""
@echo off @echo off
@ -76,6 +86,7 @@ def create_update_script(current_exe, new_exe, updater_folder, program_name):
return script_path return script_path
def if_internet_available(func): def if_internet_available(func):
if func == "return": if func == "return":
try: try:
@ -83,23 +94,28 @@ def if_internet_available(func):
return True return True
except: except:
return False return False
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
try: try:
requests.get("https://www.google.com", timeout=3) requests.get("https://www.google.com", timeout=3)
return func(*args, **kwargs) return func(*args, **kwargs)
except: except:
show_message("Offline", "No internet connection. Please check your internet connection and try again.") show_message(
"Offline", "No internet connection. Please check your internet connection and try again.")
return return
return wrapper return wrapper
@if_internet_available @if_internet_available
def check_for_updates_func(window, ignore_up_todate=False): def check_for_updates_func(window, ignore_up_todate=False):
try: try:
latest_version = get_latest_release_version() latest_version = get_latest_release_version()
current_version = VERSION current_version = VERSION
int_latest_version = int(latest_version.replace("v", "").replace(".", "")) int_latest_version = int(
int_current_version = int(current_version.replace("v", "").replace(".", "")) latest_version.replace("v", "").replace(".", ""))
int_current_version = int(
current_version.replace("v", "").replace(".", ""))
if latest_version and int_latest_version > int_current_version: if latest_version and int_latest_version > int_current_version:
msg_box = CTkMessagebox(title="Update Available", message=f"An update is available! Install now?\n\nCurrent Version: {current_version}\nLatest Version: {latest_version}", option_1="View", option_2="No", option_3="Yes", fade_in_duration=int(1), sound=True) msg_box = CTkMessagebox(title="Update Available", message=f"An update is available! Install now?\n\nCurrent Version: {current_version}\nLatest Version: {latest_version}", option_1="View", option_2="No", option_3="Yes", fade_in_duration=int(1), sound=True)
@ -107,7 +123,8 @@ def check_for_updates_func(window, ignore_up_todate=False):
result = msg_box.get() result = msg_box.get()
if result == "View": if result == "View":
webbrowser.open(f"https://git.rimmyscorner.com/{GITHUB_REPO}/releases/latest") webbrowser.open(
f"https://github.com/{GITHUB_REPO}/releases/latest")
if result == "Yes": if result == "Yes":
from src.update_window import UpdateWindow from src.update_window import UpdateWindow
@ -125,15 +142,18 @@ def check_for_updates_func(window, ignore_up_todate=False):
elif int_latest_version == int_current_version: elif int_latest_version == int_current_version:
if ignore_up_todate: if ignore_up_todate:
return return
msg_box = CTkMessagebox(title="Up to Date!", message="No Updates Available!", option_1="Ok", sound=True) msg_box = CTkMessagebox(
title="Up to Date!", message="No Updates Available!", option_1="Ok", sound=True)
result = msg_box.get() result = msg_box.get()
else: else:
show_message("Error!", "An error occured while checking for updates!\nCheck your internet and try again") show_message(
"Error!", "An error occured while checking for updates!\nCheck your internet and try again")
except Exception as e: except Exception as e:
show_message("Error", f"Error while checking for updates: \n{e}", icon="cancel") show_message("Error", f"Error while checking for updates: \n{e}", icon="cancel")
def extract_workshop_id(link): def extract_workshop_id(link):
try: try:
pattern = r'(?<=id=)(\d+)' pattern = r'(?<=id=)(\d+)'
@ -146,6 +166,7 @@ def extract_workshop_id(link):
except: except:
return None return None
def check_steamcmd(): def check_steamcmd():
steamcmd_path = get_steamcmd_path() steamcmd_path = get_steamcmd_path()
steamcmd_exe_path = os.path.join(steamcmd_path, "steamcmd.exe") steamcmd_exe_path = os.path.join(steamcmd_path, "steamcmd.exe")
@ -155,25 +176,31 @@ def check_steamcmd():
return True return True
def initialize_steam(master): def initialize_steam(master):
try: try:
steamcmd_path = get_steamcmd_path() steamcmd_path = get_steamcmd_path()
steamcmd_exe_path = os.path.join(steamcmd_path, "steamcmd.exe") steamcmd_exe_path = os.path.join(steamcmd_path, "steamcmd.exe")
process = subprocess.Popen([steamcmd_exe_path, "+quit"], creationflags=subprocess.CREATE_NEW_CONSOLE) process = subprocess.Popen(
[steamcmd_exe_path, "+quit"], creationflags=subprocess.CREATE_NEW_CONSOLE)
master.attributes('-alpha', 0.0) master.attributes('-alpha', 0.0)
process.wait() process.wait()
if is_steamcmd_initialized(): if is_steamcmd_initialized():
show_message("SteamCMD has terminated!", "T7xWD is ready for action.", icon="info") show_message("SteamCMD has terminated!",
"T7xWD is ready for action.", icon="info")
else: else:
show_message("SteamCMD has terminated!!", "SteamCMD isn't initialized yet") show_message("SteamCMD has terminated!!",
"SteamCMD isn't initialized yet")
except: except:
show_message("Error!", "An error occurred please check your paths and try again.", icon="cancel") show_message(
"Error!", "An error occurred please check your paths and try again.", icon="cancel")
master.attributes('-alpha', 1.0) master.attributes('-alpha', 1.0)
@if_internet_available @if_internet_available
def valid_id(workshop_id): def valid_id(workshop_id):
data = item_steam_api(workshop_id) data = item_steam_api(workshop_id)
if check_config("skip_invalid", "skip") == "no": if check_config("skip_invalid", "on") == "off":
if data: if data:
return True return True
if "consumer_app_id" in data['response']['publishedfiledetails'][0]: if "consumer_app_id" in data['response']['publishedfiledetails'][0]:
@ -181,6 +208,7 @@ def valid_id(workshop_id):
else: else:
return False return False
def convert_speed(speed_bytes): def convert_speed(speed_bytes):
if speed_bytes < 1024: if speed_bytes < 1024:
return speed_bytes, "B/s" return speed_bytes, "B/s"
@ -191,27 +219,31 @@ def convert_speed(speed_bytes):
else: else:
return speed_bytes / (1024 * 1024 * 1024), "GB/s" return speed_bytes / (1024 * 1024 * 1024), "GB/s"
def create_default_config(): def create_default_config():
config = configparser.ConfigParser() config = configparser.ConfigParser()
config["Settings"] = { config["Settings"] = {
"SteamCMDPath": APPLICATION_PATH, "SteamCMDPath": APPLICATION_PATH,
"DestinationFolder": "", "DestinationFolder": "",
"checkforupdtes": "on", "checkforupdates": "on",
"console": "off" "console": "off"
} }
with open(CONFIG_FILE_PATH, "w") as config_file: with open(CONFIG_FILE_PATH, "w") as config_file:
config.write(config_file) config.write(config_file)
def get_steamcmd_path(): def get_steamcmd_path():
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.read(CONFIG_FILE_PATH) config.read(CONFIG_FILE_PATH)
return config.get("Settings", "SteamCMDPath", fallback=APPLICATION_PATH) return config.get("Settings", "SteamCMDPath", fallback=APPLICATION_PATH)
def extract_json_data(json_path, key): def extract_json_data(json_path, key):
with open(json_path, 'r') as json_file: with open(json_path, 'r') as json_file:
data = json.load(json_file) data = json.load(json_file)
return data.get(key, '') return data.get(key, '')
def convert_bytes_to_readable(size_in_bytes, no_symb=None): def convert_bytes_to_readable(size_in_bytes, no_symb=None):
for unit in ['B', 'KB', 'MB', 'GB', 'TB']: for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
if size_in_bytes < 1024.0: if size_in_bytes < 1024.0:
@ -220,6 +252,7 @@ def convert_bytes_to_readable(size_in_bytes, no_symb=None):
return f"{size_in_bytes:.2f} {unit}" return f"{size_in_bytes:.2f} {unit}"
size_in_bytes /= 1024.0 size_in_bytes /= 1024.0
def get_workshop_file_size(workshop_id, raw=None): def get_workshop_file_size(workshop_id, raw=None):
url = f"https://steamcommunity.com/sharedfiles/filedetails/?id={workshop_id}&searchtext=" url = f"https://steamcommunity.com/sharedfiles/filedetails/?id={workshop_id}&searchtext="
response = requests.get(url) response = requests.get(url)
@ -230,23 +263,34 @@ def get_workshop_file_size(workshop_id, raw=None):
if raw: if raw:
file_size_text = file_size_element.get_text(strip=True) file_size_text = file_size_element.get_text(strip=True)
file_size_text = file_size_text.replace(",", "") file_size_text = file_size_text.replace(",", "")
file_size_in_mb = float(file_size_text.replace(" MB", "")) if "GB" in file_size_text:
file_size_in_bytes = int(file_size_in_mb * 1024 * 1024) file_size_in_gb = float(file_size_text.replace(" GB", ""))
file_size_in_bytes = int(file_size_in_gb * 1024 * 1024 * 1024)
else:
file_size_in_mb = float(file_size_text.replace(" MB", ""))
file_size_in_bytes = int(file_size_in_mb * 1024 * 1024)
return convert_bytes_to_readable(file_size_in_bytes) return convert_bytes_to_readable(file_size_in_bytes)
if file_size_element: if file_size_element:
file_size_text = file_size_element.get_text(strip=True) file_size_text = file_size_element.get_text(strip=True)
file_size_text = file_size_text.replace(",", "") file_size_text = file_size_text.replace(",", "")
file_size_in_mb = float(file_size_text.replace(" MB", "")) if "GB" in file_size_text:
file_size_in_bytes = int(file_size_in_mb * 1024 * 1024) file_size_in_gb = float(file_size_text.replace(" GB", ""))
file_size_in_bytes = int(file_size_in_gb * 1024 * 1024 * 1024)
else:
file_size_in_mb = float(file_size_text.replace(" MB", ""))
file_size_in_bytes = int(file_size_in_mb * 1024 * 1024)
return file_size_in_bytes return file_size_in_bytes
return None return None
except: except Exception as e:
print(e)
return None return None
def show_message(title, message, icon="warning", _return=False, option_1="No", option_2="Ok"): def show_message(title, message, icon="warning", _return=False, option_1="No", option_2="Ok"):
if _return: if _return:
msg = CTkMessagebox(title=title, message=message, icon=icon, option_1=option_1, option_2=option_2, sound=True) msg = CTkMessagebox(title=title, message=message, icon=icon,
option_1=option_1, option_2=option_2, sound=True)
response = msg.get() response = msg.get()
if response == option_1: if response == option_1:
return False return False
@ -259,18 +303,22 @@ def show_message(title, message, icon="warning", _return=False, option_1="No", o
CTkMessagebox(title=title, message=message, icon=icon, sound=True) CTkMessagebox(title=title, message=message, icon=icon, sound=True)
main_app.app.after(0, callback) main_app.app.after(0, callback)
def launch_T7x_func(path):
procname = "t7x.exe" def launch_game_func(path, procname="BlackOps3.exe", flags=""):
if not procname.endswith(('.exe', '.bat', '.sh', '.lnk')):
procname += '.exe'
try: try:
if procname in (p.name() for p in psutil.process_iter()): if procname in (p.name() for p in psutil.process_iter()):
for proc in psutil.process_iter(): for proc in psutil.process_iter():
if proc.name() == procname: if proc.name() == procname:
proc.kill() proc.kill()
T7x_path = os.path.join(path, procname) game_path = os.path.join(path, procname)
subprocess.Popen([T7x_path ,"-launch"] , cwd=path) subprocess.Popen([game_path, flags], cwd=path)
show_message("Please wait!", "The game has launched in the background it will open up in a sec!", icon="info") show_message(
"Please wait!", "The game has launched in the background it will open up in a sec!", icon="info")
except Exception as e: except Exception as e:
show_message("Error: Failed to launch T7x", f"Failed to launch t7x.exe\nMake sure to put in your correct T7x path\n{e}") show_message(f"Error: Failed to launch game", f"Failed to start {procname}\n\nMake sure the game path is correct.\n\n{e}")
def remove_tree(folder_path, show_error=None): def remove_tree(folder_path, show_error=None):
if show_error: if show_error:
@ -283,11 +331,13 @@ def remove_tree(folder_path, show_error=None):
except Exception as e: except Exception as e:
pass pass
def convert_seconds(seconds): def convert_seconds(seconds):
minutes, seconds = divmod(seconds, 60) minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60) hours, minutes = divmod(minutes, 60)
return hours, minutes, seconds return hours, minutes, seconds
def get_folder_size(folder_path): def get_folder_size(folder_path):
total_size = 0 total_size = 0
for path, dirs, files in os.walk(folder_path): for path, dirs, files in os.walk(folder_path):
@ -296,6 +346,7 @@ def get_folder_size(folder_path):
total_size += os.stat(fp).st_size total_size += os.stat(fp).st_size
return total_size return total_size
def is_steamcmd_initialized(): def is_steamcmd_initialized():
steamcmd_path = get_steamcmd_path() steamcmd_path = get_steamcmd_path()
steamcmd_exe_path = os.path.join(steamcmd_path, "steamcmd.exe") steamcmd_exe_path = os.path.join(steamcmd_path, "steamcmd.exe")
@ -304,6 +355,7 @@ def is_steamcmd_initialized():
return False return False
return True return True
def get_button_state_colors(file_path, state): def get_button_state_colors(file_path, state):
try: try:
with open(file_path, 'r') as json_file: with open(file_path, 'r') as json_file:
@ -321,10 +373,12 @@ def get_button_state_colors(file_path, state):
except json.JSONDecodeError: except json.JSONDecodeError:
return None return None
def reset_steamcmd(no_warn=None): def reset_steamcmd(no_warn=None):
steamcmd_path = get_steamcmd_path() steamcmd_path = get_steamcmd_path()
directories_to_reset = ["steamapps", "dumps", "logs", "depotcache", "appcache","userdata",] directories_to_reset = ["steamapps", "dumps",
"logs", "depotcache", "appcache", "userdata",]
for directory in directories_to_reset: for directory in directories_to_reset:
directory_path = os.path.join(steamcmd_path, directory) directory_path = os.path.join(steamcmd_path, directory)
@ -338,10 +392,13 @@ def reset_steamcmd(no_warn=None):
os.remove(file_path) os.remove(file_path)
if not no_warn: if not no_warn:
show_message("Success!", "SteamCMD has been reset successfully!", icon="info") show_message(
"Success!", "SteamCMD has been reset successfully!", icon="info")
else: else:
if not no_warn: if not no_warn:
show_message("Warning!", "steamapps folder was not found, maybe already removed?", icon="warning") show_message(
"Warning!", "steamapps folder was not found, maybe already removed?", icon="warning")
def get_item_name(id): def get_item_name(id):
try: try:
@ -355,8 +412,52 @@ def get_item_name(id):
return False return False
# you gotta use my modded CTkToolTip originaly by Akascape # you gotta use my modded CTkToolTip originaly by Akascape
def show_noti(widget ,message, event=None, noti_dur=3.0, topmost=False):
CTkToolTip(widget, message=message, is_noti=True, noti_event=event, noti_dur=noti_dur, topmost=topmost)
def show_noti(widget, message, event=None, noti_dur=3.0, topmost=False):
CTkToolTip(widget, message=message, is_noti=True,
noti_event=event, noti_dur=noti_dur, topmost=topmost)
def get_update_time_from_html(workshop_id):
current_year = datetime.now().year
date_format_with_year = "%d %b, %Y @ %I:%M%p"
date_format_with_added_year = "%d %b @ %I:%M%p, %Y"
url = f"https://steamcommunity.com/sharedfiles/filedetails/?id={workshop_id}"
try:
response = requests.get(url)
response.raise_for_status()
content = response.text
soup = BeautifulSoup(content, "html.parser")
details_stats_container = soup.find(
"div", class_="detailsStatsContainerRight")
if details_stats_container:
details_stat_elements = details_stats_container.find_all(
"div", class_="detailsStatRight")
try:
date_updated = details_stat_elements[2].text.strip()
except:
date_updated = None
if not date_updated:
return None
try:
date_updated = datetime.strptime(
date_updated, date_format_with_year)
except ValueError:
date_updated = datetime.strptime(
date_updated + f", {current_year}", date_format_with_added_year)
return int(date_updated.timestamp())
except Exception as e:
print(f"Error getting update time for URL {url}: {e}")
return None
def check_item_date(down_date, date_updated, format=False): def check_item_date(down_date, date_updated, format=False):
current_year = datetime.now().year current_year = datetime.now().year
@ -364,15 +465,19 @@ def check_item_date(down_date, date_updated, format=False):
date_format_with_added_year = "%d %b @ %I:%M%p, %Y" date_format_with_added_year = "%d %b @ %I:%M%p, %Y"
try: try:
try: try:
download_datetime = datetime.strptime(down_date, date_format_with_year) download_datetime = datetime.strptime(
down_date, date_format_with_year)
except ValueError: except ValueError:
download_datetime = datetime.strptime(down_date + f", {current_year}", date_format_with_added_year) download_datetime = datetime.strptime(
down_date + f", {current_year}", date_format_with_added_year)
if format: if format:
try: try:
date_updated = datetime.strptime(date_updated, date_format_with_year) date_updated = datetime.strptime(
date_updated, date_format_with_year)
except ValueError: except ValueError:
date_updated = datetime.strptime(date_updated + f", {current_year}", date_format_with_added_year) date_updated = datetime.strptime(
date_updated + f", {current_year}", date_format_with_added_year)
if date_updated >= download_datetime: if date_updated >= download_datetime:
return True return True
@ -381,6 +486,7 @@ def check_item_date(down_date, date_updated, format=False):
except: except:
return False return False
def get_window_size_from_registry(): def get_window_size_from_registry():
try: try:
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, REGISTRY_KEY_PATH, 0, winreg.KEY_READ) as key: with winreg.OpenKey(winreg.HKEY_CURRENT_USER, REGISTRY_KEY_PATH, 0, winreg.KEY_READ) as key:
@ -392,16 +498,19 @@ def get_window_size_from_registry():
except (FileNotFoundError, OSError, ValueError, FileNotFoundError): except (FileNotFoundError, OSError, ValueError, FileNotFoundError):
return None, None, None, None return None, None, None, None
def save_window_size_to_registry(width, height, x, y): def save_window_size_to_registry(width, height, x, y):
try: try:
with winreg.CreateKey(winreg.HKEY_CURRENT_USER, REGISTRY_KEY_PATH) as key: with winreg.CreateKey(winreg.HKEY_CURRENT_USER, REGISTRY_KEY_PATH) as key:
winreg.SetValueEx(key, "WindowWidth", 0, winreg.REG_SZ, str(width)) winreg.SetValueEx(key, "WindowWidth", 0, winreg.REG_SZ, str(width))
winreg.SetValueEx(key, "WindowHeight", 0, winreg.REG_SZ, str(height)) winreg.SetValueEx(key, "WindowHeight", 0,
winreg.REG_SZ, str(height))
winreg.SetValueEx(key, "WindowX", 0, winreg.REG_SZ, str(x)) winreg.SetValueEx(key, "WindowX", 0, winreg.REG_SZ, str(x))
winreg.SetValueEx(key, "WindowY", 0, winreg.REG_SZ, str(y)) winreg.SetValueEx(key, "WindowY", 0, winreg.REG_SZ, str(y))
except Exception as e: except Exception as e:
print(f"Error saving to registry: {e}") print(f"Error saving to registry: {e}")
def item_steam_api(id): def item_steam_api(id):
try: try:
url = ITEM_INFO_API url = ITEM_INFO_API
@ -416,6 +525,7 @@ def item_steam_api(id):
print(e) print(e)
return False return False
def get_item_dates(ids): def get_item_dates(ids):
try: try:
data = { data = {
@ -429,12 +539,44 @@ def get_item_dates(ids):
if "response" in response_data: if "response" in response_data:
item_details = response_data["response"]["publishedfiledetails"] item_details = response_data["response"]["publishedfiledetails"]
return {item["publishedfileid"]: item["time_updated"] for item in item_details} return_list = {item["publishedfileid"]: item.get(
"time_updated", None) for item in item_details}
return return_list
return {} return {}
except Exception as e: except Exception as e:
print(e) print("Error: could not fetch all update times. Breaking early.")
return {} return {}
def isNullOrWhiteSpace(str):
if (str is None) or (str == "") or (str.isspace()):
return True
return False
def concatenate_sublists(a):
out = []
for sublist in a:
out.extend(sublist)
return out
def nextnonexistentdir(f, dir=os.path.dirname(os.path.realpath(__file__))):
i = 0
def already_exists(i):
if i==0 and os.path.exists(os.path.join(dir, (f).strip())):
return True
else:
return os.path.exists(os.path.join(dir, (f + f'_{str(i)}').strip()))
while already_exists(i):
i += 1
root_i_ext = [f, i]
return root_i_ext
# End helper functions # End helper functions

View File

@ -46,4 +46,4 @@ LIBRARY_FILE = "T7xwd_library.json"
RESOURCES_DIR = os.path.join(os.path.dirname(__file__), '..', 'resources') RESOURCES_DIR = os.path.join(os.path.dirname(__file__), '..', 'resources')
UPDATER_FOLDER = "update" UPDATER_FOLDER = "update"
REGISTRY_KEY_PATH = r"Software\T7xWD" REGISTRY_KEY_PATH = r"Software\T7xWD"
VERSION = "v0.3.3" VERSION = "v0.3.5"

View File

@ -231,7 +231,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
item_type, item_name = item[5], item[0] item_type, item_name = item[5], item[0]
return (0, item_name) if item_type == "map" else (1, item_name) return (0, item_name) if item_type == "map" else (1, item_name)
def load_items(self, T7xFolder, dont_add=False): def load_items(self, gameFolder, dont_add=False):
if self.refresh_next_time and not dont_add: if self.refresh_next_time and not dont_add:
self.refresh_next_time = False self.refresh_next_time = False
status = self.refresh_items() status = self.refresh_items()
@ -243,8 +243,8 @@ class LibraryTab(ctk.CTkScrollableFrame):
self.ids_added.clear() self.ids_added.clear()
self.refresh_next_time = True self.refresh_next_time = True
maps_folder = Path(T7xFolder) / "mods" maps_folder = Path(gameFolder) / "mods"
mods_folder = Path(T7xFolder) / "usermaps" mods_folder = Path(gameFolder) / "usermaps"
mod_img = os.path.join(RESOURCES_DIR, "mod_image.png") mod_img = os.path.join(RESOURCES_DIR, "mod_image.png")
map_img = os.path.join(RESOURCES_DIR, "map_image.png") map_img = os.path.join(RESOURCES_DIR, "map_image.png")
b_mod_img = os.path.join(RESOURCES_DIR, "b_mod_image.png") b_mod_img = os.path.join(RESOURCES_DIR, "b_mod_image.png")
@ -262,14 +262,14 @@ class LibraryTab(ctk.CTkScrollableFrame):
except: pass except: pass
for folder_path in folders_to_process: for folder_path in folders_to_process:
for zone_path in folder_path.glob("**/zone"): for zone_path in folder_path.glob("*/zone"):
json_path = zone_path / "workshop.json" json_path = zone_path / "workshop.json"
if json_path.exists(): if json_path.exists():
curr_folder_name = zone_path.parent.name curr_folder_name = zone_path.parent.name
workshop_id = extract_json_data(json_path, "PublisherID") or "None" workshop_id = extract_json_data(json_path, "PublisherID") or "None"
name = re.sub(r'\^\d', '', extract_json_data(json_path, "Title")) or "None" name = re.sub(r'\^\d', '', extract_json_data(json_path, "Title")) or "None"
name = name[:45] + "..." if len(name) > 45 else name name = name[:60] + "..." if len(name) > 60 else name
item_type = extract_json_data(json_path, "Type") or "None" item_type = extract_json_data(json_path, "Type") or "None"
folder_name = extract_json_data(json_path, "FolderName") or "None" folder_name = extract_json_data(json_path, "FolderName") or "None"
folder_size_bytes = get_folder_size(zone_path.parent) folder_size_bytes = get_folder_size(zone_path.parent)
@ -298,7 +298,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
if curr_folder_name not in self.added_folders: if curr_folder_name not in self.added_folders:
image_path = mod_img if item_type == "mod" else map_img image_path = mod_img if item_type == "mod" else map_img
if not (str(curr_folder_name).strip() == str(workshop_id).strip() or str(curr_folder_name).strip() == str(folder_name).strip() if not (str(curr_folder_name).strip() == str(workshop_id).strip() or str(curr_folder_name).strip() == str(folder_name).strip()
or str(curr_folder_name).strip() == f"{folder_name}_{workshop_id}"): or str(curr_folder_name).strip() == f"{folder_name}_{workshop_id}" or str(curr_folder_name).strip() == f"{folder_name}_duplicated"):
try: self.remove_item_by_option(items_file, curr_folder_name, "folder_name") try: self.remove_item_by_option(items_file, curr_folder_name, "folder_name")
except: pass except: pass
self.item_block_list.add(curr_folder_name) self.item_block_list.add(curr_folder_name)
@ -308,6 +308,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
elif (curr_folder_name not in self.added_folders and (workshop_id in self.ids_added or workshop_id == "None")): elif (curr_folder_name not in self.added_folders and (workshop_id in self.ids_added or workshop_id == "None")):
try: self.remove_item_by_option(items_file, curr_folder_name, "folder_name") try: self.remove_item_by_option(items_file, curr_folder_name, "folder_name")
except: pass except: pass
self.item_block_list.add(workshop_id)
text_to_add = re.sub(r'ID:\s+(?:\d+|None)', f'Folder: {curr_folder_name}', text_to_add) text_to_add = re.sub(r'ID:\s+(?:\d+|None)', f'Folder: {curr_folder_name}', text_to_add)
image_path = b_mod_img if item_type == "mod" else b_map_img image_path = b_mod_img if item_type == "mod" else b_map_img
text_to_add += " | ⚠️" text_to_add += " | ⚠️"
@ -325,7 +326,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
"folder_name": curr_folder_name, "folder_name": curr_folder_name,
"json_folder_name": folder_name "json_folder_name": folder_name
} }
# when item is blocked ,item_exists_in_file() returns None for folder_found # when item is blocked item_exists_in_file() returns None for folder_found
if not id_found and folder_found == None: if not id_found and folder_found == None:
self.remove_item_by_option(items_file, curr_folder_name, "folder_name") self.remove_item_by_option(items_file, curr_folder_name, "folder_name")
elif not id_found and not folder_found and curr_folder_name not in self.item_block_list and workshop_id not in self.ids_added: elif not id_found and not folder_found and curr_folder_name not in self.item_block_list and workshop_id not in self.ids_added:
@ -339,14 +340,14 @@ class LibraryTab(ctk.CTkScrollableFrame):
f.seek(0) f.seek(0)
json.dump(items_data, f, indent=4) json.dump(items_data, f, indent=4)
if id_found and not folder_found and curr_folder_name not in self.item_block_list and workshop_id not in self.ids_added: if curr_folder_name not in self.item_block_list:
self.update_or_add_item_by_id(items_file, item_info, workshop_id) self.update_or_add_item_by_id(items_file, item_info, workshop_id)
# keep here cuz of item_exists_in_file() testing # keep here cuz of item_exists_in_file() testing
self.added_folders.add(curr_folder_name) self.added_folders.add(curr_folder_name)
# added that cuz it sometimes can add blocked ids first # added that cuz it sometimes can add blocked ids first
# and legit ids will be blocked cuz theyll be added to "ids_added" # and legit ids will be blocked cuz theyll be added to "ids_added"
if not workshop_id in self.ids_added and curr_folder_name not in self.item_block_list: if not workshop_id in self.ids_added and curr_folder_name not in self.item_block_list and workshop_id!='None':
self.ids_added.add(workshop_id) self.ids_added.add(workshop_id)
# sort items by type then alphabet # sort items by type then alphabet
@ -354,7 +355,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
for item in ui_items_to_add: for item in ui_items_to_add:
self.add_item_helper(*item) self.add_item_helper(*item)
if not self.file_cleaned and os.path.exists(items_file): if os.path.exists(items_file):
self.file_cleaned = True self.file_cleaned = True
self.clean_json_file(items_file) self.clean_json_file(items_file)
@ -372,22 +373,22 @@ class LibraryTab(ctk.CTkScrollableFrame):
self.show_no_items_message(only_up=True) self.show_no_items_message(only_up=True)
return "No items in current selected folder" return "No items in current selected folder"
def update_item(self, T7xFolder, id, item_type, foldername): def update_item(self, gameFolder, id, item_type, foldername):
try: try:
if item_type == "map": if item_type == "map":
folder_path = Path(T7xFolder) / "usermaps" / f"{foldername}" folder_path = Path(gameFolder) / "usermaps" / f"{foldername}"
elif item_type == "mod": elif item_type == "mod":
folder_path = Path(T7xFolder) / "mods" / f"{foldername}" folder_path = Path(gameFolder) / "mods" / f"{foldername}"
else: else:
raise ValueError("Unsupported item_type. It must be 'map' or 'mod'.") raise ValueError("Unsupported item_type. It must be 'map' or 'mod'.")
for zone_path in folder_path.glob("**/zone"): for zone_path in folder_path.glob("*/zone"):
json_path = zone_path / "workshop.json" json_path = zone_path / "workshop.json"
if json_path.exists(): if json_path.exists():
workshop_id = extract_json_data(json_path, "PublisherID") workshop_id = extract_json_data(json_path, "PublisherID")
if workshop_id == id: if workshop_id == id:
name = extract_json_data(json_path, "Title").replace(">", "").replace("^", "") name = extract_json_data(json_path, "Title").replace(">", "").replace("^", "")
name = name[:45] + "..." if len(name) > 45 else name name = name[:60] + "..." if len(name) > 60 else name
item_type = extract_json_data(json_path, "Type") item_type = extract_json_data(json_path, "Type")
folder_name = extract_json_data(json_path, "FolderName") folder_name = extract_json_data(json_path, "FolderName")
size = convert_bytes_to_readable(get_folder_size(zone_path.parent)) size = convert_bytes_to_readable(get_folder_size(zone_path.parent))
@ -456,7 +457,8 @@ class LibraryTab(ctk.CTkScrollableFrame):
self.added_items.clear() self.added_items.clear()
self.added_folders.clear() self.added_folders.clear()
self.ids_added.clear() self.ids_added.clear()
status = self.load_items(main_app.app.edit_destination_folder.get().strip())
status = self.load_items( main_app.app.settings_tab.edit_destination_folder.get().strip())
main_app.app.title(f"T7x Workshop Downloader - Library ➜ {status}") main_app.app.title(f"T7x Workshop Downloader - Library ➜ {status}")
# main_app library event needs a return for status => when refresh_next_time is true # main_app library event needs a return for status => when refresh_next_time is true
return status return status
@ -471,7 +473,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
if only_up: if only_up:
return return
self.no_items_label.grid(row=1, column=0, padx=10, pady=(0, 10), sticky="n") self.no_items_label.grid(row=1, column=0, padx=10, pady=(0, 10), sticky="n")
self.no_items_label.configure(text="No items found in the selected folder. \nMake sure you have a mod/map downloaded and or have the right T7x folder selected.") self.no_items_label.configure(text="No items found in the selected folder. \nMake sure you have a mod/map downloaded and or have the right game folder selected.")
def hide_no_items_message(self): def hide_no_items_message(self):
self.update_tooltip.configure(message="Check items for updates") self.update_tooltip.configure(message="Check items for updates")
@ -587,7 +589,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
if json_path.exists(): if json_path.exists():
workshop_id = extract_json_data(json_path, "PublisherID") or "None" workshop_id = extract_json_data(json_path, "PublisherID") or "None"
name = re.sub(r'\^\w+', '', extract_json_data(json_path, "Title")) or "None" name = re.sub(r'\^\w+', '', extract_json_data(json_path, "Title")) or "None"
map_name = name[:45] + "..." if len(name) > 45 else name map_name = name[:60] + "..." if len(name) > 60 else name
map_mod_type = extract_json_data(json_path, "Type") or "None" map_mod_type = extract_json_data(json_path, "Type") or "None"
preview_iamge = json_path.parent / "previewimage.png" preview_iamge = json_path.parent / "previewimage.png"
if preview_iamge.exists(): if preview_iamge.exists():
@ -693,7 +695,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
main_app.app.edit_workshop_id.delete(0, "end") main_app.app.edit_workshop_id.delete(0, "end")
main_app.app.edit_workshop_id.insert(0, workshop_id) main_app.app.edit_workshop_id.insert(0, workshop_id)
main_app.app.main_button_event() main_app.app.main_button_event()
if invalid_warn and check_config("update_invalid", "no") == "yes": if invalid_warn and check_config("update_invalid", "off") == "on":
main_app.app.download_map(update=True, invalid_item_folder=os.path.basename(folder)) main_app.app.download_map(update=True, invalid_item_folder=os.path.basename(folder))
else: else:
main_app.app.download_map(update=True) main_app.app.download_map(update=True)
@ -810,8 +812,8 @@ class LibraryTab(ctk.CTkScrollableFrame):
update_btn.configure(state="disabled") update_btn.configure(state="disabled")
update_btn_tooltip.configure(message="Currently offline") update_btn_tooltip.configure(message="Currently offline")
view_button_tooltip.configure(message="Currently offline") view_button_tooltip.configure(message="Currently offline")
if check_config("update_invalid", "no") == "yes": if check_config("update_invalid", "off") == "on":
update_btn_tooltip.configure(message="update_invalid is set to 'yes' in config.ini") update_btn_tooltip.configure(message="update_invalid is set to 'on' in config.ini")
elif invalid_warn: elif invalid_warn:
update_btn.configure(text="Update", state="disabled") update_btn.configure(text="Update", state="disabled")
update_btn_tooltip.configure(message="Disabled due to item being blocked or duplicated") update_btn_tooltip.configure(message="Disabled due to item being blocked or duplicated")
@ -869,12 +871,19 @@ class LibraryTab(ctk.CTkScrollableFrame):
item_data = get_item_dates(item_ids) item_data = get_item_dates(item_ids)
for item_id, date_updated in item_data.items(): for item_id, date_updated in item_data.items():
item_date = item_dates[item_id] if not date_updated:
try:
new_date = get_update_time_from_html(item_id)
date_updated = new_date if new_date else 1
except:
date_updated = 1
item_date = item_dates[str(item_id)] if str(item_id) in item_dates else ""
date_updated = datetime.fromtimestamp(date_updated) date_updated = datetime.fromtimestamp(date_updated)
if check_item_date(item_date, date_updated): if check_item_date(item_date, date_updated):
date_updated = date_updated.strftime("%d %b @ %I:%M%p, %Y") if item_date != "":
self.to_update.add(texts[item_id] + f" | Updated: {date_updated}") date_updated = date_updated.strftime("%d %b @ %I:%M%p, %Y")
self.to_update.add(texts[item_id] + f" | Updated: {date_updated}")
except Exception as e: except Exception as e:
show_message("Error", f"Error occurred\n{e}", icon="cancel") show_message("Error", f"Error occurred\n{e}", icon="cancel")
@ -884,7 +893,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
lib_data = None lib_data = None
if not os.path.exists(os.path.join(APPLICATION_PATH, LIBRARY_FILE)): if not os.path.exists(os.path.join(APPLICATION_PATH, LIBRARY_FILE)):
show_message("Error checking for item updates! -> Setting is on", "Please visit library tab at least once with the correct T7x path!, you also need to have at least 1 item!") show_message("Error checking for item updates! -> Setting is on", "Please visit library tab at least once with the correct game path! You also need to have at least 1 item!")
return return
with open(os.path.join(APPLICATION_PATH, LIBRARY_FILE), 'r') as file: with open(os.path.join(APPLICATION_PATH, LIBRARY_FILE), 'r') as file:
@ -897,7 +906,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
if_ids_need_update(item_ids, item_dates, texts) if_ids_need_update(item_ids, item_dates, texts)
except: except:
show_message("Error checking for item updates!", "Please visit the library tab at least once with the correct T7x path!, you also need to have at least 1 item!") show_message("Error checking for item updates!", "Please visit the library tab at least once with the correct game path! You also need to have at least 1 item!")
return return
check_for_update() check_for_update()

View File

@ -23,37 +23,40 @@ class T7xWD(ctk.CTk):
self.geometry(f"{920}x{560}") self.geometry(f"{920}x{560}")
self.minsize(920, 560) self.minsize(920, 560)
ctk.set_window_scaling(float(check_config("scaling", 1.0)) + 0.25)
if os.path.exists(os.path.join(RESOURCES_DIR, "ryuk.ico")): if os.path.exists(os.path.join(RESOURCES_DIR, "ryuk.ico")):
self.wm_iconbitmap(os.path.join(RESOURCES_DIR, "ryuk.ico")) self.wm_iconbitmap(os.path.join(RESOURCES_DIR, "ryuk.ico"))
self.protocol("WM_DELETE_WINDOW", self.on_closing) self.protocol("WM_DELETE_WINDOW", self.on_closing)
# Qeue frame/tab, keep here or app will start shrinked eveytime # Queue frame/tab, keep here or app will start shrinked eveytime
self.qeueuframe = ctk.CTkFrame(self) self.queue_tab_enabled = False # Enable Queue Tab
self.qeueuframe.columnconfigure(1, weight=1)
self.qeueuframe.columnconfigure(2, weight=1)
self.qeueuframe.columnconfigure(3, weight=1)
self.qeueuframe.rowconfigure(1, weight=1)
self.qeueuframe.rowconfigure(2, weight=1)
self.qeueuframe.rowconfigure(3, weight=1)
self.qeueuframe.rowconfigure(4, weight=1)
self.workshop_queue_label = ctk.CTkLabel(self.qeueuframe, text="Workshop IDs/Links -> press help to see examples:") self.queueframe = ctk.CTkFrame(self)
self.workshop_queue_label.grid(row=0, column=0, padx=(20, 20), pady=(20, 20), sticky="wns") self.queueframe.columnconfigure(1, weight=1)
self.queueframe.columnconfigure(2, weight=1)
self.queueframe.columnconfigure(3, weight=1)
self.queueframe.rowconfigure(1, weight=1)
self.queueframe.rowconfigure(2, weight=0)
self.queueframe.rowconfigure(3, weight=0)
self.queueframe.rowconfigure(4, weight=0)
self.help_button = ctk.CTkButton(master=self.qeueuframe, text="Help", command=self.help_queue_text_func, width=10, height=10, fg_color="#585858") self.workshop_queue_label = ctk.CTkLabel(self.queueframe, text="Batch Downloader:")
self.help_button.grid(row=0, column=0, padx=(352, 0), pady=(23, 0), sticky="en") self.workshop_queue_label.grid(row=0, column=0, padx=(20, 20), pady=(10, 10), sticky="wns")
self.help_button = ctk.CTkButton(master=self.queueframe, text="Help", command=self.help_queue_text_func, width=10, height=10, fg_color="#585858")
self.help_button.grid(row=0, column=3, padx=(0, 20), pady=(10, 0), sticky="en")
self.help_restore_content = None self.help_restore_content = None
self.queuetextarea = ctk.CTkTextbox(master=self.qeueuframe, font=("", 15)) self.queuetextarea = ctk.CTkTextbox(master=self.queueframe, font=("", 15))
self.queuetextarea.grid(row=1, column=0, columnspan=4, padx=(20, 20), pady=(0, 20), sticky="nwse") self.queuetextarea.grid(row=1, column=0, columnspan=4, padx=(20, 20), pady=(0, 40), sticky="nwse")
self.status_text = ctk.CTkLabel(self.qeueuframe, text="Status: Standby!") self.status_text = ctk.CTkLabel(self.queueframe, text="Status: Standby!")
self.status_text.grid(row=3, column=0, padx=(20, 20), pady=(0, 20), sticky="ws") self.status_text.grid(row=1, column=0, padx=(20, 20), pady=(40, 0), sticky="ws")
self.skip_boutton = ctk.CTkButton(master=self.qeueuframe, text="Skip", command=self.skip_current_queue_item, width=10, height=10, fg_color="#585858") self.skip_button = ctk.CTkButton(master=self.queueframe, text="Skip", command=self.skip_current_queue_item, width=10, height=10, fg_color="#585858")
self.qeueuframe.grid_remove() self.queueframe.grid_remove()
# configure grid layout (4x4) # configure grid layout (4x4)
self.grid_columnconfigure(1, weight=1) self.grid_columnconfigure(1, weight=1)
@ -78,18 +81,22 @@ class T7xWD(ctk.CTk):
self.txt_label.grid(row=1, column=0, padx=20, pady=(20, 10)) self.txt_label.grid(row=1, column=0, padx=20, pady=(20, 10))
self.sidebar_main = ctk.CTkButton(self.sidebar_frame, height=28) self.sidebar_main = ctk.CTkButton(self.sidebar_frame, height=28)
self.sidebar_main.grid(row=2, column=0, padx=10, pady=(20, 6)) self.sidebar_main.grid(row=2, column=0, padx=10, pady=(20, 6))
self.sidebar_queue = ctk.CTkButton(self.sidebar_frame, height=28) if self.queue_tab_enabled:
self.sidebar_queue.grid(row=3, column=0, padx=10, pady=6) self.sidebar_queue = ctk.CTkButton(self.sidebar_frame, height=28)
self.sidebar_queue.grid(row=3, column=0, padx=10, pady=6)
self.sidebar_library = ctk.CTkButton(self.sidebar_frame, height=28) self.sidebar_library = ctk.CTkButton(self.sidebar_frame, height=28)
self.sidebar_library.grid(row=4, column=0, padx=10, pady=6, sticky="n") self.sidebar_library.grid(row=4, column=0, padx=10, pady=6, sticky="n")
self.launch_game = ctk.CTkButton(self.sidebar_frame, text="Launch game", command=self.settings_launch_game, height=28)
self.launch_game.grid(row=5, column=0, padx=10, pady=6, sticky="n")
self.sidebar_settings = ctk.CTkButton(self.sidebar_frame, height=28) self.sidebar_settings = ctk.CTkButton(self.sidebar_frame, height=28)
self.sidebar_settings.grid(row=5, column=0, padx=10, pady=6, sticky="n") self.sidebar_settings.grid(row=6, column=0, padx=10, pady=6, sticky="n")
# create optionsframe # create optionsframe
self.optionsframe = ctk.CTkFrame(self) self.optionsframe = ctk.CTkFrame(self)
self.optionsframe.grid(row=0, column=1, rowspan=2, padx=(0, 20), pady=(20, 0), sticky="nsew") self.optionsframe.grid(row=0, column=1, rowspan=1, padx=(0, 20), pady=(20, 0), sticky="nsew")
self.txt_main = ctk.CTkLabel(self.optionsframe, text="💎 T7xWD 💎", font=(font, 20)) self.txt_main = ctk.CTkLabel(self.optionsframe, text="💎 T7xWD 💎", font=(font, 20))
self.txt_main.grid(row=0, column=1, columnspan=5, padx=0, pady=(20, 20), sticky="n") self.txt_main.grid(row=0, column=1, columnspan=1, padx=0, pady=(20, 20), sticky="n")
self.queueframe.grid(row=1, column=1, rowspan=1, padx=(0, 20), pady=(20, 0), sticky="nsew")
# create slider and progressbar frame # create slider and progressbar frame
self.slider_progressbar_frame = ctk.CTkFrame(self) self.slider_progressbar_frame = ctk.CTkFrame(self)
@ -103,9 +110,6 @@ class T7xWD(ctk.CTk):
self.slider_progressbar_frame.rowconfigure(2, weight=1) self.slider_progressbar_frame.rowconfigure(2, weight=1)
self.slider_progressbar_frame.rowconfigure(3, weight=1) self.slider_progressbar_frame.rowconfigure(3, weight=1)
# self.spacer = ctk.CTkLabel(master=self.slider_progressbar_frame, text="")
# self.spacer.grid(row=0, column=0, columnspan=1)
self.label_speed = ctk.CTkLabel(master=self.slider_progressbar_frame, text="Awaiting Download!") self.label_speed = ctk.CTkLabel(master=self.slider_progressbar_frame, text="Awaiting Download!")
self.label_speed.grid(row=1, column=0, padx=20, pady=(0, 10), sticky="w") self.label_speed.grid(row=1, column=0, padx=20, pady=(0, 10), sticky="w")
self.elapsed_time = ctk.CTkLabel(master=self.slider_progressbar_frame, text="", anchor="center") self.elapsed_time = ctk.CTkLabel(master=self.slider_progressbar_frame, text="", anchor="center")
@ -127,79 +131,61 @@ class T7xWD(ctk.CTk):
# options frame # options frame
self.optionsframe.columnconfigure(1, weight=1) self.optionsframe.columnconfigure(1, weight=1)
self.optionsframe.columnconfigure(2, weight=1) self.optionsframe.columnconfigure(2, weight=0)
self.optionsframe.columnconfigure(3, weight=1) self.optionsframe.columnconfigure(3, weight=0)
self.optionsframe.rowconfigure(1, weight=1) self.optionsframe.rowconfigure(1, weight=0)
self.optionsframe.rowconfigure(2, weight=1) self.optionsframe.rowconfigure(2, weight=0)
self.optionsframe.rowconfigure(3, weight=1) self.optionsframe.rowconfigure(3, weight=0)
self.optionsframe.rowconfigure(4, weight=1) self.optionsframe.rowconfigure(4, weight=0)
self.label_workshop_id = ctk.CTkLabel(master=self.optionsframe, text="Enter the Workshop ID or Link of the map/mod you want to download:\n") self.label_workshop_id = ctk.CTkLabel(master=self.optionsframe, text="Enter a Workshop ID or Link to download:\n")
self.label_workshop_id.grid(row=1, column=1, padx=20, pady=(10, 0), columnspan=4, sticky="ws") self.label_workshop_id.grid(row=1, column=1, padx=(10, 5), pady=(10, 0), columnspan=1, sticky="ws")
self.check_if_changed = ctk.StringVar() self.check_if_changed = ctk.StringVar()
self.check_if_changed.trace_add("write", self.id_chnaged_handler) self.check_if_changed.trace_add("write", self.id_changed_handler)
self.edit_workshop_id = ctk.CTkEntry(master=self.optionsframe, textvariable=self.check_if_changed) self.edit_workshop_id = ctk.CTkEntry(master=self.optionsframe, textvariable=self.check_if_changed)
self.edit_workshop_id.grid(row=2, column=1, padx=20, pady=(0, 10), columnspan=4, sticky="ewn") self.edit_workshop_id.grid(row=2, column=1, padx=(5, 5), pady=(0, 10), columnspan=1, sticky="ewn")
self.button_browse = ctk.CTkButton(master=self.optionsframe, text="Workshop", command=self.open_browser, width=10) self.button_browse = ctk.CTkButton(master=self.optionsframe, text="Workshop", command=self.open_browser, width=10)
self.button_browse.grid(row=2, column=5, padx=(0, 20), pady=(0, 10), sticky="en") self.button_browse.grid(row=2, column=2, padx=(5, 5), pady=(0, 10), sticky="en")
self.button_browse_tooltip = CTkToolTip(self.button_browse, message="Will open steam workshop for T7x in your browser") self.button_browse_tooltip = CTkToolTip(self.button_browse, message="Will open steam workshop in your browser")
self.info_button = ctk.CTkButton(master=self.optionsframe, text="Details", command=self.show_map_info, width=10) self.info_button = ctk.CTkButton(master=self.optionsframe, text="Details", command=self.show_map_info, width=10)
self.info_button.grid(row=2, column=5, padx=(0, 20), pady=(0, 10), sticky="wn") self.info_button.grid(row=2, column=3, padx=(5, 10), pady=(0, 10), sticky="wn")
self.label_destination_folder = ctk.CTkLabel(master=self.optionsframe, text='Enter Your T7x folder:')
self.label_destination_folder.grid(row=3, column=1, padx=20, pady=(0, 0), columnspan=4, sticky="ws")
self.edit_destination_folder = ctk.CTkEntry(master=self.optionsframe, placeholder_text="Your T7x Instalation folder")
self.edit_destination_folder.grid(row=4, column=1, padx=20, pady=(0, 25), columnspan=4, sticky="ewn")
self.button_T7x_browse = ctk.CTkButton(master=self.optionsframe, text="Select", command=self.open_T7x_browser)
self.button_T7x_browse.grid(row=4, column=5, padx=(0, 20), pady=(0, 10), sticky="ewn")
self.label_steamcmd_path = ctk.CTkLabel(master=self.optionsframe, text="Enter SteamCMD path:")
self.label_steamcmd_path.grid(row=5, column=1, padx=20, pady=(0, 0), columnspan=3, sticky="wn")
self.edit_steamcmd_path = ctk.CTkEntry(master=self.optionsframe, placeholder_text="Enter your SteamCMD path")
self.edit_steamcmd_path.grid(row=6, column=1, padx=20, pady=(0, 30), columnspan=4, sticky="ewn")
self.button_steamcmd_browse = ctk.CTkButton(master=self.optionsframe, text="Select", command=self.open_steamcmd_path_browser)
self.button_steamcmd_browse.grid(row=6, column=5, padx=(0, 20), pady=(0, 30), sticky="ewn")
# set default values # set default values
self.active_color = get_button_state_colors(check_custom_theme(check_config("theme", fallback="T7xwd_theme.json")), "button_active_state_color") self.active_color = get_button_state_colors(check_custom_theme(check_config("theme", fallback="T7xwd_theme.json")), "button_active_state_color")
self.normal_color = get_button_state_colors(check_custom_theme(check_config("theme", fallback="T7xwd_theme.json")), "button_normal_state_color") self.normal_color = get_button_state_colors(check_custom_theme(check_config("theme", fallback="T7xwd_theme.json")), "button_normal_state_color")
self.progress_color = get_button_state_colors(check_custom_theme(check_config("theme", fallback="T7xwd_theme.json")), "progress_bar_fill_color") self.progress_color = get_button_state_colors(check_custom_theme(check_config("theme", fallback="T7xwd_theme.json")), "progress_bar_fill_color")
self.settings_tab.appearance_mode_optionemenu.set("Dark") self.settings_tab.appearance_mode_optionemenu.set("Dark")
self.settings_tab.scaling_optionemenu.set("100%") self.settings_tab.scaling_optionemenu.set("80%")
self.progress_bar.set(0.0) self.progress_bar.set(0.0)
self.progress_bar.configure(progress_color=self.progress_color) self.progress_bar.configure(progress_color=self.progress_color)
self.hide_settings_widgets() self.hide_settings_widgets()
self.button_stop.configure(state="disabled") self.button_stop.configure(state="disabled")
self.is_pressed = False self.is_pressed = False
self.queue_enabled = False
self.queue_stop_button = False self.queue_stop_button = False
self.is_downloading = False self.is_downloading = False
self.is_steamcmd_updating = False
self.item_skipped = False self.item_skipped = False
self.steam_updater_size = "Unknown size"
self.fail_threshold = 0 self.fail_threshold = 0
# sidebar windows bouttons # sidebar windows buttons
self.sidebar_main.configure(command=self.main_button_event, text="Main ⬇️", fg_color=(self.active_color), state="active") self.sidebar_main.configure(command=self.main_button_event, text="Main ⬇️", fg_color=(self.active_color), state="active")
self.sidebar_library.configure(text="Library 📙", command=self.library_button_event) self.sidebar_library.configure(text="Library 📙", command=self.library_button_event)
self.sidebar_queue.configure(text="Queue 🚧", command=self.queue_button_event) if self.queue_tab_enabled: self.sidebar_queue.configure(text="Queue 🚧", command=self.queue_button_event)
sidebar_settings_button_image = os.path.join(RESOURCES_DIR, "sett10.png") sidebar_settings_button_image = os.path.join(RESOURCES_DIR, "sett10.png")
self.sidebar_settings.configure(command=self.settings_button_event, text="", image=ctk.CTkImage(Image.open(sidebar_settings_button_image), size=(int(35), int(35))), fg_color="transparent", width=45, height=45) self.sidebar_settings.configure(command=self.settings_button_event, text="", image=ctk.CTkImage(Image.open(sidebar_settings_button_image), size=(int(35), int(35))), fg_color="transparent", width=45, height=45)
self.sidebar_settings_tooltip = CTkToolTip(self.sidebar_settings, message="Settings") self.sidebar_settings_tooltip = CTkToolTip(self.sidebar_settings, message="Settings")
self.sidebar_library_tooltip = CTkToolTip(self.sidebar_library, message="Experimental") self.sidebar_library_tooltip = CTkToolTip(self.sidebar_library, message="Experimental")
self.sidebar_queue_tooltip = CTkToolTip(self.sidebar_queue, message="Experimental") if self.queue_tab_enabled: self.sidebar_queue_tooltip = CTkToolTip(self.sidebar_queue, message="Experimental")
self.bind("<Configure>", lambda e: self.save_window_size_position()) self.bind("<Configure>", lambda e: self.save_window_size_position())
# context_menus # context_menus
self.create_context_menu(self.edit_workshop_id) self.create_context_menu(self.edit_workshop_id)
self.create_context_menu(self.edit_destination_folder) self.create_context_menu(self.settings_tab.edit_destination_folder)
self.create_context_menu(self.edit_steamcmd_path) self.create_context_menu(self.settings_tab.edit_steamcmd_path)
self.create_context_menu(self.queuetextarea, textbox=True) self.create_context_menu(self.queuetextarea, textbox=True)
self.create_context_menu(self.library_tab.filter_entry, textbox=False, library=True) self.create_context_menu(self.library_tab.filter_entry, textbox=False, library=True)
# valid event required for filter_items() # valid event required for filter_items()
@ -210,9 +196,10 @@ class T7xWD(ctk.CTk):
# load ui configs # load ui configs
self.load_configs() self.load_configs()
if check_config("checkforupdtes") == "on": if check_config("checkforupdates") == "on":
self.withdraw() self.withdraw()
check_for_updates_func(self, ignore_up_todate=True) try: check_for_updates_func(self, ignore_up_todate=True)
except: pass
self.update() self.update()
self.deiconify() self.deiconify()
@ -331,12 +318,16 @@ class T7xWD(ctk.CTk):
print("Invalid geometry format:", geometry) print("Invalid geometry format:", geometry)
def on_closing(self): def on_closing(self):
save_config("DestinationFolder" ,self.edit_destination_folder.get()) save_config("DestinationFolder",self.settings_tab.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.edit_steamcmd_path.get()) save_config("SteamCMDPath",self.settings_tab.edit_steamcmd_path.get())
startupexe = str(self.settings_tab.edit_startup_exe.get())
launchargs = str(self.settings_tab.edit_launch_args.get())
save_config("GameExecutable",startupexe if not isNullOrWhiteSpace(startupexe) else "BlackOps3")
save_config("LaunchParameters",launchargs if not isNullOrWhiteSpace(launchargs) else " ")
self.stop_download(on_close=True) self.stop_download(on_close=True)
os._exit(0) os._exit(0)
def id_chnaged_handler(self, some=None, other=None ,shit=None): def id_changed_handler(self, some=None, other=None ,shit=None):
self.after(1, self.label_file_size.configure(text=f"File size: 0KB")) self.after(1, self.label_file_size.configure(text=f"File size: 0KB"))
def check_for_updates(self): def check_for_updates(self):
@ -349,16 +340,22 @@ class T7xWD(ctk.CTk):
def change_scaling_event(self, new_scaling: str): def change_scaling_event(self, new_scaling: str):
new_scaling_float = int(new_scaling.replace("%", "")) / 100 new_scaling_float = int(new_scaling.replace("%", "")) / 100
ctk.set_widget_scaling(new_scaling_float) ctk.set_widget_scaling(new_scaling_float)
ctk.set_window_scaling(new_scaling_float+0.25)
save_config("scaling", str(new_scaling_float)) save_config("scaling", str(new_scaling_float))
def hide_main_widgets(self): def hide_main_widgets(self):
self.optionsframe.grid_forget() self.optionsframe.grid_forget()
self.slider_progressbar_frame.grid_forget() self.slider_progressbar_frame.grid_forget()
self.hide_queue_widgets()
def use_queue_download(self):
return not self.queuetextarea.get("1.0", "end").isspace()
def show_main_widgets(self): def show_main_widgets(self):
self.title("T7x Workshop Downloader - Main") self.title("T7x Workshop Downloader - Main")
self.slider_progressbar_frame.grid(row=2, column=1, rowspan=1, padx=(0, 20), pady=(20, 20), sticky="nsew") self.slider_progressbar_frame.grid(row=2, column=1, rowspan=1, padx=(0, 20), pady=(20, 20), sticky="nsew")
self.optionsframe.grid(row=0, column=1, rowspan=2, padx=(0, 20), pady=(20, 0), sticky="nsew") self.optionsframe.grid(row=0, column=1, rowspan=1, padx=(0, 20), pady=(20, 0), sticky="nsew")
self.queueframe.grid(row=1, column=1, rowspan=1, padx=(0, 20), pady=(20, 0), sticky="nsew")
def hide_settings_widgets(self): def hide_settings_widgets(self):
self.settings_tab.grid_forget() self.settings_tab.grid_forget()
@ -373,26 +370,24 @@ class T7xWD(ctk.CTk):
def show_library_widgets(self): def show_library_widgets(self):
self.title("T7x Workshop Downloader - Library ➜ Loading... ⏳") self.title("T7x Workshop Downloader - Library ➜ Loading... ⏳")
status = self.library_tab.load_items(self.edit_destination_folder.get()) status = self.library_tab.load_items(self.settings_tab.edit_destination_folder.get())
self.library_tab.grid(row=0, rowspan=3, column=1, padx=(0, 20), pady=(20, 20), sticky="nsew") self.library_tab.grid(row=0, rowspan=3, column=1, padx=(0, 20), pady=(20, 20), sticky="nsew")
self.title(f"T7x Workshop Downloader - Library ➜ {status}") self.title(f"T7x Workshop Downloader - Library ➜ {status}")
def show_queue_widgets(self): def show_queue_widgets(self):
self.title("T7x Workshop Downloader - Queue") self.title("T7x Workshop Downloader - Queue")
self.optionsframe.grid_forget() self.optionsframe.grid_forget()
self.queue_enabled = True
self.slider_progressbar_frame.grid(row=2, column=1, rowspan=1, padx=(0, 20), pady=(20, 20), sticky="nsew") self.slider_progressbar_frame.grid(row=2, column=1, rowspan=1, padx=(0, 20), pady=(20, 20), sticky="nsew")
self.qeueuframe.grid(row=0, column=1, rowspan=2, padx=(0, 20), pady=(20, 0), sticky="nsew") self.queueframe.grid(row=0, column=1, rowspan=2, padx=(0, 20), pady=(20, 0), sticky="nsew")
def hide_queue_widgets(self): def hide_queue_widgets(self):
self.queue_enabled = False self.queueframe.grid_forget()
self.qeueuframe.grid_forget()
def main_button_event(self): def main_button_event(self):
self.sidebar_main.configure(state="active", fg_color=(self.active_color)) self.sidebar_main.configure(state="active", fg_color=(self.active_color))
self.sidebar_settings.configure(state="normal", fg_color="transparent") self.sidebar_settings.configure(state="normal", fg_color="transparent")
self.sidebar_library.configure(state="normal", fg_color=(self.normal_color)) self.sidebar_library.configure(state="normal", fg_color=(self.normal_color))
self.sidebar_queue.configure(state="normal", fg_color=(self.normal_color)) if self.queue_tab_enabled: self.sidebar_queue.configure(state="normal", fg_color=(self.normal_color))
self.hide_settings_widgets() self.hide_settings_widgets()
self.hide_library_widgets() self.hide_library_widgets()
self.hide_queue_widgets() self.hide_queue_widgets()
@ -401,7 +396,7 @@ class T7xWD(ctk.CTk):
def settings_button_event(self): def settings_button_event(self):
self.sidebar_main.configure(state="normal", fg_color=(self.normal_color)) self.sidebar_main.configure(state="normal", fg_color=(self.normal_color))
self.sidebar_library.configure(state="normal", fg_color=(self.normal_color)) self.sidebar_library.configure(state="normal", fg_color=(self.normal_color))
self.sidebar_queue.configure(state="normal", fg_color=(self.normal_color)) if self.queue_tab_enabled: self.sidebar_queue.configure(state="normal", fg_color=(self.normal_color))
self.sidebar_settings.configure(state="active", fg_color=(self.active_color)) self.sidebar_settings.configure(state="active", fg_color=(self.active_color))
self.hide_main_widgets() self.hide_main_widgets()
self.hide_library_widgets() self.hide_library_widgets()
@ -411,7 +406,7 @@ class T7xWD(ctk.CTk):
def library_button_event(self): def library_button_event(self):
self.sidebar_main.configure(state="normal", fg_color=(self.normal_color)) self.sidebar_main.configure(state="normal", fg_color=(self.normal_color))
self.sidebar_settings.configure(state="normal", fg_color="transparent") self.sidebar_settings.configure(state="normal", fg_color="transparent")
self.sidebar_queue.configure(state="normal", fg_color=(self.normal_color)) if self.queue_tab_enabled: self.sidebar_queue.configure(state="normal", fg_color=(self.normal_color))
self.sidebar_library.configure(state="active", fg_color=(self.active_color)) self.sidebar_library.configure(state="active", fg_color=(self.active_color))
self.hide_main_widgets() self.hide_main_widgets()
self.hide_settings_widgets() self.hide_settings_widgets()
@ -422,7 +417,7 @@ class T7xWD(ctk.CTk):
self.sidebar_main.configure(state="normal", fg_color=(self.normal_color)) self.sidebar_main.configure(state="normal", fg_color=(self.normal_color))
self.sidebar_settings.configure(state="normal", fg_color="transparent") self.sidebar_settings.configure(state="normal", fg_color="transparent")
self.sidebar_library.configure(state="normal", fg_color=(self.normal_color)) self.sidebar_library.configure(state="normal", fg_color=(self.normal_color))
self.sidebar_queue.configure(state="active", fg_color=(self.active_color)) if self.queue_tab_enabled: self.sidebar_queue.configure(state="active", fg_color=(self.active_color))
self.hide_settings_widgets() self.hide_settings_widgets()
self.hide_library_widgets() self.hide_library_widgets()
self.show_queue_widgets() self.show_queue_widgets()
@ -431,12 +426,19 @@ class T7xWD(ctk.CTk):
if os.path.exists(CONFIG_FILE_PATH): if os.path.exists(CONFIG_FILE_PATH):
destination_folder = check_config("DestinationFolder", "") destination_folder = check_config("DestinationFolder", "")
steamcmd_path = check_config("SteamCMDPath", APPLICATION_PATH) steamcmd_path = check_config("SteamCMDPath", APPLICATION_PATH)
startup_exe = check_config("GameExecutable", "BlackOps3")
launch_params = check_config("LaunchParameters", None)
new_appearance_mode = check_config("appearance", "Dark") new_appearance_mode = check_config("appearance", "Dark")
new_scaling = check_config("scaling", 1.0) new_scaling = check_config("scaling", 1.0)
self.edit_destination_folder.delete(0, "end") self.settings_tab.edit_destination_folder.delete(0, "end")
self.edit_destination_folder.insert(0, destination_folder) self.settings_tab.edit_destination_folder.insert(0, destination_folder)
self.edit_steamcmd_path.delete(0, "end") self.settings_tab.edit_steamcmd_path.delete(0, "end")
self.edit_steamcmd_path.insert(0, steamcmd_path) self.settings_tab.edit_steamcmd_path.insert(0, steamcmd_path)
self.settings_tab.edit_startup_exe.delete(0, "end")
self.settings_tab.edit_startup_exe.insert(0, startup_exe)
if not isNullOrWhiteSpace(launch_params):
self.settings_tab.edit_launch_args.delete(0, "end")
self.settings_tab.edit_launch_args.insert(0, launch_params)
ctk.set_appearance_mode(new_appearance_mode) ctk.set_appearance_mode(new_appearance_mode)
ctk.set_widget_scaling(float(new_scaling)) ctk.set_widget_scaling(float(new_scaling))
self.settings_tab.appearance_mode_optionemenu.set(new_appearance_mode) self.settings_tab.appearance_mode_optionemenu.set(new_appearance_mode)
@ -452,18 +454,20 @@ class T7xWD(ctk.CTk):
scaling_float = float(new_scaling)*100 scaling_float = float(new_scaling)*100
scaling_int = math.trunc(scaling_float) scaling_int = math.trunc(scaling_float)
self.settings_tab.scaling_optionemenu.set(f"{scaling_int}%") self.settings_tab.scaling_optionemenu.set(f"{scaling_int}%")
self.edit_steamcmd_path.delete(0, "end") self.settings_tab.edit_steamcmd_path.delete(0, "end")
self.edit_steamcmd_path.insert(0, APPLICATION_PATH) self.settings_tab.edit_steamcmd_path.insert(0, APPLICATION_PATH)
self.settings_tab.edit_startup_exe.delete(0, "end")
self.settings_tab.edit_startup_exe.insert(0, "BlackOps3")
create_default_config() create_default_config()
def help_queue_text_func(self, event=None): def help_queue_text_func(self, event=None):
textarea_content = self.queuetextarea.get("1.0", "end").strip() textarea_content = self.queuetextarea.get("1.0", "end").strip()
help_text = "3010399939,2976006537,2118338989,...\nor:\n3010399939\n2976006537\n2113146805\n..." help_text = "3010399939,2976006537,2118338989 \n\nor:\n\n3010399939\n2976006537\n2113146805"
if any(char.strip() for char in textarea_content): if any(char.strip() for char in textarea_content):
if help_text in textarea_content: if help_text in textarea_content:
self.workshop_queue_label.configure(text="Workshop IDs/Links => press help to see examples:") self.workshop_queue_label.configure(text="Batch Downloader:")
self.help_button.configure(text="Help") self.help_button.configure(text="Help")
self.queuetextarea.configure(state="normal") self.queuetextarea.configure(state="normal",text_color="#fcfcfc")
self.queuetextarea.delete(1.0, "end") self.queuetextarea.delete(1.0, "end")
self.queuetextarea.insert(1.0, "") self.queuetextarea.insert(1.0, "")
if self.help_restore_content: if self.help_restore_content:
@ -472,37 +476,37 @@ class T7xWD(ctk.CTk):
self.queuetextarea.insert(1.0, "") self.queuetextarea.insert(1.0, "")
else: else:
if not help_text in textarea_content: if not help_text in textarea_content:
self.help_restore_content = textarea_content self.help_restore_content = textarea_content
self.workshop_queue_label.configure(text="Workshop IDs/Links => press help to see examples:") self.workshop_queue_label.configure(text="Batch Downloader:")
self.help_button.configure(text="Restore") self.help_button.configure(text="Restore")
self.queuetextarea.configure(state="normal") self.queuetextarea.configure(state="normal",text_color="#fcfcfc")
self.queuetextarea.delete(1.0, "end") self.queuetextarea.delete(1.0, "end")
self.queuetextarea.insert(1.0, "") self.queuetextarea.insert(1.0, "")
self.workshop_queue_label.configure(text="Workshop IDs/Links => press restore to remove examples:") self.workshop_queue_label.configure(text="Batch Downloader Example:")
self.queuetextarea.insert(1.0, help_text) self.queuetextarea.insert(1.0, help_text)
self.queuetextarea.configure(state="disabled") self.queuetextarea.configure(state="disabled", text_color="gray74")
else: else:
self.help_restore_content = textarea_content self.help_restore_content = textarea_content
self.workshop_queue_label.configure(text="Workshop IDs/Links => press restore to remove examples:") self.workshop_queue_label.configure(text="Batch Downloader Example:")
self.help_button.configure(text="Restore") self.help_button.configure(text="Restore")
self.queuetextarea.insert(1.0, help_text) self.queuetextarea.insert(1.0, help_text)
self.queuetextarea.configure(state="disabled") self.queuetextarea.configure(state="disabled", text_color="gray74")
def open_T7x_browser(self): def open_T7x_browser(self):
selected_folder = ctk.filedialog.askdirectory(title="Select T7x Folder") selected_folder = ctk.filedialog.askdirectory(title="Select Game Folder")
if selected_folder: if selected_folder:
self.edit_destination_folder.delete(0, "end") self.settings_tab.edit_destination_folder.delete(0, "end")
self.edit_destination_folder.insert(0, selected_folder) self.settings_tab.edit_destination_folder.insert(0, selected_folder)
save_config("DestinationFolder" ,self.edit_destination_folder.get()) save_config("DestinationFolder" ,self.settings_tab.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.edit_steamcmd_path.get()) save_config("SteamCMDPath" ,self.settings_tab.edit_steamcmd_path.get())
def open_steamcmd_path_browser(self): def open_steamcmd_path_browser(self):
selected_folder = ctk.filedialog.askdirectory(title="Select SteamCMD Folder") selected_folder = ctk.filedialog.askdirectory(title="Select SteamCMD Folder")
if selected_folder: if selected_folder:
self.edit_steamcmd_path.delete(0, "end") self.settings_tab.edit_steamcmd_path.delete(0, "end")
self.edit_steamcmd_path.insert(0, selected_folder) self.settings_tab.edit_steamcmd_path.insert(0, selected_folder)
save_config("DestinationFolder" ,self.edit_destination_folder.get()) save_config("DestinationFolder" ,self.settings_tab.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.edit_steamcmd_path.get()) save_config("SteamCMDPath" ,self.settings_tab.edit_steamcmd_path.get())
def show_steam_warning_message(self): def show_steam_warning_message(self):
def callback(): def callback():
@ -519,12 +523,15 @@ class T7xWD(ctk.CTk):
link = "https://steamcommunity.com/app/311210/workshop/" link = "https://steamcommunity.com/app/311210/workshop/"
webbrowser.open(link) webbrowser.open(link)
def settings_launch_game(self):
launch_game_func(check_config("destinationfolder"), self.settings_tab.edit_startup_exe.get(), self.settings_tab.edit_launch_args.get())
@if_internet_available @if_internet_available
def download_steamcmd(self): def download_steamcmd(self):
self.edit_steamcmd_path.delete(0, "end") self.settings_tab.edit_steamcmd_path.delete(0, "end")
self.edit_steamcmd_path.insert(0, APPLICATION_PATH) self.settings_tab.edit_steamcmd_path.insert(0, APPLICATION_PATH)
save_config("DestinationFolder" ,self.edit_destination_folder.get()) save_config("DestinationFolder" ,self.settings_tab.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.edit_steamcmd_path.get()) save_config("SteamCMDPath" ,self.settings_tab.edit_steamcmd_path.get())
steamcmd_url = "https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip" steamcmd_url = "https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip"
steamcmd_zip_path = os.path.join(APPLICATION_PATH, "steamcmd.zip") steamcmd_zip_path = os.path.join(APPLICATION_PATH, "steamcmd.zip")
@ -795,6 +802,61 @@ class T7xWD(ctk.CTk):
self.after(0, main_thread) self.after(0, main_thread)
def check_if_steamcmd_updating(self, log_file_path):
temp_file_path = log_file_path + '.temp'
if not os.path.exists(log_file_path):
if os.path.isdir(log_file_path):
try: os.makedirs(log_file_path)
except: return False
else:
try:
with open(log_file_path, 'w') as file:
file.write('')
except: pass
try: shutil.copy2(log_file_path, temp_file_path)
except: return False
try:
with open(temp_file_path, 'r') as log_file:
log_file.seek(0, os.SEEK_END)
file_size = log_file.tell()
position = file_size
lines_found = 0
while lines_found < 7 and position > 0:
position -= 1
log_file.seek(position, os.SEEK_SET)
char = log_file.read(1)
if char == '\n':
lines_found += 1
lines = log_file.readlines()[-7:]
for _line in reversed(lines):
line = _line.lower().strip()
if "downloading update" in line:
print(line)
match = re.search(r'(\d{1,3}(?:,\d{3})* of \d{1,3}(?:,\d{3})* [kKMGTP]{0,1}B)', _line)
if match:
update_size_str = match.group(1)
self.steam_updater_size = update_size_str
return True
elif self.is_steamcmd_updating and "downloading update" not in line:
self.steam_updater_size = "Installing"
return False
except:
try:
os.remove(temp_file_path)
except:
return False
finally:
try: os.remove(temp_file_path)
except: pass
def check_steamcmd_stdout(self, log_file_path, target_item_id): def check_steamcmd_stdout(self, log_file_path, target_item_id):
temp_file_path = log_file_path + '.temp' temp_file_path = log_file_path + '.temp'
if not os.path.exists(log_file_path): if not os.path.exists(log_file_path):
@ -846,7 +908,7 @@ class T7xWD(ctk.CTk):
def skip_current_queue_item(self): def skip_current_queue_item(self):
if self.button_download._state == "normal": if self.button_download._state == "normal":
self.skip_boutton.grid_remove() self.skip_button.grid_remove()
self.after(1, self.status_text.configure(text=f"Status: Standby!")) self.after(1, self.status_text.configure(text=f"Status: Standby!"))
return return
self.settings_tab.stopped = True self.settings_tab.stopped = True
@ -858,7 +920,7 @@ class T7xWD(ctk.CTk):
subprocess.run(['taskkill', '/F', '/IM', 'steamcmd.exe'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, subprocess.run(['taskkill', '/F', '/IM', 'steamcmd.exe'], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
creationflags=subprocess.CREATE_NO_WINDOW) creationflags=subprocess.CREATE_NO_WINDOW)
self.skip_boutton.grid_remove() self.skip_button.grid_remove()
self.after(2, self.status_text.configure(text=f"Status: Skipping...")) self.after(2, self.status_text.configure(text=f"Status: Skipping..."))
self.label_speed.configure(text="Network Speed: 0 KB/s") self.label_speed.configure(text="Network Speed: 0 KB/s")
self.progress_text.configure(text="0%") self.progress_text.configure(text="0%")
@ -867,6 +929,7 @@ class T7xWD(ctk.CTk):
# the real deal # the real deal
def run_steamcmd_command(self, command, map_folder, wsid, queue=None): def run_steamcmd_command(self, command, map_folder, wsid, queue=None):
steamcmd_path = get_steamcmd_path() steamcmd_path = get_steamcmd_path()
steamcmd_bootstrap_logs = os.path.join(steamcmd_path, "logs", "bootstrap_log.txt")
stdout_path = os.path.join(steamcmd_path, "logs", "workshop_log.txt") stdout_path = os.path.join(steamcmd_path, "logs", "workshop_log.txt")
timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S") timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
@ -874,9 +937,13 @@ class T7xWD(ctk.CTk):
except: pass except: pass
try: try:
with open(steamcmd_bootstrap_logs, 'w') as file:
file.write('')
with open(stdout_path, 'w') as file: with open(stdout_path, 'w') as file:
file.write('') file.write('')
except: except:
try: os.rename(stdout_path, os.path.join(map_folder, os.path.join(steamcmd_bootstrap_logs, f"bootstrap_log_couldntremove_{timestamp}.txt")))
except: pass
try: os.rename(stdout_path, os.path.join(map_folder, os.path.join(stdout_path, f"workshop_log_couldntremove_{timestamp}.txt"))) try: os.rename(stdout_path, os.path.join(map_folder, os.path.join(stdout_path, f"workshop_log_couldntremove_{timestamp}.txt")))
except: pass except: pass
@ -918,14 +985,18 @@ class T7xWD(ctk.CTk):
if process.poll() is not None: if process.poll() is not None:
break break
if not self.is_downloading: if not self.is_downloading:
if self.check_if_steamcmd_updating(steamcmd_bootstrap_logs):
self.is_steamcmd_updating = True
if self.check_steamcmd_stdout(stdout_path, wsid): if self.check_steamcmd_stdout(stdout_path, wsid):
start_time = time.time() start_time = time.time()
self.is_downloading = True self.is_downloading = True
self.is_steamcmd_updating = False
elapsed_time = time.time() - start_time elapsed_time = time.time() - start_time
time.sleep(1) time.sleep(1)
# print("Broken freeeee!") # print("Broken freeeee!")
self.is_downloading = False self.is_downloading = False
self.is_steamcmd_updating = False
try: try:
with open(stdout_path, 'w') as file: with open(stdout_path, 'w') as file:
file.write('') file.write('')
@ -968,6 +1039,8 @@ class T7xWD(ctk.CTk):
if process.poll() is not None: if process.poll() is not None:
break break
if not self.is_downloading: if not self.is_downloading:
if self.check_if_steamcmd_updating(steamcmd_bootstrap_logs):
self.is_steamcmd_updating = True
if self.check_steamcmd_stdout(stdout_path, wsid): if self.check_steamcmd_stdout(stdout_path, wsid):
start_time = time.time() start_time = time.time()
self.is_downloading = True self.is_downloading = True
@ -976,6 +1049,7 @@ class T7xWD(ctk.CTk):
# print("Broken freeeee!") # print("Broken freeeee!")
self.is_downloading = False self.is_downloading = False
self.is_steamcmd_updating = False
try: try:
with open(stdout_path, 'w') as file: with open(stdout_path, 'w') as file:
file.write('') file.write('')
@ -1011,7 +1085,7 @@ class T7xWD(ctk.CTk):
msg = CTkMessagebox(title="Downloads Complete", message=message, icon="info", option_1="Launch", option_2="Ok", sound=True) msg = CTkMessagebox(title="Downloads Complete", message=message, icon="info", option_1="Launch", option_2="Ok", sound=True)
response = msg.get() response = msg.get()
if response=="Launch": if response=="Launch":
launch_T7x_func(self.edit_destination_folder.get().strip()) launch_game_func(self.settings_tab.edit_destination_folder.get().strip(), self.settings_tab.edit_startup_exe.get(), self.settings_tab.edit_launch_args.get())
if response=="Ok": if response=="Ok":
return return
self.after(0, callback) self.after(0, callback)
@ -1023,8 +1097,8 @@ class T7xWD(ctk.CTk):
if not self.is_pressed: if not self.is_pressed:
self.after(1, self.label_speed.configure(text=f"Loading...")) self.after(1, self.label_speed.configure(text=f"Loading..."))
self.is_pressed = True self.is_pressed = True
self.library_tab.load_items(self.edit_destination_folder.get(), dont_add=True) self.library_tab.load_items(self.settings_tab.edit_destination_folder.get(), dont_add=True)
if self.queue_enabled: if self.use_queue_download():
self.item_skipped = False self.item_skipped = False
start_down_thread = threading.Thread(target=self.queue_download_thread, args=(update,)) start_down_thread = threading.Thread(target=self.queue_download_thread, args=(update,))
start_down_thread.start() start_down_thread.start()
@ -1038,8 +1112,8 @@ class T7xWD(ctk.CTk):
self.stopped = False self.stopped = False
self.queue_stop_button = False self.queue_stop_button = False
try: try:
save_config("DestinationFolder" ,self.edit_destination_folder.get()) save_config("DestinationFolder" ,self.settings_tab.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.edit_steamcmd_path.get()) save_config("SteamCMDPath" ,self.settings_tab.edit_steamcmd_path.get())
if not check_steamcmd(): if not check_steamcmd():
self.show_steam_warning_message() self.show_steam_warning_message()
@ -1065,7 +1139,7 @@ class T7xWD(ctk.CTk):
self.stop_download() self.stop_download()
return return
destination_folder = self.edit_destination_folder.get().strip() destination_folder = self.settings_tab.edit_destination_folder.get().strip()
if not destination_folder or not os.path.exists(destination_folder): if not destination_folder or not os.path.exists(destination_folder):
show_message("Error", "Please select a valid destination folder => in the main tab!.") show_message("Error", "Please select a valid destination folder => in the main tab!.")
@ -1102,13 +1176,16 @@ class T7xWD(ctk.CTk):
ws_file_size = get_workshop_file_size(workshop_id) ws_file_size = get_workshop_file_size(workshop_id)
file_size = ws_file_size file_size = ws_file_size
items_ws_sizes[workshop_id] = ws_file_size
self.total_queue_size += ws_file_size
if file_size is None: if file_size is None:
show_message("Error", "Failed to retrieve file size.", icon="cancel") ws_file_size = 1
self.stop_download() file_size = 1
return show_message("Error", "Failed to retrieve file size, Continuing anyway", icon="cancel")
# self.stop_download()
# return
items_ws_sizes[workshop_id] = ws_file_size
self.total_queue_size += ws_file_size
if any(workshop_id in item for item in self.library_tab.added_items): if any(workshop_id in item for item in self.library_tab.added_items):
self.already_installed.append(workshop_id) self.already_installed.append(workshop_id)
@ -1160,7 +1237,7 @@ class T7xWD(ctk.CTk):
self.stop_download() self.stop_download()
return return
ws_file_size = get_workshop_file_size(workshop_id) ws_file_size = get_workshop_file_size(workshop_id)
file_size = ws_file_size file_size = ws_file_size if ws_file_size is not None else 1
self.after(1, lambda mid=workshop_id: self.label_file_size.configure(text=f"File size: {get_workshop_file_size(mid ,raw=True)}")) self.after(1, lambda mid=workshop_id: self.label_file_size.configure(text=f"File size: {get_workshop_file_size(mid ,raw=True)}"))
download_folder = os.path.join(get_steamcmd_path(), "steamapps", "workshop", "downloads", "311210", workshop_id) download_folder = os.path.join(get_steamcmd_path(), "steamapps", "workshop", "downloads", "311210", workshop_id)
map_folder = os.path.join(get_steamcmd_path(), "steamapps", "workshop", "content", "311210", workshop_id) map_folder = os.path.join(get_steamcmd_path(), "steamapps", "workshop", "content", "311210", workshop_id)
@ -1170,7 +1247,7 @@ class T7xWD(ctk.CTk):
def check_and_update_progress(): def check_and_update_progress():
previous_net_speed = 0 previous_net_speed = 0
est_downloaded_bytes = 0 est_downloaded_bytes = 0
file_size = ws_file_size file_size = ws_file_size if ws_file_size is not None else 1
item_name = get_item_name(workshop_id) if get_item_name(workshop_id) else "Error getting name" item_name = get_item_name(workshop_id) if get_item_name(workshop_id) else "Error getting name"
while not self.settings_tab.stopped: while not self.settings_tab.stopped:
@ -1196,7 +1273,10 @@ class T7xWD(ctk.CTk):
self.item_skipped = False self.item_skipped = False
while not self.is_downloading and not self.settings_tab.stopped: while not self.is_downloading and not self.settings_tab.stopped:
self.after(1, self.label_speed.configure(text=f"Waiting for steamcmd...")) if self.is_steamcmd_updating:
self.after(1, self.label_speed.configure(text=f"Updating steamcmd ({self.steam_updater_size})..."))
else:
self.after(1, self.label_speed.configure(text=f"Waiting for steamcmd..."))
time_elapsed = time.time() - start_time time_elapsed = time.time() - start_time
elapsed_hours, elapsed_minutes, elapsed_seconds = convert_seconds(time_elapsed) elapsed_hours, elapsed_minutes, elapsed_seconds = convert_seconds(time_elapsed)
if self.settings_tab.show_fails: if self.settings_tab.show_fails:
@ -1206,11 +1286,12 @@ class T7xWD(ctk.CTk):
self.after(1, self.status_text.configure( self.after(1, self.status_text.configure(
text=f"Status: Total size: ~{convert_bytes_to_readable(self.total_queue_size)} | ID: {workshop_id} | {item_name} | Waiting {current_number}/{total_items}")) text=f"Status: Total size: ~{convert_bytes_to_readable(self.total_queue_size)} | ID: {workshop_id} | {item_name} | Waiting {current_number}/{total_items}"))
if len(items) > 1: if len(items) > 1:
self.skip_boutton.grid(row=3, column=1, padx=(10, 20), pady=(0, 25), sticky="ws") self.skip_button.grid(row=1, column=3, padx=(0, 20), pady=(0, 8), sticky="es")
if index == len(items) - 1: if index == len(items) - 1:
self.skip_boutton.grid_remove() self.skip_button.grid_remove()
time.sleep(1) time.sleep(1)
if self.is_downloading: if self.is_downloading:
self.is_steamcmd_updating = False
break break
try: try:
@ -1355,7 +1436,7 @@ class T7xWD(ctk.CTk):
remove_tree(map_folder) remove_tree(map_folder)
remove_tree(download_folder) remove_tree(download_folder)
self.library_tab.update_item(self.edit_destination_folder.get(), workshop_id, mod_type, folder_name) self.library_tab.update_item(self.settings_tab.edit_destination_folder.get(), workshop_id, mod_type, folder_name)
if index == len(items) - 1: if index == len(items) - 1:
self.after(1, self.status_text.configure(text=f"Status: Done! => Please press stop only if you see no popup window (rare bug)")) self.after(1, self.status_text.configure(text=f"Status: Done! => Please press stop only if you see no popup window (rare bug)"))
@ -1378,7 +1459,7 @@ class T7xWD(ctk.CTk):
self.button_download.configure(state="normal") self.button_download.configure(state="normal")
self.button_stop.configure(state="disabled") self.button_stop.configure(state="disabled")
self.after(1, self.status_text.configure(text=f"Status: Done!")) self.after(1, self.status_text.configure(text=f"Status: Done!"))
self.skip_boutton.grid_remove() self.skip_button.grid_remove()
self.after(1, self.label_file_size.configure(text=f"File size: 0KB")) self.after(1, self.label_file_size.configure(text=f"File size: 0KB"))
self.settings_tab.stopped = True self.settings_tab.stopped = True
self.stop_download() self.stop_download()
@ -1393,8 +1474,8 @@ class T7xWD(ctk.CTk):
try: try:
self.settings_tab.stopped = False self.settings_tab.stopped = False
save_config("DestinationFolder" ,self.edit_destination_folder.get()) save_config("DestinationFolder" ,self.settings_tab.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.edit_steamcmd_path.get()) save_config("SteamCMDPath" ,self.settings_tab.edit_steamcmd_path.get())
if not check_steamcmd(): if not check_steamcmd():
self.show_steam_warning_message() self.show_steam_warning_message()
@ -1408,7 +1489,7 @@ class T7xWD(ctk.CTk):
workshop_id = self.edit_workshop_id.get().strip() workshop_id = self.edit_workshop_id.get().strip()
destination_folder = self.edit_destination_folder.get().strip() destination_folder = self.settings_tab.edit_destination_folder.get().strip()
if not destination_folder or not os.path.exists(destination_folder): if not destination_folder or not os.path.exists(destination_folder):
show_message("Error", "Please select a valid destination folder.") show_message("Error", "Please select a valid destination folder.")
@ -1437,14 +1518,16 @@ class T7xWD(ctk.CTk):
file_size = ws_file_size file_size = ws_file_size
if not valid_id(workshop_id): if not valid_id(workshop_id):
show_message("Warning", "Please enter a valid Workshop ID/Link.", icon="warning") show_message("Warning", "Invalid Workshop ID/Link.\nYou can attempt to update with 'Skip Invalid Items' disabled.", icon="warning")
self.stop_download() self.stop_download()
return return
if file_size is None: if file_size is None:
show_message("Error", "Failed to retrieve file size.", icon="cancel") ws_file_size = 1
self.stop_download() file_size = 1
return show_message("Error", "Failed to retrieve file size, Continuing anyway", icon="cancel")
# self.stop_download()
# return
if not update: if not update:
if any(workshop_id in item for item in self.library_tab.added_items): if any(workshop_id in item for item in self.library_tab.added_items):
@ -1464,7 +1547,7 @@ class T7xWD(ctk.CTk):
previous_net_speed = 0 previous_net_speed = 0
est_downloaded_bytes = 0 est_downloaded_bytes = 0
start_time = time.time() start_time = time.time()
file_size = ws_file_size file_size = ws_file_size if ws_file_size is not None else 1
while not self.settings_tab.stopped: while not self.settings_tab.stopped:
if self.settings_tab.steamcmd_reset: if self.settings_tab.steamcmd_reset:
@ -1473,7 +1556,10 @@ class T7xWD(ctk.CTk):
est_downloaded_bytes = 0 est_downloaded_bytes = 0
while not self.is_downloading and not self.settings_tab.stopped: while not self.is_downloading and not self.settings_tab.stopped:
self.after(1, self.label_speed.configure(text=f"Waiting for steamcmd...")) if self.is_steamcmd_updating:
self.after(1, self.label_speed.configure(text=f"Updating steamcmd ({self.steam_updater_size})..."))
else:
self.after(1, self.label_speed.configure(text=f"Waiting for steamcmd..."))
time_elapsed = time.time() - start_time time_elapsed = time.time() - start_time
elapsed_hours, elapsed_minutes, elapsed_seconds = convert_seconds(time_elapsed) elapsed_hours, elapsed_minutes, elapsed_seconds = convert_seconds(time_elapsed)
if self.settings_tab.show_fails: if self.settings_tab.show_fails:
@ -1482,6 +1568,7 @@ class T7xWD(ctk.CTk):
self.after(1, lambda h=elapsed_hours, m=elapsed_minutes, s=elapsed_seconds: self.elapsed_time.configure(text=f"Elapsed Time: {int(h):02d}:{int(m):02d}:{int(s):02d}")) self.after(1, lambda h=elapsed_hours, m=elapsed_minutes, s=elapsed_seconds: self.elapsed_time.configure(text=f"Elapsed Time: {int(h):02d}:{int(m):02d}:{int(s):02d}"))
time.sleep(1) time.sleep(1)
if self.is_downloading: if self.is_downloading:
self.is_steamcmd_updating = False
break break
try: try:
@ -1626,7 +1713,7 @@ class T7xWD(ctk.CTk):
remove_tree(download_folder) remove_tree(download_folder)
if not invalid_item_folder: if not invalid_item_folder:
self.library_tab.update_item(self.edit_destination_folder.get(), workshop_id, mod_type, folder_name) self.library_tab.update_item(self.settings_tab.edit_destination_folder.get(), workshop_id, mod_type, folder_name)
self.show_complete_message(message=f"{mod_type.capitalize()} files were downloaded\nYou can run the game now!\nPS: You have to restart the game \n(pressing launch will launch/restarts)") self.show_complete_message(message=f"{mod_type.capitalize()} files were downloaded\nYou can run the game now!\nPS: You have to restart the game \n(pressing launch will launch/restarts)")
self.button_download.configure(state="normal") self.button_download.configure(state="normal")
self.button_stop.configure(state="disabled") self.button_stop.configure(state="disabled")
@ -1692,4 +1779,4 @@ class T7xWD(ctk.CTk):
self.progress_bar.set(0.0) self.progress_bar.set(0.0)
self.after(50, self.status_text.configure(text=f"Status: Standby!")) self.after(50, self.status_text.configure(text=f"Status: Standby!"))
self.after(1, self.label_speed.configure(text=f"Awaiting Download!")) self.after(1, self.label_speed.configure(text=f"Awaiting Download!"))
self.skip_boutton.grid_remove() self.skip_button.grid_remove()

View File

@ -28,12 +28,16 @@ class SettingsTab(ctk.CTkFrame):
self.grid_columnconfigure(1, weight=1) self.grid_columnconfigure(1, weight=1)
self.grid_columnconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1)
left_frame = ctk.CTkFrame(self) left_frame = ctk.CTkFrame(self)
left_frame.grid(row=0, column=0, padx=(20, 20), pady=(20, 0), sticky="nsew") left_frame.grid(row=0, column=0, columnspan=2, padx=(20, 20), pady=(20, 0), sticky="nsew")
left_frame.grid_columnconfigure(1, weight=1) left_frame.grid_columnconfigure(1, weight=1)
right_frame = ctk.CTkFrame(self) right_frame = ctk.CTkFrame(self)
right_frame.grid(row=0, column=1, padx=(20, 20), pady=(20, 0), sticky="nsew") right_frame.grid(row=0, column=2, padx=(0, 20), pady=(20, 0), sticky="nsew")
right_frame.grid_columnconfigure(1, weight=1) right_frame.grid_columnconfigure(1, weight=1)
self.update_idletasks() self.update_idletasks()
# Save button
self.save_button = ctk.CTkButton(self, text="Save", command=self.save_settings, state='disabled')
self.save_button.grid(row=3, column=1, padx=40, pady=(20, 20), sticky="ne")
# Check for updates checkbox # Check for updates checkbox
self.check_updates_var = ctk.BooleanVar() self.check_updates_var = ctk.BooleanVar()
@ -45,7 +49,7 @@ class SettingsTab(ctk.CTkFrame):
# Show console checkbox # Show console checkbox
self.console_var = ctk.BooleanVar() self.console_var = ctk.BooleanVar()
self.console_var.trace_add("write", self.enable_save_button) self.console_var.trace_add("write", self.enable_save_button)
self.checkbox_show_console = ctk.CTkSwitch(left_frame, text="Console (On Download)", variable=self.console_var) self.checkbox_show_console = ctk.CTkSwitch(left_frame, text="Display SteamCMD console", variable=self.console_var)
self.checkbox_show_console.grid(row=1, column=1, padx=20, pady=(20, 0), sticky="nw") self.checkbox_show_console.grid(row=1, column=1, padx=20, pady=(20, 0), sticky="nw")
self.checkbox_show_console_tooltip = CTkToolTip(self.checkbox_show_console, message="Toggle SteamCMD console\nPlease don't close the Console If you want to stop press the Stop button") self.checkbox_show_console_tooltip = CTkToolTip(self.checkbox_show_console, message="Toggle SteamCMD console\nPlease don't close the Console If you want to stop press the Stop button")
self.console_var.set(self.load_settings("console")) self.console_var.set(self.load_settings("console"))
@ -53,7 +57,7 @@ class SettingsTab(ctk.CTkFrame):
# Show continuous checkbox # Show continuous checkbox
self.continuous_var = ctk.BooleanVar() self.continuous_var = ctk.BooleanVar()
self.continuous_var.trace_add("write", self.enable_save_button) self.continuous_var.trace_add("write", self.enable_save_button)
self.checkbox_continuous = ctk.CTkSwitch(left_frame, text="Continuous Download", variable=self.continuous_var) self.checkbox_continuous = ctk.CTkSwitch(left_frame, text="Continuous download", variable=self.continuous_var)
self.checkbox_continuous.grid(row=2, column=1, padx=20, pady=(20, 0), sticky="nw") self.checkbox_continuous.grid(row=2, column=1, padx=20, pady=(20, 0), sticky="nw")
self.checkbox_continuous_tooltip = CTkToolTip(self.checkbox_continuous, message="This will make sure that the download restarts and resumes! until it finishes if steamcmd crashes randomly (it will not redownload from the start)") self.checkbox_continuous_tooltip = CTkToolTip(self.checkbox_continuous, message="This will make sure that the download restarts and resumes! until it finishes if steamcmd crashes randomly (it will not redownload from the start)")
self.continuous_var.set(self.load_settings("continuous_download")) self.continuous_var.set(self.load_settings("continuous_download"))
@ -69,16 +73,16 @@ class SettingsTab(ctk.CTkFrame):
# Show estimated_progress checkbox # Show estimated_progress checkbox
self.estimated_progress_var = ctk.BooleanVar() self.estimated_progress_var = ctk.BooleanVar()
self.estimated_progress_var.trace_add("write", self.enable_save_button) self.estimated_progress_var.trace_add("write", self.enable_save_button)
self.estimated_progress_cb = ctk.CTkSwitch(left_frame, text="Estimated Progress Bar", variable=self.estimated_progress_var) self.estimated_progress_cb = ctk.CTkSwitch(left_frame, text="Estimated progress bar", variable=self.estimated_progress_var)
self.estimated_progress_cb.grid(row=4, column=1, padx=20, pady=(20, 0), sticky="nw") self.estimated_progress_cb.grid(row=4, column=1, padx=20, pady=(20, 0), sticky="nw")
self.estimated_progress_var_tooltip = CTkToolTip(self.estimated_progress_cb, message="This will change how to progress bar works by estimating how long the download will take\ self.estimated_progress_var_tooltip = CTkToolTip(self.estimated_progress_cb, message="This will change how to progress bar works by estimating how long the download will take\
\nThis is not accurate ,it's better than with it off which is calculating the downloaded folder size which steamcmd dumps the full size rigth mostly") \nThis is not accurate ,it's better than with it off which is calculating the downloaded folder size which steamcmd dumps the full size rigth mostly")
self.estimated_progress_var.set(self.load_settings("estimated_progress", "on")) self.estimated_progress_var.set(self.load_settings("estimated_progress", "on"))
# Show show fails checkbox # Show fails checkbox
self.show_fails_var = ctk.BooleanVar() self.show_fails_var = ctk.BooleanVar()
self.show_fails_var.trace_add("write", self.enable_save_button) self.show_fails_var.trace_add("write", self.enable_save_button)
self.show_fails_cb = ctk.CTkSwitch(left_frame, text="Show fails (on top of progress bar)", variable=self.show_fails_var) self.show_fails_cb = ctk.CTkSwitch(left_frame, text="Show fails", variable=self.show_fails_var)
self.show_fails_cb.grid(row=5, column=1, padx=20, pady=(20, 0), sticky="nw") self.show_fails_cb.grid(row=5, column=1, padx=20, pady=(20, 0), sticky="nw")
self.show_fails_tooltip = CTkToolTip(self.show_fails_cb, message="Display how many times steamcmd has failed/crashed\nIf the number is getting high quickly then try pressing Reset SteamCMD and try again, otherwise its fine") self.show_fails_tooltip = CTkToolTip(self.show_fails_cb, message="Display how many times steamcmd has failed/crashed\nIf the number is getting high quickly then try pressing Reset SteamCMD and try again, otherwise its fine")
self.estimated_progress_var.set(self.load_settings("show_fails", "on")) self.estimated_progress_var.set(self.load_settings("show_fails", "on"))
@ -94,75 +98,138 @@ class SettingsTab(ctk.CTkFrame):
# check items for update on launch # check items for update on launch
self.check_items_var = ctk.BooleanVar() self.check_items_var = ctk.BooleanVar()
self.check_items_var.trace_add("write", self.enable_save_button) self.check_items_var.trace_add("write", self.enable_save_button)
self.check_items_ch = ctk.CTkSwitch(left_frame, text="Check Library items on launch", variable=self.check_items_var) self.check_items_ch = ctk.CTkSwitch(left_frame, text="Check library items on launch", variable=self.check_items_var)
self.check_items_ch.grid(row=7, column=1, padx=20, pady=(20, 0), sticky="nw") self.check_items_ch.grid(row=7, column=1, padx=20, pady=(20, 0), sticky="nw")
self.check_items_tooltip = CTkToolTip(self.check_items_ch, message="This will show a window on launch of items that have pending updates -> you can open it manually from library tab") self.check_items_tooltip = CTkToolTip(self.check_items_ch, message="This will show a window on launch of items that have pending updates -> you can open it manually from library tab")
self.check_items_var.set(self.load_settings("check_items", "off")) self.check_items_var.set(self.load_settings("check_items", "off"))
# TODO: get windows size to padx for the following checkboxes
# update invalid
self.invalid_items_var = ctk.BooleanVar()
self.invalid_items_var.trace_add("write", self.enable_save_button)
self.invalid_items_ch = ctk.CTkSwitch(left_frame, text="Update invalid items", variable=self.invalid_items_var)
self.invalid_items_ch.grid(row=0, column=1, padx=(300,0), pady=(20, 0), sticky="nw")
self.invalid_items_tooltip = CTkToolTip(self.invalid_items_ch, message="Allow updating invalid items from the details tab")
self.invalid_items_var.set(self.load_settings("update_invalid", "off"))
# Resetr steam on many fails # skip invalid
self.skip_items_var = ctk.BooleanVar()
self.skip_items_var.trace_add("write", self.enable_save_button)
self.skip_items_ch = ctk.CTkSwitch(left_frame, text="Skip invalid items", variable=self.skip_items_var)
self.skip_items_ch.grid(row=1, column=1, padx=(300,0), pady=(20, 0), sticky="nw")
self.skip_items_tooltip = CTkToolTip(self.skip_items_ch, message="Skip invalid items")
self.skip_items_var.set(self.load_settings("skip_invalid", "off"))
# text input fields
self.label_destination_folder = ctk.CTkLabel(left_frame, text='Enter Game folder:')
self.label_destination_folder.grid(row=8, column=1, padx=20, pady=(20, 0), columnspan=1, sticky="ws")
self.entry_var1 = ctk.StringVar(value="")
self.edit_destination_folder = ctk.CTkEntry(left_frame, placeholder_text="game installation folder", textvariable=self.entry_var1)
self.edit_destination_folder.grid(row=9, column=1, padx=20, pady=(0, 10), columnspan=1, sticky="ewn")
self.entry_var1.trace_add("write", self.enable_save_button)
self.button_T7x_browse = ctk.CTkButton(left_frame, text="Select", command=self.open_T7x_browser)
self.button_T7x_browse.grid(row=9, column=2, padx=(0, 20), pady=(0, 10), sticky="ewn")
self.label_steamcmd_path = ctk.CTkLabel(left_frame, text="Enter SteamCMD path:")
self.label_steamcmd_path.grid(row=10, column=1, padx=20, pady=(0, 0), columnspan=1, sticky="wn")
self.entry_var2 = ctk.StringVar(value="")
self.edit_steamcmd_path = ctk.CTkEntry(left_frame, placeholder_text="Enter SteamCMD path", textvariable=self.entry_var2)
self.edit_steamcmd_path.grid(row=11, column=1, padx=20, pady=(0, 10), columnspan=1, sticky="ewn")
self.entry_var2.trace_add("write", self.enable_save_button)
self.button_steamcmd_browse = ctk.CTkButton(left_frame, text="Select", command=self.open_steamcmd_path_browser)
self.button_steamcmd_browse.grid(row=11, column=2, padx=(0, 20), pady=(0, 10), sticky="ewn")
self.label_launch_args = ctk.CTkLabel(left_frame, text='Launch Parameters:')
self.label_launch_args.grid(row=12, column=1, padx=20, pady=(0, 0), columnspan=1, sticky="ws")
self.edit_startup_exe = ctk.CTkEntry(left_frame, placeholder_text="exe")
self.edit_startup_exe.grid(row=13, column=1, padx=(20,0), pady=(0, 20), columnspan=1, sticky="we")
self.edit_launch_args = ctk.CTkEntry(left_frame, placeholder_text="launch arguments")
self.edit_launch_args.grid(row=13, column=1, padx=(140,20), pady=(0, 20), columnspan=2, sticky="we")
# Check for updates button n Launch game
self.check_for_updates = ctk.CTkButton(right_frame, text="Check for updates", command=self.settings_check_for_updates)
self.check_for_updates.grid(row=1, column=1, padx=20, pady=(20, 0), sticky="n")
# self.launch_game = ctk.CTkButton(right_frame, text="Launch game", command=self.settings_launch_game)
# self.launch_game.grid(row=2, column=1, padx=20, pady=(10, 0), sticky="n")
self.reset_steamcmd = ctk.CTkButton(right_frame, text="Reset SteamCMD", command=self.settings_reset_steamcmd)
self.reset_steamcmd.grid(row=3, column=1, padx=20, pady=(10, 0), sticky="n")
self.reset_steamcmd_tooltip = CTkToolTip(self.reset_steamcmd, message="This will remove steamapps folder + all the maps that are potentioaly corrupted\nor not so use at ur own risk (could fix some issues as well)")
self.workshop_to_gamedir = ctk.CTkButton(right_frame, text="Workshop Transfer", command=self.workshop_to_gamedir_toplevel)
self.workshop_to_gamedir.grid(row=4, column=1, padx=20, pady=(10, 0), sticky="n")
self.workshop_to_gamedir_tooltip = CTkToolTip(self.workshop_to_gamedir, message="Copy/Move maps and mods from Workshop to the game directory (opens up a window)")
# appearance
self.appearance_mode_label = ctk.CTkLabel(right_frame, text="Appearance:", anchor="w")
self.appearance_mode_label.grid(row=5, column=1, padx=20, pady=(150, 0), sticky="nw")
self.appearance_mode_optionemenu = ctk.CTkOptionMenu(right_frame, values=["Light", "Dark", "System"],
command=master.change_appearance_mode_event)
self.appearance_mode_optionemenu.grid(row=6, column=1, padx=20, pady=(0, 0), sticky="nw")
self.scaling_label = ctk.CTkLabel(right_frame, text="UI Scaling:", anchor="w")
self.scaling_label.grid(row=7, column=1, padx=20, pady=(0, 0), sticky="nw")
self.scaling_optionemenu = ctk.CTkOptionMenu(right_frame, values=["60%", "70%", "80%", "90%", "100%", "110%"],
command=master.change_scaling_event)
self.scaling_optionemenu.grid(row=8, column=1, padx=20, pady=(0, 0), sticky="nw")
# self.custom_theme = ctk.CTkButton(right_frame, text="Custom theme", command=self.T7xwd_custom_theme)
# self.custom_theme.grid(row=9, column=1, padx=20, pady=(20, 0), sticky="n")
self.theme_options_label = ctk.CTkLabel(right_frame, text="Theme:", anchor="w")
self.theme_options_label.grid(row=9, column=1, padx=20, pady=(0, 0), sticky="nw")
self.theme_options = ctk.CTkOptionMenu(right_frame, values=["Default", "Blue", "Grey", "Obsidian", "Ghost","NeonBanana", "Custom"],
command=self.theme_options_func)
self.theme_options.grid(row=10, column=1, padx=20, pady=(0, 0), sticky="nw")
self.theme_options.set(value=self.load_settings("theme", "Default"))
# Reset steam on many fails
self.reset_steamcmd_on_fail_var = ctk.IntVar() self.reset_steamcmd_on_fail_var = ctk.IntVar()
self.reset_steamcmd_on_fail_var.trace_add("write", self.enable_save_button) self.reset_steamcmd_on_fail_var.trace_add("write", self.enable_save_button)
self.reset_steamcmd_on_fail_text = ctk.CTkLabel(left_frame, text=f"Reset steamcmd: (n of fails):", anchor="w") self.reset_steamcmd_on_fail_text = ctk.CTkLabel(right_frame, text=f"Download Attempts:", anchor="w")
self.reset_steamcmd_on_fail_text.grid(row=8, column=1, padx=20, pady=(10, 0), sticky="nw") self.reset_steamcmd_on_fail_text.grid(row=11, column=1, padx=20, pady=(0, 0), sticky="nw")
self.reset_steamcmd_on_fail = ctk.CTkOptionMenu(left_frame, values=["5", "10", "20", "30", "40", "Custom", "Disable"], variable=self.reset_steamcmd_on_fail_var, command=self.reset_steamcmd_on_fail_func) self.reset_steamcmd_on_fail = ctk.CTkOptionMenu(right_frame, values=["5", "10", "15", "20", "Custom", "Disable"], variable=self.reset_steamcmd_on_fail_var, command=self.reset_steamcmd_on_fail_func)
self.reset_steamcmd_on_fail.grid(row=8, column=1, padx=(190, 0), pady=(10, 0), sticky="nw") self.reset_steamcmd_on_fail.grid(row=12, column=1, padx=20, pady=(0, 0), sticky="nw")
self.reset_steamcmd_on_fail_tooltip = CTkToolTip(self.reset_steamcmd_on_fail, message="This actually fixes steamcmd when its crashing way too much") self.reset_steamcmd_on_fail_tooltip = CTkToolTip(self.reset_steamcmd_on_fail, message="Number of failed download attempts before resetting SteamCMD")
self.reset_steamcmd_on_fail.set(value=self.load_settings("reset_on_fail", "10")) self.reset_steamcmd_on_fail.set(value=self.load_settings("reset_on_fail", "10"))
# item folder naming # item folder naming
self.folder_options_label_var = ctk.IntVar() self.folder_options_label_var = ctk.IntVar()
self.folder_options_label_var.trace_add("write", self.enable_save_button) self.folder_options_label_var.trace_add("write", self.enable_save_button)
self.folder_options_label = ctk.CTkLabel(left_frame, text="Items Folder Naming:", anchor="nw") self.folder_options_label = ctk.CTkLabel(right_frame, text="Items Folder Naming:", anchor="w")
self.folder_options_label.grid(row=10, column=1, padx=20, pady=(10, 0), sticky="nw") self.folder_options_label.grid(row=13, column=1, padx=(20,0), pady=(0, 0), sticky="nw")
self.folder_options = ctk.CTkOptionMenu(left_frame, values=["PublisherID", "FolderName"], command=self.change_folder_naming, self.folder_options = ctk.CTkOptionMenu(right_frame, values=["PublisherID", "FolderName"], command=self.change_folder_naming,
variable=self.folder_options_label_var) variable=self.folder_options_label_var)
self.folder_options.grid(row=10, column=1, padx=(150, 0), pady=(3, 0), sticky="nw") self.folder_options.grid(row=14, column=1, padx=20, pady=(0, 0), sticky="nw")
self.folder_options.set(value=self.load_settings("folder_naming", "PublisherID")) self.folder_options.set(value=self.load_settings("folder_naming", "PublisherID"))
# Check for updates button n Launch T7x
self.check_for_updates = ctk.CTkButton(right_frame, text="Check for updates", command=self.settings_check_for_updates)
self.check_for_updates.grid(row=1, column=1, padx=20, pady=(20, 0), sticky="n")
self.launch_T7x = ctk.CTkButton(right_frame, text="Launch T7x", command=self.settings_launch_T7x)
self.launch_T7x.grid(row=2, column=1, padx=20, pady=(20, 0), sticky="n")
self.reset_steamcmd = ctk.CTkButton(right_frame, text="Reset SteamCMD", command=self.settings_reset_steamcmd)
self.reset_steamcmd.grid(row=3, column=1, padx=20, pady=(20, 0), sticky="n")
self.reset_steamcmd_tooltip = CTkToolTip(self.reset_steamcmd, message="This will remove steamapps folder + all the maps that are potentioaly corrupted\nor not so use at ur own risk (could fix some issues as well)")
self.steam_to_T7x = ctk.CTkButton(right_frame, text="Steam to T7x", command=self.from_steam_to_T7x_toplevel)
self.steam_to_T7x.grid(row=5, column=1, padx=20, pady=(20, 0), sticky="n")
self.steam_to_T7x_tooltip = CTkToolTip(self.steam_to_T7x, message="Moves/copies maps and mods from steam to T7x (opens up a window)")
# appearance
self.appearance_mode_label = ctk.CTkLabel(right_frame, text="Appearance Mode:", anchor="n")
self.appearance_mode_label.grid(row=6, column=1, padx=20, pady=(20, 0))
self.appearance_mode_optionemenu = ctk.CTkOptionMenu(right_frame, values=["Light", "Dark", "System"],
command=master.change_appearance_mode_event)
self.appearance_mode_optionemenu.grid(row=7, column=1, padx=20, pady=(0, 0))
self.scaling_label = ctk.CTkLabel(right_frame, text="UI Scaling:", anchor="n")
self.scaling_label.grid(row=8, column=1, padx=20, pady=(10, 0))
self.scaling_optionemenu = ctk.CTkOptionMenu(right_frame, values=["80%", "90%", "100%", "110%", "120%"],
command=master.change_scaling_event)
self.scaling_optionemenu.grid(row=9, column=1, padx=20, pady=(0, 0))
# self.custom_theme = ctk.CTkButton(right_frame, text="Custom theme", command=self.T7xwd_custom_theme)
# self.custom_theme.grid(row=8, column=1, padx=20, pady=(20, 0), sticky="n")
self.theme_options_label = ctk.CTkLabel(right_frame, text="Themes:", anchor="n")
self.theme_options_label.grid(row=10, column=1, padx=20, pady=(10, 0))
self.theme_options = ctk.CTkOptionMenu(right_frame, values=["Default", "Blue", "Grey", "Obsidian", "Ghost","NeonBanana", "Custom"],
command=self.theme_options_func)
self.theme_options.grid(row=11, column=1, padx=20, pady=(0, 0))
self.theme_options.set(value=self.load_settings("theme", "Default"))
# Save button
self.save_button = ctk.CTkButton(self, text="Save", command=self.save_settings, state='disabled')
self.save_button.grid(row=3, column=0, padx=20, pady=(20, 20), sticky="nw")
#version #version
self.version_info = ctk.CTkLabel(self, text=f"{VERSION}") self.version_info = ctk.CTkLabel(self, text=f"{VERSION}")
self.version_info.grid(row=3, column=1, padx=20, pady=(20, 20), sticky="e") self.version_info.grid(row=3, column=2, padx=20, pady=(20, 20), sticky="e")
def open_T7x_browser(self):
selected_folder = ctk.filedialog.askdirectory(title="Select Game Folder")
if selected_folder:
self.edit_destination_folder.delete(0, "end")
self.edit_destination_folder.insert(0, selected_folder)
save_config("DestinationFolder" ,self.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.edit_steamcmd_path.get())
def open_steamcmd_path_browser(self):
selected_folder = ctk.filedialog.askdirectory(title="Select SteamCMD Folder")
if selected_folder:
self.edit_steamcmd_path.delete(0, "end")
self.edit_steamcmd_path.insert(0, selected_folder)
save_config("DestinationFolder" ,self.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.edit_steamcmd_path.get())
def reset_steamcmd_on_fail_func(self, option: str): def reset_steamcmd_on_fail_func(self, option: str):
if option == "Custom": if option == "Custom":
@ -225,6 +292,9 @@ class SettingsTab(ctk.CTkFrame):
def save_settings(self): def save_settings(self):
self.save_button.configure(state='disabled') self.save_button.configure(state='disabled')
save_config("GameExecutable", str(self.edit_startup_exe.get()) if not isNullOrWhiteSpace(self.edit_startup_exe.get()) else "BlackOps3")
save_config("LaunchParameters", str(self.edit_launch_args.get()) if not isNullOrWhiteSpace(self.edit_launch_args.get()) else " ")
if self.folder_options.get() == "PublisherID": if self.folder_options.get() == "PublisherID":
save_config("folder_naming", "0") save_config("folder_naming", "0")
else: else:
@ -234,11 +304,21 @@ class SettingsTab(ctk.CTkFrame):
save_config("check_items", "on") save_config("check_items", "on")
else: else:
save_config("check_items", "off") save_config("check_items", "off")
if self.invalid_items_var.get():
save_config("update_invalid", "on")
else:
save_config("update_invalid", "off")
if self.skip_items_var.get():
save_config("skip_invalid", "on")
else:
save_config("skip_invalid", "off")
if self.check_updates_checkbox.get(): if self.check_updates_checkbox.get():
save_config("checkforupdtes", "on") save_config("checkforupdates", "on")
else: else:
save_config("checkforupdtes", "off") save_config("checkforupdates", "off")
if self.checkbox_show_console.get(): if self.checkbox_show_console.get():
save_config("console", "on") save_config("console", "on")
@ -380,8 +460,8 @@ class SettingsTab(ctk.CTkFrame):
file_to_rename = os.path.join(APPLICATION_PATH, "T7xwd_theme.json") file_to_rename = os.path.join(APPLICATION_PATH, "T7xwd_theme.json")
if os.path.exists(file_to_rename): if os.path.exists(file_to_rename):
timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S") timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
new_name = f"T7xwd_theme_{timestamp}.json" name = f"T7xwd_theme_{timestamp}.json"
os.rename(file_to_rename, os.path.join(APPLICATION_PATH, new_name)) os.rename(file_to_rename, os.path.join(APPLICATION_PATH, name))
if not disable_only: if not disable_only:
show_message("Preset file renamed", "Custom preset disabled, file has been renmaed\n* Restart the app to take effect", icon="info") show_message("Preset file renamed", "Custom preset disabled, file has been renmaed\n* Restart the app to take effect", icon="info")
@ -399,9 +479,9 @@ class SettingsTab(ctk.CTkFrame):
# make this rename to {id}_duplicate as a fallback # make this rename to {id}_duplicate as a fallback
def rename_all_folders(self, option): def rename_all_folders(self, option):
T7xFolder = main_app.app.edit_destination_folder.get() gameFolder = self.edit_destination_folder.get()
maps_folder = os.path.join(T7xFolder, "mods") mods_folder = os.path.join(gameFolder, "mods")
mods_folder = os.path.join(T7xFolder, "usermaps") maps_folder = os.path.join(gameFolder, "usermaps")
folders_to_process = [] folders_to_process = []
@ -412,61 +492,105 @@ class SettingsTab(ctk.CTkFrame):
folders_to_process.append(maps_folder) folders_to_process.append(maps_folder)
if not os.path.exists(maps_folder) and not os.path.exists(mods_folder): if not os.path.exists(maps_folder) and not os.path.exists(mods_folder):
show_message("Warning -> Check T7x path", f"You don't have any items yet ,from now on item's folders will be named as their {option}") show_message("Warning -> Check game path",
f"You don't have any items yet, from now on item's folders will be named as their {option}")
return 0 return 0
processed_names = set() processed_names = set()
for folder_path in folders_to_process: files = Path(folders_to_process[0]).glob("*/zone/workshop.json")
for folder_name in os.listdir(folder_path): items = dict()
zone_path = os.path.join(folder_path, folder_name, "zone") ignored_folders = []
if not os.path.isdir(zone_path):
continue
if folder_name in main_app.app.library_tab.item_block_list:
continue
json_path = os.path.join(zone_path, "workshop.json") for idx, file in enumerate(files):
publisher_id = extract_json_data(json_path, 'PublisherID') curr_folder_name = os.path.relpath(file, folders_to_process[0]).split("\\", 1)[0]
new_name = extract_json_data(json_path, option)
if folder_name == new_name:
continue
rename_flag = True with open(file, 'r') as json_file:
data = json.load(json_file)
_item = {
'PublisherID': data.get('PublisherID'),
'Name': data.get(option),
'current_folder': curr_folder_name
}
if _item.get('PublisherID')!="" and int(_item.get('PublisherID'))>0:
items[idx] = _item
else:
ignored_folders.append(curr_folder_name)
if os.path.exists(json_path): IDs = [x['PublisherID'] for x in items.values()]
folder_to_rename = os.path.join(folder_path, folder_name) Names = [x['Name'] for x in items.values()]
new_folder_name = new_name currFolder = [x['current_folder'] for x in items.values()]
while new_folder_name in processed_names:
if new_name == publisher_id: def indices(lst, item):
new_folder_name += f"_duplicated" return [i for i, x in enumerate(lst) if item in x]
def find_duplicate_items_in_list(list, items):
return dict((x, indices(list, x)) for x in [y for y in items if items.count(y) > 1])
def prep_rename(changelist, orig, new):
return changelist.append((os.path.join(folders_to_process[0], orig), os.path.join(folders_to_process[0], new)))
duplicates = find_duplicate_items_in_list(Names, Names)
duplicates_IDs = find_duplicate_items_in_list(IDs, IDs)
duplicate_idx = concatenate_sublists(duplicates.values())
changelist = []
for i in range(len(IDs)):
if i not in duplicate_idx:
prep_rename(changelist, currFolder[i], Names[i])
for v in duplicates.values():
if len(v) == 2:
if IDs[v[0]] == IDs[v[1]]:
prep_rename(changelist, currFolder[v[0]], Names[v[0]])
prep_rename(
changelist, currFolder[v[1]], Names[v[1]]+"_duplicate")
else:
prep_rename(
changelist, currFolder[v[0]], Names[v[0]]+f"_{IDs[v[0]]}")
prep_rename(
changelist, currFolder[v[1]], Names[v[1]]+f"_{IDs[v[1]]}")
if len(v) > 2:
for j, i in enumerate(v):
if i in (duplicates_IDs.get(f'{IDs[i]}') if duplicates_IDs.get(f'{IDs[i]}') is not None else []):
if i == v[0]:
if Names[i].startswith(IDs[i]):
prep_rename(
changelist, currFolder[i], Names[i])
else:
prep_rename(
changelist, currFolder[i], Names[i]+f"_{IDs[i]}")
else: else:
new_folder_name += f"_{publisher_id}" if Names[i].startswith(IDs[i]):
if folder_name == new_folder_name: newname = Names[i]+f"_duplicate"
rename_flag = False else:
break newname = Names[i]+f"_{IDs[i]}"
new_path = os.path.join(folder_path, new_folder_name)
while os.path.exists(new_path): if j > 0:
if new_name == publisher_id: newname += '_' + str(j)
new_folder_name += f"_duplicated"
else:
new_folder_name += f"_{publisher_id}"
if folder_name == new_folder_name:
rename_flag = False
break
new_path = os.path.join(folder_path, new_folder_name)
if rename_flag: prep_rename(changelist, currFolder[i], newname)
os.rename(folder_to_rename, new_path) else:
processed_names.add(new_folder_name) prep_rename(
changelist, currFolder[i], Names[i]+f"_{IDs[i]}")
for n in changelist:
safe_name = nextnonexistentdir(*tuple(reversed(os.path.split(n[1]))))
if safe_name[0] in ignored_folders and safe_name[1] > 0:
os.rename(n[0], os.path.join(n[0], '{}_{}'.format(*safe_name)))
else:
os.rename(n[0], n[1])
return 1 return 1
def change_folder_naming(self, option): def change_folder_naming(self, option):
main_app.app.title("T7x Workshop Downloader - Settings ➜ Loading... ⏳") main_app.app.title("T7x Workshop Downloader - Settings ➜ Loading... ⏳")
try: try:
if os.path.exists(main_app.app.edit_destination_folder.get()): if os.path.exists(self.edit_destination_folder.get()):
lib = main_app.app.library_tab.load_items(main_app.app.edit_destination_folder.get(), dont_add=True) lib = main_app.app.library_tab.load_items(self.edit_destination_folder.get(), dont_add=True)
if not "No items" in lib: if not "No items" in lib:
if show_message("Renaming", "Would you like to rename all your exisiting item folders now?", _return=True): if show_message("Renaming", "Would you like to rename all your exisiting item folders now?", _return=True):
main_app.app.title("T7x Workshop Downloader - Settings ➜ Renaming... ⏳") main_app.app.title("T7x Workshop Downloader - Settings ➜ Renaming... ⏳")
@ -476,13 +600,13 @@ class SettingsTab(ctk.CTkFrame):
return 0 return 0
else: else:
show_message("Done!", "All folders have been renamed", icon="info") show_message("Done!", "All folders have been renamed", icon="info")
main_app.app.library_tab.load_items(main_app.app.edit_destination_folder.get(), dont_add=True) main_app.app.library_tab.load_items(self.edit_destination_folder.get(), dont_add=True)
else: else:
show_message("Heads up!", "Only newly downloaded items will be affected", icon="info") show_message("Heads up!", "Only newly downloaded items will be affected", icon="info")
else: else:
show_message("Warning -> Check T7x path", f"You don't have any items yet ,from now on item's folders will be named as their {option}") show_message("Warning -> Check game path", f"You don't have any items yet ,from now on item's folders will be named as their {option}")
else: else:
show_message("Warning -> Check T7x path", f"You don't have any items yet ,from now on item's folders will be named as their {option}") show_message("Warning -> Check game path", f"You don't have any items yet ,from now on item's folders will be named as their {option}")
except Exception as e: except Exception as e:
show_message("Error", f"Error occured \n{e}") show_message("Error", f"Error occured \n{e}")
finally: finally:
@ -490,7 +614,7 @@ class SettingsTab(ctk.CTkFrame):
self.save_settings() self.save_settings()
def load_on_switch_screen(self): def load_on_switch_screen(self):
self.check_updates_var.set(self.load_settings("checkforupdtes")) self.check_updates_var.set(self.load_settings("checkforupdates"))
self.console_var.set(self.load_settings("console")) self.console_var.set(self.load_settings("console"))
self.reset_steamcmd_on_fail.set(value=self.load_settings("reset_on_fail", "10")) self.reset_steamcmd_on_fail.set(value=self.load_settings("reset_on_fail", "10"))
self.estimated_progress_var.set(self.load_settings("estimated_progress", "on")) self.estimated_progress_var.set(self.load_settings("estimated_progress", "on"))
@ -502,20 +626,20 @@ class SettingsTab(ctk.CTkFrame):
# keep last cuz of trace_add() # keep last cuz of trace_add()
self.save_button.configure(state='disabled') self.save_button.configure(state='disabled')
def settings_launch_T7x(self): def settings_launch_game(self):
launch_T7x_func(check_config("destinationfolder")) launch_game_func(check_config("destinationfolder"), self.edit_startup_exe.get(), self.edit_launch_args.get())
def settings_reset_steamcmd(self): def settings_reset_steamcmd(self):
reset_steamcmd() reset_steamcmd()
def from_steam_to_T7x_toplevel(self): def workshop_to_gamedir_toplevel(self):
try: try:
# to make sure json file is up to date # to make sure json file is up to date
main_app.app.library_tab.load_items(main_app.app.edit_destination_folder.get(), dont_add=True) main_app.app.library_tab.load_items(self.edit_destination_folder.get(), dont_add=True)
top = ctk.CTkToplevel(self) top = ctk.CTkToplevel(self)
if os.path.exists(os.path.join(RESOURCES_DIR, "ryuk.ico")): if os.path.exists(os.path.join(RESOURCES_DIR, "ryuk.ico")):
top.after(210, lambda: top.iconbitmap(os.path.join(RESOURCES_DIR, "ryuk.ico"))) top.after(210, lambda: top.iconbitmap(os.path.join(RESOURCES_DIR, "ryuk.ico")))
top.title("Steam to T7x") top.title("Workshop Transfer")
_, _, x, y = get_window_size_from_registry() _, _, x, y = get_window_size_from_registry()
top.geometry(f"+{x}+{y}") top.geometry(f"+{x}+{y}")
# top.attributes('-topmost', 'true') # top.attributes('-topmost', 'true')
@ -527,9 +651,10 @@ class SettingsTab(ctk.CTkFrame):
steam_folder_label = ctk.CTkLabel(center_frame, text="Steam Folder:") steam_folder_label = ctk.CTkLabel(center_frame, text="Steam Folder:")
steam_folder_entry = ctk.CTkEntry(center_frame, width=225) steam_folder_entry = ctk.CTkEntry(center_frame, width=225)
button_steam_browse = ctk.CTkButton(center_frame, text="Select", width=10) button_steam_browse = ctk.CTkButton(center_frame, text="Select", width=10)
T7x_folder_label = ctk.CTkLabel(center_frame, text="T7x Folder:") game_folder_label = ctk.CTkLabel(center_frame, text="Game Folder:")
T7x_folder_entry = ctk.CTkEntry(center_frame, width=225) game_folder_entry = ctk.CTkEntry(center_frame, width=225)
button_T7x_browse = ctk.CTkButton(center_frame, text="Select", width=10) button_T7x_browse = ctk.CTkButton(center_frame, text="Select", width=10)
# Create option to choose between cut or copy # Create option to choose between cut or copy
operation_label = ctk.CTkLabel(center_frame, text="Choose operation:") operation_label = ctk.CTkLabel(center_frame, text="Choose operation:")
copy_var = ctk.BooleanVar() copy_var = ctk.BooleanVar()
@ -574,11 +699,11 @@ class SettingsTab(ctk.CTkFrame):
if copy_var.get(): if copy_var.get():
copy_button.configure(text=f"Start (Copy)") copy_button.configure(text=f"Start (Copy)")
def open_T7x_browser(): def open_game_browser():
selected_folder = ctk.filedialog.askdirectory(title="Select T7x Folder") selected_folder = ctk.filedialog.askdirectory(title="Select Game Folder")
if selected_folder: if selected_folder:
T7x_folder_entry.delete(0, "end") game_folder_entry.delete(0, "end")
T7x_folder_entry.insert(0, selected_folder) game_folder_entry.insert(0, selected_folder)
def open_steam_browser(): def open_steam_browser():
selected_folder = ctk.filedialog.askdirectory(title="Select Steam Folder (ex: C:/Program Files (x86)/Steam)") selected_folder = ctk.filedialog.askdirectory(title="Select Steam Folder (ex: C:/Program Files (x86)/Steam)")
@ -591,20 +716,20 @@ class SettingsTab(ctk.CTkFrame):
def start_thread(): def start_thread():
try: try:
if not cut_var.get() and not copy_var.get(): if not cut_var.get() and not copy_var.get():
show_message("Choose operation!", "Please choose an operation, Copy or Cut files from steam!") show_message("Choose operation!", "Please choose an operation, Copy or Cut files from Steam!")
return return
copy_button.configure(state="disabled") copy_button.configure(state="disabled")
steam_folder = steam_folder_entry.get() steam_folder = steam_folder_entry.get()
ws_folder = os.path.join(steam_folder, "steamapps/workshop/content/311210") ws_folder = os.path.join(steam_folder, "steamapps/workshop/content/311210")
T7x_folder = T7x_folder_entry.get() game_folder = game_folder_entry.get()
if not os.path.exists(steam_folder) and not os.path.exists(ws_folder): if not os.path.exists(steam_folder) and not os.path.exists(ws_folder):
show_message("Not found", "Either you have no items downloaded from Steam or wrong path, please recheck path (ex: C:/Program Files (x86)/Steam)") show_message("Not found", "Either you have no items downloaded from Steam or wrong path, please recheck path (ex: C:/Program Files (x86)/Steam)")
return return
if not os.path.exists(T7x_folder): if not os.path.exists(game_folder):
show_message("Not found", "T7x folder not found, please recheck path") show_message("Not found", "game folder not found, please recheck path")
return return
top.after(0, progress_text.configure(text="Loading...")) top.after(0, progress_text.configure(text="Loading..."))
@ -644,10 +769,10 @@ class SettingsTab(ctk.CTkFrame):
folder_name = extract_json_data(json_file_path, "publisherID") folder_name = extract_json_data(json_file_path, "publisherID")
if mod_type == "mod": if mod_type == "mod":
path_folder = os.path.join(T7x_folder, "mods") path_folder = os.path.join(game_folder, "mods")
folder_name_path = os.path.join(path_folder, folder_name, "zone") folder_name_path = os.path.join(path_folder, folder_name, "zone")
elif mod_type == "map": elif mod_type == "map":
path_folder = os.path.join(T7x_folder, "usermaps") path_folder = os.path.join(game_folder, "usermaps")
folder_name_path = os.path.join(path_folder, folder_name, "zone") folder_name_path = os.path.join(path_folder, folder_name, "zone")
else: else:
show_message("Error", "Invalid workshop type in workshop.json, are you sure this is a map or a mod?.", icon="cancel") show_message("Error", "Invalid workshop type in workshop.json, are you sure this is a map or a mod?.", icon="cancel")
@ -669,17 +794,17 @@ class SettingsTab(ctk.CTkFrame):
if cut_var.get(): if cut_var.get():
remove_tree(os.path.join(map_folder, dir_name)) remove_tree(os.path.join(map_folder, dir_name))
main_app.app.library_tab.update_item(main_app.app.edit_destination_folder.get(), workshop_id, mod_type, folder_name) main_app.app.library_tab.update_item(self.edit_destination_folder.get(), workshop_id, mod_type, folder_name)
else: else:
# if its last folder to check # if its last folder to check
if i == total_folders: if i == total_folders:
show_message("Error", f"workshop.json not found in {dir_name}", icon="cancel") show_message("Error", f"workshop.json not found in {dir_name}", icon="cancel")
main_app.app.library_tab.load_items(main_app.app.edit_destination_folder.get(), dont_add=True) main_app.app.library_tab.load_items(self.edit_destination_folder.get(), dont_add=True)
return return
continue continue
if subfolders: if subfolders:
main_app.app.library_tab.load_items(main_app.app.edit_destination_folder.get(), dont_add=True) main_app.app.library_tab.load_items(self.edit_destination_folder.get(), dont_add=True)
main_app.app.show_complete_message(message=f"All items were moved\nYou can run the game now!\nPS: You have to restart the game\n(pressing launch will launch/restarts)") main_app.app.show_complete_message(message=f"All items were moved\nYou can run the game now!\nPS: You have to restart the game\n(pressing launch will launch/restarts)")
finally: finally:
@ -699,8 +824,8 @@ class SettingsTab(ctk.CTkFrame):
button_steam_browse.grid(row=1, column=2, padx=(0, 20), pady=(10, 10), sticky="wnes") button_steam_browse.grid(row=1, column=2, padx=(0, 20), pady=(10, 10), sticky="wnes")
steam_folder_label.grid(row=0, column=0, padx=(20, 20), pady=(10, 0), sticky='w') steam_folder_label.grid(row=0, column=0, padx=(20, 20), pady=(10, 0), sticky='w')
steam_folder_entry.grid(row=1, column=0, columnspan=2, padx=(0, 20), pady=(10, 10), sticky='nes') steam_folder_entry.grid(row=1, column=0, columnspan=2, padx=(0, 20), pady=(10, 10), sticky='nes')
T7x_folder_label.grid(row=2, column=0, padx=(20, 20), pady=(10, 0), sticky='w') game_folder_label.grid(row=2, column=0, padx=(20, 20), pady=(10, 0), sticky='w')
T7x_folder_entry.grid(row=3, column=0, columnspan=2, padx=(0, 20), pady=(10, 10), sticky='nes') game_folder_entry.grid(row=3, column=0, columnspan=2, padx=(0, 20), pady=(10, 10), sticky='nes')
button_T7x_browse.grid(row=3, column=2, padx=(0, 20), pady=(10, 10), sticky="wnes") button_T7x_browse.grid(row=3, column=2, padx=(0, 20), pady=(10, 10), sticky="wnes")
operation_label.grid(row=4, column=0, padx=(20, 20), pady=(10, 10), sticky='wnes') operation_label.grid(row=4, column=0, padx=(20, 20), pady=(10, 10), sticky='wnes')
copy_check.grid(row=4, column=1, padx=(0, 10), pady=(10, 10), sticky='wnes') copy_check.grid(row=4, column=1, padx=(0, 10), pady=(10, 10), sticky='wnes')
@ -711,14 +836,14 @@ class SettingsTab(ctk.CTkFrame):
progress_color = get_button_state_colors(check_custom_theme(check_config("theme", fallback="T7xwd_theme.json")), "progress_bar_fill_color") progress_color = get_button_state_colors(check_custom_theme(check_config("theme", fallback="T7xwd_theme.json")), "progress_bar_fill_color")
progress_bar.configure(progress_color=progress_color) progress_bar.configure(progress_color=progress_color)
steam_folder_entry.insert(1, check_config("steam_folder", "")) steam_folder_entry.insert(1, check_config("steam_folder", ""))
T7x_folder_entry.insert(1, main_app.app.edit_destination_folder.get()) game_folder_entry.insert(1, self.edit_destination_folder.get())
button_T7x_browse.configure(command=open_T7x_browser) button_T7x_browse.configure(command=open_game_browser)
button_steam_browse.configure(command=open_steam_browser) button_steam_browse.configure(command=open_steam_browser)
copy_button.configure(command=start_copy_operation) copy_button.configure(command=start_copy_operation)
cut_check.configure(command = lambda: check_status(cut_var, copy_var)) cut_check.configure(command = lambda: check_status(cut_var, copy_var))
copy_check.configure(command = lambda: check_status(copy_var, cut_var)) copy_check.configure(command = lambda: check_status(copy_var, cut_var))
main_app.app.create_context_menu(steam_folder_entry) main_app.app.create_context_menu(steam_folder_entry)
main_app.app.create_context_menu(T7x_folder_entry) main_app.app.create_context_menu(game_folder_entry)
copy_var.set(True) copy_var.set(True)
progress_bar.set(0) progress_bar.set(0)
top.after(150, top.focus_force) top.after(150, top.focus_force)