@@ -13,9 +13,7 @@ class Problems(IntEnum):
1313 """
1414 Enums for IPluginDiagnose.
1515 """
16- # PAK files placed in incorrect locations
1716 MISPLACED_PAK_FILES = auto ()
18- # Missing mod directory structure
1917 MISSING_MOD_DIRECTORIES = auto ()
2018
2119
@@ -24,7 +22,6 @@ class S2HoCGame(BasicGame, mobase.IPluginFileMapper, mobase.IPluginDiagnose):
2422 Author = "MkHaters"
2523 Version = "1.1.0"
2624
27- # Game details for MO2, using paths common for Unreal Engine-based games.
2825 GameName = "Stalker 2: Heart of Chornobyl"
2926 GameShortName = "stalker2heartofchornobyl"
3027 GameNexusName = "stalker2heartofchornobyl"
@@ -35,27 +32,32 @@ class S2HoCGame(BasicGame, mobase.IPluginFileMapper, mobase.IPluginDiagnose):
3532 GameSteamId = 1643320
3633 GameGogId = 1529799785
3734 GameBinary = "Stalker2.exe"
38- GameDataPath = "%GAME_PATH%/ Stalker2"
35+ GameDataPath = "Stalker2"
3936 GameIniFiles = [
4037 "%GAME_DOCUMENTS%/Saved/Config/Windows/Game.ini" ,
4138 "%GAME_DOCUMENTS%/Saved/Config/Windows/GameUserSettings.ini" ,
4239 "%GAME_DOCUMENTS%/Saved/Config/Windows/Engine.ini"
4340 ]
4441
4542 _main_window : QMainWindow
46- _paks_tab : QWidget # Will be S2HoCPaksTabWidget when imported
43+ _paks_tab : QWidget
4744
4845 def __init__ (self ):
49- # Initialize parent classes.
5046 BasicGame .__init__ (self )
5147 mobase .IPluginFileMapper .__init__ (self )
5248 mobase .IPluginDiagnose .__init__ (self )
5349
5450 def resolve_path (self , path : str ) -> str :
55- # Replace MO2 variables with actual paths
5651 path = path .replace ("%USERPROFILE%" , os .environ .get ("USERPROFILE" , "" ))
57- path = path .replace ("%GAME_DOCUMENTS%" , self .GameDocumentsDirectory )
58- path = path .replace ("%GAME_PATH%" , self .GameDataPath )
52+
53+ if "%GAME_DOCUMENTS%" in path :
54+ game_docs = self .GameDocumentsDirectory .replace ("%USERPROFILE%" , os .environ .get ("USERPROFILE" , "" ))
55+ path = path .replace ("%GAME_DOCUMENTS%" , game_docs )
56+
57+ if "%GAME_PATH%" in path :
58+ game_path = self ._gamePath if hasattr (self , '_gamePath' ) else ""
59+ path = path .replace ("%GAME_PATH%" , game_path )
60+
5961 return path
6062
6163 def init (self , organizer : mobase .IOrganizer ) -> bool :
@@ -65,25 +67,21 @@ def init(self, organizer: mobase.IOrganizer) -> bool:
6567 BasicLocalSavegames (QDir (self .resolve_path (self .GameSavesDirectory )))
6668 )
6769
68- # Create the directory more reliably
6970 if (
7071 self ._organizer .managedGame ()
7172 and self ._organizer .managedGame ().gameName () == self .gameName ()
7273 ):
73- # Get the absolute path as a string
7474 mod_path = self .paksModsDirectory ().absolutePath ()
7575 try :
76- # Create the directory with parents if needed
7776 os .makedirs (mod_path , exist_ok = True )
78- # Verify the directory was actually created
7977 if not os .path .exists (mod_path ):
80- print (f"Warning: Failed to create directory: { mod_path } " )
78+ self ._organizer .log (mobase .LogLevel .WARNING , f"Failed to create directory: { mod_path } " )
79+ except OSError as e :
80+ self ._organizer .log (mobase .LogLevel .ERROR , f"OS error creating mod directory: { e } " )
8181 except Exception as e :
82- print ( f"Error creating mod directory: { e } " )
82+ self . _organizer . log ( mobase . LogLevel . ERROR , f"Unexpected error creating mod directory: { e } " )
8383
84- # Initialize PAK tab when UI is ready
8584 organizer .onUserInterfaceInitialized (self .init_tab )
86-
8785 return True
8886
8987 def init_tab (self , main_window : QMainWindow ):
@@ -97,54 +95,59 @@ def init_tab(self, main_window: QMainWindow):
9795 self ._main_window = main_window
9896 tab_widget : QTabWidget = main_window .findChild (QTabWidget , "tabWidget" )
9997 if not tab_widget :
100- print ( "No main tab widget found!" )
98+ self . _organizer . log ( mobase . LogLevel . WARNING , "No main tab widget found!" )
10199 return
102100
103- # Import here to avoid circular imports
104101 from .stalker2heartofchornobyl .paks import S2HoCPaksTabWidget
105102 self ._paks_tab = S2HoCPaksTabWidget (main_window , self ._organizer )
106103
107- # Insert after the last tab (like Oblivion Remastered)
108104 tab_widget .addTab (self ._paks_tab , "PAK Files" )
109- print ("PAK Files tab added!" )
105+ self ._organizer .log (mobase .LogLevel .INFO , "PAK Files tab added!" )
106+ except ImportError as e :
107+ self ._organizer .log (mobase .LogLevel .ERROR , f"Failed to import PAK tab widget: { e } " )
110108 except Exception as e :
111- print ( f"Error initializing PAK tab: { e } " )
109+ self . _organizer . log ( mobase . LogLevel . ERROR , f"Error initializing PAK tab: { e } " )
112110 import traceback
113111 traceback .print_exc ()
114112
115113 def mappings (self ) -> List [mobase .Mapping ]:
116- return [
117- mobase . Mapping ( "*.pak" , " Content/Paks/~mods/", False ),
118- mobase . Mapping ( "*.utoc" , "Content/Paks/~mods/" , False ),
119- mobase . Mapping ( "*.ucas" , "Content/Paks/~mods/" , False ),
120- mobase . Mapping ( "Paks/*.pak" , "Content/Paks/~mods/" , False ),
121- mobase . Mapping ( "Paks/*.utoc" , "Content/Paks/~mods/" , False ),
122- mobase .Mapping ("Paks/*.ucas" , "Content/Paks/~mods/" , False ),
123- mobase . Mapping ( "~mods/*.pak" , "Content/Paks/~mods/" , False ),
124- mobase . Mapping ( "~mods/*.utoc " , "Content/Paks/~mods/" , False ),
125- mobase . Mapping ( "~mods/*.ucas" , "Content/Paks/~mods/" , False ),
126- mobase . Mapping ( "Content/Paks/~mods/*.pak" , "Content/Paks/~mods/" , False ),
127- mobase .Mapping ("Content/Paks/~mods/*.utoc " , "Content/Paks/~mods/" , False ),
128- mobase . Mapping ( "Content/Paks/~mods/*.ucas" , "Content/Paks/~mods/" , False ),
129- ]
114+ pak_extensions = [ "*.pak" , "*.utoc" , "*.ucas" ]
115+ target_dir = " Content/Paks/~mods/"
116+
117+ mappings = []
118+
119+ for ext in pak_extensions :
120+ mappings . append ( mobase .Mapping (ext , target_dir , False ))
121+
122+ source_dirs = [ "Paks/" , "~mods/" , "Content/Paks/~mods/" ]
123+ for source_dir in source_dirs :
124+ for ext in pak_extensions :
125+ mappings . append ( mobase .Mapping (f" { source_dir } { ext } " , target_dir , False ))
126+
127+ return mappings
130128
131129 def gameDirectory (self ) -> QDir :
132130 return QDir (self ._gamePath )
133131
134132 def paksDirectory (self ) -> QDir :
135- return QDir (self .gameDirectory ().absolutePath () + "/Stalker2/Content/Paks" )
133+ path = os .path .join (self .gameDirectory ().absolutePath (), self .GameDataPath , "Content" , "Paks" )
134+ return QDir (path )
136135
137136 def paksModsDirectory (self ) -> QDir :
138- # Use os.path.join for more reliable path construction
139- path = os .path .join (self .paksDirectory ().absolutePath (), "~mods" )
140- return QDir (path )
137+ try :
138+ path = os .path .join (self .paksDirectory ().absolutePath (), "~mods" )
139+ return QDir (path )
140+ except Exception as e :
141+ fallback = os .path .join (self .gameDirectory ().absolutePath (), self .GameDataPath , "Content" , "Paks" , "~mods" )
142+ return QDir (fallback )
141143
142144 def logicModsDirectory (self ) -> QDir :
143- # Update path to place LogicMods under Paks
144- return QDir (self . gameDirectory (). absolutePath () + "/Stalker2/Content/Paks/LogicMods" )
145+ path = os . path . join ( self . gameDirectory (). absolutePath (), self . GameDataPath , "Content" , " Paks" , "LogicMods" )
146+ return QDir (path )
145147
146148 def binariesDirectory (self ) -> QDir :
147- return QDir (self .gameDirectory ().absolutePath () + "/Stalker2/Binaries/Win64" )
149+ path = os .path .join (self .gameDirectory ().absolutePath (), self .GameDataPath , "Binaries" , "Win64" )
150+ return QDir (path )
148151
149152 def getModMappings (self ) -> dict [str , list [str ]]:
150153 return {
@@ -155,18 +158,15 @@ def activeProblems(self) -> list[int]:
155158 problems = set ()
156159 if self ._organizer .managedGame () == self :
157160
158- # More reliable directory check using os.path
159161 mod_path = self .paksModsDirectory ().absolutePath ()
160162 if not os .path .isdir (mod_path ):
161163 problems .add (Problems .MISSING_MOD_DIRECTORIES )
162- print ( f"Missing mod directory: { mod_path } " )
164+ self . _organizer . log ( mobase . LogLevel . DEBUG , f"Missing mod directory: { mod_path } " )
163165
164- # Check for misplaced PAK files
165166 for mod in self ._organizer .modList ().allMods ():
166167 mod_info = self ._organizer .modList ().getMod (mod )
167168 filetree = mod_info .fileTree ()
168169
169- # Check for PAK files at the root level (remove LogicMods paths)
170170 for entry in filetree :
171171 if entry .name ().endswith (('.pak' , '.utoc' , '.ucas' )) and not any (
172172 entry .path ().startswith (p ) for p in ['Content/Paks/~mods' , 'Paks' , '~mods' ]
@@ -216,21 +216,22 @@ def shortDescription(self, key: int) -> str:
216216 def startGuidedFix (self , key : int ) -> None :
217217 match key :
218218 case Problems .MISSING_MOD_DIRECTORIES :
219- # Create only the ~mods directory
220- os .makedirs (self .paksModsDirectory ().absolutePath (), exist_ok = True )
219+ try :
220+ os .makedirs (self .paksModsDirectory ().absolutePath (), exist_ok = True )
221+ self ._organizer .log (mobase .LogLevel .INFO , "Created missing mod directories" )
222+ except Exception as e :
223+ self ._organizer .log (mobase .LogLevel .ERROR , f"Failed to create mod directories: { e } " )
221224 case _:
222225 pass
223226
224227
225228class S2HoCModDataChecker (BasicModDataChecker ):
226229 def __init__ (self , patterns : GlobPatterns = GlobPatterns ()):
227- # Define valid mod directories and the file movement rules.
228230 move_patterns = {
229231 "*.pak" : "Content/Paks/~mods/" ,
230232 "*.utoc" : "Content/Paks/~mods/" ,
231233 "*.ucas" : "Content/Paks/~mods/"
232234 }
233- # Define valid mod roots - remove LogicMods
234235 valid_roots = ["Content" , "Paks" , "~mods" ]
235236 base_patterns = GlobPatterns (valid = valid_roots , move = move_patterns )
236237 merged_patterns = base_patterns .merge (patterns )
0 commit comments