2929
3030logger = logging .getLogger (__name__ )
3131
32+ WECHAT_EXECUTABLE_NAMES = ("Weixin.exe" , "WeChat.exe" )
33+
3234
3335def _summarize_aes_key (value : Any ) -> str :
3436 raw = str (value or "" ).strip ()
@@ -109,19 +111,72 @@ def _resolve_wxid_dir_for_image_key(
109111 raise FileNotFoundError ("无法定位该账号的 wxid_dir,请传入有效的 db_storage_path 或先完成数据库解密" )
110112
111113
114+ def _normalize_user_path (value : Any ) -> str :
115+ raw = str (value or "" ).strip ().strip ('"' ).strip ("'" )
116+ if not raw :
117+ return ""
118+ try :
119+ return os .path .normpath (os .path .expandvars (raw ))
120+ except Exception :
121+ return raw
122+
123+
124+ def _read_wechat_version_from_exe (exe_path : str ) -> str :
125+ normalized = _normalize_user_path (exe_path )
126+ if not normalized :
127+ return ""
128+ try :
129+ import win32api
130+
131+ version_info = win32api .GetFileVersionInfo (normalized , "\\ " )
132+ return (
133+ f"{ version_info ['FileVersionMS' ] >> 16 } ."
134+ f"{ version_info ['FileVersionMS' ] & 0xFFFF } ."
135+ f"{ version_info ['FileVersionLS' ] >> 16 } ."
136+ f"{ version_info ['FileVersionLS' ] & 0xFFFF } "
137+ )
138+ except Exception :
139+ return ""
140+
141+
142+ def _resolve_manual_wechat_exe_path (wechat_install_path : Optional [str ] = None ) -> str :
143+ normalized = _normalize_user_path (wechat_install_path )
144+ if not normalized :
145+ return ""
146+
147+ candidate = Path (normalized ).expanduser ()
148+ executable_names = {name .lower () for name in WECHAT_EXECUTABLE_NAMES }
149+ if candidate .is_file ():
150+ if candidate .name .lower () not in executable_names :
151+ raise RuntimeError ("手动路径必须指向微信安装目录,或直接指向 Weixin.exe / WeChat.exe" )
152+ return str (candidate )
153+
154+ if candidate .is_dir ():
155+ for exe_name in WECHAT_EXECUTABLE_NAMES :
156+ exe_path = candidate / exe_name
157+ if exe_path .is_file ():
158+ return str (exe_path )
159+ raise RuntimeError ("手动指定的微信安装目录中未找到 Weixin.exe 或 WeChat.exe" )
160+
161+ raise RuntimeError (f"手动指定的微信安装目录不存在: { candidate } " )
162+
163+
112164# ====================== 以下是hook逻辑 ======================================
113165
114166class WeChatKeyFetcher :
115167 def __init__ (self ):
116- self .process_name = "Weixin.exe"
168+ self .process_names = { name . lower () for name in WECHAT_EXECUTABLE_NAMES }
117169 self .timeout_seconds = 60
118170
171+ def _is_wechat_process (self , name : Any ) -> bool :
172+ return str (name or "" ).strip ().lower () in self .process_names
173+
119174 def kill_wechat (self ):
120175 """检测并查杀微信进程"""
121176 killed = False
122177 for proc in psutil .process_iter (['pid' , 'name' ]):
123178 try :
124- if proc .info ['name' ] == self . process_name :
179+ if self . _is_wechat_process ( proc .info ['name' ]) :
125180 logger .info (f"Killing WeChat process: { proc .info ['pid' ]} " )
126181 proc .terminate ()
127182 killed = True
@@ -134,11 +189,14 @@ def kill_wechat(self):
134189 def launch_wechat (self , exe_path : str ) -> int :
135190 """启动微信并返回 PID"""
136191 try :
137- process = subprocess .Popen (exe_path )
192+ normalized_exe_path = _normalize_user_path (exe_path )
193+ process = subprocess .Popen (normalized_exe_path )
138194 time .sleep (2 )
139195 candidates = []
196+ target_process_name = Path (normalized_exe_path ).name .lower ()
140197 for proc in psutil .process_iter (['pid' , 'name' , 'create_time' ]):
141- if proc .info ['name' ] == self .process_name :
198+ proc_name = str (proc .info .get ('name' ) or "" ).strip ().lower ()
199+ if proc_name == target_process_name or self ._is_wechat_process (proc_name ):
142200 candidates .append (proc )
143201
144202 if candidates :
@@ -152,19 +210,32 @@ def launch_wechat(self, exe_path: str) -> int:
152210 logger .error (f"启动微信失败: { e } " )
153211 raise RuntimeError (f"无法启动微信: { e } " )
154212
155- def fetch_db_key (self ) -> dict :
213+ def fetch_db_key (self , wechat_install_path : Optional [ str ] = None ) -> dict :
156214 """调用 wx_key 仅获取数据库密钥 (Hook 模式)"""
157215 if wx_key is None :
158216 raise RuntimeError ("wx_key 模块未安装或加载失败" )
159217
160- install_info = detect_wechat_installation ()
161- exe_path = install_info .get ('wechat_exe_path' )
162- version = install_info .get ('wechat_version' )
218+ manual_path = _normalize_user_path (wechat_install_path )
219+ if manual_path :
220+ exe_path = _resolve_manual_wechat_exe_path (manual_path )
221+ version = _read_wechat_version_from_exe (exe_path )
222+ logger .info (
223+ "[db_key] 使用手动指定的微信安装路径: input=%s exe_path=%s version=%s" ,
224+ manual_path ,
225+ exe_path ,
226+ version or "unknown" ,
227+ )
228+ else :
229+ install_info = detect_wechat_installation ()
230+ exe_path = _normalize_user_path (install_info .get ('wechat_exe_path' ))
231+ version = str (install_info .get ('wechat_version' ) or "" ).strip ()
163232
164- if not exe_path or not version :
165- raise RuntimeError ("无法自动定位微信安装路径或版本" )
233+ if not exe_path :
234+ raise RuntimeError ("无法自动定位微信安装路径,请手动填写微信安装目录" )
235+ if not Path (exe_path ).is_file ():
236+ raise RuntimeError (f"微信可执行文件不存在: { exe_path } " )
166237
167- logger .info (f"Detect WeChat: { version } at { exe_path } " )
238+ logger .info (f"Detect WeChat: { version or 'unknown' } at { exe_path } " )
168239
169240 self .kill_wechat ()
170241 pid = self .launch_wechat (exe_path )
@@ -204,9 +275,9 @@ def fetch_db_key(self) -> dict:
204275 "db_key" : found_db_key
205276 }
206277
207- def get_db_key_workflow ():
278+ def get_db_key_workflow (wechat_install_path : Optional [ str ] = None ):
208279 fetcher = WeChatKeyFetcher ()
209- return fetcher .fetch_db_key ()
280+ return fetcher .fetch_db_key (wechat_install_path = wechat_install_path )
210281
211282
212283# ============================== 以下是图片密钥逻辑 =====================================
0 commit comments