11"""
2- Garry's Mod Bridge for SourceBox (GMod 9-12 )
3- Automatically installs Lua addon to sourcemods
2+ Garry's Mod Bridge for SourceBox (GMod 9-13 )
3+ Automatically installs Lua addon to sourcemods and retail installs
44"""
55
66import os
@@ -18,6 +18,15 @@ class GModBridge:
1818 'garrysmod' : 'GMod 11' ,
1919 'garrysmod12' : 'GMod 12'
2020 }
21+
22+ # retail gmod 13 install (steamapps/common/GarrysMod/garrysmod)
23+ GMOD_RETAIL = {
24+ 'gmod13' : {
25+ 'name' : 'GMod 13' ,
26+ 'install_dir' : 'GarrysMod' ,
27+ 'game_dir' : 'garrysmod'
28+ }
29+ }
2130
2231 def __init__ (self ):
2332 self .data_path = None
@@ -131,6 +140,16 @@ def _parse_library_folders_vdf(self, steam_path):
131140 return libraries
132141 except :
133142 return [steam_path ]
143+
144+ def _get_retail_gmod_path (self , library_path ):
145+ """return retail gmod13 garrysmod directory if it exists"""
146+ retail_info = self .GMOD_RETAIL ['gmod13' ]
147+ for steamapps_dir in ['steamapps' , 'SteamApps' ]:
148+ game_root = os .path .join (library_path , steamapps_dir , 'common' , retail_info ['install_dir' ])
149+ mod_path = os .path .join (game_root , retail_info ['game_dir' ])
150+ if os .path .exists (mod_path ):
151+ return mod_path
152+ return None
134153
135154 def _detect_gmod (self ):
136155 """detect gmod installation"""
@@ -155,6 +174,7 @@ def _detect_gmod(self):
155174
156175 running_gmod = self ._detect_running_gmod ()
157176
177+ # first scan sourcemod variants (gmod 9-12)
158178 for library_path in all_libraries :
159179 sourcemods_path = os .path .join (library_path , 'steamapps' , 'sourcemods' )
160180 if not os .path .exists (sourcemods_path ):
@@ -176,13 +196,32 @@ def _detect_gmod(self):
176196 os .makedirs (data_path , exist_ok = True )
177197
178198 if running_gmod and running_gmod == mod_folder :
179- self ._setup_paths (data_path , addon_path , mod_name , is_gmod9 )
199+ self ._setup_paths (data_path , addon_path , mod_name , is_gmod9 , mod_folder )
180200 self ._install_lua_addon ()
181201 print (f" [found] { mod_name } (RUNNING)" )
182202 print (f" [library] { library_path } " )
183203 return
184204 elif not self .active_gmod :
185205 print (f" [installed] { mod_name } (in { library_path } )" )
206+
207+ # then scan retail gmod 13 install
208+ for library_path in all_libraries :
209+ retail_path = self ._get_retail_gmod_path (library_path )
210+ if not retail_path :
211+ continue
212+
213+ data_path = os .path .join (retail_path , 'data' )
214+ addon_path = os .path .join (retail_path , 'addons' , 'sourcebox' )
215+ os .makedirs (data_path , exist_ok = True )
216+
217+ if running_gmod in ['gmod13' , 'garrysmod' ]:
218+ self ._setup_paths (data_path , addon_path , self .GMOD_RETAIL ['gmod13' ]['name' ], False , 'gmod13' )
219+ self ._install_lua_addon ()
220+ print (f" [found] { self .GMOD_RETAIL ['gmod13' ]['name' ]} (RUNNING)" )
221+ print (f" [library] { library_path } " )
222+ return
223+ elif not self .active_gmod :
224+ print (f" [installed] { self .GMOD_RETAIL ['gmod13' ]['name' ]} (in { library_path } )" )
186225
187226 def _detect_running_gmod (self ):
188227 """detect running gmod"""
@@ -198,21 +237,31 @@ def _detect_running_gmod(self):
198237 continue
199238
200239 cmdline_str = ' ' .join (cmdline ).lower ()
240+ exe_path = proc .info .get ('exe' ) or ''
241+ exe_lower = exe_path .lower ()
242+ proc_lower = proc_name .lower ()
201243
202- # check for hl2.exe with -game argument (sourcemods)
203- if proc_name . lower () == 'hl2.exe' :
244+ # hl2 based binaries or gmod specific binaries
245+ if proc_lower in [ 'hl2.exe' , 'hl2_linux' , 'gmod.exe' , 'gmod' , 'gmod64' , 'gmod32' , 'gmod_linux' ] :
204246 for i , arg in enumerate (cmdline ):
205247 if arg .lower () == '-game' and i + 1 < len (cmdline ):
206248 game_arg = cmdline [i + 1 ].strip ('"' ).lower ()
207249
208- if 'gmod9' in game_arg :
250+ if 'gmod9' in game_arg or 'garrysmod9' in game_arg :
209251 return 'gmod9'
210252 elif 'garrysmod12' in game_arg :
211253 return 'garrysmod12'
212254 elif 'garrysmod10classic' in game_arg :
213255 return 'garrysmod10classic'
214- elif 'garrysmod' in game_arg :
215- return 'garrysmod'
256+ elif 'garrysmod' in game_arg :
257+ # differentiate sourcemod vs retail by path hint
258+ if 'sourcemods' in game_arg or 'sourcemods' in exe_lower :
259+ return 'garrysmod'
260+ return 'gmod13'
261+
262+ # fallback: detect retail gmod if binary path clearly in GarrysMod
263+ if 'garrysmod' in exe_lower and 'sourcemods' not in exe_lower :
264+ return 'gmod13'
216265
217266 except (psutil .NoSuchProcess , psutil .AccessDenied , psutil .ZombieProcess ):
218267 continue
@@ -221,12 +270,13 @@ def _detect_running_gmod(self):
221270
222271 return None
223272
224- def _setup_paths (self , data_path , addon_path , gmod_name , is_gmod9 = False ):
273+ def _setup_paths (self , data_path , addon_path , gmod_name , is_gmod9 = False , gmod_version = None ):
225274 """setup paths"""
226275 self .data_path = data_path
227276 self .addon_path = addon_path
228277 self .active_gmod = gmod_name
229278 self .is_gmod9 = is_gmod9
279+ self .gmod_version = gmod_version or ('gmod9' if is_gmod9 else None )
230280 self .command_file = os .path .join (data_path , "sourcebox_command.txt" )
231281 self .response_file = os .path .join (data_path , "sourcebox_response.txt" )
232282
@@ -276,18 +326,30 @@ def _install_lua_addon(self):
276326 sourcebox_path = os .path .join (autorun_path , 'sourcebox' )
277327
278328 os .makedirs (sourcebox_path , exist_ok = True )
279-
280- # write info.txt
281- info_content = '''sourcebox
329+ # write addon metadata
330+ if self .gmod_version == 'gmod13' :
331+ addon_content = '''"AddonInfo"
332+ {
333+ "name" "SourceBox"
334+ "version" "1.0"
335+ "author_name" "SourceBox Team"
336+ "info" "Python bridge for Garry's Mod"
337+ "override" "0"
338+ }
339+ '''
340+ with open (os .path .join (self .addon_path , 'addon.txt' ), 'w' ) as f :
341+ f .write (addon_content )
342+ else :
343+ info_content = '''sourcebox
282344{
283345 name "SourceBox"
284346 version "1.0"
285347 author "SourceBox Team"
286348 info "Python bridge for Garry's Mod"
287349}
288350'''
289- with open (os .path .join (self .addon_path , 'info.txt' ), 'w' ) as f :
290- f .write (info_content )
351+ with open (os .path .join (self .addon_path , 'info.txt' ), 'w' ) as f :
352+ f .write (info_content )
291353
292354 # write sourcebox_init.lua
293355 init_content = self ._get_init_lua ()
@@ -1777,4 +1839,4 @@ def cleanup(self):
17771839 if self .response_file and os .path .exists (self .response_file ):
17781840 os .remove (self .response_file )
17791841 except :
1780- pass
1842+ pass
0 commit comments