-
Notifications
You must be signed in to change notification settings - Fork 60
/
__init__.py
270 lines (228 loc) · 10.9 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
import os
import time
from ctypes import *
from traceback import format_exc
from typing import Callable, Optional, Union
from FFxivPythonTrigger import PluginBase, process_event, api
from FFxivPythonTrigger.AddressManager import AddressManager
from FFxivPythonTrigger.Utils import Counter, wait_until
from FFxivPythonTrigger.memory import scan_pattern
from .FindAddr2 import find_recv2, find_send2
from .BundleDecoder import BundleDecoder, extract_single, pack_single
from .Structs import ServerMessageHeader, RecvNetworkEventBase, SendNetworkEventBase, FFXIVBundleHeader
from .RecvProcessors import processors as recv_processors, version_opcodes as recv_version_opcodes
from .SendProcessors import processors as send_processors, version_opcodes as send_version_opcodes
from .CombatReset import CombatReset
class RecvRawEvent(RecvNetworkEventBase):
name = "network recv event"
class SendRawEvent(SendNetworkEventBase):
name = "network send event"
class UnkRecvRawEvent(RecvNetworkEventBase):
name = "network unknown recv event"
id = "network/unk_recv"
def text(self):
return f"opcode:{self.header.msg_type} len:{len(self.raw_msg)}"
class UnkSendRawEvent(SendNetworkEventBase):
name = "network unknown send event"
id = "network/unk_send"
def text(self):
return f"opcode:{self.header.msg_type} len:{len(self.raw_msg)}"
recv_events_classes = dict()
send_events_classes = dict()
_unknown_opcode = set()
send_sig = "48 83 EC ? 48 8B 49 ? 45 33 C9 FF 15 ? ? ? ? 85 C0"
recv_sig = "48 83 EC ? 48 8B 49 ? 45 33 C9 FF 15 ? ? ? ? 83 F8 ?"
sig2 = "40 53 48 83 EC ? 48 8B D9 48 8B 49 ? 48 83 F9 ? 74 ? 45 33 C9 FF 15 ? ? ? ?"
header_size = sizeof(ServerMessageHeader)
msg_header_keys = {
'login_user_id': None,
'unk1': None,
'unk2': None,
'unk3': None,
'unk4': None,
}
AcceptOpcode = Union[int, str]
def get_send_opcode(opcode: AcceptOpcode) -> int:
if isinstance(opcode, int): return opcode
if opcode in send_version_opcodes: return send_version_opcodes[opcode]
raise Exception(f"[{opcode}] is not a valid send opcode")
def get_recv_opcode(opcode: AcceptOpcode) -> int:
if isinstance(opcode, int): return opcode
if opcode in recv_version_opcodes: return recv_version_opcodes[opcode]
raise Exception(f"[{opcode}] is not a valid recv opcode")
class XivNetwork(PluginBase):
name = "XivNetwork"
git_repo = 'nyaoouo/FFxivPythonTrigger2'
repo_path = 'plugins/XivNetwork'
hash_path = os.path.dirname(__file__)
def __init__(self):
super().__init__()
self.send_decoder = BundleDecoder(self.process_send_msg)
self.recv_decoder = BundleDecoder(self.process_recv_msg)
class WebActionHook(self.PluginHook):
socket = None
argtypes = [c_int64, POINTER(c_ubyte), c_int]
restype = c_int
class SendHook(WebActionHook):
def hook_function(_self, socket, buffer, size):
if size > 64: _self.socket = socket
# self.logger(hex(socket), hex(cast(buffer, c_void_p).value), size)
new_data = self.makeup_data(bytearray(buffer[:size]))
if new_data:
return _self.send(new_data, return_size=size, socket=socket)
return size
def send(_self, data: bytearray, process=True, return_size=None, socket=None):
# self.logger('*', data.hex())
if socket is None:
if _self.socket is None:
raise Exception("No socket record")
socket = _self.socket
size = len(data)
new_data = (c_ubyte * size).from_buffer(data)
success_size = _self.original(socket, new_data, size)
if success_size and process:
self.send_decoder.store_data(data[:success_size])
return success_size if return_size is None else return_size
class RecvHook(WebActionHook):
def hook_function(_self, socket, buffer, size):
_self.socket = socket
success_size = _self.original(socket, buffer, size)
if success_size:
self.recv_decoder.store_data(bytearray(buffer[:success_size]).copy())
return success_size
am = AddressManager(self.storage.data, self.logger)
self.recv_hook1 = RecvHook(am.get('recv', scan_pattern, recv_sig), True)
self.recv_hook2 = RecvHook(am.get('recv2', find_recv2, sig2), True)
self.send_hook1 = SendHook(am.get('send', scan_pattern, send_sig), True)
self.send_hook2 = SendHook(am.get('send2', find_send2, sig2), True)
self.storage.save()
self.send_counter = Counter()
self.wait_response = dict()
self.response_data = dict()
self.makeups = dict()
self.register_api('XivNetwork', type('obj', (object,), {
'register_makeup': self.register_makeup,
'unregister_makeup': self.unregister_makeup,
'send_messages': self.send_messages,
}))
def register_makeup(self, opcode, call: callable):
opcode = get_send_opcode(opcode)
if opcode not in self.makeups:
self.makeups[opcode] = set()
self.makeups[opcode].add(call)
def unregister_makeup(self, opcode, call: callable):
try:
self.makeups[get_send_opcode(opcode)].remove(call)
except KeyError:
pass
def makeup_data(self, data: bytearray) -> bytearray:
try:
temp = extract_single(data)
if temp is None: return data
data_header, messages = temp
new_messages = []
for msg in messages:
if len(msg) >= header_size:
msg_header = ServerMessageHeader.from_buffer(msg)
if msg_header.msg_type in self.makeups:
for call in self.makeups[msg_header.msg_type]:
try:
msg_header2, msg2 = call(msg_header, msg[header_size:])
if msg_header2 is None:
msg = None
break
msg = bytearray(msg_header2) + msg2
except Exception:
self.logger.error("error in makeup data:\n", format_exc())
if msg is not None:
new_messages.append(msg)
if not new_messages: return bytearray()
new_data = pack_single(data_header, new_messages)
return data if new_data is None else new_data
except Exception:
self.logger.error("error in makeup data:\n", format_exc())
return data
def process_recv_msg(self, msg_time, msg):
if len(msg) < header_size: return
header = ServerMessageHeader.from_buffer(msg)
if header.msg_type not in recv_events_classes:
recv_events_classes[header.msg_type] = type(
f"NetworkRecv{header.msg_type}RawEvent",
(RecvRawEvent,),
{'id': f'network/recv/{header.msg_type}'}
)
raw_msg = msg[header_size:]
process_event(recv_events_classes[header.msg_type](msg_time, header, raw_msg))
event = (recv_processors[header.msg_type] if header.msg_type in recv_processors else UnkRecvRawEvent)(msg_time, header, raw_msg)
if header.msg_type in self.wait_response:
waitings = self.wait_response[header.msg_type]
for waiting in waitings.copy():
try:
is_response = waiting[1] is None or waiting[1](event)
except Exception as e:
self.logger.error(f"error occurred in response: {waiting[1]},an exception will be returned:\n{format_exc()}")
self.response_data[waiting[0]] = e
waitings.remove(waiting)
else:
if is_response:
self.response_data[waiting[0]] = event
waitings.remove(waiting)
if not waitings:
del self.wait_response[header.msg_type]
process_event(event)
def process_send_msg(self, msg_time, msg):
if len(msg) < header_size: return
header = ServerMessageHeader.from_buffer(msg)
for key in msg_header_keys.keys():
msg_header_keys[key] = getattr(header, key)
if header.msg_type not in send_events_classes:
send_events_classes[header.msg_type] = type(
f"NetworkSend{header.msg_type}RawEvent",
(SendRawEvent,),
{'id': f'network/send/{header.msg_type}'}
)
raw_msg = msg[header_size:]
process_event(send_events_classes[header.msg_type](msg_time, header, raw_msg))
event = (send_processors[header.msg_type] if header.msg_type in send_processors else UnkSendRawEvent)(msg_time, header, raw_msg)
if event is not None: process_event(event)
def get_response(self, response_id: int):
if response_id in self.response_data:
temp = self.response_data[response_id]
del self.response_data[response_id]
return temp
def send_messages(self,
messages: list[tuple[AcceptOpcode, bytearray]],
process=True,
response_opcode: AcceptOpcode = None,
response_statement: Callable[[any], bool] = None,
response_timeout: float = 5.,
response_period: float = 0.01):
me = api.XivMemory.actor_table.get_me()
me_id = me.id if me is not None else 0
_messages = []
for opcode, msg in messages:
_messages.append(bytearray(ServerMessageHeader(
msg_length=len(msg) + header_size,
actor_id=me_id,
msg_type=get_send_opcode(opcode),
sec=int(time.time()),
**msg_header_keys,
)) + msg)
response_id = self.send_counter.get()
if response_opcode is not None:
self.wait_response.setdefault(get_recv_opcode(response_opcode), set()).add((response_id, response_statement))
success_len = self.send_hook1.send(pack_single(None, _messages), process)
if not success_len:
raise Exception("Failed to send messages")
if response_opcode is not None:
res = wait_until(lambda: self.get_response(response_id), timeout=response_timeout, period=response_period)
if isinstance(res, Exception):
raise res
else:
return res
def _start(self):
self.create_mission(self.recv_decoder.process, limit_sec=-1)
self.create_mission(self.send_decoder.process, limit_sec=-1)
def _onunload(self):
self.send_decoder.stop_process()
self.recv_decoder.stop_process()