Skip to content

Commit d9bfbbe

Browse files
Merge remote-tracking branch 'Phobos-developers/develop' into dev-docs
2 parents fd772f6 + 165c1b7 commit d9bfbbe

14 files changed

Lines changed: 3353 additions & 23 deletions

File tree

.agents/skills/check-hooks/SKILL.md

Lines changed: 719 additions & 0 deletions
Large diffs are not rendered by default.

.agents/skills/check-hooks/ares_3.0p1_hooks.cpp

Lines changed: 1461 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
#!/usr/bin/env python3
2+
"""Check new hooks against existing hooks in HookAnalysis.txt for conflicts.
3+
4+
Usage:
5+
python check_hook_conflicts.py <new_hooks_json>
6+
7+
new_hooks_json is a JSON file (or '-' for stdin) containing an array of new hook objects:
8+
[{"address": "0x46BDD9", "size": 5, "name": "MyHook", "returns": "0x46BDE0"}]
9+
10+
"returns" can be:
11+
- "0" or missing/empty → returns 0 (resolves to hook address, safe, no return-check needed)
12+
- "0x..." → fixed return address to check
13+
- "R->Origin() + N" → relative return (hook address + N) to check
14+
15+
Output is JSON with conflict results.
16+
"""
17+
18+
import json
19+
import os
20+
import sys
21+
import re
22+
23+
# Ensure the script's own directory is in sys.path so that
24+
# 'import parse_hook_log' works regardless of the working directory.
25+
_script_dir = os.path.dirname(os.path.abspath(__file__))
26+
if _script_dir not in sys.path:
27+
sys.path.insert(0, _script_dir)
28+
29+
def parse_existing_hooks(script_dir):
30+
"""Parse existing hooks from HookAnalysis.txt or ares_3.0p1_hooks.cpp.
31+
32+
Tries HookAnalysis.txt first (faster, richer metadata).
33+
Falls back to ares_3.0p1_hooks.cpp if HookAnalysis.txt does not exist.
34+
"""
35+
log_path = os.path.join(script_dir, 'HookAnalysis.txt')
36+
if os.path.exists(log_path):
37+
import parse_hook_log
38+
return parse_hook_log.parse_hook_log(log_path)
39+
40+
cpp_path = os.path.join(script_dir, 'ares_3.0p1_hooks.cpp')
41+
if os.path.exists(cpp_path):
42+
import parse_hooks_cpp
43+
return parse_hooks_cpp.parse_hooks_cpp(cpp_path)
44+
45+
print(json.dumps({
46+
'errors': [],
47+
'notes': [{
48+
'problem': 'Setup',
49+
'type': 'error',
50+
'message': (
51+
"Neither HookAnalysis.txt nor ares_3.0p1_hooks.cpp found "
52+
f"in {script_dir}. Cannot perform conflict check."
53+
)
54+
}]
55+
}, indent=2, ensure_ascii=False))
56+
sys.exit(1)
57+
58+
59+
def check_hooks(new_hooks, existing_hooks):
60+
results = []
61+
errors = []
62+
notes = []
63+
64+
for nh in new_hooks:
65+
addr = nh['address']
66+
addr_int = int(addr, 16)
67+
size = nh['size']
68+
name = nh.get('name', '<unknown>')
69+
ret = nh.get('returns', '0') # returns "0", "0x...", or "R->Origin() + N"
70+
range_start = addr_int
71+
range_end = addr_int + size
72+
73+
# Resolve return address
74+
ret_addr = None
75+
if ret == '0' or not ret:
76+
ret_addr = None # means safe, no return-check needed
77+
elif re.match(r'^0x[0-9A-Fa-f]+$', ret):
78+
ret_addr = int(ret, 16)
79+
else:
80+
m = re.match(r'R->Origin\(\)\s*\+\s*(\d+)', ret)
81+
if m:
82+
ret_addr = addr_int + int(m.group(1))
83+
84+
# Check Problem 0: size >= 5
85+
if size < 5:
86+
errors.append({
87+
'problem': 'Problem 0',
88+
'hook': name,
89+
'address': addr,
90+
'message': f"Hook '{name}' at {addr} has size {size} (< 5). The JMP instruction requires at least 5 bytes."
91+
})
92+
93+
# Check Problem 1: conflicts
94+
found_conflict = False
95+
for eh in existing_hooks:
96+
e_range_start = eh['range_start']
97+
e_range_end = eh['range_end']
98+
e_addr = eh['address']
99+
100+
# Check address range overlap
101+
if range_start < e_range_end and range_end > e_range_start:
102+
if range_start == e_range_start and range_end == e_range_end:
103+
# Exact overlap - stacked hooks, not an error
104+
notes.append({
105+
'problem': 'Problem 1',
106+
'hook': name,
107+
'address': addr,
108+
'size': size,
109+
'existing_hook': eh['name'],
110+
'existing_dll': eh['dll'],
111+
'existing_address': e_addr,
112+
'existing_size': eh['size'],
113+
'type': 'stacked',
114+
'message': (
115+
f"Hook '{name}' at {addr} (size {size}) exactly matches "
116+
f"existing hook '{eh['name']}' from {eh['dll']}. "
117+
f"This is a stacked hook — the second will execute after the first returns 0. "
118+
f"Verify this is intended."
119+
)
120+
})
121+
else:
122+
# Partial overlap - conflict
123+
errors.append({
124+
'problem': 'Problem 1',
125+
'hook': name,
126+
'address': addr,
127+
'size': size,
128+
'range': f"[0x{range_start:08X}, 0x{range_end:08X})",
129+
'existing_hook': eh['name'],
130+
'existing_dll': eh['dll'],
131+
'existing_address': e_addr,
132+
'existing_size': eh['size'],
133+
'existing_range': f"[0x{e_range_start:08X}, 0x{e_range_end:08X})",
134+
'type': 'conflict',
135+
'message': (
136+
f"Hook '{name}' at {addr} (size {size}, range "
137+
f"[0x{range_start:08X}, 0x{range_end:08X})) conflicts with "
138+
f"existing hook '{eh['name']}' from {eh['dll']} at "
139+
f"{e_addr} (size {eh['size']}, range "
140+
f"[0x{e_range_start:08X}, 0x{e_range_end:08X})). "
141+
f"The address ranges overlap."
142+
)
143+
})
144+
found_conflict = True
145+
146+
# Check return address
147+
if ret_addr is not None and ret_addr != addr_int:
148+
if e_range_start <= ret_addr < e_range_end:
149+
errors.append({
150+
'problem': 'Problem 1',
151+
'hook': name,
152+
'address': addr,
153+
'returns': ret,
154+
'return_addr': f"0x{ret_addr:08X}",
155+
'existing_hook': eh['name'],
156+
'existing_dll': eh['dll'],
157+
'existing_range': f"[0x{e_range_start:08X}, 0x{e_range_end:08X})",
158+
'type': 'return_conflict',
159+
'message': (
160+
f"Hook '{name}' at {addr} returns to 0x{ret_addr:08X}, "
161+
f"which falls within existing hook '{eh['name']}' from {eh['dll']} "
162+
f"covering [0x{e_range_start:08X}, 0x{e_range_end:08X})."
163+
)
164+
})
165+
166+
if not found_conflict:
167+
notes.append({
168+
'problem': 'Problem 1',
169+
'hook': name,
170+
'address': addr,
171+
'type': 'ok',
172+
'message': f"No conflicts detected for hook '{name}' at {addr}."
173+
})
174+
175+
return {'errors': errors, 'notes': notes}
176+
177+
178+
def main():
179+
if len(sys.argv) < 2:
180+
new_hooks = json.load(sys.stdin)
181+
else:
182+
new_hooks_input = sys.argv[1]
183+
if new_hooks_input == '-':
184+
new_hooks = json.load(sys.stdin)
185+
else:
186+
with open(new_hooks_input, 'r', encoding='utf-8') as f:
187+
new_hooks = json.load(f)
188+
189+
script_dir = os.path.dirname(os.path.abspath(__file__))
190+
existing_hooks = parse_existing_hooks(script_dir)
191+
192+
results = check_hooks(new_hooks, existing_hooks)
193+
json.dump(results, sys.stdout, indent=2, ensure_ascii=False)
194+
195+
196+
if __name__ == '__main__':
197+
main()

0 commit comments

Comments
 (0)