Skip to content

Commit 278df1d

Browse files
authored
Merge pull request #72 from monkeyman192/gh-71
gh-71: Improve path injection for folders of mods
2 parents cb57591 + 40ca8b0 commit 278df1d

File tree

13 files changed

+260
-85
lines changed

13 files changed

+260
-85
lines changed

pymhf/core/hooking.py

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from pymhf.core.memutils import _get_binary_info, find_pattern_in_binary, get_addressof
2727
from pymhf.utils.iced import HAS_ICED, generate_load_stack_pointer_bytes, get_first_jmp_addr
2828

29-
hook_logger = logging.getLogger("HookManager")
29+
logger = logging.getLogger(__name__)
3030

3131

3232
BITS = struct.calcsize("P") * 8
@@ -142,7 +142,7 @@ def _determine_detour_list(self, detour: HookProtocol) -> Optional[list[HookProt
142142
else:
143143
detour_list = self._after_detours
144144
else:
145-
hook_logger.error(f"Detour {detour} has an invalid detour time: {detour._hook_time}")
145+
logger.error(f"Detour {detour} has an invalid detour time: {detour._hook_time}")
146146
return detour_list
147147

148148
def add_detour(self, detour: HookProtocol):
@@ -154,15 +154,15 @@ def add_detour(self, detour: HookProtocol):
154154

155155
if getattr(detour, "_noop", False):
156156
self._has_noop = True
157-
hook_logger.warning(
157+
logger.warning(
158158
f"The hook {detour._hook_func_name} has been marked as NOOP. If there are multiple detours "
159159
"registered for this hook they may behave weirdly and the game may crash if this hook is not "
160160
"defined well."
161161
)
162162

163163
# Determine the detour list to use. If none, then return.
164164
if (detour_list := self._determine_detour_list(detour)) is None:
165-
hook_logger.error(
165+
logger.error(
166166
f"Unable to assign {detour} to a detour type. Please check it. It has not been added."
167167
)
168168
return
@@ -186,11 +186,11 @@ def _one_shot(
186186
self._disabled_detours.add(detour)
187187
detour_list.remove(self._oneshot_detours[detour])
188188
except ValueError:
189-
hook_logger.warning(
189+
logger.warning(
190190
f"Had an issue removing one-shot {detour._hook_func_name} from detour list."
191191
)
192192
except Exception:
193-
hook_logger.error(traceback.format_exc())
193+
logger.error(traceback.format_exc())
194194

195195
self._oneshot_detours[detour] = _one_shot
196196
detour_list.append(_one_shot)
@@ -250,9 +250,9 @@ def bind(self) -> bool:
250250
super().__init__(signature=self.signature, target=self.target)
251251
except cyminhook._cyminhook.Error as e: # type: ignore
252252
if e.status == cyminhook._cyminhook.Status.MH_ERROR_ALREADY_CREATED:
253-
hook_logger.error("Hook is already created")
254-
hook_logger.error(f"Failed to initialize hook {self._name} at 0x{self.target:X}")
255-
hook_logger.error(e.status.name[3:].replace("_", " ") + f" ({e})")
253+
logger.error("Hook is already created")
254+
logger.error(f"Failed to initialize hook {self._name} at 0x{self.target:X}")
255+
logger.error(e.status.name[3:].replace("_", " ") + f" ({e})")
256256
self.state = "failed"
257257
return False
258258
self.state = "initialized"
@@ -269,8 +269,8 @@ def _compound_detour(self, *args):
269269
ret = r
270270
except Exception:
271271
bad_detour = self._before_detours.pop(i)
272-
hook_logger.error(f"There was an error with detour {bad_detour}. It has been disabled.")
273-
hook_logger.error(traceback.format_exc())
272+
logger.error(f"There was an error with detour {bad_detour}. It has been disabled.")
273+
logger.error(traceback.format_exc())
274274
self._disabled_detours.add(bad_detour)
275275

276276
# If we don't have any decorators which NOOP the original function then run as usual.
@@ -301,8 +301,8 @@ def _compound_detour(self, *args):
301301
bad_detour = self._after_detours.pop(i)
302302
else:
303303
bad_detour = self._after_detours_with_results.pop(j)
304-
hook_logger.error(f"There was an error with detour {bad_detour}. It has been disabled.")
305-
hook_logger.error(traceback.format_exc())
304+
logger.error(f"There was an error with detour {bad_detour}. It has been disabled.")
305+
logger.error(traceback.format_exc())
306306
self._disabled_detours.add(bad_detour)
307307

308308
if after_ret is not None:
@@ -463,7 +463,7 @@ def NOOP(detour: HookProtocol) -> HookProtocol:
463463
if getattr(detour, "_hook_time", None) == DetourTime.BEFORE:
464464
setattr(detour, "_noop", True)
465465
else:
466-
hook_logger.warning(
466+
logger.warning(
467467
"NOOP decorator can only be applied to 'before' hooks."
468468
"Either change the detour to a before detour, or, if it is, ensure that this decorator is "
469469
"applied above the hook decorator."
@@ -716,11 +716,11 @@ def register_hook(self, hook: HookProtocol):
716716
hook_offset = ctypes.cast(func_ptr, ctypes.c_void_p).value
717717
hook_offset_is_absolute = True
718718
else:
719-
hook_logger.error(f"Cannot find {hook_binary} in the import list")
719+
logger.error(f"Cannot find {hook_binary} in the import list")
720720
return
721721
elif hook._is_exported_func_hook:
722722
if _internal.BINARY_PATH is None:
723-
hook_logger.error("Current running binary path unknown. Cannot hook exported functions")
723+
logger.error("Current running binary path unknown. Cannot hook exported functions")
724724
return
725725
# TODO: This is inefficient. We should only instantiate the "dll" once.
726726
own_dll = ctypes.WinDLL(_internal.BINARY_PATH)
@@ -731,7 +731,7 @@ def register_hook(self, hook: HookProtocol):
731731
if hook_offset is not None:
732732
func_id = FunctionIdentifier(hook_func_name, hook_offset, hook_binary, hook_offset_is_absolute)
733733
else:
734-
hook_logger.error(f"Unable to find offset for {hook_func_name}. Hook will not be registered.")
734+
logger.error(f"Unable to find offset for {hook_func_name}. Hook will not be registered.")
735735
return
736736

737737
if func_id not in self.hooks:
@@ -744,7 +744,7 @@ def register_hook(self, hook: HookProtocol):
744744
offset_is_absolute=func_id.is_absolute,
745745
)
746746
except Exception:
747-
hook_logger.exception(f"There was an issue creating the func hook for {func_id}")
747+
logger.exception(f"There was an issue creating the func hook for {func_id}")
748748
self._uninitialized_hooks.add(func_id)
749749
self._hook_id_mapping[hook] = func_id
750750
self.hooks[func_id].add_detour(hook)
@@ -774,16 +774,16 @@ def initialize_hooks(self) -> int:
774774
else:
775775
offset = hook.offset
776776
prefix = f"{hook._binary}+"
777-
hook_logger.info(f"Enabled hook for {hook_func_id.name} at {prefix}0x{offset:X}")
777+
logger.debug(f"Enabled hook for {hook_func_id.name} at {prefix}0x{offset:X}")
778778
except Exception:
779-
hook_logger.error(f"Unable to enable {hook_func_id.name} because:")
780-
hook_logger.error(traceback.format_exc())
779+
logger.error(f"Unable to enable {hook_func_id.name} because:")
780+
logger.error(traceback.format_exc())
781781

782782
# If any of the hooked functions want to log where they were called from, we need to overwrite
783783
# part of the trampoline bytes to capture the RSP register.
784784
if hook_func_id in self._get_caller_detours:
785785
if not HAS_ICED:
786-
hook_logger.error(
786+
logger.error(
787787
f"Cannot get calling address of {hook_func_id.name} as `iced_x86` package is not "
788788
"installed.\nPlease install and try again."
789789
)
@@ -806,7 +806,7 @@ def initialize_hooks(self) -> int:
806806
data_at_detour[i] = rsp_load_bytes[i]
807807
for j in range(len(orig_bytes)):
808808
data_at_detour[i + j + 1] = orig_bytes[j]
809-
hook_logger.info(
809+
logger.info(
810810
f"The function {hook_func_id.name} has a modified hook to get the calling address."
811811
)
812812

@@ -817,15 +817,15 @@ def initialize_hooks(self) -> int:
817817
def _debug_show_states(self):
818818
# Return the states of all the registered hooks
819819
for hook_func_id, hook in self.hooks.items():
820-
hook_logger.info(f"Functions registered for {hook_func_id.name}:")
820+
logger.info(f"Functions registered for {hook_func_id.name}:")
821821
if hook._before_detours:
822-
hook_logger.info(" Before Detours:")
822+
logger.info(" Before Detours:")
823823
for func in hook._before_detours:
824-
hook_logger.info(f" {func}")
824+
logger.info(f" {func}")
825825
if hook._after_detours:
826-
hook_logger.info(" After Detours:")
826+
logger.info(" After Detours:")
827827
for func in hook._after_detours:
828-
hook_logger.info(f" {func}")
828+
logger.info(f" {func}")
829829

830830

831831
class Structure(ctypes.Structure):
@@ -925,16 +925,16 @@ def _call(self, *args, **kwargs) -> Optional[R]:
925925
try:
926926
val = cfunc(*_args)
927927
except ctypes.ArgumentError:
928-
hook_logger.error(
928+
logger.error(
929929
f"{self._func.__qualname__!r} has function signature {self._funcdef.arg_types} "
930930
f"but was called with {_args}"
931931
)
932932
raise
933933
return val
934934
else:
935-
hook_logger.error(f"Unable to call {self._func.__qualname__!r} - Cannot find function.")
935+
logger.error(f"Unable to call {self._func.__qualname__!r} - Cannot find function.")
936936
except Exception:
937-
hook_logger.exception(f"There was an exception calling {self._func.__qualname__!r}")
937+
logger.exception(f"There was an exception calling {self._func.__qualname__!r}")
938938
return None
939939

940940
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> Optional[R]:
@@ -962,7 +962,7 @@ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> Optional[R]:
962962
# If it's not a pointer, then we'll assume it's an int and pass the address...
963963
return self._call(ctypes.addressof(self._bound_class), *args, **kwargs)
964964
except Exception:
965-
hook_logger.exception(f"Failed to call {self._func.__qualname__} with args {args}")
965+
logger.exception(f"Failed to call {self._func.__qualname__} with args {args}")
966966
else:
967967
raise ValueError("Not bound to anything...")
968968

pymhf/core/importing.py

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1+
import ast
12
import importlib
23
import importlib.util
34
import logging
45
import os.path as op
56
import string
67
import sys
7-
import traceback
88
from types import ModuleType
99
from typing import Optional
1010

11-
logger = logging.getLogger("pymfh.core.importing")
11+
logger = logging.getLogger(__name__)
1212

1313

1414
VALID_CHARS = string.ascii_letters + string.digits + "_"
@@ -27,6 +27,53 @@ def _clean_name(name: str) -> str:
2727
return out
2828

2929

30+
def _fully_unpack_ast_attr(obj: ast.Attribute) -> str:
31+
name = ""
32+
_obj = obj
33+
while isinstance(_obj, ast.Attribute):
34+
if name:
35+
name = f"{_obj.attr}.{name}"
36+
else:
37+
name = _obj.attr
38+
_obj = _obj.value
39+
else:
40+
if isinstance(_obj, ast.Name):
41+
name = f"{_obj.id}.{name}"
42+
return name
43+
44+
45+
def parse_file_for_mod(data: str) -> bool:
46+
"""Parse the provided data and determine if there is at least one mod class in it."""
47+
tree = ast.parse(data)
48+
mod_class_name = None
49+
for node in tree.body:
50+
# First, determine the name the Mod object is imported as.
51+
if isinstance(node, ast.Import):
52+
for node_ in node.names:
53+
if isinstance(node_, ast.alias):
54+
if node_.name in ("pymhf", "pymhf.core.mod_loader"):
55+
mod_class_name = (node_.asname or node_.name) + ".Mod"
56+
if isinstance(node, ast.ImportFrom):
57+
if node.module in ("pymhf", "pymhf.core.mod_loader"):
58+
for node_ in node.names:
59+
if isinstance(node_, ast.alias):
60+
if node_.name == "Mod":
61+
mod_class_name = node_.asname or node_.name
62+
# Now, when we go over the class nodes, check the base classes.
63+
if isinstance(node, ast.ClassDef):
64+
for base in node.bases:
65+
# For a simple name, it's easy - just match it.
66+
if isinstance(base, ast.Name):
67+
if base.id == mod_class_name:
68+
return True
69+
# If it's an attribute it's a bit trickier...
70+
elif isinstance(base, ast.Attribute):
71+
resolved_base = _fully_unpack_ast_attr(base)
72+
if resolved_base == mod_class_name:
73+
return True
74+
return False
75+
76+
3077
def import_file(fpath: str) -> Optional[ModuleType]:
3178
try:
3279
module_name = _clean_name(op.splitext(op.basename(fpath))[0])
@@ -44,5 +91,4 @@ def import_file(fpath: str) -> Optional[ModuleType]:
4491
else:
4592
print("failed")
4693
except Exception:
47-
logger.error(f"Error loading {fpath}")
48-
logger.error(traceback.format_exc())
94+
logger.exception(f"Error loading {fpath}")

pymhf/core/memutils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
BLACKLIST = type, ModuleType, FunctionType
2121

2222

23-
mem_logger = logging.getLogger("MemUtils")
23+
logger = logging.getLogger(__name__)
2424

2525
MEM_ACCESS_R = 0x100 # Read only.
2626
MEM_ACCESS_RW = 0x200 # Read and Write access.
@@ -276,7 +276,7 @@ def find_pattern_in_binary(
276276
binary we ran, but it could be some other dll under the same process.)
277277
"""
278278
if (_cached_offset := cache.offset_cache.get(pattern, binary)) is not None:
279-
mem_logger.debug(
279+
logger.debug(
280280
f"Using cached offset 0x{_cached_offset:X} for pattern {pattern}"
281281
f" and binary {binary or _internal.EXE_NAME}"
282282
)
@@ -293,6 +293,6 @@ def find_pattern_in_binary(
293293
_offset = _offset - module.lpBaseOfDll
294294
# Cache even if there is no result (so we don't repeatedly look for it when it's not there in case there
295295
# is an issue.)
296-
mem_logger.debug(f"Found {pattern} at 0x{_offset:X} for binary {binary}")
296+
logger.debug(f"Found {pattern} at 0x{_offset:X} for binary {binary}")
297297
cache.offset_cache.set(pattern, _offset, binary, True)
298298
return _offset

0 commit comments

Comments
 (0)