fmp_server.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908
  1. #! /usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # SPDX-FileCopyrightText: Copyright (C) 2021-2023 MH3SP Server Project
  4. # SPDX-License-Identifier: AGPL-3.0-or-later
  5. """Monster Hunter FMP server."""
  6. import struct
  7. import mh.pat_item as pati
  8. from mh.constants import PatID4
  9. from mh.pat import PatRequestHandler, PatServer
  10. from other.utils import hexdump, server_base, server_main, to_str
  11. class FmpServer(PatServer):
  12. """Basic FMP server class."""
  13. pass
  14. class FmpRequestHandler(PatRequestHandler):
  15. """Basic FMP server request handler class."""
  16. def recvAnsConnection(self, packet_id, data, seq):
  17. """AnsConnection packet."""
  18. connection_data = pati.ConnectionData.unpack(data)
  19. self.server.debug("Connection: {!r}".format(connection_data))
  20. self.sendNtcLogin(3, connection_data, seq)
  21. def sendAnsLayerDown(self, layer_id, layer_set, seq):
  22. """AnsLayerDown packet.
  23. ID: 64140200
  24. JP: レイヤダウン返答
  25. TR: Layer down response
  26. """
  27. self.session.layer_down(layer_id)
  28. if self.session.layer == 1: # Gate
  29. user = pati.LayerUserInfo()
  30. user.capcom_id = pati.String(self.session.capcom_id)
  31. user.hunter_name = pati.String(self.session.hunter_name)
  32. user.stats = pati.Binary(self.session.hunter_info.pack())
  33. data = pati.lp2_string(self.session.capcom_id)
  34. data += user.pack()
  35. self.server.layer_broadcast(self.session, PatID4.NtcLayerIn,
  36. data, seq)
  37. data = struct.pack(">H", layer_id)
  38. self.send_packet(PatID4.AnsLayerDown, data, seq)
  39. def recvReqUserBinarySet(self, packet_id, data, seq):
  40. """ReqUserBinarySet packet.
  41. ID: 66310100
  42. JP: ユーザ表示用バイナリ設定要求
  43. TR: Binary setting request for user display
  44. The game sends the updated user display binary settings to the server.
  45. Examples:
  46. - Online > Settings > Profile
  47. - Online > Settings > Status Indicator
  48. """
  49. offset, length = struct.unpack_from(">IH", data)
  50. binary = pati.unpack_lp2_string(data, 4)
  51. self.session.hunter_info.unpack(data[6:], length, offset)
  52. self.sendAnsUserBinarySet(offset, binary, seq)
  53. def sendAnsUserBinarySet(self, unk1, profile_info, seq):
  54. """AnsUserBinarySet packet.
  55. ID: 66310200
  56. JP: ユーザ表示用バイナリ設定返答
  57. TR: Binary setting reply for user display
  58. TODO: Properly handle binary settings.
  59. """
  60. self.send_packet(PatID4.AnsUserBinarySet, b"", seq)
  61. def recvReqLayerUserList(self, packet_id, data, seq):
  62. """ReqLayerUserList packet.
  63. ID: 64630100
  64. JP: レイヤ同期ユーザリスト要求
  65. TR: Layer sync user list request
  66. """
  67. count, = struct.unpack_from(">B", data)
  68. unk = struct.unpack_from(">"+count * "B", data, 1)
  69. self.sendAnsLayerUserList(unk, seq)
  70. def sendAnsLayerUserList(self, unk, seq):
  71. """AnsLayerUserList packet.
  72. ID: 64630200
  73. JP: レイヤ同期ユーザリスト返答
  74. TR: Layer sync user list response
  75. """
  76. players = self.session.get_layer_players()
  77. with players.lock():
  78. count = len(players)
  79. data = struct.pack(">I", count)
  80. for _, player in players:
  81. user = pati.LayerUserInfo()
  82. user.capcom_id = pati.String(player.capcom_id)
  83. user.hunter_name = pati.String(player.hunter_name)
  84. user.stats = pati.Binary(player.hunter_info.pack())
  85. # TODO: Other fields?
  86. data += user.pack()
  87. self.send_packet(PatID4.AnsLayerUserList, data, seq)
  88. def recvReqLayerHost(self, packet_id, data, seq):
  89. """ReqLayerHost packet.
  90. ID: 64410100
  91. JP: レイヤのホスト者要求
  92. TR: Layer host request
  93. """
  94. self.sendAnsLayerHost(data, seq)
  95. def sendAnsLayerHost(self, unk_data, seq):
  96. """AnsLayerHost packet.
  97. ID: 64410200
  98. JP: レイヤのホスト者返答
  99. TR: Layer host response
  100. """
  101. city = self.session.get_city()
  102. leader = city.leader
  103. assert leader != self.session
  104. self.server.debug("ReqLayerHost: Req ({}, {}) Host ({}, {})".format(
  105. self.session.capcom_id, self.session.hunter_name,
  106. leader.capcom_id, leader.hunter_name))
  107. data = unk_data
  108. data += pati.lp2_string(leader.capcom_id)
  109. data += pati.lp2_string(leader.hunter_name)
  110. self.send_packet(PatID4.AnsLayerHost, data, seq)
  111. # Notify the city's players of the new player
  112. user = pati.LayerUserInfo()
  113. user.capcom_id = pati.String(self.session.capcom_id)
  114. user.hunter_name = pati.String(self.session.hunter_name)
  115. user.stats = pati.Binary(self.session.hunter_info.pack())
  116. data = pati.lp2_string(self.session.capcom_id)
  117. data += user.pack()
  118. self.server.layer_broadcast(self.session, PatID4.NtcLayerIn,
  119. data, seq)
  120. def recvNtcLayerUserPosition(self, packet_id, data, seq):
  121. """NtcLayerUserPosition packet.
  122. ID: 64711000
  123. JP: レイヤゲームポジション受信通知
  124. TR: Layer game position reception notification
  125. """
  126. self.sendNtcLayerUserPosition(data, seq)
  127. def sendNtcLayerUserPosition(self, fo, seq):
  128. """NtcLayerUserPosition packet.
  129. ID: 64711000
  130. JP: レイヤゲームポジション通知
  131. TR: Layer game position notification
  132. """
  133. data = pati.lp2_string(self.session.capcom_id)
  134. data += fo
  135. self.server.layer_broadcast(self.session, PatID4.NtcLayerUserPosition,
  136. data, seq)
  137. def recvNtcLayerBinary(self, packet_id, data, seq):
  138. """NtcLayerBinary packet.
  139. ID: 64701000
  140. JP: レイヤユーザ用バイナリ通知
  141. TR: Binary notifications for layer users
  142. """
  143. with pati.Unpacker(data, check=False) as unpacker:
  144. sender_blank = unpacker.LayerUserInfo() # noqa: F841
  145. unk_data = data[unpacker.offset:]
  146. self.sendNtcLayerBinary(unk_data, seq)
  147. def sendNtcLayerBinary(self, unk_data, seq):
  148. """NtcLayerBinary packet.
  149. ID: 64701000
  150. JP: レイヤユーザ用バイナリ送信
  151. TR: Binary transmission for layer users
  152. """
  153. sender = pati.LayerBinaryInfo()
  154. sender.unk_long_0x01 = pati.Long(0x12345678)
  155. sender.capcom_id = pati.String(self.session.capcom_id)
  156. sender.hunter_name = pati.String(self.session.hunter_name)
  157. self.server.debug("NtcLayerBinary: From ({}, {})".format(
  158. self.session.capcom_id, self.session.hunter_name))
  159. data = pati.lp2_string(self.session.capcom_id)
  160. data += sender.pack()
  161. data += unk_data
  162. self.server.layer_broadcast(self.session, PatID4.NtcLayerBinary,
  163. data, seq)
  164. def recvNtcLayerBinary2(self, packet_id, data, seq):
  165. """NtcLayerBinary packet.
  166. ID: 64751000
  167. JP: レイヤユーザ用バイナリ通知 (相手指定)
  168. TR: Binary notification for layer users (specify the other party)
  169. """
  170. with pati.Unpacker(data, check=False) as unpacker:
  171. partner_id = to_str(unpacker.lp2_string())
  172. binary_info = unpacker.LayerBinaryInfo() # noqa: F841
  173. unk_data = data[unpacker.offset:]
  174. self.server.debug("NtcLayerBinary2: From ({}, {})\n{}".format(
  175. self.session.capcom_id, self.session.hunter_name,
  176. hexdump(unk_data)))
  177. self.sendNtcLayerBinary2(partner_id, unk_data, seq)
  178. def sendNtcLayerBinary2(self, partner_id, unk_data, seq):
  179. """NtcLayerBinary packet.
  180. ID: 64751000
  181. JP: レイヤユーザ用バイナリ通知 (相手指定)
  182. TR: Binary transmission for layer users (specify the other party)
  183. """
  184. city = self.session.get_city()
  185. with city.lock():
  186. partner_session = city.players.find_by_capcom_id(partner_id)
  187. if partner_session is None:
  188. self.server.error("sendNtcLayerBinary2: {} not found".format(
  189. partner_id
  190. ))
  191. return
  192. data = pati.lp2_string(self.session.capcom_id)
  193. self_data = pati.LayerBinaryInfo()
  194. self_data.capcom_id = pati.String(self.session.capcom_id)
  195. self_data.hunter_name = pati.String(self.session.hunter_name)
  196. data += self_data.pack()
  197. data += unk_data
  198. handler = self.server.get_pat_handler(partner_session)
  199. if handler:
  200. handler.try_send_packet(PatID4.NtcLayerBinary2, data, seq)
  201. def recvReqLayerUp(self, packet_id, data, seq):
  202. """ReqLayerUp packet.
  203. ID: 64150100
  204. JP: レイヤアップ要求
  205. TR: Layer up request
  206. Sent by the game when leaving the gate via the entrance:
  207. - Relocate > Select Server
  208. """
  209. self.sendAnsLayerUp(data, seq)
  210. def recvReqUserSearchInfoMine(self, packet_id, data, seq):
  211. """ReqUserSearchInfoMine packet.
  212. ID: 66370100
  213. JP: ユーザ検索データ要求(自分)
  214. TR: User search data request (self)
  215. """
  216. search_info = pati.UserSearchInfo.unpack(data)
  217. self.server.debug("SearchInfo: {!r}".format(search_info))
  218. self.sendAnsUserSearchInfoMine(search_info, seq)
  219. def sendAnsUserSearchInfoMine(self, search_info, seq):
  220. """AnsUserSearchInfoMine packet.
  221. ID: 66370200
  222. JP: ユーザ検索データ返答(自分)
  223. TR: User search data reply (self)
  224. TODO: Figure out what to do with it.
  225. Maybe prevent the same profile to be connected twice simultaneously.
  226. """
  227. info_mine_0x0f = int(hash(self.session.capcom_id)) & 0xffffffff
  228. info_mine_0x10 = int(hash(self.session.capcom_id[::-1])) & 0xffffffff
  229. self.server.debug("SearchInfoMine: {:08X} {:08X}".format(
  230. info_mine_0x0f, info_mine_0x10))
  231. search_info = pati.UserSearchInfo()
  232. # This fields are used to identify a user.
  233. # Specifically when a client is deserializing data from the packets
  234. # `NtcLayerBinary` and `NtcLayerBinary2`
  235. # TODO: Proper field value and name
  236. search_info.info_mine_0x0f = pati.Long(info_mine_0x0f)
  237. search_info.info_mine_0x10 = pati.Long(info_mine_0x10)
  238. data = search_info.pack()
  239. self.send_packet(PatID4.AnsUserSearchInfoMine, data, seq)
  240. def recvReqCircleListLayer(self, packet_id, data, seq):
  241. """ReqCircleListLayer packet.
  242. ID: 65270100
  243. JP: サークル同期リスト要求 (レイヤ)
  244. TR: Circle sync list request (layer)
  245. """
  246. self.sendAnsCircleListLayer(data, seq)
  247. def sendAnsCircleListLayer(self, data, seq):
  248. """AnsCircleListLayer packet.
  249. ID: 65270200
  250. JP: サークル同期リスト返答 (レイヤ)
  251. TR: Circle sync list response (layer)
  252. """
  253. unk = 0
  254. count = 0
  255. data = b''
  256. city = self.session.get_city()
  257. with city.lock():
  258. for i, circle in enumerate(city.circles):
  259. with circle.lock():
  260. if not circle.is_empty():
  261. data += pati.CircleInfo.pack_from(circle, i+1)
  262. count += 1
  263. data = struct.pack(">II", unk, count) + data
  264. self.send_packet(PatID4.AnsCircleListLayer, data, seq)
  265. def recvReqCircleCreate(self, packet_id, data, seq):
  266. """ReqCircleCreate packet.
  267. ID: 65010100
  268. JP: サークル作成要求
  269. TR: Circle creation request
  270. """
  271. circle_info = pati.CircleInfo.unpack(data)
  272. circle_optional_fields = pati.unpack_optional_fields(
  273. data, len(circle_info.pack()))
  274. self.server.debug("CircleCreate: {!r}, {!r}".format(
  275. circle_info, circle_optional_fields))
  276. city = self.session.get_city()
  277. circle, circle_index = city.get_first_empty_circle()
  278. if circle is None:
  279. self.sendAnsAlert(
  280. PatID4.AnsCircleCreate,
  281. "<LF=8><BODY><CENTER>No Quest Slot Available<END>",
  282. seq
  283. )
  284. return
  285. with circle.lock():
  286. circle.leader = self.session
  287. if "password" in circle_info:
  288. circle.password = pati.unpack_string(circle_info.password)
  289. if "remarks" in circle_info:
  290. circle.remarks = pati.unpack_string(circle_info.remarks)
  291. if "unk_byte_0x0e" in circle_info:
  292. circle.unk_byte_0x0e = pati.unpack_byte(
  293. circle_info.unk_byte_0x0e
  294. )
  295. circle.reset_players(pati.unpack_long(circle_info.capacity))
  296. circle.players.add(self.session)
  297. self.session.join_circle(circle_index)
  298. # Extra fields
  299. # - field_id 0x01: Party capacity
  300. # - field_id 0x02: Quest ID
  301. for field_id, value in circle_optional_fields:
  302. if field_id == 0x02: # QuestId
  303. circle.quest_id = value
  304. # Notify every city's player
  305. self.sendNtcCircleListLayerChange(circle, circle_index + 1, seq)
  306. self.sendAnsCircleCreate(circle_index+1, seq)
  307. def sendAnsCircleCreate(self, circle_index, seq):
  308. """AnsCircleCreate packet.
  309. ID: 65010200
  310. JP: サークル作成返答
  311. TR: Circle creation response
  312. """
  313. data = struct.pack(">I", circle_index)
  314. self.send_packet(PatID4.AnsCircleCreate, data, seq)
  315. def recvReqCircleMatchOptionSet(self, packet_id, data, seq):
  316. """ReqCircleMatchOptionSet packet.
  317. ID: 65100100
  318. JP: マッチングオプション設定要求
  319. TR: Match option settings request
  320. """
  321. options = pati.CircleUserData.unpack(data)
  322. self.server.debug("MatchOptionSet: {!r}".format(options))
  323. self.sendAnsCircleMatchOptionSet(options, seq)
  324. def sendAnsCircleMatchOptionSet(self, options, seq):
  325. """AnsCircleMatchOptionSet packet.
  326. ID: 65100200
  327. JP: マッチングオプション設定返答
  328. TR: Match option settings response
  329. """
  330. is_standby = 'is_standby' in options and \
  331. pati.unpack_byte(options.is_standby) == 1
  332. self.session.set_circle_standby(is_standby)
  333. circle = self.session.get_circle()
  334. options.capcom_id = pati.String(self.session.capcom_id)
  335. options.hunter_name = pati.String(self.session.hunter_name)
  336. options.player_index = pati.Byte(circle.players.index(self.session)+1)
  337. ntc_data = options.pack()
  338. self.server.circle_broadcast(circle, PatID4.NtcCircleMatchOptionSet,
  339. ntc_data, seq, self.session)
  340. self.send_packet(PatID4.AnsCircleMatchOptionSet, b"", seq)
  341. def recvReqCircleInfo(self, packet_id, data, seq):
  342. """ReqCircleInfo packet.
  343. ID: 65020100
  344. JP: サークルデータ取得要求
  345. TR: Circle data acquisition request
  346. """
  347. circle_index, = struct.unpack_from(">I", data)
  348. unk2 = data[4:4+0xd]
  349. unk3 = data[4+0xd:]
  350. self.server.debug("ReqCircleInfo: {}, {!r}, {!r}".format(
  351. circle_index, unk2, unk3))
  352. self.sendAnsCircleInfo(circle_index, unk2, unk3, seq)
  353. def sendAnsCircleInfo(self, circle_index, unk2, unk3, seq):
  354. """AnsCircleInfo packet.
  355. ID: 65020200
  356. JP: サークルデータ取得返答
  357. TR: Circle data acquisition reply
  358. """
  359. city = self.session.get_city()
  360. # TODO: Verify circle index
  361. circle = city.circles[circle_index-1]
  362. data = struct.pack(">I", circle_index)
  363. data += pati.CircleInfo.pack_from(circle, circle_index)
  364. self.send_packet(PatID4.AnsCircleInfo, data, seq)
  365. def recvReqCircleJoin(self, packet_id, data, seq):
  366. """ReqCircleJoin packet.
  367. ID: 65030100
  368. JP: サークルイン要求
  369. TR: Circle-in request
  370. """
  371. circle_index, = struct.unpack_from(">I", data)
  372. # In this circle info, the only possible field filled are:
  373. # unk_long_0x0b, has_password, password
  374. circle_info = pati.CircleInfo.unpack(data, 4)
  375. city = self.session.get_city()
  376. with city.lock():
  377. if circle_index-1 >= len(city.circles):
  378. self.sendAnsAlert(
  379. PatID4.AnsCircleJoin,
  380. "<LF=8><BODY><CENTER>Invalid Quest Slot<END>",
  381. seq
  382. )
  383. return
  384. circle = city.circles[circle_index-1]
  385. with circle.lock():
  386. if circle.has_password() and (
  387. "password" not in circle_info or
  388. pati.unpack_string(circle_info.password) != circle.password
  389. ):
  390. self.sendAnsAlert(PatID4.AnsCircleJoin,
  391. "<LF=8><BODY><CENTER>Wrong Password!<END>",
  392. seq)
  393. return
  394. player_index = circle.players.add(self.session)
  395. if player_index == -1:
  396. self.sendAnsAlert(PatID4.AnsCircleJoin,
  397. "<LF=8><BODY><CENTER>Quest is full!<END>",
  398. seq)
  399. return
  400. self.session.join_circle(circle_index-1)
  401. self.sendAnsCircleJoin(circle_index, player_index+1, seq)
  402. def sendAnsCircleJoin(self, circle_index, player_index, seq):
  403. """AnsCircleJoin packet.
  404. ID: 65030200
  405. JP: サークルイン返答
  406. TR: Circle-in reply
  407. """
  408. assert circle_index > 0 and player_index > 0
  409. data = struct.pack(">IB", circle_index, player_index)
  410. self.send_packet(PatID4.AnsCircleJoin, data, seq)
  411. city = self.session.get_city()
  412. circle = city.circles[circle_index-1]
  413. ntc_data = struct.pack(">I", circle_index)
  414. ntc_data += pati.lp2_string(self.session.capcom_id)
  415. ntc_data += pati.lp2_string(self.session.hunter_name)
  416. # If state == 2 it increment a variable on NetworkSessionManagerPat
  417. state = 0
  418. ntc_data += struct.pack(">BB", player_index, state)
  419. self.server.circle_broadcast(circle, PatID4.NtcCircleJoin, ntc_data,
  420. seq, self.session)
  421. def recvReqCircleUserList(self, packet_id, data, seq):
  422. """ReqCircleUserList packet.
  423. ID: 65600100
  424. JP: サークル同期ユーザリスト要求
  425. TR: Circle sync user list request
  426. """
  427. # Ignore packet data
  428. self.sendAnsCircleUserList(seq)
  429. def sendAnsCircleUserList(self, seq):
  430. """AnsCircleUserList packet.
  431. ID: 65600200
  432. JP: サークル同期ユーザリスト返答
  433. TR: Circle sync user list reply
  434. """
  435. circle = self.session.get_circle()
  436. with circle.lock(), circle.players.lock():
  437. data = struct.pack(">I", circle.get_population())
  438. for i, player in circle.players:
  439. circle_user_data = pati.CircleUserData()
  440. circle_user_data.is_standby = pati.Byte(
  441. int(player.is_circle_standby()))
  442. circle_user_data.player_index = pati.Byte(i+1)
  443. circle_user_data.capcom_id = pati.String(player.capcom_id)
  444. circle_user_data.hunter_name = pati.String(player.hunter_name)
  445. data += circle_user_data.pack()
  446. self.send_packet(PatID4.AnsCircleUserList, data, seq)
  447. def recvReqCircleHost(self, packet_id, data, seq):
  448. """ReqCircleHost packet.
  449. ID: 65410100
  450. JP: サークルのホスト者要求
  451. TR: Circle host request
  452. """
  453. circle_index, = struct.unpack_from(">I", data)
  454. self.sendAnsCircleHost(circle_index, seq)
  455. def sendAnsCircleHost(self, circle_index, seq):
  456. """AnsCircleHost packet.
  457. ID: 65410200
  458. JP: サークルのホスト者返答
  459. TR: Circle host response
  460. """
  461. city = self.session.get_city()
  462. circle = city.circles[circle_index-1]
  463. with circle.lock():
  464. leader_index = circle.players.index(circle.leader)
  465. if leader_index == -1:
  466. self.sendAnsAlert(
  467. PatID4.AnsCircleHost,
  468. "<LF=8><BODY><CENTER>Unknown Quest Leader<END>",
  469. seq
  470. )
  471. return
  472. data = struct.pack(">IB", circle_index, leader_index+1)
  473. data += pati.lp2_string(circle.leader.capcom_id)
  474. data += pati.lp2_string(circle.leader.hunter_name)
  475. self.send_packet(PatID4.AnsCircleHost, data, seq)
  476. def recvNtcCircleBinary(self, packet_id, data, seq):
  477. """NtcCircleBinary packet.
  478. ID: 65701000
  479. JP: サークルバイナリ通知
  480. TR: Circle binary notification
  481. """
  482. circle_index, = struct.unpack_from(">I", data)
  483. sender_blank = pati.LayerUserInfo.unpack(data, 4)
  484. unk_data = data[len(sender_blank.pack())+4:]
  485. self.sendNtcCircleBinary(circle_index, unk_data, seq)
  486. def sendNtcCircleBinary(self, circle_index, unk_data, seq):
  487. """NtcCircleBinary packet.
  488. ID: 65701000
  489. JP: サークルバイナリ送信
  490. TR: Circle binary transmission
  491. """
  492. sender = pati.LayerBinaryInfo()
  493. sender.unk_long_0x01 = pati.Long(0x12345678)
  494. sender.capcom_id = pati.String(self.session.capcom_id)
  495. sender.hunter_name = pati.String(self.session.hunter_name)
  496. self.server.debug("NtcCircleBinary: From ({}, {})".format(
  497. self.session.capcom_id, self.session.hunter_name))
  498. data = struct.pack(">I", circle_index)
  499. data += pati.lp2_string(self.session.capcom_id)
  500. data += sender.pack()
  501. data += unk_data
  502. city = self.session.get_city()
  503. circle = city.circles[circle_index-1]
  504. self.server.circle_broadcast(circle, PatID4.NtcCircleBinary, data,
  505. seq, self.session)
  506. def recvNtcCircleBinary2(self, packet_id, data, seq):
  507. """NtcCircleBinary2 packet.
  508. ID: 65711000
  509. JP: サークルバイナリ通知 (相手指定)
  510. TR: Circle binary notification (specified by the other party)
  511. """
  512. with pati.Unpacker(data, check=False) as unpacker:
  513. circle_index, = unpacker.struct(">I")
  514. partner_id = to_str(unpacker.lp2_string())
  515. binary_info = unpacker.LayerBinaryInfo() # noqa: F841
  516. unk_data = data[unpacker.offset:]
  517. self.server.debug("NtcCircleBinary2: From ({}, {})\n{}".format(
  518. self.session.capcom_id, self.session.hunter_name,
  519. hexdump(unk_data)))
  520. self.sendNtcCircleBinary2(circle_index, partner_id, unk_data, seq)
  521. def sendNtcCircleBinary2(self, circle_index, partner_id, unk_data, seq):
  522. """NtcCircleBinary2 packet.
  523. ID: 65711000
  524. JP: サークルバイナリ送信 (相手指定)
  525. TR: Circle binary transmission (specified by the other party)
  526. """
  527. city = self.session.get_city()
  528. circle = city.circles[circle_index-1]
  529. with circle.lock():
  530. partner_session = circle.players.find_by_capcom_id(partner_id)
  531. if partner_session is None:
  532. self.server.error("sendNtcCircleBinary2: {} not found".format(
  533. partner_id
  534. ))
  535. return
  536. data = struct.pack(">I", circle_index)
  537. data += pati.lp2_string(self.session.capcom_id)
  538. self_data = pati.LayerBinaryInfo()
  539. self_data.capcom_id = pati.String(self.session.capcom_id)
  540. self_data.hunter_name = pati.String(self.session.hunter_name)
  541. data += self_data.pack()
  542. data += unk_data
  543. handler = self.server.get_pat_handler(partner_session)
  544. if handler:
  545. handler.try_send_packet(PatID4.NtcCircleBinary2, data, seq)
  546. def recvReqCircleLeave(self, packet_id, data, seq):
  547. """ReqCircleLeave packet.
  548. ID: 65040100
  549. JP: サークルアウト要求
  550. TR: Circle out request
  551. """
  552. circle_index, = struct.unpack(">I", data)
  553. self.sendAnsCircleLeave(circle_index, seq)
  554. def sendAnsCircleLeave(self, circle_index, seq):
  555. """AnsCircleLeave packet.
  556. ID: 65040200
  557. JP: サークルアウト返答
  558. TR: Circle out reply
  559. """
  560. self.notify_circle_leave(circle_index, seq)
  561. data = struct.pack(">I", circle_index)
  562. self.send_packet(PatID4.AnsCircleLeave, data, seq)
  563. def sendNtcCircleBreak(self, circle, seq):
  564. """NtcCircleBreak packet.
  565. ID: 65051000
  566. JP: サークル解散通知
  567. TR: Circle dissolution notice
  568. """
  569. # Unknown field but it doesn't matter because the client ignores it
  570. unk1 = 0
  571. data = struct.pack(">I", unk1)
  572. self.server.circle_broadcast(circle, PatID4.NtcCircleBreak, data, seq,
  573. self.session)
  574. def sendNtcCircleKick(self, circle, seq):
  575. """NtcCircleKick packet.
  576. ID: 65351000
  577. JP: サークルからキック通知
  578. TR: Kick notification from the circle
  579. """
  580. # Unknown field but it doesn't matter because the client ignores them
  581. unk1 = 0
  582. unk2 = b""
  583. data = struct.pack(">B", unk1)
  584. data += pati.lp2_string(unk2)
  585. self.server.circle_broadcast(circle, PatID4.NtcCircleKick, data, seq,
  586. self.session)
  587. def recvReqCircleInfoSet(self, packet_id, data, seq):
  588. """ReqCircleInfoSet packet.
  589. ID: 65200100
  590. JP: サークルデータ設定要求
  591. TR: Circle data setting request
  592. """
  593. circle_index, = struct.unpack_from(">I", data)
  594. circle_info = pati.CircleInfo.unpack(data, 4)
  595. offset = 4 + len(circle_info.pack())
  596. optional_fields = pati.unpack_optional_fields(data, offset)
  597. self.server.debug("CircleInfo: {!r}".format(circle_info))
  598. city = self.session.get_city()
  599. circle = city.circles[circle_index-1]
  600. self.sendAnsCircleInfoSet(circle_index, circle_info, optional_fields,
  601. seq)
  602. self.sendNtcCircleListLayerChange(circle, circle_index, seq)
  603. def sendAnsCircleInfoSet(self, circle_index, circle_info, optional_fields,
  604. seq):
  605. """AnsCircleInfoSet packet.
  606. ID: 65200200
  607. JP: サークルデータ設定返答
  608. TR: Circle data setting reply
  609. """
  610. ntc_data = struct.pack(">I", circle_index)
  611. ntc_data += circle_info.pack()
  612. ntc_data += pati.pack_optional_fields(optional_fields)
  613. city = self.session.get_city()
  614. circle = city.circles[circle_index-1]
  615. self.server.circle_broadcast(circle, PatID4.NtcCircleInfoSet, ntc_data,
  616. seq, self.session)
  617. data = struct.pack(">I", circle_index)
  618. self.send_packet(PatID4.AnsCircleInfoSet, data, seq)
  619. def recvReqCircleMatchStart(self, packet_id, data, seq):
  620. """ReqCircleMatchStart packet.
  621. ID: 65120100
  622. JP: マッチング開始要求
  623. TR: Matching start request
  624. """
  625. self.sendAnsCircleMatchStart(seq)
  626. def sendAnsCircleMatchStart(self, seq):
  627. """AnsCircleMatchStart packet.
  628. ID: 65120200
  629. JP: マッチング開始返答
  630. TR: Matching start reply
  631. """
  632. self.sendNtcCircleMatchStart(seq)
  633. self.send_packet(PatID4.AnsCircleMatchStart, b"", seq)
  634. def sendNtcCircleMatchStart(self, seq):
  635. """NtcCircleMatchStart packet.
  636. ID: 65121000
  637. JP: マッチング開始通知
  638. TR: Matching start notification
  639. """
  640. circle = self.session.get_circle()
  641. with circle.lock():
  642. circle.departed = True
  643. count = 0
  644. changed = False
  645. data = b''
  646. for i, player in circle.players:
  647. if player.is_circle_standby():
  648. data += struct.pack(">B", i+1)
  649. data += pati.lp2_string(player.capcom_id)
  650. data += pati.lp2_string(b"\1")
  651. data += struct.pack(">H", 21) # TODO: Field??
  652. player.set_in_quest()
  653. count += 1
  654. else:
  655. # Client ignore field
  656. ntc_circle_kick = struct.pack(">B", 0) + \
  657. pati.lp2_string('')
  658. handler = self.server.get_pat_handler(player)
  659. handler.try_send_packet(
  660. PatID4.NtcCircleKick, ntc_circle_kick, seq
  661. )
  662. handler.notify_circle_leave(
  663. player.local_info['circle_id'] + 1, seq
  664. )
  665. changed = True
  666. data = struct.pack(">I", count)+data
  667. data = struct.pack(">H", len(data))+data
  668. data += struct.pack(">I", 1) # Field??
  669. self.server.circle_broadcast(
  670. circle, PatID4.NtcCircleMatchStart, data, seq
  671. )
  672. if changed:
  673. circle_index = circle.parent.circles.index(circle) + 1
  674. self.sendNtcCircleListLayerChange(circle, circle_index, seq)
  675. def recvReqCircleMatchEnd(self, packet_id, data, seq):
  676. """ReqCircleMatchEnd packet.
  677. ID: 65130100
  678. JP: マッチング終了要求
  679. TR: Matching end request
  680. """
  681. unk, = struct.unpack_from(">B", data)
  682. # unk is a bolean, but is unknown what it represent
  683. self.sendAnsCircleMatchEnd(seq)
  684. def sendAnsCircleMatchEnd(self, seq):
  685. """AnsCircleMatchEnd packet.
  686. ID: 65130200
  687. JP: マッチング終了返答
  688. TR: Matching end reply
  689. """
  690. self.send_packet(PatID4.AnsCircleMatchEnd, b"", seq)
  691. def sendNtcCircleListLayerDelete(self, circle, seq):
  692. """NtcCircleListLayerDelete packet.
  693. ID: 65831000
  694. JP: サークル削除通知 (レイヤ)
  695. TR: Circle deletion notification (layer)
  696. It can be sent when leaving a circle (alongside AnsCircleLeave).
  697. TODO: Use it. This function is currently implemented but unused.
  698. """
  699. circle_index = circle.parent.circles.index(circle) + 1
  700. ntc_data = struct.pack(">I", circle_index)
  701. self.server.layer_broadcast(self.session,
  702. PatID4.NtcCircleListLayerDelete, ntc_data,
  703. seq, False)
  704. def sendNtcCircleListLayerChange(self, circle, circle_index, seq):
  705. """NtcCircleListLayerChange packet.
  706. ID: 65821000
  707. JP: サークル変更通知 (レイヤ)
  708. TR: Circle change notification (layer)
  709. """
  710. ntc_data = struct.pack(">I", circle_index)
  711. ntc_data += pati.CircleInfo.pack_from(circle, circle_index)
  712. self.server.layer_broadcast(self.session,
  713. PatID4.NtcCircleListLayerChange, ntc_data,
  714. seq, False)
  715. BASE = server_base("FMP", FmpServer, FmpRequestHandler)
  716. if __name__ == "__main__":
  717. server_main(*BASE)