diff --git a/steam_workshop_gui.py b/steam_workshop_gui.py
index 6635a37..7c10d11 100644
--- a/steam_workshop_gui.py
+++ b/steam_workshop_gui.py
@@ -1160,7 +1160,7 @@ class SteamWorkshopGUI:
self.log_to_output("🔍 Please provide a valid RimWorld installation path to continue.\n")
self.log_to_output(" The 'RimWorld Game Folder' label will stop blinking once a valid path is detected.\n\n")
- # Initialize update checker
+ # Initialize update checker (for manual checks only)
config = get_update_config()
self.update_checker = UpdateChecker(config["current_version"])
@@ -1173,10 +1173,6 @@ class SteamWorkshopGUI:
# Start blinking animation for RimWorld label (with slight delay to ensure GUI is ready)
self.root.after(1000, self.start_rimworld_label_blink)
- # Check for updates on startup if enabled
- if config["check_on_startup"]:
- self.root.after(config["startup_check_delay"], self.check_for_updates_on_startup)
-
def create_header_frame(self, parent):
"""Create header frame with progression logo"""
header_frame = tk.Frame(parent, bg='#2b2b2b', height=100)
@@ -2127,7 +2123,13 @@ class SteamWorkshopGUI:
def generate_rml_xml(self, mod_data):
"""Generate the XML content for the .rml file"""
# Get core mods from ModsConfig.xml and RimWorld Data folder
+ msg = "=== GENERATING RML XML ===\n"
+ self._safe_update_output(msg)
+ print(msg.strip())
core_mods = self.get_core_mods_from_config()
+ msg = f"Core mods returned: {core_mods}\n"
+ self._safe_update_output(msg)
+ print(msg.strip())
# Extract mod names from About.xml files for workshop mods
mod_names = []
@@ -2144,8 +2146,14 @@ class SteamWorkshopGUI:
]
# Add core mods first
+ msg = "Adding core mods to XML:\n"
+ self._safe_update_output(msg)
+ print(msg.strip())
for package_id, mod_name in core_mods:
xml_lines.append(f'\t\t\t
{package_id}')
+ msg = f" Added core mod: {package_id} ({mod_name})\n"
+ self._safe_update_output(msg)
+ print(msg.strip())
# Add workshop mod package IDs to meta section
for workshop_id, package_id in mod_data:
@@ -2214,14 +2222,20 @@ class SteamWorkshopGUI:
return '\n'.join(xml_lines)
def get_core_mods_from_config(self):
- """Get core mods from ModsConfig.xml knownExpansions section and their info from RimWorld Data folder"""
+ """Get core mods from ModsConfig.xml knownExpansions section - includes ALL DLC the user owns"""
core_mods = []
try:
if not self.modsconfig_path or not os.path.exists(self.modsconfig_path):
- self._safe_update_output("ModsConfig.xml not found, using default core mods\n")
+ msg = "ModsConfig.xml not found, using default core mods\n"
+ self._safe_update_output(msg)
+ print(msg.strip())
return [('ludeon.rimworld', 'RimWorld')]
+ msg = f"Reading ModsConfig.xml from: {self.modsconfig_path}\n"
+ self._safe_update_output(msg)
+ print(msg.strip())
+
# Parse ModsConfig.xml
tree = ET.parse(self.modsconfig_path)
root = tree.getroot()
@@ -2231,31 +2245,122 @@ class SteamWorkshopGUI:
if self.rimworld_var.get().strip():
rimworld_game_path = self.rimworld_var.get().strip()
rimworld_data_path = os.path.join(rimworld_game_path, "Data")
+ msg = f"RimWorld Data path: {rimworld_data_path}\n"
+ self._safe_update_output(msg)
+ print(msg.strip())
+ else:
+ msg = "No RimWorld path set, using fallback names\n"
+ self._safe_update_output(msg)
+ print(msg.strip())
- # Find knownExpansions section
+ # Find knownExpansions section and include ALL expansions found
known_expansions_element = root.find('knownExpansions')
if known_expansions_element is not None:
+ msg = "Found knownExpansions section, processing all DLC...\n"
+ self._safe_update_output(msg)
+ print(msg.strip())
for li in known_expansions_element.findall('li'):
expansion_id = li.text
if expansion_id:
+ expansion_id = expansion_id.strip()
+ msg = f"Processing DLC: {expansion_id}\n"
+ self._safe_update_output(msg)
+ print(msg.strip())
# Use the same method as DLC detection to get real names
- mod_name = self.get_expansion_real_name(expansion_id.strip(), rimworld_data_path)
+ mod_name = self.get_expansion_real_name(expansion_id, rimworld_data_path)
+ msg = f"get_expansion_real_name returned: {mod_name}\n"
+ self._safe_update_output(msg)
+ print(msg.strip())
if not mod_name:
- mod_name = self.get_default_mod_name(expansion_id.strip())
- core_mods.append((expansion_id.strip(), mod_name))
+ mod_name = self.get_default_mod_name(expansion_id)
+ msg = f"Using fallback name: {mod_name}\n"
+ self._safe_update_output(msg)
+ print(msg.strip())
+ core_mods.append((expansion_id, mod_name))
+ msg = f"Added to core_mods: {expansion_id} -> {mod_name}\n"
+ self._safe_update_output(msg)
+ print(msg.strip())
+ else:
+ msg = "No knownExpansions section found in ModsConfig.xml\n"
+ self._safe_update_output(msg)
+ print(msg.strip())
if not core_mods:
- # Fallback if no expansions found
+ # Fallback if no expansions found - just base game
core_mods = [('ludeon.rimworld', 'RimWorld')]
+ msg = "No expansions found, using fallback base game only\n"
+ self._safe_update_output(msg)
+ print(msg.strip())
- self._safe_update_output(f"Found {len(core_mods)} core expansions from ModsConfig.xml\n")
+ msg = f"Final core_mods list: {core_mods}\n"
+ self._safe_update_output(msg)
+ print(msg.strip())
+ msg = f"Found {len(core_mods)} core expansions from knownExpansions in ModsConfig.xml\n"
+ self._safe_update_output(msg)
+ print(msg.strip())
except Exception as e:
- self._safe_update_output(f"Error reading core mods: {str(e)}\n")
+ msg = f"Error reading core mods: {str(e)}\n"
+ self._safe_update_output(msg)
+ print(msg.strip())
core_mods = [('ludeon.rimworld', 'RimWorld')]
return core_mods
+ def get_expansion_real_name(self, expansion_id, rimworld_data_path):
+ """Get the real expansion name from RimWorld Data folder About.xml"""
+ try:
+ if not rimworld_data_path or not os.path.exists(rimworld_data_path):
+ # Fallback to extracting from ID if no data path
+ return self.extract_name_from_id(expansion_id)
+
+ # Look for expansion folder in Data directory
+ # Core expansions are usually in subfolders like Royalty, Ideology, etc.
+ possible_folders = [
+ expansion_id.replace('ludeon.rimworld.', '').capitalize(),
+ expansion_id.split('.')[-1].capitalize() if '.' in expansion_id else expansion_id,
+ expansion_id.replace('ludeon.rimworld.', ''),
+ expansion_id.replace('ludeon.rimworld', 'Core')
+ ]
+
+ for folder_name in possible_folders:
+ about_xml_path = os.path.join(rimworld_data_path, folder_name, "About", "About.xml")
+ if os.path.exists(about_xml_path):
+ try:
+ tree = ET.parse(about_xml_path)
+ root = tree.getroot()
+
+ name_element = root.find('name')
+ if name_element is not None and name_element.text:
+ return name_element.text.strip()
+ except ET.ParseError:
+ continue
+
+ # If not found in Data folder, use fallback
+ return self.extract_name_from_id(expansion_id)
+
+ except Exception as e:
+ return self.extract_name_from_id(expansion_id)
+
+ def extract_name_from_id(self, expansion_id):
+ """Extract expansion name from ID as fallback"""
+ if 'royalty' in expansion_id.lower():
+ return 'Royalty'
+ elif 'ideology' in expansion_id.lower():
+ return 'Ideology'
+ elif 'biotech' in expansion_id.lower():
+ return 'Biotech'
+ elif 'anomaly' in expansion_id.lower():
+ return 'Anomaly'
+ elif 'odyssey' in expansion_id.lower():
+ return 'Odyssey'
+ else:
+ # Extract the last part after the last dot and capitalize
+ parts = expansion_id.split('.')
+ if len(parts) > 1:
+ return parts[-1].capitalize()
+ return expansion_id
+
def get_default_mod_name(self, mod_id):
"""Get default display name for core mods"""
default_names = {
@@ -2603,20 +2708,6 @@ you must auto sort after this!"""
except Exception as e:
print(f"Could not add update button: {e}")
- def check_for_updates_on_startup(self):
- """Check for updates when the application starts"""
- def update_callback(release_info, error):
- if error or not release_info:
- return # Silently fail on startup
-
- latest_version = release_info['version']
- if (self.update_checker.is_newer_version(latest_version) and
- not self.update_checker.should_skip_version(latest_version)):
- # Show update dialog
- self.update_checker.show_update_dialog(self.root, release_info)
-
- self.update_checker.check_for_updates_async(update_callback)
-
def manual_update_check(self):
"""Manually triggered update check"""
self.update_checker.manual_check_for_updates(self.root)
@@ -2625,6 +2716,9 @@ def main():
root = tk.Tk()
root.withdraw() # Hide main window initially
+ # Import config
+ from update_config import get_update_config
+
# Set window icon
try:
icon_path = get_resource_path(os.path.join("art", "Progression.ico"))
@@ -2638,10 +2732,206 @@ def main():
root.deiconify() # Show main window
app = SteamWorkshopGUI(root)
- # Show loading screen first
- loading_screen = LoadingScreen(root, show_main_app)
+ # Check for updates SYNCHRONOUSLY before starting anything
+ config = get_update_config()
+ update_info = check_for_updates_before_startup(root)
+
+ if update_info and config.get("updates_required", True):
+ # Show blocking update dialog - app won't continue until user updates
+ show_blocking_update_dialog(root, update_info['checker'], update_info['release'])
+ else:
+ # No update needed or updates not required, start loading screen
+ loading_screen = LoadingScreen(root, show_main_app)
+
+ # If update available but not required, show non-blocking dialog
+ if update_info:
+ root.after(1000, lambda: show_optional_update_dialog(root, update_info['checker'], update_info['release']))
root.mainloop()
+def check_for_updates_before_startup(root):
+ """Check for updates synchronously before starting the application"""
+ try:
+ from update_checker import UpdateChecker
+ from update_config import get_update_config
+
+ print("Checking for updates...")
+
+ config = get_update_config()
+ update_checker = UpdateChecker(config["current_version"])
+
+ # Use synchronous check to block until complete
+ release_info, error = update_checker.check_for_updates_sync()
+
+ if error:
+ print(f"Update check failed: {error}")
+ return None
+ elif release_info:
+ latest_version = release_info['version']
+ if update_checker.is_newer_version(latest_version):
+ print(f"Update required: {latest_version}")
+ return {
+ 'checker': update_checker,
+ 'release': release_info
+ }
+ else:
+ print(f"Already up to date: {latest_version}")
+ else:
+ print("No release info available")
+
+ return None
+
+ except Exception as e:
+ print(f"Update check failed: {e}")
+ return None
+
+def show_blocking_update_dialog(root, update_checker, release_info):
+ """Show a blocking update dialog that prevents app from continuing"""
+ # Create a new window for the update dialog
+ update_window = tk.Toplevel(root)
+ update_window.title("Update Required - Progression Loader")
+ update_window.configure(bg='#2b2b2b')
+ update_window.resizable(False, False)
+ update_window.attributes('-topmost', True)
+
+ # Make it modal - user can't interact with other windows
+ update_window.transient(root)
+ update_window.grab_set()
+
+ # Set window icon
+ try:
+ icon_path = get_resource_path(os.path.join("art", "Progression.ico"))
+ if os.path.exists(icon_path):
+ update_window.iconbitmap(icon_path)
+ except:
+ pass
+
+ # Calculate window size and center it
+ window_width = 600
+ window_height = 500
+
+ # Get screen dimensions
+ screen_width = update_window.winfo_screenwidth()
+ screen_height = update_window.winfo_screenheight()
+
+ # Calculate center position
+ x = (screen_width - window_width) // 2
+ y = (screen_height - window_height) // 2
+
+ update_window.geometry(f"{window_width}x{window_height}+{x}+{y}")
+
+ # Title
+ title_label = tk.Label(update_window,
+ text="🔄 Update Required",
+ fg='#ff6b6b', bg='#2b2b2b',
+ font=('Arial', 20, 'bold'))
+ title_label.pack(pady=30)
+
+ # Message
+ message_label = tk.Label(update_window,
+ text="A new version is available and required to continue.",
+ fg='white', bg='#2b2b2b',
+ font=('Arial', 14))
+ message_label.pack(pady=10)
+
+ # Version info
+ version_frame = tk.Frame(update_window, bg='#2b2b2b')
+ version_frame.pack(pady=20)
+
+ current_label = tk.Label(version_frame,
+ text=f"Current Version: {update_checker.current_version}",
+ fg='#cccccc', bg='#2b2b2b',
+ font=('Arial', 12))
+ current_label.pack()
+
+ latest_label = tk.Label(version_frame,
+ text=f"Required Version: {release_info['version']}",
+ fg='#4ecdc4', bg='#2b2b2b',
+ font=('Arial', 12, 'bold'))
+ latest_label.pack(pady=5)
+
+ # Release notes
+ if release_info.get('body'):
+ notes_label = tk.Label(update_window,
+ text="What's New:",
+ fg='white', bg='#2b2b2b',
+ font=('Arial', 14, 'bold'))
+ notes_label.pack(pady=(30, 10))
+
+ # Create scrollable text widget for release notes
+ notes_frame = tk.Frame(update_window, bg='#2b2b2b')
+ notes_frame.pack(fill='both', expand=True, padx=40, pady=(0, 20))
+
+ notes_text = tk.Text(notes_frame,
+ height=8,
+ bg='#404040', fg='#ffffff',
+ font=('Arial', 11),
+ wrap=tk.WORD,
+ state='disabled',
+ relief='flat',
+ bd=0)
+
+ scrollbar = tk.Scrollbar(notes_frame, bg='#404040')
+ scrollbar.pack(side='right', fill='y')
+
+ notes_text.pack(side='left', fill='both', expand=True)
+ notes_text.config(yscrollcommand=scrollbar.set)
+ scrollbar.config(command=notes_text.yview)
+
+ # Insert release notes
+ notes_text.config(state='normal')
+ notes_text.insert('1.0', release_info['body'])
+ notes_text.config(state='disabled')
+
+ # Buttons
+ button_frame = tk.Frame(update_window, bg='#2b2b2b')
+ button_frame.pack(pady=30)
+
+ def download_and_exit():
+ """Open download page and exit the application"""
+ webbrowser.open(release_info['html_url'])
+ print("Please download and install the update, then restart the application.")
+ root.quit() # Exit the entire application
+
+ def exit_app():
+ """Exit the application without updating"""
+ print("Application cannot continue without updating.")
+ root.quit()
+
+ # Download Update button (primary action)
+ download_btn = tk.Button(button_frame,
+ text="Download Update & Exit",
+ command=download_and_exit,
+ bg='#4ecdc4', fg='white',
+ font=('Arial', 14, 'bold'),
+ padx=30, pady=10,
+ cursor='hand2')
+ download_btn.pack(side='left', padx=10)
+
+ # Exit button (secondary action)
+ exit_btn = tk.Button(button_frame,
+ text="Exit Application",
+ command=exit_app,
+ bg='#666666', fg='white',
+ font=('Arial', 12),
+ padx=20, pady=10,
+ cursor='hand2')
+ exit_btn.pack(side='left', padx=10)
+
+ # Handle window close (same as exit)
+ update_window.protocol("WM_DELETE_WINDOW", exit_app)
+
+ # Focus on the download button
+ download_btn.focus_set()
+
+ print("Blocking update dialog shown - app will not continue until user updates")
+
+def show_optional_update_dialog(root, update_checker, release_info):
+ """Show a non-blocking update dialog"""
+ try:
+ update_checker.show_update_dialog(root, release_info)
+ except Exception as e:
+ print(f"Could not show update dialog: {e}")
+
if __name__ == "__main__":
main()
diff --git a/update_config.py b/update_config.py
index 1f781c2..0e52a6b 100644
--- a/update_config.py
+++ b/update_config.py
@@ -5,7 +5,7 @@ Configuration settings for the update checker
# Update checker configuration
UPDATE_CONFIG = {
# Current version of the application
- "current_version": "0.0.2",
+ "current_version": "0.0.1",
# Repository information
"repo_owner": "HRiggs",
@@ -18,8 +18,8 @@ UPDATE_CONFIG = {
# Whether to check for updates on startup
"check_on_startup": True,
- # Delay before checking for updates on startup (in milliseconds)
- "startup_check_delay": 5000,
+ # Whether updates are required (blocks app if true)
+ "updates_required": True,
# Whether to include pre-releases in update checks
"include_prereleases": True,