@@ -316,18 +316,20 @@ def start_task(self):
316316 self .root .after (0 , lambda : self .stop_btn .config (state = "normal" ))
317317 self .task_count += 1
318318
319- def end_task (self ):
319+ def end_task (self , callback = None ):
320320 with self .task_lock :
321321 self .task_count -= 1
322322 if self .task_count <= 0 :
323323 self .task_count = 0
324324 self .root .after (0 , lambda : self .stop_btn .config (state = "disabled" ))
325325 self .root .after (0 , self .reset_progress )
326+ if callback :
327+ self .root .after (1000 , callback )
326328
327329 def stop_operation (self ):
328330 self .stop_event .set ()
329331 self .log ("Stopping operations..." , "warning" )
330- for p in self .active_processes :
332+ for p in list ( self .active_processes ) :
331333 try : p .terminate ()
332334 except : pass
333335
@@ -348,12 +350,18 @@ def start_download(self):
348350 self .progress .start (10 )
349351 self .progress_label .config (text = "INITIALIZING..." , fg = BZ_CYAN )
350352 self .start_task ()
351- threading .Thread (target = self .download_logic , args = (mid ,), daemon = True ).start ()
353+
354+ sc_path = self .steamcmd_var .get ()
355+ cache_path = self .cache_var .get ()
356+ game_path = self .path_var .get ()
357+ use_physical = self .use_physical_var .get ()
358+
359+ threading .Thread (target = self .download_logic , args = (mid , sc_path , cache_path , game_path , use_physical ), daemon = True ).start ()
352360
353- def download_logic (self , mod_id ):
361+ def download_logic (self , mod_id , sc_path , cache_path , game_path , use_physical ):
354362 try :
355- self .ensure_steamcmd ()
356- cache = os .path .abspath (self . cache_var . get () )
363+ final_sc_path = self .ensure_steamcmd (sc_path )
364+ cache = os .path .abspath (cache_path )
357365 mod_path = os .path .join (cache , "steamapps/workshop/content" , BZ98R_APPID , mod_id )
358366
359367 if os .path .exists (mod_path ):
@@ -362,7 +370,7 @@ def download_logic(self, mod_id):
362370 self .log (f"Initializing new download for Mod { mod_id } ..." )
363371
364372 # FIX: force_install_dir BEFORE login
365- cmd = [self . steamcmd_var . get () , "+force_install_dir" , cache , "+login" , "anonymous" ,
373+ cmd = [final_sc_path , "+force_install_dir" , cache , "+login" , "anonymous" ,
366374 "+workshop_download_item" , BZ98R_APPID , mod_id , "+quit" ]
367375
368376 p = subprocess .Popen (cmd , stdout = subprocess .PIPE , stderr = subprocess .STDOUT , text = True , creationflags = subprocess .CREATE_NO_WINDOW )
@@ -384,11 +392,11 @@ def download_logic(self, mod_id):
384392 if p in self .active_processes : self .active_processes .remove (p )
385393
386394 src = os .path .normpath (mod_path )
387- dst = os .path .normpath (os .path .join (self . path_var . get () , "mods" , mod_id ))
395+ dst = os .path .normpath (os .path .join (game_path , "mods" , mod_id ))
388396
389397 if os .path .exists (src ):
390398 if not os .path .exists (os .path .dirname (dst )): os .makedirs (os .path .dirname (dst ))
391- if self . use_physical_var . get () :
399+ if use_physical :
392400 if os .path .exists (dst ): shutil .rmtree (dst )
393401 shutil .copytree (src , dst )
394402 else :
@@ -397,11 +405,9 @@ def download_logic(self, mod_id):
397405 self .root .after (0 , lambda : self .dl_btn .config (text = "DEPLOYED" ))
398406 self .root .after (3000 , lambda : self .dl_btn .config (text = "INSTALL MOD" , state = "normal" ))
399407
400- if not self .stop_event .is_set ():
401- self .root .after (0 , self .refresh_list )
402408 except Exception as e : self .log (f"CRITICAL: { e } " , "error" )
403409 finally :
404- self .end_task ()
410+ self .end_task (self . refresh_list if not self . stop_event . is_set () else None )
405411
406412 def update_progress (self , value ):
407413 self .progress .stop ()
@@ -479,11 +485,10 @@ def initialize_engine(self):
479485
480486 self .log ("Ready for mod deployment." , "info" )
481487
482- def ensure_steamcmd (self ):
483- target = self .steamcmd_var .get ()
488+ def ensure_steamcmd (self , target ):
484489 if not target :
485490 target = os .path .join (self .bin_dir , "steamcmd.exe" )
486- self .steamcmd_var .set (target )
491+ self .root . after ( 0 , lambda : self . steamcmd_var .set (target ) )
487492
488493 if not os .path .exists (target ):
489494 target_dir = os .path .dirname (target )
@@ -498,6 +503,7 @@ def ensure_steamcmd(self):
498503 except Exception as e :
499504 self .log (f"SteamCMD Setup Error: { e } " , "error" )
500505 raise e
506+ return target
501507
502508 def check_admin (self ):
503509 if not ctypes .windll .shell32 .IsUserAnAdmin ():
@@ -722,17 +728,20 @@ def refresh_list(self):
722728 self .progress .config (mode = "indeterminate" ); self .progress .start (10 )
723729
724730 # Offload file system scanning to a background thread
731+ cache_path = self .cache_var .get ()
732+ game_path = self .path_var .get ()
733+
725734 self .start_task ()
726- threading .Thread (target = self ._refresh_scan_logic , daemon = True ).start ()
735+ threading .Thread (target = self ._refresh_scan_logic , args = ( cache_path , game_path ), daemon = True ).start ()
727736
728- def _refresh_scan_logic (self ):
737+ def _refresh_scan_logic (self , cache_path , game_path ):
729738 try :
730- base_cache = os .path .abspath (self . cache_var . get () )
731- game_path = os .path .abspath (self . path_var . get () )
739+ base_cache = os .path .abspath (cache_path )
740+ game_dir = os .path .abspath (game_path )
732741
733742 # Correct nested SteamCMD structure
734743 content_dir = os .path .join (base_cache , "steamapps" , "workshop" , "content" , BZ98R_APPID )
735- game_mods_dir = os .path .join (game_path , "mods" )
744+ game_mods_dir = os .path .join (game_dir , "mods" )
736745
737746 self .log ("--- SCANNING FOR ASSETS ---" , "info" )
738747
@@ -883,15 +892,18 @@ def enable_mod(self):
883892
884893 # Extract data on main thread
885894 mods_to_enable = [str (self .tree .item (item )['values' ][1 ]) for item in selected ]
895+ cache_path = self .cache_var .get ()
896+ game_path = self .path_var .get ()
897+
886898 self .start_task ()
887- threading .Thread (target = self ._enable_mod_worker , args = (mods_to_enable ,), daemon = True ).start ()
899+ threading .Thread (target = self ._enable_mod_worker , args = (mods_to_enable , cache_path , game_path ), daemon = True ).start ()
888900
889- def _enable_mod_worker (self , mods ):
901+ def _enable_mod_worker (self , mods , cache_path , game_path ):
890902 try :
891903 for mid in mods :
892904 if self .stop_event .is_set (): break
893- src = os .path .join (self . cache_var . get () , "steamapps" , "workshop" , "content" , BZ98R_APPID , mid )
894- dst = os .path .join (self . path_var . get () , "mods" , mid )
905+ src = os .path .join (cache_path , "steamapps" , "workshop" , "content" , BZ98R_APPID , mid )
906+ dst = os .path .join (game_path , "mods" , mid )
895907
896908 try :
897909 if not os .path .exists (os .path .dirname (dst )): os .makedirs (os .path .dirname (dst ))
@@ -901,25 +913,24 @@ def _enable_mod_worker(self, mods):
901913 self .log (f"Mod { mid } enabled (Junction created)." , "success" )
902914 except Exception as e :
903915 self .log (f"Link Error for { mid } : { e } " , "error" )
904- if not self .stop_event .is_set ():
905- self .root .after (0 , self .refresh_list )
906916 finally :
907- self .end_task ()
917+ self .end_task (self . refresh_list if not self . stop_event . is_set () else None )
908918
909919 def disable_mod (self ):
910920 """Disables all selected mods by removing their Junction links."""
911921 selected = self .tree .selection ()
912922 if not selected : return
913923
914924 mods_to_disable = [str (self .tree .item (item )['values' ][1 ]) for item in selected ]
925+ game_path = self .path_var .get ()
915926 self .start_task ()
916- threading .Thread (target = self ._disable_mod_worker , args = (mods_to_disable ,), daemon = True ).start ()
927+ threading .Thread (target = self ._disable_mod_worker , args = (mods_to_disable , game_path ), daemon = True ).start ()
917928
918- def _disable_mod_worker (self , mods ):
929+ def _disable_mod_worker (self , mods , game_path ):
919930 try :
920931 for mid in mods :
921932 if self .stop_event .is_set (): break
922- dst = os .path .join (self . path_var . get () , "mods" , mid )
933+ dst = os .path .join (game_path , "mods" , mid )
923934
924935 try :
925936 if os .path .lexists (dst ):
@@ -932,10 +943,8 @@ def _disable_mod_worker(self, mods):
932943 self .log (f"Mod { mid } decoupled from game engine." , "info" )
933944 except Exception as e :
934945 self .log (f"DECOUPLE ERROR for { mid } : { e } " , "error" )
935- if not self .stop_event .is_set ():
936- self .root .after (0 , self .refresh_list )
937946 finally :
938- self .end_task ()
947+ self .end_task (self . refresh_list if not self . stop_event . is_set () else None )
939948
940949 def is_junction (self , path ):
941950 """Helper to detect if a directory is a Windows Junction."""
@@ -957,9 +966,15 @@ def update_all_mods(self):
957966 return
958967
959968 self .log (f"Initializing batch update for { len (to_update )} mods..." , "info" )
969+
970+ sc_path = self .steamcmd_var .get ()
971+ cache_path = self .cache_var .get ()
972+ game_path = self .path_var .get ()
973+ use_physical = self .use_physical_var .get ()
974+
960975 for mid in to_update :
961976 self .start_task ()
962- threading .Thread (target = self .download_logic , args = (mid ,), daemon = True ).start ()
977+ threading .Thread (target = self .download_logic , args = (mid , sc_path , cache_path , game_path , use_physical ), daemon = True ).start ()
963978
964979 def delete_mod_physically (self ):
965980 """Wipes the selected mods from the SteamCMD cache and breaks any links."""
@@ -975,15 +990,17 @@ def delete_mod_physically(self):
975990
976991 if messagebox .askyesno ("TERMINATE ASSET(S)" , prompt_message ):
977992 mods_to_delete = [str (self .tree .item (item )['values' ][1 ]) for item in selected ]
993+ cache_path = self .cache_var .get ()
994+ game_path = self .path_var .get ()
978995 self .start_task ()
979- threading .Thread (target = self ._delete_mod_worker , args = (mods_to_delete ,), daemon = True ).start ()
996+ threading .Thread (target = self ._delete_mod_worker , args = (mods_to_delete , cache_path , game_path ), daemon = True ).start ()
980997
981- def _delete_mod_worker (self , mods ):
998+ def _delete_mod_worker (self , mods , cache_path , game_path ):
982999 try :
9831000 for mid in mods :
9841001 if self .stop_event .is_set (): break
9851002 # 1. Break Link
986- link_path = os .path .join (self . path_var . get () , "mods" , mid )
1003+ link_path = os .path .join (game_path , "mods" , mid )
9871004 if os .path .lexists (link_path ):
9881005 try :
9891006 if os .path .isdir (link_path ):
@@ -994,18 +1011,16 @@ def _delete_mod_worker(self, mods):
9941011 self .log (f"Note: Could not remove link for { mid } during purge: { e } " , "warning" )
9951012
9961013 # 2. Delete Folder from cache
997- cache_path = os .path .join (self . cache_var . get () , "steamapps/workshop/content" , BZ98R_APPID , mid )
1014+ mod_cache_path = os .path .join (cache_path , "steamapps/workshop/content" , BZ98R_APPID , mid )
9981015 try :
999- if os .path .exists (cache_path ):
1000- shutil .rmtree (cache_path )
1016+ if os .path .exists (mod_cache_path ):
1017+ shutil .rmtree (mod_cache_path )
10011018 self .log (f"Asset { mid } purged from local storage." , "warning" )
10021019 except Exception as e :
10031020 self .log (f"Purge Error for { mid } : { e } " , "error" )
10041021
1005- if not self .stop_event .is_set ():
1006- self .root .after (0 , self .refresh_list )
10071022 finally :
1008- self .end_task ()
1023+ self .end_task (self . refresh_list if not self . stop_event . is_set () else None )
10091024
10101025 def update_selected_mod (self , force = False ):
10111026 """Triggers a re-download via SteamCMD for the selected mods."""
@@ -1019,8 +1034,14 @@ def update_selected_mod(self, force=False):
10191034 continue
10201035
10211036 self .log (f"Updating mod { mid } ..." , "info" )
1037+
1038+ sc_path = self .steamcmd_var .get ()
1039+ cache_path = self .cache_var .get ()
1040+ game_path = self .path_var .get ()
1041+ use_physical = self .use_physical_var .get ()
1042+
10221043 self .start_task ()
1023- threading .Thread (target = self .download_logic , args = (mid ,), daemon = True ).start ()
1044+ threading .Thread (target = self .download_logic , args = (mid , sc_path , cache_path , game_path , use_physical ), daemon = True ).start ()
10241045if __name__ == "__main__" :
10251046 root = TkinterDnD .Tk () if HAS_DND else tk .Tk ()
10261047 app = BZModMaster (root )
0 commit comments