thought ill add a reorganized version (package), ill improve it even more

This commit is contained in:
faroukbmiled 2023-08-31 22:07:52 +01:00
parent ef8033cd07
commit 644b5b6c7e
24 changed files with 3073 additions and 11 deletions

View File

@ -1159,12 +1159,13 @@ class BOIIIWD(ctk.CTk):
self.workshop_queue_label = ctk.CTkLabel(self.qeueuframe, text="Workshop IDs/Links -> press help to see examples:")
self.workshop_queue_label.grid(row=0, column=0, padx=(20, 20), pady=(20, 20), sticky="wns")
self.help_button = ctk.CTkButton(master=self.qeueuframe, text="help", command=self.help_queue_text_func, width=10, height=10, fg_color="#585858")
self.help_button = ctk.CTkButton(master=self.qeueuframe, text="Help", command=self.help_queue_text_func, width=10, height=10, fg_color="#585858")
self.help_button.grid(row=0, column=1, padx=(0, 20), pady=(20, 20), sticky="wns")
self.help_button_tooltip = CTkToolTip(self.help_button, message="This only works if the text area is empty (press twice if you had something in it)")
self.help_restore_content = None
self.queuetextarea = ctk.CTkTextbox(master=self.qeueuframe, font=("", 15))
self.queuetextarea.grid(row=1, column=0, columnspan=2,rowspan=2, padx=(20, 20), pady=(0, 20), sticky="nwse")
self.queuetextarea.grid(row=1, column=0, columnspan=4, padx=(20, 20), pady=(0, 20), sticky="nwse")
self.status_text = ctk.CTkLabel(self.qeueuframe, text="Status: Not Downloading")
self.status_text.grid(row=3, column=0, padx=(20, 20), pady=(0, 20), sticky="ws")
@ -1494,15 +1495,36 @@ class BOIIIWD(ctk.CTk):
self.edit_steamcmd_path.insert(0, cwd())
create_default_config()
def help_queue_text_func(self):
if any(char.strip() for char in self.queuetextarea.get("1.0", "end")):
self.workshop_queue_label.configure(text="Workshop IDs/Links => press help to see examples:")
self.queuetextarea.configure(state="normal")
self.queuetextarea.delete(1.0, "end")
self.queuetextarea.insert(1.0, "")
def help_queue_text_func(self, event=None):
textarea_content = self.queuetextarea.get("1.0", "end").strip()
help_text = "3010399939,2976006537,2118338989,...\nor:\n3010399939\n2976006537\n2113146805\n..."
if any(char.strip() for char in textarea_content):
if help_text in textarea_content:
self.workshop_queue_label.configure(text="Workshop IDs/Links => press help to see examples:")
self.help_button.configure(text="Help")
self.queuetextarea.configure(state="normal")
self.queuetextarea.delete(1.0, "end")
self.queuetextarea.insert(1.0, "")
if self.help_restore_content:
self.queuetextarea.insert(1.0, self.help_restore_content.strip())
else:
self.queuetextarea.insert(1.0, "")
else:
if not help_text in textarea_content:
self.help_restore_content = textarea_content
self.workshop_queue_label.configure(text="Workshop IDs/Links => press help to see examples:")
self.help_button.configure(text="Restore")
self.queuetextarea.configure(state="normal")
self.queuetextarea.delete(1.0, "end")
self.queuetextarea.insert(1.0, "")
self.workshop_queue_label.configure(text="Workshop IDs/Links => press restore to remove examples:")
self.queuetextarea.insert(1.0, help_text)
self.queuetextarea.configure(state="disabled")
else:
self.workshop_queue_label.configure(text="Workshop IDs/Links => press help again to remove examples:")
self.queuetextarea.insert(1.0, "3010399939,2976006537,2118338989,...\nor:\n3010399939\n2976006537\n2113146805\n...")
self.help_restore_content = textarea_content
self.workshop_queue_label.configure(text="Workshop IDs/Links => press restore to remove examples:")
self.help_button.configure(text="Restore")
self.queuetextarea.insert(1.0, help_text)
self.queuetextarea.configure(state="disabled")
def open_BOIII_browser(self):
@ -1977,7 +1999,8 @@ class BOIIIWD(ctk.CTk):
if not any(isinstance(item, int) for item in items):
self.stop_download
return
show_message("Heads up! map/s not skipped => skip is off in settings", f"These item IDs may already be installed:\n{item_ids}", icon="info")
else:
show_message("Heads up! map/s not skipped => skip is off in settings", f"These item IDs may already be installed:\n{item_ids}", icon="info")
self.after(1, self.status_text.configure(text=f"Status: Total size: ~{convert_bytes_to_readable(self.total_queue_size)}"))
start_time = time.time()

View File

View File

@ -0,0 +1,5 @@
from src import boiiiwd_class
if __name__ == "__main__":
app = boiiiwd_class.BOIIIWD()
app.mainloop()

View File

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,299 @@
from src.imports import *
# Start Helper Functions
def cwd():
if getattr(sys, 'frozen', False):
return os.path.dirname(sys.executable)
else:
return os.path.dirname(os.path.abspath(__file__))
def check_config(name, fallback=None):
config = configparser.ConfigParser()
config.read(CONFIG_FILE_PATH)
if fallback:
return config.get("Settings", name, fallback=fallback)
return config.get("Settings", name, fallback="on")
def save_config(name, value):
config = configparser.ConfigParser()
config.read(CONFIG_FILE_PATH)
if name and value:
config.set("Settings", name, value)
with open(CONFIG_FILE_PATH, "w") as config_file:
config.write(config_file)
def check_custom_theme(theme_name):
if os.path.exists(os.path.join(cwd(), theme_name)):
return os.path.join(cwd(), theme_name)
else:
try:
return os.path.join(RESOURCES_DIR, theme_name)
except:
return os.path.join(RESOURCES_DIR, "boiiiwd_theme.json")
# theme initialization
ctk.set_appearance_mode(check_config("appearance", "Dark")) # Modes: "System" (standard), "Dark", "Light"
try:
ctk.set_default_color_theme(check_custom_theme(check_config("theme", fallback="boiiiwd_theme.json")))
except:
save_config("theme", "boiiiwd_theme.json")
ctk.set_default_color_theme(os.path.join(RESOURCES_DIR, "boiiiwd_theme.json"))
def get_latest_release_version():
try:
release_api_url = f"https://api.github.com/repos/{GITHUB_REPO}/releases/latest"
response = requests.get(release_api_url)
response.raise_for_status()
data = response.json()
return data["tag_name"]
except requests.exceptions.RequestException as e:
show_message("Warning", f"Error while checking for updates: \n{e}")
return None
def create_update_script(current_exe, new_exe, updater_folder, program_name):
script_content = f"""
@echo off
echo Terminating BOIIIWD.exe...
taskkill /im "{program_name}" /t /f
echo Replacing BOIIIWD.exe...
cd "{updater_folder}"
taskkill /im "{program_name}" /t /f
move /y "{new_exe}" "../"{program_name}""
echo Starting BOIIIWD.exe...
cd ..
start "" "{current_exe}"
echo Exiting!
exit
"""
script_path = os.path.join(updater_folder, "boiiiwd_updater.bat")
with open(script_path, "w") as script_file:
script_file.write(script_content)
return script_path
def extract_workshop_id(link):
try:
pattern = r'(?<=id=)(\d+)'
match = re.search(pattern, link)
if match:
return match.group(0)
else:
return None
except:
return None
def check_steamcmd():
steamcmd_path = get_steamcmd_path()
steamcmd_exe_path = os.path.join(steamcmd_path, "steamcmd.exe")
if not os.path.exists(steamcmd_exe_path):
return False
return True
def initialize_steam(master):
try:
steamcmd_path = get_steamcmd_path()
steamcmd_exe_path = os.path.join(steamcmd_path, "steamcmd.exe")
process = subprocess.Popen([steamcmd_exe_path, "+quit"], creationflags=subprocess.CREATE_NEW_CONSOLE)
master.attributes('-alpha', 0.0)
process.wait()
if is_steamcmd_initialized():
show_message("SteamCMD has terminated!", "BOIIIWD is ready for action.", icon="info")
else:
show_message("SteamCMD has terminated!!", "SteamCMD isn't initialized yet")
except:
show_message("Error!", "An error occurred please check your paths and try again.", icon="cancel")
master.attributes('-alpha', 1.0)
def valid_id(workshop_id):
url = f"https://steamcommunity.com/sharedfiles/filedetails/?id={workshop_id}"
response = requests.get(url)
response.raise_for_status()
content = response.text
soup = BeautifulSoup(content, "html.parser")
try:
soup.find("div", class_="rightDetailsBlock").text.strip()
soup.find("div", class_="workshopItemTitle").text.strip()
soup.find("div", class_="detailsStatRight").text.strip()
stars_div = soup.find("div", class_="fileRatingDetails")
stars_div.find("img")["src"]
return True
except:
return False
def convert_speed(speed_bytes):
if speed_bytes < 1024:
return speed_bytes, "B/s"
elif speed_bytes < 1024 * 1024:
return speed_bytes / 1024, "KB/s"
elif speed_bytes < 1024 * 1024 * 1024:
return speed_bytes / (1024 * 1024), "MB/s"
else:
return speed_bytes / (1024 * 1024 * 1024), "GB/s"
def create_default_config():
config = configparser.ConfigParser()
config["Settings"] = {
"SteamCMDPath": cwd(),
"DestinationFolder": "",
"checkforupdtes": "on",
"console": "off"
}
with open(CONFIG_FILE_PATH, "w") as config_file:
config.write(config_file)
def get_steamcmd_path():
config = configparser.ConfigParser()
config.read(CONFIG_FILE_PATH)
return config.get("Settings", "SteamCMDPath", fallback=cwd())
def extract_json_data(json_path, key):
with open(json_path, "r") as json_file:
data = json.load(json_file)
return data[key]
def convert_bytes_to_readable(size_in_bytes, no_symb=None):
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
if size_in_bytes < 1024.0:
if no_symb:
return f"{size_in_bytes:.2f}"
return f"{size_in_bytes:.2f} {unit}"
size_in_bytes /= 1024.0
def get_workshop_file_size(workshop_id, raw=None):
url = f"https://steamcommunity.com/sharedfiles/filedetails/?id={workshop_id}&searchtext="
response = requests.get(url)
soup = BeautifulSoup(response.text, "html.parser")
file_size_element = soup.find("div", class_="detailsStatRight")
try:
if raw:
file_size_text = file_size_element.get_text(strip=True)
file_size_text = file_size_text.replace(",", "")
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)
if file_size_element:
file_size_text = file_size_element.get_text(strip=True)
file_size_text = file_size_text.replace(",", "")
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 None
except:
return None
def show_message(title, message, icon="warning", exit_on_close=False):
if exit_on_close:
msg = CTkMessagebox(title=title, message=message, icon=icon, option_1="No", option_2="Ok")
response = msg.get()
if response=="No":
return False
if response=="Ok":
return True
else:
return False
else:
msg = CTkMessagebox(title=title, message=message, icon=icon)
def launch_boiii_func(path):
procname = "boiii.exe"
try:
if procname in (p.name() for p in psutil.process_iter()):
for proc in psutil.process_iter():
if proc.name() == procname:
proc.kill()
boiii_path = os.path.join(path, procname)
subprocess.Popen([boiii_path ,"-launch"] , cwd=path)
show_message("Please wait!", "The game has launched in the background it will open up in a sec!", icon="info")
except Exception as e:
show_message("Error: Failed to launch BOIII", f"Failed to launch boiii.exe\nMake sure to put in your correct boiii path\n{e}")
def remove_tree(folder_path, show_error=None):
if show_error:
try:
shutil.rmtree(folder_path)
except Exception as e:
show_message("Error!", f"An error occurred while trying to remove files:\n{e}", icon="cancel")
try:
shutil.rmtree(folder_path)
except Exception as e:
pass
def convert_seconds(seconds):
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)
return hours, minutes, seconds
def get_folder_size(folder_path):
total_size = 0
for path, dirs, files in os.walk(folder_path):
for f in files:
fp = os.path.join(path, f)
total_size += os.stat(fp).st_size
return total_size
def is_steamcmd_initialized():
steamcmd_path = get_steamcmd_path()
steamcmd_exe_path = os.path.join(steamcmd_path, "steamcmd.exe")
steamcmd_size = os.path.getsize(steamcmd_exe_path)
if steamcmd_size < 3 * 1024 * 1024:
return False
return True
def get_button_state_colors(file_path, state):
try:
with open(file_path, 'r') as json_file:
data = json.load(json_file)
if 'BOIIIWD_Globals' in data:
boiiiwd_globals = data['BOIIIWD_Globals']
if state in boiiiwd_globals:
return boiiiwd_globals[state]
else:
return None
else:
return None
except FileNotFoundError:
return None
except json.JSONDecodeError:
return None
def reset_steamcmd(no_warn=None):
steamcmd_path = get_steamcmd_path()
steamcmd_steamapps = os.path.join(steamcmd_path, "steamapps")
if os.path.exists(steamcmd_steamapps):
remove_tree(steamcmd_steamapps, show_error=True)
if not no_warn:
show_message("Success!", "SteamCMD has been reset successfully!", icon="info")
else:
if not no_warn:
show_message("Warning!", "steamapps folder was not found, maybe already removed?", icon="warning")
def get_item_name(id):
try:
url = f"https://steamcommunity.com/sharedfiles/filedetails/?id={id}"
response = requests.get(url)
response.raise_for_status()
content = response.text
soup = BeautifulSoup(content, "html.parser")
try:
map_name = soup.find("div", class_="workshopItemTitle").text.strip()
name = map_name[:32] + "..." if len(map_name) > 32 else map_name
return name
except:
return True
except:
return False
# End helper functions

View File

@ -0,0 +1,29 @@
from CTkMessagebox import CTkMessagebox
from tkinter import Menu, END, Event
from bs4 import BeautifulSoup
import customtkinter as ctk
from CTkToolTip import *
from PIL import Image
import configparser
import webbrowser
import subprocess
import threading
import datetime
import requests
import zipfile
import shutil
import psutil
import json
import math
import time
import sys
import io
import os
import re
VERSION = "v0.2.5"
GITHUB_REPO = "faroukbmiled/BOIIIWD"
LATEST_RELEASE_URL = "https://github.com/faroukbmiled/BOIIIWD/releases/latest/download/Release.zip"
UPDATER_FOLDER = "update"
CONFIG_FILE_PATH = "config.ini"
RESOURCES_DIR = os.path.join(os.path.dirname(__file__), 'resources')

View File

@ -0,0 +1,315 @@
from src.imports import *
from src.helpers import show_message, get_folder_size, convert_bytes_to_readable,\
extract_json_data, get_workshop_file_size, extract_workshop_id
class LibraryTab(ctk.CTkScrollableFrame):
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)
self.added_items = set()
self.grid_columnconfigure(0, weight=1)
self.radiobutton_variable = ctk.StringVar()
self.no_items_label = ctk.CTkLabel(self, text="", anchor="w")
self.filter_entry = ctk.CTkEntry(self, placeholder_text="Your search query here, or type in mod or map to only see that")
self.filter_entry.bind("<KeyRelease>", self.filter_items)
self.filter_entry.grid(row=0, column=0, padx=(10, 20), pady=(10, 20), sticky="we")
filter_refresh_button_image = os.path.join(RESOURCES_DIR, "Refresh_icon.svg.png")
self.filter_refresh_button = ctk.CTkButton(self, image=ctk.CTkImage(Image.open(filter_refresh_button_image)), command=self.refresh_items, width=20, height=20,
fg_color="transparent", text="")
self.filter_refresh_button.grid(row=0, column=1, padx=(10, 20), pady=(10, 20), sticky="enw")
lib_filter_menu = Menu(self.filter_entry, tearoff=False, background='#565b5e', fg='white', borderwidth=0, bd=0)
paste_event = Event()
paste_event.x = 0
paste_event.y = 0
lib_filter_menu.add_command(label="Paste", command=lambda: self.paste_update_filter(paste_event))
lib_filter_menu.add_command(label="Copy", command=lambda: self.clipboard_copy(self.filter_entry))
self.filter_entry.bind("<Button-3>", lambda event: self.do_popup(event, frame=lib_filter_menu))
self.label_list = []
self.button_list = []
self.button_view_list = []
self.filter_type = True
def do_popup(self, event, frame):
try:
frame.tk_popup(event.x_root, event.y_root)
finally:
frame.grab_release()
def clipboard_copy(self, text):
text.clipboard_clear()
text.clipboard_append(text.get())
def paste_update_filter(self, event):
self.filter_entry.insert(END, self.clipboard_get())
self.filter_items(event)
def add_item(self, item, image=None, item_type="map", workshop_id=None, folder=None):
label = ctk.CTkLabel(self, text=item, image=image, compound="left", padx=5, anchor="w")
button = ctk.CTkButton(self, text="Remove", width=60, height=24, fg_color="#3d3f42")
button_view = ctk.CTkButton(self, text="Details", width=55, height=24, fg_color="#3d3f42")
button.configure(command=lambda: self.remove_item(item, folder))
button_view.configure(command=lambda: self.show_map_info(workshop_id))
button_view_tooltip = CTkToolTip(button_view, message="Opens up a window that shows basic details")
button_tooltip = CTkToolTip(button, message="Removes the map/mod from your game")
label.grid(row=len(self.label_list) + 1, column=0, pady=(0, 10), padx=(5, 10), sticky="w")
button.grid(row=len(self.button_list) + 1, column=1, pady=(0, 10), padx=(50, 10), sticky="e")
button_view.grid(row=len(self.button_view_list) + 1, column=1, pady=(0, 10), padx=(10, 75), sticky="w")
self.label_list.append(label)
self.button_list.append(button)
self.button_view_list.append(button_view)
def filter_items(self, event):
filter_text = self.filter_entry.get().lower()
for label, button, button_view_list in zip(self.label_list, self.button_list, self.button_view_list):
item_text = label.cget("text").lower()
if filter_text in item_text:
label.grid()
button.grid()
button_view_list.grid()
else:
label.grid_remove()
button_view_list.grid_remove()
button.grid_remove()
def load_items(self, boiiiFolder):
# if you add this under init the whole app shrinks for some reason
global boiiiFolderGlobal
boiiiFolderGlobal = boiiiFolder
maps_folder = os.path.join(boiiiFolder, "mods")
mods_folder = os.path.join(boiiiFolder, "usermaps")
folders_to_process = [mods_folder, maps_folder]
for folder_path in folders_to_process:
for root, _, _ in os.walk(folder_path):
zone_path = os.path.join(root, "zone")
if os.path.exists(zone_path):
json_path = os.path.join(zone_path, "workshop.json")
if os.path.exists(json_path):
name = extract_json_data(json_path, "Title").replace(">", "").replace("^", "")
name = name[:45] + "..." if len(name) > 45 else name
item_type = extract_json_data(json_path, "Type")
workshop_id = extract_json_data(json_path, "PublisherID")
folder_name = extract_json_data(json_path, "FolderName")
size = convert_bytes_to_readable(get_folder_size(root))
text_to_add = f"{name} | Type: {item_type.capitalize()}"
mode_type = "ZM" if item_type == "map" and folder_name.startswith("zm") else "MP" if folder_name.startswith("mp") and item_type == "map" else None
if mode_type:
text_to_add += f" | Mode: {mode_type}"
text_to_add += f" | ID: {workshop_id} | Size: {size}"
if text_to_add not in self.added_items:
self.added_items.add(text_to_add)
if item_type == "mod":
image_path = os.path.join(RESOURCES_DIR, "mod_image.png")
else:
image_path = os.path.join(RESOURCES_DIR, "map_image.png")
self.add_item(text_to_add, image=ctk.CTkImage(Image.open(image_path)), item_type=item_type, workshop_id=workshop_id, folder=root)
if not self.added_items:
self.show_no_items_message()
else:
self.hide_no_items_message()
def remove_item(self, item, folder):
for label, button, button_view_list in zip(self.label_list, self.button_list, self.button_view_list):
if item == label.cget("text"):
try:
shutil.rmtree(folder)
except Exception as e:
show_message("Error" ,f"Error removing folder '{folder}': {e}", icon="cancel")
return
label.destroy()
button.destroy()
button_view_list.destroy()
self.label_list.remove(label)
self.button_list.remove(button)
self.added_items.remove(label.cget("text"))
self.button_view_list.remove(button_view_list)
def refresh_items(self):
for label, button, button_view_list in zip(self.label_list, self.button_list, self.button_view_list):
label.destroy()
button.destroy()
button_view_list.destroy()
self.label_list.clear()
self.button_list.clear()
self.button_view_list.clear()
self.added_items.clear()
self.load_items(boiiiFolderGlobal)
def view_item(self, workshop_id):
url = f"https://steamcommunity.com/sharedfiles/filedetails/?id={workshop_id}"
webbrowser.open(url)
def show_no_items_message(self):
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 boiii folder selected.")
def hide_no_items_message(self):
self.no_items_label.configure(text="")
self.no_items_label.forget()
# i know i know ,please make a pull request i cant be bother
def show_map_info(self, workshop):
for button_view in self.button_view_list:
button_view.configure(state="disabled")
def show_map_thread():
workshop_id = workshop
if not workshop_id.isdigit():
try:
if extract_workshop_id(workshop_id).strip().isdigit():
workshop_id = extract_workshop_id(workshop_id).strip()
else:
show_message("Warning", "Not a valid Workshop ID.")
except:
show_message("Warning", "Not a valid Workshop ID.")
return
try:
url = f"https://steamcommunity.com/sharedfiles/filedetails/?id={workshop_id}"
response = requests.get(url)
response.raise_for_status()
content = response.text
soup = BeautifulSoup(content, "html.parser")
try:
map_mod_type = soup.find("div", class_="rightDetailsBlock").text.strip()
map_name = soup.find("div", class_="workshopItemTitle").text.strip()
map_size = map_size = get_workshop_file_size(workshop_id, raw=True)
details_stats_container = soup.find("div", class_="detailsStatsContainerRight")
details_stat_elements = details_stats_container.find_all("div", class_="detailsStatRight")
date_created = details_stat_elements[1].text.strip()
try:
ratings = soup.find('div', class_='numRatings')
ratings_text = ratings.get_text()
except:
ratings = "Not found"
ratings_text= "Not enough ratings"
try:
date_updated = details_stat_elements[2].text.strip()
except:
date_updated = "Not updated"
stars_div = soup.find("div", class_="fileRatingDetails")
starts = stars_div.find("img")["src"]
except:
show_message("Warning", "Not a valid Workshop ID\nCouldn't get information.")
for button_view in self.button_view_list:
button_view.configure(state="normal")
return
try:
preview_image_element = soup.find("img", id="previewImage")
workshop_item_image_url = preview_image_element["src"]
except:
try:
preview_image_element = soup.find("img", id="previewImageMain")
workshop_item_image_url = preview_image_element["src"]
except Exception as e:
show_message("Warning", f"Failed to get preview image ,probably wrong link/id if not please open an issue on github.\n{e}")
for button_view in self.button_view_list:
button_view.configure(state="normal")
return
starts_image_response = requests.get(starts)
stars_image = Image.open(io.BytesIO(starts_image_response.content))
stars_image_size = stars_image.size
image_response = requests.get(workshop_item_image_url)
image_response.raise_for_status()
image = Image.open(io.BytesIO(image_response.content))
image_size = image.size
self.toplevel_info_window(map_name, map_mod_type, map_size, image, image_size, date_created ,
date_updated, stars_image, stars_image_size, ratings_text, url)
except requests.exceptions.RequestException as e:
show_message("Error", f"Failed to fetch map information.\nError: {e}", icon="cancel")
for button_view in self.button_view_list:
button_view.configure(state="normal")
return
info_thread = threading.Thread(target=show_map_thread)
info_thread.start()
def toplevel_info_window(self, map_name, map_mod_type, map_size, image, image_size,
date_created ,date_updated, stars_image, stars_image_size, ratings_text, url):
try:
top = ctk.CTkToplevel(self)
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.title("Map/Mod Information")
top.attributes('-topmost', 'true')
def close_window():
top.destroy()
def view_map_mod():
webbrowser.open(url)
# frames
stars_frame = ctk.CTkFrame(top)
stars_frame.grid(row=0, column=0, columnspan=2, padx=20, pady=(20, 0), sticky="nsew")
stars_frame.columnconfigure(0, weight=0)
stars_frame.rowconfigure(0, weight=1)
image_frame = ctk.CTkFrame(top)
image_frame.grid(row=1, column=0, columnspan=2, padx=20, pady=0, sticky="nsew")
info_frame = ctk.CTkFrame(top)
info_frame.grid(row=2, column=0, columnspan=2, padx=20, pady=20, sticky="nsew")
buttons_frame = ctk.CTkFrame(top)
buttons_frame.grid(row=3, column=0, columnspan=2, padx=20, pady=(0, 20), sticky="nsew")
# fillers
name_label = ctk.CTkLabel(info_frame, text=f"Name: {map_name}")
name_label.grid(row=0, column=0, columnspan=2, sticky="w", padx=20, pady=5)
type_label = ctk.CTkLabel(info_frame, text=f"Type: {map_mod_type}")
type_label.grid(row=1, column=0, columnspan=2, sticky="w", padx=20, pady=5)
size_label = ctk.CTkLabel(info_frame, text=f"Size (Workshop): {map_size}")
size_label.grid(row=2, column=0, columnspan=2, sticky="w", padx=20, pady=5)
date_created_label = ctk.CTkLabel(info_frame, text=f"Posted: {date_created}")
date_created_label.grid(row=3, column=0, columnspan=2, sticky="w", padx=20, pady=5)
date_updated_label = ctk.CTkLabel(info_frame, text=f"Updated: {date_updated}")
date_updated_label.grid(row=4, column=0, columnspan=2, sticky="w", padx=20, pady=5)
stars_image_label = ctk.CTkLabel(stars_frame)
stars_width, stars_height = stars_image_size
stars_image_widget = ctk.CTkImage(stars_image, size=(int(stars_width), int(stars_height)))
stars_image_label.configure(image=stars_image_widget, text="")
stars_image_label.pack(side="left", padx=(10, 20), pady=(10, 10))
ratings = ctk.CTkLabel(stars_frame)
ratings.configure(text=ratings_text)
ratings.pack(side="right", padx=(10, 20), pady=(10, 10))
image_label = ctk.CTkLabel(image_frame)
width, height = image_size
image_widget = ctk.CTkImage(image, size=(int(width), int(height)))
image_label.configure(image=image_widget, text="")
image_label.pack(expand=True, fill="both", padx=(10, 20), pady=(10, 10))
# Buttons
close_button = ctk.CTkButton(buttons_frame, text="View", command=view_map_mod)
close_button.pack(side="left", padx=(10, 20), pady=(10, 10))
view_button = ctk.CTkButton(buttons_frame, text="Close", command=close_window)
view_button.pack(side="right", padx=(10, 20), pady=(10, 10))
top.grid_rowconfigure(0, weight=0)
top.grid_rowconfigure(1, weight=0)
top.grid_rowconfigure(2, weight=1)
top.grid_columnconfigure(0, weight=1)
top.grid_columnconfigure(1, weight=1)
finally:
for button_view in self.button_view_list:
button_view.configure(state="normal")

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@ -0,0 +1,161 @@
{
"BOIIIWD_Globals": {
"button_active_state_color": "#0e2540",
"button_normal_state_color": "#1f538d",
"progress_bar_fill_color": "#1f538d",
"this_is_a_comment": "For button hover color check CTkButton bellow + other stuff"
},
"CTk": {
"fg_color": ["gray95", "gray10"]
},
"CTkToplevel": {
"fg_color": ["gray95", "gray10"]
},
"CTkFrame": {
"corner_radius": 6,
"border_width": 0,
"fg_color": ["gray90", "gray13"],
"top_fg_color": ["gray85", "gray16"],
"border_color": ["gray65", "gray28"]
},
"CTkButton": {
"corner_radius": 6,
"border_width": 0,
"fg_color": ["#3a7ebf", "#1f538d"],
"hover_color": ["#325882", "#14375e"],
"border_color": ["#3E454A", "#949A9F"],
"text_color": ["#DCE4EE", "#DCE4EE"],
"text_color_disabled": ["gray74", "gray60"]
},
"CTkLabel": {
"corner_radius": 0,
"fg_color": "transparent",
"text_color": ["gray14", "gray84"]
},
"CTkEntry": {
"corner_radius": 6,
"border_width": 2,
"fg_color": ["#F9F9FA", "#343638"],
"border_color": ["#979DA2", "#565B5E"],
"text_color": ["gray14", "gray84"],
"placeholder_text_color": ["gray52", "gray62"]
},
"CTkCheckBox": {
"corner_radius": 6,
"border_width": 3,
"fg_color": ["#3a7ebf", "#1f538d"],
"border_color": ["#3E454A", "#949A9F"],
"hover_color": ["#325882", "#14375e"],
"checkmark_color": ["#DCE4EE", "gray90"],
"text_color": ["gray14", "gray84"],
"text_color_disabled": ["gray60", "gray45"]
},
"CTkSwitch": {
"corner_radius": 1000,
"border_width": 3,
"button_length": 0,
"fg_color": ["#939BA2", "#4A4D50"],
"progress_color": ["#3a7ebf", "#1f538d"],
"button_color": ["gray36", "#D5D9DE"],
"button_hover_color": ["gray20", "gray100"],
"text_color": ["gray14", "gray84"],
"text_color_disabled": ["gray60", "gray45"]
},
"CTkRadioButton": {
"corner_radius": 1000,
"border_width_checked": 6,
"border_width_unchecked": 3,
"fg_color": ["#3a7ebf", "#1f538d"],
"border_color": ["#3E454A", "#949A9F"],
"hover_color": ["#325882", "#14375e"],
"text_color": ["gray14", "gray84"],
"text_color_disabled": ["gray60", "gray45"]
},
"CTkProgressBar": {
"corner_radius": 1000,
"border_width": 0,
"fg_color": ["#939BA2", "#4A4D50"],
"progress_color": ["#3a7ebf", "#1f538d"],
"border_color": ["gray", "gray"]
},
"CTkSlider": {
"corner_radius": 1000,
"button_corner_radius": 1000,
"border_width": 6,
"button_length": 0,
"fg_color": ["#939BA2", "#4A4D50"],
"progress_color": ["gray40", "#AAB0B5"],
"button_color": ["#3a7ebf", "#1f538d"],
"button_hover_color": ["#325882", "#14375e"]
},
"CTkOptionMenu": {
"corner_radius": 6,
"fg_color": ["#3a7ebf", "#1f538d"],
"button_color": ["#325882", "#14375e"],
"button_hover_color": ["#234567", "#1e2c40"],
"text_color": ["#DCE4EE", "#DCE4EE"],
"text_color_disabled": ["gray74", "gray60"]
},
"CTkComboBox": {
"corner_radius": 6,
"border_width": 2,
"fg_color": ["#F9F9FA", "#343638"],
"border_color": ["#979DA2", "#565B5E"],
"button_color": ["#979DA2", "#565B5E"],
"button_hover_color": ["#6E7174", "#7A848D"],
"text_color": ["gray14", "gray84"],
"text_color_disabled": ["gray50", "gray45"]
},
"CTkScrollbar": {
"corner_radius": 1000,
"border_spacing": 4,
"fg_color": "transparent",
"button_color": ["gray55", "gray41"],
"button_hover_color": ["gray40", "gray53"]
},
"CTkSegmentedButton": {
"corner_radius": 6,
"border_width": 2,
"fg_color": ["#979DA2", "gray29"],
"selected_color": ["#3a7ebf", "#1f538d"],
"selected_hover_color": ["#325882", "#14375e"],
"unselected_color": ["#979DA2", "gray29"],
"unselected_hover_color": ["gray70", "gray41"],
"text_color": ["#DCE4EE", "#DCE4EE"],
"text_color_disabled": ["gray74", "gray60"]
},
"CTkTextbox": {
"corner_radius": 6,
"border_width": 0,
"fg_color": ["gray100", "gray20"],
"border_color": ["#979DA2", "#565B5E"],
"text_color": ["gray14", "gray84"],
"scrollbar_button_color": ["gray55", "gray41"],
"scrollbar_button_hover_color": ["gray40", "gray53"]
},
"CTkScrollableFrame": {
"label_fg_color": ["gray80", "gray21"]
},
"DropdownMenu": {
"fg_color": ["gray90", "gray20"],
"hover_color": ["gray75", "gray28"],
"text_color": ["gray14", "gray84"]
},
"CTkFont": {
"macOS": {
"family": "SF Display",
"size": 13,
"weight": "normal"
},
"Windows": {
"family": "Roboto",
"size": 13,
"weight": "normal"
},
"Linux": {
"family": "Roboto",
"size": 13,
"weight": "normal"
}
}
}

View File

@ -0,0 +1,161 @@
{
"BOIIIWD_Globals": {
"button_active_state_color": "#28292b",
"button_normal_state_color": "#3e3f42",
"progress_bar_fill_color": "#616368",
"this_is_a_comment": "For button hover color check CTkButton bellow + other stuff"
},
"CTk": {
"fg_color": ["gray95", "gray10"]
},
"CTkToplevel": {
"fg_color": ["gray95", "gray10"]
},
"CTkFrame": {
"corner_radius": 6,
"border_width": 0,
"fg_color": ["gray90", "gray13"],
"top_fg_color": ["gray85", "gray16"],
"border_color": ["gray65", "gray28"]
},
"CTkButton": {
"corner_radius": 6,
"border_width": 0,
"fg_color": ["#3e3f42", "#3e3f42"],
"hover_color": ["#505256", "#505256"],
"border_color": ["#3E454A", "#949A9F"],
"text_color": ["#fcfcfc", "#fcfcfc"],
"text_color_disabled": ["gray74", "gray60"]
},
"CTkLabel": {
"corner_radius": 0,
"fg_color": "transparent",
"text_color": ["gray14", "gray84"]
},
"CTkEntry": {
"corner_radius": 6,
"border_width": 2,
"fg_color": ["#F9F9FA", "#343638"],
"border_color": ["#979DA2", "#565B5E"],
"text_color": ["gray14", "gray84"],
"placeholder_text_color": ["gray52", "gray62"]
},
"CTkCheckBox": {
"corner_radius": 6,
"border_width": 3,
"fg_color": ["#3a3a3a", "#3a3a3a"],
"hover_color": ["#737373", "#737373"],
"border_color": ["#3E454A", "#949A9F"],
"checkmark_color": ["#DCE4EE", "gray90"],
"text_color": ["gray14", "gray84"],
"text_color_disabled": ["gray60", "gray45"]
},
"CTkSwitch": {
"corner_radius": 1000,
"border_width": 3,
"button_length": 0,
"fg_color": ["#3a3a3a", "#3a3a3a"],
"progress_color": ["green", "green"],
"button_color": ["gray36", "#D5D9DE"],
"button_hover_color": ["gray20", "gray100"],
"text_color": ["gray14", "gray84"],
"text_color_disabled": ["gray60", "gray45"]
},
"CTkRadioButton": {
"corner_radius": 1000,
"border_width_checked": 6,
"border_width_unchecked": 3,
"fg_color": ["#3a3a3a", "#3a3a3a"],
"border_color": ["#3E454A", "#949A9F"],
"hover_color": ["#325882", "#14375e"],
"text_color": ["gray14", "gray84"],
"text_color_disabled": ["gray60", "gray45"]
},
"CTkProgressBar": {
"corner_radius": 1000,
"border_width": 0,
"fg_color": ["#3a3a3a", "#3a3a3a"],
"progress_color": ["#3a7ebf", "#1f538d"],
"border_color": ["gray", "gray"]
},
"CTkSlider": {
"corner_radius": 1000,
"button_corner_radius": 1000,
"border_width": 6,
"button_length": 0,
"fg_color": ["#939BA2", "#4A4D50"],
"progress_color": ["gray40", "#AAB0B5"],
"button_color": ["#3a7ebf", "#1f538d"],
"button_hover_color": ["#325882", "#14375e"]
},
"CTkOptionMenu": {
"corner_radius": 6,
"fg_color": ["#505256", "#505256"],
"button_color": ["#3e3f42", "#3e3f42"],
"button_hover_color": ["#343638", "#343638"],
"text_color": ["#DCE4EE", "#DCE4EE"],
"text_color_disabled": ["gray74", "gray60"]
},
"CTkComboBox": {
"corner_radius": 6,
"border_width": 2,
"fg_color": ["#F9F9FA", "#343638"],
"border_color": ["#979DA2", "#565B5E"],
"button_color": ["#979DA2", "#565B5E"],
"button_hover_color": ["#6E7174", "#7A848D"],
"text_color": ["gray14", "gray84"],
"text_color_disabled": ["gray50", "gray45"]
},
"CTkScrollbar": {
"corner_radius": 1000,
"border_spacing": 4,
"fg_color": "transparent",
"button_color": ["gray55", "gray41"],
"button_hover_color": ["gray40", "gray53"]
},
"CTkSegmentedButton": {
"corner_radius": 6,
"border_width": 2,
"fg_color": ["#979DA2", "gray29"],
"selected_color": ["#3a7ebf", "#1f538d"],
"selected_hover_color": ["#325882", "#14375e"],
"unselected_color": ["#979DA2", "gray29"],
"unselected_hover_color": ["gray70", "gray41"],
"text_color": ["#DCE4EE", "#DCE4EE"],
"text_color_disabled": ["gray74", "gray60"]
},
"CTkTextbox": {
"corner_radius": 6,
"border_width": 0,
"fg_color": ["gray100", "gray20"],
"border_color": ["#979DA2", "#565B5E"],
"text_color": ["gray14", "gray84"],
"scrollbar_button_color": ["gray55", "gray41"],
"scrollbar_button_hover_color": ["gray40", "gray53"]
},
"CTkScrollableFrame": {
"label_fg_color": ["gray80", "gray21"]
},
"DropdownMenu": {
"fg_color": ["gray90", "gray20"],
"hover_color": ["gray75", "gray28"],
"text_color": ["gray14", "gray84"]
},
"CTkFont": {
"macOS": {
"family": "SF Display",
"size": 13,
"weight": "normal"
},
"Windows": {
"family": "Roboto",
"size": 13,
"weight": "normal"
},
"Linux": {
"family": "Roboto",
"size": 13,
"weight": "normal"
}
}
}

View File

@ -0,0 +1,161 @@
{
"BOIIIWD_Globals": {
"button_active_state_color": "#351400",
"button_normal_state_color": "#d35600",
"progress_bar_fill_color": "#d35600",
"this_is_a_comment": "For button hover color check CTkButton bellow + other stuff"
},
"CTk": {
"fg_color": ["gray95", "gray10"]
},
"CTkToplevel": {
"fg_color": ["gray95", "gray10"]
},
"CTkFrame": {
"corner_radius": 6,
"border_width": 0,
"fg_color": ["gray90", "gray13"],
"top_fg_color": ["gray85", "gray16"],
"border_color": ["gray65", "gray28"]
},
"CTkButton": {
"corner_radius": 6,
"border_width": 0,
"fg_color": ["#d35600", "#d35600"],
"hover_color": ["#863600", "#863600"],
"border_color": ["#3E454A", "#949A9F"],
"text_color": ["#fcfcfc", "#fcfcfc"],
"text_color_disabled": ["gray74", "gray60"]
},
"CTkLabel": {
"corner_radius": 0,
"fg_color": "transparent",
"text_color": ["gray14", "gray84"]
},
"CTkEntry": {
"corner_radius": 6,
"border_width": 2,
"fg_color": ["#F9F9FA", "#343638"],
"border_color": ["#979DA2", "#565B5E"],
"text_color": ["gray14", "gray84"],
"placeholder_text_color": ["gray52", "gray62"]
},
"CTkCheckBox": {
"corner_radius": 6,
"border_width": 3,
"fg_color": ["#3a3a3a", "#3a3a3a"],
"hover_color": ["#737373", "#737373"],
"border_color": ["#3E454A", "#949A9F"],
"checkmark_color": ["#DCE4EE", "gray90"],
"text_color": ["gray14", "gray84"],
"text_color_disabled": ["gray60", "gray45"]
},
"CTkSwitch": {
"corner_radius": 1000,
"border_width": 3,
"button_length": 0,
"fg_color": ["#3a3a3a", "#3a3a3a"],
"progress_color": ["#d35600", "#d35600"],
"button_color": ["gray36", "#D5D9DE"],
"button_hover_color": ["gray20", "gray100"],
"text_color": ["gray14", "gray84"],
"text_color_disabled": ["gray60", "gray45"]
},
"CTkRadioButton": {
"corner_radius": 1000,
"border_width_checked": 6,
"border_width_unchecked": 3,
"fg_color": ["#3a3a3a", "#3a3a3a"],
"border_color": ["#3E454A", "#949A9F"],
"hover_color": ["#325882", "#14375e"],
"text_color": ["gray14", "gray84"],
"text_color_disabled": ["gray60", "gray45"]
},
"CTkProgressBar": {
"corner_radius": 1000,
"border_width": 0,
"fg_color": ["#3a3a3a", "#3a3a3a"],
"progress_color": ["#3a7ebf", "#1f538d"],
"border_color": ["gray", "gray"]
},
"CTkSlider": {
"corner_radius": 1000,
"button_corner_radius": 1000,
"border_width": 6,
"button_length": 0,
"fg_color": ["#939BA2", "#4A4D50"],
"progress_color": ["gray40", "#AAB0B5"],
"button_color": ["#3a7ebf", "#1f538d"],
"button_hover_color": ["#325882", "#14375e"]
},
"CTkOptionMenu": {
"corner_radius": 6,
"fg_color": ["#3a3a3a", "#3a3a3a"],
"button_color": ["#d35600", "#d35600"],
"button_hover_color": ["#343638", "#343638"],
"text_color": ["#DCE4EE", "#DCE4EE"],
"text_color_disabled": ["gray74", "gray60"]
},
"CTkComboBox": {
"corner_radius": 6,
"border_width": 2,
"fg_color": ["#F9F9FA", "#343638"],
"border_color": ["#979DA2", "#565B5E"],
"button_color": ["#979DA2", "#565B5E"],
"button_hover_color": ["#6E7174", "#7A848D"],
"text_color": ["gray14", "gray84"],
"text_color_disabled": ["gray50", "gray45"]
},
"CTkScrollbar": {
"corner_radius": 1000,
"border_spacing": 4,
"fg_color": "transparent",
"button_color": ["gray55", "gray41"],
"button_hover_color": ["gray40", "gray53"]
},
"CTkSegmentedButton": {
"corner_radius": 6,
"border_width": 2,
"fg_color": ["#979DA2", "gray29"],
"selected_color": ["#3a7ebf", "#1f538d"],
"selected_hover_color": ["#325882", "#14375e"],
"unselected_color": ["#979DA2", "gray29"],
"unselected_hover_color": ["gray70", "gray41"],
"text_color": ["#DCE4EE", "#DCE4EE"],
"text_color_disabled": ["gray74", "gray60"]
},
"CTkTextbox": {
"corner_radius": 6,
"border_width": 0,
"fg_color": ["gray100", "gray20"],
"border_color": ["#979DA2", "#565B5E"],
"text_color": ["gray14", "gray84"],
"scrollbar_button_color": ["gray55", "gray41"],
"scrollbar_button_hover_color": ["gray40", "gray53"]
},
"CTkScrollableFrame": {
"label_fg_color": ["gray80", "gray21"]
},
"DropdownMenu": {
"fg_color": ["gray90", "gray20"],
"hover_color": ["gray75", "gray28"],
"text_color": ["gray14", "gray84"]
},
"CTkFont": {
"macOS": {
"family": "SF Display",
"size": 13,
"weight": "normal"
},
"Windows": {
"family": "Roboto",
"size": 13,
"weight": "normal"
},
"Linux": {
"family": "Roboto",
"size": 13,
"weight": "normal"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,388 @@
from src.imports import *
from src.helpers import show_message, cwd, check_config,\
save_config, reset_steamcmd, launch_boiii_func, get_latest_release_version
def check_for_updates_func(window, ignore_up_todate=False):
try:
latest_version = get_latest_release_version()
current_version = VERSION
int_latest_version = int(latest_version.replace("v", "").replace(".", ""))
int_current_version = int(current_version.replace("v", "").replace(".", ""))
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))
result = msg_box.get()
if result == "View":
webbrowser.open(f"https://github.com/{GITHUB_REPO}/releases/latest")
from src.update_window import UpdateWindow
if result == "Yes":
update_window = UpdateWindow(window, LATEST_RELEASE_URL)
update_window.start_update()
if result == "No":
return
elif int_latest_version < int_current_version:
if ignore_up_todate:
return
msg_box = CTkMessagebox(title="Up to Date!", message=f"Unreleased version!\nCurrent Version: {current_version}\nLatest Version: {latest_version}", option_1="Ok")
result = msg_box.get()
elif int_latest_version == int_current_version:
if ignore_up_todate:
return
msg_box = CTkMessagebox(title="Up to Date!", message="No Updates Available!", option_1="Ok")
result = msg_box.get()
else:
show_message("Error!", "An error occured while checking for updates!\nCheck your internet and try again")
except Exception as e:
show_message("Error", f"Error while checking for updates: \n{e}", icon="cancel")
class SettingsTab(ctk.CTkFrame):
def __init__(self, master=None):
super().__init__(master)
# settings default bools
self.skip_already_installed = True
self.stopped = False
self.console = False
self.clean_on_finish = True
self.continuous = True
self.estimated_progress = True
self.steam_fail_counter_toggle = True
self.steam_fail_counter = 0
self.steam_fail_number = 10
self.steamcmd_reset = False
self.show_fails = True
# Left and right frames, use fg_color="transparent"
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(1, weight=1)
self.grid_columnconfigure(0, weight=1)
left_frame = ctk.CTkFrame(self)
left_frame.grid(row=0, column=0, padx=(20, 20), pady=(20, 0), sticky="nsew")
left_frame.grid_columnconfigure(1, weight=1)
right_frame = ctk.CTkFrame(self)
right_frame.grid(row=0, column=1, padx=(20, 20), pady=(20, 0), sticky="nsew")
right_frame.grid_columnconfigure(1, weight=1)
self.update_idletasks()
# Check for updates checkbox
self.check_updates_var = ctk.BooleanVar()
self.check_updates_var.trace_add("write", self.enable_save_button)
self.check_updates_checkbox = ctk.CTkSwitch(left_frame, text="Check for updates on launch", variable=self.check_updates_var)
self.check_updates_checkbox.grid(row=0, column=1, padx=20 , pady=(20, 0), sticky="nw")
self.check_updates_var.set(self.load_settings("checkforupdates"))
# Show console checkbox
self.console_var = ctk.BooleanVar()
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.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.console_var.set(self.load_settings("console"))
# Show continuous checkbox
self.continuous_var = ctk.BooleanVar()
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.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.continuous_var.set(self.load_settings("continuous_download"))
# clean on finish checkbox
self.clean_checkbox_var = ctk.BooleanVar()
self.clean_checkbox_var.trace_add("write", self.enable_save_button)
self.clean_checkbox = ctk.CTkSwitch(left_frame, text="Clean on finish", variable=self.clean_checkbox_var)
self.clean_checkbox.grid(row=3, column=1, padx=20, pady=(20, 0), sticky="nw")
self.clean_checkbox_tooltip = CTkToolTip(self.clean_checkbox, message="Cleans the map that have been downloaded and installed from steamcmd's steamapps folder ,to save space")
self.clean_checkbox_var.set(self.load_settings("clean_on_finish", "on"))
# Show estimated_progress checkbox
self.estimated_progress_var = ctk.BooleanVar()
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.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\
\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"))
# Show show fails checkbox
self.show_fails_var = ctk.BooleanVar()
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.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.estimated_progress_var.set(self.load_settings("show_fails", "on"))
# Show skip_already_installed maps checkbox
self.skip_already_installed_var = ctk.BooleanVar()
self.skip_already_installed_var.trace_add("write", self.enable_save_button)
self.skip_already_installed_ch = ctk.CTkSwitch(left_frame, text="Skip already installed maps:", variable=self.skip_already_installed_var)
self.skip_already_installed_ch.grid(row=6, column=1, padx=20, pady=(20, 0), sticky="nw")
self.skip_already_installed_ch_tooltip = CTkToolTip(self.skip_already_installed_ch, message="If on it will not download installed maps,\nthis can miss sometimes if you remove maps manually and not from library tab while the app is running")
self.skip_already_installed_var.set(self.load_settings("skip_already_installed", "on"))
# Resetr steam on many fails
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_text = ctk.CTkLabel(left_frame, text=f"Reset steamcmd on % fails: (n of fails)", anchor="w")
self.reset_steamcmd_on_fail_text.grid(row=7, column=1, padx=20, pady=(10, 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.grid(row=8, 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.set(value=self.load_settings("reset_on_fail", "10"))
# Check for updates button n Launch boiii
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_boiii = ctk.CTkButton(right_frame, text="Launch boiii", command=self.settings_launch_boiii)
self.launch_boiii.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 or not so use at ur own risk (could fix some issues as well)")
# appearance
self.appearance_mode_label = ctk.CTkLabel(right_frame, text="Appearance Mode:", anchor="n")
self.appearance_mode_label.grid(row=4, 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=5, column=1, padx=20, pady=(0, 0))
self.scaling_label = ctk.CTkLabel(right_frame, text="UI Scaling:", anchor="n")
self.scaling_label.grid(row=6, 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=7, column=1, padx=20, pady=(0, 0))
# self.custom_theme = ctk.CTkButton(right_frame, text="Custom theme", command=self.boiiiwd_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=8, column=1, padx=20, pady=(10, 0))
self.theme_options = ctk.CTkOptionMenu(right_frame, values=["Default", "Blue", "Grey", "Custom"],
command=self.theme_options_func)
self.theme_options.grid(row=9, 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
self.version_info = ctk.CTkLabel(self, text=f"{VERSION}")
self.version_info.grid(row=3, column=1, padx=20, pady=(20, 20), sticky="e")
def reset_steamcmd_on_fail_func(self, option: str):
if option == "Custom":
try:
save_config("reset_on_fail", "10")
if show_message("config.ini" ,"change reset_on_fail value to whatever you want", exit_on_close=True):
os.system(f"notepad {os.path.join(cwd(), 'config.ini')}")
except:
show_message("Couldn't open config.ini" ,"you can do so by yourself and change reset_on_fail value to whatever you want")
else:
pass
def theme_options_func(self, option: str):
if option == "Default":
self.boiiiwd_custom_theme(disable_only=True)
save_config("theme", "boiiiwd_theme.json")
if option == "Blue":
self.boiiiwd_custom_theme(disable_only=True)
save_config("theme", "boiiiwd_blue.json")
if option == "Grey":
self.boiiiwd_custom_theme(disable_only=True)
save_config("theme", "boiiiwd_grey.json")
if option == "Custom":
self.boiiiwd_custom_theme()
save_config("theme", "boiiiwd_theme.json")
if not option == "Custom":
show_message("Restart to take effect!", f"{option} theme has been set ,please restart to take effect", icon="info")
def enable_save_button(self, *args):
try:
self.save_button.configure(state='normal')
except:
pass
def save_settings(self):
self.save_button.configure(state='disabled')
if self.check_updates_checkbox.get():
save_config("checkforupdtes", "on")
else:
save_config("checkforupdtes", "off")
if self.checkbox_show_console.get():
save_config("console", "on")
self.console = True
else:
save_config("console", "off")
self.console = False
if self.skip_already_installed_ch.get():
save_config("skip_already_installed", "on")
self.skip_already_installed = True
else:
save_config("skip_already_installed", "off")
self.skip_already_installed = False
if self.clean_checkbox.get():
save_config("clean_on_finish", "on")
self.clean_on_finish = True
else:
save_config("clean_on_finish", "off")
self.clean_on_finish = False
if self.checkbox_continuous.get():
save_config("continuous_download", "on")
self.continuous = True
else:
save_config("continuous_download", "off")
self.continuous = False
if self.estimated_progress_cb.get():
save_config("estimated_progress", "on")
self.estimated_progress = True
else:
save_config("estimated_progress", "off")
self.estimated_progress = False
if self.show_fails_cb.get():
save_config("show_fails", "on")
self.show_fails = True
else:
save_config("show_fails", "off")
self.show_fails = False
if self.reset_steamcmd_on_fail.get():
value = self.reset_steamcmd_on_fail.get()
if value == "Disable":
self.steam_fail_counter_toggle = False
else:
self.steam_fail_counter_toggle = True
self.steam_fail_number = int(value)
save_config("reset_on_fail", value)
def load_settings(self, setting, fallback=None):
if setting == "console":
if check_config(setting, fallback) == "on":
self.console = True
return 1
else:
self.console = False
return 0
if setting == "continuous_download":
if check_config(setting, "on") == "on":
self.continuous = True
return 1
else:
self.continuous = False
return 0
if setting == "clean_on_finish":
if check_config(setting, fallback) == "on":
self.clean_on_finish = True
return 1
else:
self.clean_on_finish = False
return 0
if setting == "estimated_progress":
if check_config(setting, fallback) == "on":
self.estimated_progress = True
return 1
else:
self.estimated_progress = False
return 0
if setting == "reset_on_fail":
option = check_config(setting, fallback)
if option == "Disable" or option == "Custom":
self.steam_fail_counter_toggle = False
return "Disable"
else:
try:
self.steam_fail_number = int(option)
self.steam_fail_counter_toggle = True
return option
except:
self.steam_fail_counter_toggle = True
self.steam_fail_number = 10
return "10"
if setting == "show_fails":
if check_config(setting, fallback) == "on":
self.show_fails = True
return 1
else:
self.show_fails = False
return 0
if setting == "skip_already_installed":
if check_config(setting, fallback) == "on":
self.skip_already_installed = True
return 1
else:
self.skip_already_installed = False
return 0
if setting == "theme":
if os.path.exists(os.path.join(cwd(), "boiiiwd_theme.json")):
return "Custom"
if check_config("theme", "boiiiwd_theme.json") == "boiiiwd_theme.json":
return "Default"
if check_config("theme", "boiiiwd_theme.json") == "boiiiwd_grey.json":
return "Grey"
if check_config("theme", "boiiiwd_theme.json") == "boiiiwd_blue.json":
return "Blue"
else:
if check_config(setting, fallback) == "on":
return 1
else:
return 0
def boiiiwd_custom_theme(self, disable_only=None):
file_to_rename = os.path.join(cwd(), "boiiiwd_theme.json")
if os.path.exists(file_to_rename):
timestamp = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
new_name = f"boiiiwd_theme_{timestamp}.json"
os.rename(file_to_rename, os.path.join(cwd(), new_name))
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")
else:
if disable_only:
return
try:
shutil.copy(os.path.join(RESOURCES_DIR, check_config("theme", "boiiiwd_theme.json")), os.path.join(cwd(), "boiiiwd_theme.json"))
except:
shutil.copy(os.path.join(RESOURCES_DIR, "boiiiwd_theme.json"), os.path.join(cwd(), "boiiiwd_theme.json"))
show_message("Preset file created", "You can now edit boiiiwd_theme.json in the current directory to your liking\n* Edits will apply next time you open boiiiwd\n* Program will always take boiiiwd_theme.json as the first theme option if found\n* Click on this button again to disable your custom theme or just rename boiiiwd_theme.json", icon="info")
def settings_check_for_updates(self):
check_for_updates_func(self, ignore_up_todate=False)
def load_on_switch_screen(self):
self.check_updates_var.set(self.load_settings("checkforupdtes"))
self.console_var.set(self.load_settings("console"))
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.clean_checkbox_var.set(self.load_settings("clean_on_finish", "on"))
self.continuous_var.set(self.load_settings("continuous_download"))
self.show_fails_var.set(self.load_settings("show_fails", "on"))
self.skip_already_installed_var.set(self.load_settings("skip_already_installed", "on"))
# keep last cuz of trace_add()
self.save_button.configure(state='disabled')
def settings_launch_boiii(self):
launch_boiii_func(check_config("destinationfolder"))
def settings_reset_steamcmd(self):
reset_steamcmd()

View File

@ -0,0 +1,107 @@
from src.imports import *
from src.helpers import show_message, check_config, check_custom_theme,\
get_button_state_colors, convert_bytes_to_readable, create_update_script
class UpdateWindow(ctk.CTkToplevel):
def __init__(self, master, update_url):
global master_win
master_win = master
super().__init__(master)
self.title("BOIIIWD Self-Updater")
self.geometry("400x150")
if os.path.exists(os.path.join(RESOURCES_DIR, "ryuk.ico")):
self.after(250, lambda: self.iconbitmap(os.path.join(RESOURCES_DIR, "ryuk.ico")))
self.protocol("WM_DELETE_WINDOW", self.cancel_update)
self.attributes('-topmost', 'true')
self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=1)
self.rowconfigure(0, weight=1)
self.rowconfigure(1, weight=1)
self.label_download = ctk.CTkLabel(self, text="Starting...")
self.label_download.grid(row=0, column=0, padx=30, pady=(10, 0), sticky="w")
self.label_size = ctk.CTkLabel(self, text="Size: 0")
self.label_size.grid(row=0, column=1, padx=30, pady=(10, 0), sticky="e")
self.progress_color = get_button_state_colors(check_custom_theme(check_config("theme", fallback="boiiiwd_theme.json")), "progress_bar_fill_color")
self.progress_bar = ctk.CTkProgressBar(self, mode="determinate", height=20, corner_radius=7, progress_color=self.progress_color)
self.progress_bar.grid(row=1, column=0, columnspan=4, padx=30, pady=10, sticky="ew")
self.progress_bar.set(0)
self.progress_label = ctk.CTkLabel(self.progress_bar, text="0%", font=("Helvetica", 12), fg_color="transparent", height=0, width=0, corner_radius=0)
self.progress_label.place(relx=0.5, rely=0.5, anchor="center")
self.cancel_button = ctk.CTkButton(self, text="Cancel", command=self.cancel_update)
self.cancel_button.grid(row=2, column=0, padx=30, pady=(0, 10), sticky="w")
self.update_url = update_url
self.total_size = None
self.up_cancelled = False
def update_progress_bar(self):
try:
update_dir = os.path.join(os.getcwd(), UPDATER_FOLDER)
response = requests.get(LATEST_RELEASE_URL, stream=True)
response.raise_for_status()
current_exe = sys.argv[0]
program_name = os.path.basename(current_exe)
new_exe = os.path.join(update_dir, "BOIIIWD.exe")
if not os.path.exists(update_dir):
os.makedirs(update_dir)
self.progress_bar.set(0.0)
self.total_size = int(response.headers.get('content-length', 0))
self.label_size.configure(text=f"Size: {convert_bytes_to_readable(self.total_size)}")
zip_path = os.path.join(update_dir, "latest_version.zip")
with open(zip_path, "wb") as file:
downloaded_size = 0
for chunk in response.iter_content(chunk_size=8192):
if self.up_cancelled:
break
if chunk:
file.write(chunk)
downloaded_size += len(chunk)
progress = int((downloaded_size / self.total_size) * 100)
self.after(1, lambda p=progress: self.label_download.configure(text=f"Downloading update..."))
self.after(1, lambda v=progress / 100.0: self.progress_bar.set(v))
self.after(1, lambda p=progress: self.progress_label.configure(text=f"{p}%"))
if not self.up_cancelled:
self.progress_bar.set(1.0)
with zipfile.ZipFile(zip_path, "r") as zip_ref:
zip_ref.extractall(update_dir)
self.label_download.configure(text="Update Downloaded successfully!")
if not show_message("Success!", "Update Downloaded successfully!\nPress ok to install it", icon="info", exit_on_close=True):
return
script_path = create_update_script(current_exe, new_exe, update_dir, program_name)
subprocess.run(('cmd', '/C', 'start', '', fr'{script_path}'))
sys.exit(0)
else:
if os.path.exists(zip_path):
os.remove(fr"{zip_path}")
self.label_download.configure(text="Update cancelled.")
self.progress_bar.set(0.0)
# there's a better solution ill implement it later
global master_win
try:
master_win.attributes('-alpha', 1.0)
except:
pass
show_message("Cancelled!", "Update cancelled by user", icon="warning")
except Exception as e:
self.progress_bar.set(0.0)
self.label_download.configure(text="Update failed")
show_message("Error", f"Error installing the update\n{e}", icon="cancel")
def start_update(self):
self.thread = threading.Thread(target=self.update_progress_bar)
self.thread.start()
def cancel_update(self):
self.up_cancelled = True
self.withdraw()