pat_item.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095
  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 PAT item module."""
  6. import struct
  7. from collections import OrderedDict
  8. from mh.constants import pad
  9. from other.utils import to_bytearray, get_config, get_ip, GenericUnpacker
  10. from mh.database import Server, Gate, City
  11. class ItemType:
  12. Custom = 0
  13. Byte = 1
  14. Word = 2
  15. Long = 3
  16. LongLong = 4
  17. String = 5
  18. Binary = 6
  19. Object = 7
  20. def lp_string(s):
  21. """1-byte length-prefixed string."""
  22. s = to_bytearray(s)
  23. return struct.pack(">B", len(s)) + s
  24. def unpack_lp_string(data, offset=0):
  25. """Unpack lp_string."""
  26. size, = struct.unpack_from(">B", data, offset)
  27. return data[offset+1:offset+1+size]
  28. def lp2_string(s):
  29. """2-bytes length-prefixed string."""
  30. s = to_bytearray(s)
  31. return struct.pack(">H", len(s)) + s
  32. def unpack_lp2_string(data, offset=0):
  33. """Unpack lp2_string."""
  34. size, = struct.unpack_from(">H", data, offset)
  35. return data[offset+2:offset+2+size]
  36. class Item(bytes):
  37. pass
  38. def pack_byte(b):
  39. """Pack PAT item byte."""
  40. return struct.pack(">BB", ItemType.Byte, b)
  41. def unpack_byte(data, offset=0):
  42. """Unpack PAT item byte."""
  43. item_type, value = struct.unpack_from(">BB", data, offset)
  44. if item_type != ItemType.Byte:
  45. raise AssertionError("Invalid type for byte item: {}".format(
  46. item_type
  47. ))
  48. return value
  49. def pack_optional_fields(info):
  50. """Pack optional fields (list of field_id and int value).
  51. Optional fields are retrieved after the following function calls:
  52. - getLayerData
  53. - getLayerUserInfo
  54. - getCircleInfo
  55. - getUserSearchInfo
  56. It follows a simple format:
  57. - field_id, a byte used as an identifier
  58. - has_value, a byte telling if it has a value
  59. - value: an integer with the field's value (only set if has_value is true)
  60. """
  61. data = struct.pack(">B", len(info))
  62. for field_id, value in info:
  63. has_value = value is not None
  64. data += struct.pack(">BB", field_id, int(has_value))
  65. if has_value:
  66. data += struct.pack(">I", value)
  67. return data
  68. def unpack_optional_fields(data, offset=0):
  69. """Unpack optional fields (list of field_id and int value).
  70. Optional fields are stored after the following function calls:
  71. - putLayerSet
  72. - putCircleInfo
  73. and is also used in PatInterface::sendReqUserSearchSet.
  74. It seems related to the city info. If correct:
  75. - field_id 0x01: ???
  76. - field_id 0x02: City's HR limit (if not applicable, 0xffffffff is set)
  77. - field_id 0x03: City's goal (if not applicable, 0xffffffff is set)
  78. - field_id 0x04: City's seeking
  79. """
  80. info = []
  81. count, = struct.unpack_from(">B", data, offset)
  82. offset += 1
  83. for _ in range(count):
  84. field_id, has_value = struct.unpack_from(">BB", data, offset)
  85. offset += 2
  86. if has_value:
  87. value, = struct.unpack_from(">I", data, offset)
  88. offset += 4
  89. else:
  90. value = None
  91. info.append((field_id, value))
  92. return info
  93. def pack_detailed_optional_fields(info):
  94. """Similar to optional fields but with more fields."""
  95. data = struct.pack(">II", info["circle"], len(info["data"]))
  96. for field_id, field_type, value in info["data"]:
  97. has_value = value is not None
  98. data += struct.pack(">BBB", field_id, int(has_value), field_type)
  99. if has_value:
  100. data += struct.pack(">I", value)
  101. return data
  102. def unpack_detailed_optional_fields(data, offset=0):
  103. """Similar to optional fields but with more fields.
  104. These fields are used by the following packets:
  105. - ReqLayerUserSearchHead
  106. - ReqLayerDetailSearchHead
  107. - ReqCircleSearchHead
  108. - ReqUserSearchHead
  109. Structure format:
  110. - unk_circle, set to 0 unless from sendReqCircleSearchHead
  111. - field_count, number of fields
  112. - array of fields
  113. Field structure:
  114. - field_id, a byte used as an identifier
  115. - has_value, a byte telling if it has a value
  116. - field_type?, an unknown byte which often holds the value 0x05
  117. - value, an integer with the field's value (only set if has_value is true)
  118. """
  119. info = []
  120. unk_circle, count = struct.unpack_from(">II", data, offset)
  121. offset += 8
  122. for _ in range(count):
  123. field_id, has_value, field_type = struct.unpack_from(">BBB",
  124. data, offset)
  125. offset += 3
  126. if has_value:
  127. value, = struct.unpack_from(">I", data, offset)
  128. offset += 4
  129. else:
  130. value = None
  131. info.append((field_id, field_type, value))
  132. return {"circle": unk_circle, "data": info}
  133. class Byte(Item):
  134. """PAT item byte class."""
  135. def __new__(cls, b):
  136. return Item.__new__(cls, pack_byte(b))
  137. def __repr__(self):
  138. return "Byte({!r})".format(unpack_byte(self))
  139. class ByteDec(Byte):
  140. """PAT item byte that will be decremented."""
  141. pass
  142. def pack_word(w):
  143. """Pack PAT item word."""
  144. return struct.pack(">BH", ItemType.Word, w)
  145. def unpack_word(data, offset=0):
  146. """Unpack PAT item word."""
  147. item_type, value = struct.unpack_from(">BH", data, offset)
  148. if item_type != ItemType.Word:
  149. raise AssertionError("Invalid type for word item: {}".format(
  150. item_type
  151. ))
  152. return value
  153. class Word(Item):
  154. """PAT item word class."""
  155. def __new__(cls, w):
  156. return Item.__new__(cls, pack_word(w))
  157. def __repr__(self):
  158. return "Word({!r})".format(unpack_word(self))
  159. class WordDec(Word):
  160. """PAT item word that will be decremented."""
  161. pass
  162. def pack_long(lg):
  163. """Pack PAT item long."""
  164. return struct.pack(">BI", ItemType.Long, lg)
  165. def unpack_long(data, offset=0):
  166. """Unpack PAT item long."""
  167. item_type, value = struct.unpack_from(">BI", data, offset)
  168. if item_type != ItemType.Long:
  169. raise AssertionError("Invalid type for long item: {}".format(
  170. item_type
  171. ))
  172. return value
  173. class Long(Item):
  174. """PAT item long class."""
  175. def __new__(cls, lg):
  176. return Item.__new__(cls, pack_long(lg))
  177. def __repr__(self):
  178. return "Long({!r})".format(unpack_long(self))
  179. def pack_longlong(q):
  180. """Pack PAT item long long."""
  181. return struct.pack(">BQ", ItemType.LongLong, q)
  182. def unpack_longlong(data, offset=0):
  183. """Unpack PAT item long long."""
  184. item_type, value = struct.unpack_from(">BQ", data, offset)
  185. if item_type != ItemType.LongLong:
  186. raise AssertionError("Invalid type for long long item: {}".format(
  187. item_type
  188. ))
  189. return value
  190. class LongLong(Item):
  191. """PAT item long long class."""
  192. def __new__(cls, q):
  193. return Item.__new__(cls, pack_longlong(q))
  194. def __repr__(self):
  195. return "LongLong({!r})".format(unpack_longlong(self))
  196. def pack_string(s):
  197. """Pack PAT item string."""
  198. return struct.pack(">B", ItemType.String) + lp2_string(s)
  199. def unpack_string(data, offset=0):
  200. """Unpack PAT item string."""
  201. item_type, length = struct.unpack_from(">BH", data, offset)
  202. if item_type != ItemType.String:
  203. raise AssertionError("Invalid type for string item: {}".format(
  204. item_type
  205. ))
  206. return data[offset+3:offset+3+length]
  207. class String(Item):
  208. """PAT item string class."""
  209. def __new__(cls, s):
  210. return Item.__new__(cls, pack_string(s))
  211. def __repr__(self):
  212. return "String({!r})".format(unpack_string(self))
  213. def pack_binary(s):
  214. """Pack PAT item binary."""
  215. s = to_bytearray(s)
  216. return struct.pack(">BH", ItemType.Binary, len(s)) + s
  217. def unpack_binary(data, offset=0):
  218. """Unpack PAT item binary."""
  219. item_type, length = struct.unpack_from(">BH", data, offset)
  220. if item_type != ItemType.Binary:
  221. raise AssertionError("Invalid type for binary item: {}".format(
  222. item_type
  223. ))
  224. return data[offset+3:offset+3+length]
  225. class Binary(Item):
  226. """PAT item binary class."""
  227. def __new__(cls, b):
  228. return Item.__new__(cls, pack_binary(b))
  229. def __repr__(self):
  230. return "Binary({!r})".format(repr(unpack_binary(self)))
  231. def unpack_any(data, offset=0):
  232. """Unpack PAT item."""
  233. item_type, = struct.unpack_from(">B", data, offset)
  234. handler = {
  235. ItemType.Byte: unpack_byte,
  236. ItemType.Word: unpack_word,
  237. ItemType.Long: unpack_long,
  238. ItemType.LongLong: unpack_longlong,
  239. ItemType.String: unpack_string,
  240. ItemType.Binary: unpack_binary
  241. }
  242. return item_type, handler[item_type](data, offset)
  243. class Custom(Item):
  244. """PAT custom item class."""
  245. def __new__(cls, b, item_type=b'\0'):
  246. return Item.__new__(cls, item_type + b)
  247. def __repr__(self):
  248. return "Custom({!r})".format(repr(self[1:]))
  249. class FallthroughBug(Custom):
  250. """Wordaround a fallthrough bug.
  251. After reading LayerData's field_0x17 it falls through a getItemAny call.
  252. It clobbers the next field by reading the field_id. Then, the game tries
  253. to read the next field which was clobbered.
  254. The workaround uses a dummy field_0xff which will be clobbered on purpose.
  255. """
  256. def __new__(cls):
  257. return Custom.__new__(cls, b"\xff", b"\xff")
  258. def pack_bytes(*args):
  259. """Pack bytes list."""
  260. return struct.pack(">B", len(args)) + bytearray(args)
  261. def unpack_bytes(data, offset=0):
  262. """Unpack bytes list."""
  263. count, = struct.unpack_from(">B", data, offset)
  264. return struct.unpack_from(">" + count * "B", data, offset+1)
  265. class PatData(OrderedDict):
  266. """Pat structure holding items."""
  267. FIELDS = (
  268. (1, "field_0x01"),
  269. (2, "field_0x02"),
  270. (3, "field_0x03"),
  271. (4, "field_0x04")
  272. )
  273. def __len__(self):
  274. return len(self.pack())
  275. def __repr__(self):
  276. items = [
  277. (index, value)
  278. for index, value in self.items()
  279. if value is not None
  280. ]
  281. return "{}({})".format(
  282. type(self).__name__,
  283. ", ".join(
  284. "{}={}".format(self.field_name(index), repr(value))
  285. for index, value in items
  286. )
  287. )
  288. def __getattr__(self, name):
  289. for field_id, field_name in self.FIELDS:
  290. if name == field_name:
  291. if field_id not in self:
  292. raise AttributeError("{} not set".format(name))
  293. return self[field_id]
  294. raise AttributeError("Unknown field: {}".format(name))
  295. def __setattr__(self, name, value):
  296. for field_id, field_name in self.FIELDS:
  297. if name == field_name:
  298. if not isinstance(value, Item):
  299. raise ValueError("{!r} not a valid PAT item".format(value))
  300. self[field_id] = value
  301. return
  302. if name.startswith("_"):
  303. return OrderedDict.__setattr__(self, name, value)
  304. raise AttributeError("Cannot set unknown field: {}".format(name))
  305. def __delattr__(self, name):
  306. for field_id, field_name in self.FIELDS:
  307. if name == field_name:
  308. del self[field_id]
  309. return
  310. return OrderedDict.__delattr__(self, name)
  311. def __setitem__(self, key, value):
  312. if not isinstance(key, int) or not (0 <= key <= 255):
  313. raise IndexError("index must be a valid numeric value")
  314. elif not isinstance(value, Item):
  315. raise ValueError("{!r} not a valid PAT item".format(value))
  316. return OrderedDict.__setitem__(self, key, value)
  317. def __contains__(self, key):
  318. if isinstance(key, str):
  319. for field_id, field_name in self.FIELDS:
  320. if field_name == key:
  321. key = field_id
  322. break
  323. else:
  324. return False
  325. if not isinstance(key, int):
  326. raise IndexError("key must be a valid field's name/index")
  327. return OrderedDict.__contains__(self, key)
  328. def field_name(self, index):
  329. for field_id, field_name in self.FIELDS:
  330. if index == field_id:
  331. return field_name
  332. return "field_0x{:02x}".format(index)
  333. def pack(self):
  334. """Pack PAT items."""
  335. items = [
  336. (index, value)
  337. for index, value in self.items()
  338. if value is not None
  339. ]
  340. return struct.pack(">B", len(items)) + b"".join(
  341. (struct.pack(">B", index) + value)
  342. for index, value in items
  343. )
  344. def pack_fields(self, fields):
  345. """Pack PAT items specified fields."""
  346. items = [
  347. (index, value)
  348. for index, value in self.items()
  349. if value is not None and index in fields
  350. ]
  351. return struct.pack(">B", len(items)) + b"".join(
  352. (struct.pack(">B", index) + value)
  353. for index, value in items
  354. )
  355. @classmethod
  356. def unpack(cls, data, offset=0):
  357. obj = cls()
  358. field_count, = struct.unpack_from(">B", data, offset)
  359. offset += 1
  360. for _ in range(field_count):
  361. field_id, = struct.unpack_from(">B", data, offset)
  362. offset += 1
  363. item_type, value = unpack_any(data, offset)
  364. offset += 1
  365. if item_type == ItemType.Byte:
  366. obj[field_id] = Byte(value)
  367. offset += 1
  368. elif item_type == ItemType.Word:
  369. obj[field_id] = Word(value)
  370. offset += 2
  371. elif item_type == ItemType.Long:
  372. obj[field_id] = Long(value)
  373. offset += 4
  374. elif item_type == ItemType.LongLong:
  375. obj[field_id] = LongLong(value)
  376. offset += 8
  377. elif item_type == ItemType.String:
  378. obj[field_id] = String(value)
  379. offset += 2 + len(value)
  380. elif item_type == ItemType.Binary:
  381. obj[field_id] = Binary(value)
  382. offset += 2 + len(value)
  383. else:
  384. raise ValueError("Unknown type: {}".format(item_type))
  385. return obj
  386. def assert_fields(self, fields):
  387. items = set(self.keys())
  388. fields = set(fields)
  389. message = "Fields mismatch: {}\n -> Expected: {}".format(items, fields)
  390. assert items == fields, message
  391. class DummyData(PatData):
  392. FIELDS = tuple()
  393. class CollectionLog(PatData):
  394. FIELDS = (
  395. (0x01, "error_code"),
  396. (0x02, "unk_long_0x02"),
  397. (0x03, "timeout_value"),
  398. )
  399. class ConnectionData(PatData):
  400. # Constants are based on the US version of the game
  401. FIELDS = (
  402. (0x01, "online_support_code"),
  403. (0x02, "pat_ticket"),
  404. (0x03, "constant_0x3"),
  405. (0x04, "constant_0x2"),
  406. (0x05, "converted_country_code"),
  407. (0x06, "unknown_long_0x06"),
  408. (0x07, "media_version"),
  409. (0x08, "constant_0x4000"),
  410. (0x09, "country_code"),
  411. (0x0a, "language_code"),
  412. )
  413. class ChargeInfo(PatData):
  414. FIELDS = (
  415. (0x01, "ticket_validity1"),
  416. (0x02, "ticket_validity2"),
  417. (0x05, "unk_binary_0x05"),
  418. (0x07, "online_support_code"),
  419. )
  420. class LoginInfo(PatData):
  421. FIELDS = (
  422. (0x01, "ticket_validity1"),
  423. (0x02, "ticket_validity2"),
  424. (0x06, "unk_binary_0x06"),
  425. (0x07, "online_support_code"),
  426. (0x08, "unk_string_0x08"),
  427. (0x09, "state"),
  428. (0x0a, "nas_token"),
  429. )
  430. class UserObject(PatData):
  431. FIELDS = (
  432. (0x01, "slot_index"),
  433. (0x02, "capcom_id"),
  434. (0x03, "hunter_name"),
  435. (0x04, "unk_long_0x04"),
  436. (0x05, "unk_long_0x05"),
  437. (0x06, "unk_long_0x06"),
  438. (0x07, "unk_long_0x07"),
  439. (0x08, "unk_string_0x08"),
  440. )
  441. class FmpData(PatData):
  442. FIELDS = (
  443. (0x01, "index"),
  444. (0x02, "server_address"),
  445. (0x03, "server_port"),
  446. (0x07, "server_type"),
  447. (0x08, "player_count"),
  448. (0x09, "player_capacity"),
  449. (0x0a, "server_name"),
  450. (0x0b, "unk_string_0x0b"),
  451. (0x0c, "unk_long_0x0c"),
  452. )
  453. class UserSearchInfo(PatData):
  454. FIELDS = (
  455. (0x01, "capcom_id"),
  456. (0x02, "hunter_name"),
  457. (0x03, "stats"),
  458. (0x04, "layer_host"),
  459. (0x07, "unk_byte_0x07"), # Index
  460. (0x08, "server_name"),
  461. (0x0b, "unk_byte_0x0b"),
  462. (0x0c, "unk_string_0x0c"),
  463. (0x0d, "city_capacity"),
  464. (0x0e, "city_size"),
  465. (0x0f, "info_mine_0x0f"),
  466. (0x10, "info_mine_0x10"),
  467. )
  468. class LayerPath(object):
  469. STRUCT = struct.Struct(">IIHHH")
  470. def __init__(self, server_id=None, gate_id=None, city_id=None):
  471. # type: (int|None, int|None, int|None) -> None
  472. self.server_id = server_id or 0
  473. self.gate_id = gate_id or 0
  474. self.city_id = city_id or 0
  475. def get_depth(self):
  476. # type: () -> int
  477. if self.city_id > 0:
  478. return City.LAYER_DEPTH
  479. elif self.gate_id > 0:
  480. return Gate.LAYER_DEPTH
  481. elif self.server_id > 0:
  482. return Server.LAYER_DEPTH
  483. return -1
  484. def pack(self):
  485. # type: () -> bytes
  486. depth = self.get_depth()
  487. unk = 1
  488. return self.STRUCT.pack(depth, self.server_id, unk, self.gate_id,
  489. self.city_id)
  490. @staticmethod
  491. def unpack(data):
  492. # type: (bytes) -> LayerPath
  493. with Unpacker(data) as unpacker:
  494. _, server_id = unpacker.struct(">II")
  495. field_count = len(unpacker) // 2
  496. assert field_count <= 3
  497. gate_id = 0
  498. city_id = 0
  499. for i in range(field_count):
  500. if i == 0:
  501. _ = unpacker.struct(">H")
  502. elif i == 1:
  503. gate_id = unpacker.struct(">H")
  504. else:
  505. city_id = unpacker.struct(">H")
  506. return LayerPath(server_id, gate_id, city_id)
  507. class LayerData(PatData):
  508. FIELDS = (
  509. (0x01, "unk_long_0x01"),
  510. (0x02, "layer_host"),
  511. (0x03, "name"),
  512. (0x05, "index"),
  513. (0x06, "size"),
  514. (0x07, "size2"), # Used in search
  515. (0x08, "unk_long_0x08"),
  516. (0x09, "capacity"),
  517. (0x0a, "unk_long_0x0a"),
  518. (0x0b, "child_population"),
  519. (0x0c, "unk_long_0x0c"),
  520. (0x0d, "unk_word_0x0d"),
  521. (0x10, "state"), # 0 = Joinable / 1 = Empty / 2 = Full
  522. (0x11, "positionInterval"), # player position synchronization timer
  523. (0x12, "unk_byte_0x12"),
  524. (0x15, "layer_depth"),
  525. (0x16, "layer_pathname"),
  526. (0x17, "unk_binary_0x17"),
  527. (0xff, "fallthrough_bug") # Fill this if field 0x17 is set !!!
  528. )
  529. @staticmethod
  530. def create_from(index, layer, path=None):
  531. data = LayerData()
  532. data.unk_long_0x01 = Long(index)
  533. data.index = Word(index)
  534. data.positionInterval = Long(500)
  535. data.unk_byte_0x12 = Byte(1)
  536. if isinstance(layer, Server):
  537. data.name = String(layer.name)
  538. data.size = Long(layer.get_population())
  539. data.size2 = Long(layer.get_population())
  540. data.capacity = Long(layer.get_capacity())
  541. data.state = Byte(0)
  542. data.layer_depth = Byte(layer.LAYER_DEPTH)
  543. elif isinstance(layer, Gate):
  544. data.name = String(layer.name)
  545. data.size = Long(layer.get_population())
  546. data.size2 = Long(layer.get_population())
  547. data.capacity = Long(layer.get_capacity())
  548. data.state = Byte(layer.get_state())
  549. data.layer_depth = Byte(layer.LAYER_DEPTH)
  550. else:
  551. assert isinstance(layer, City)
  552. if path is None and layer.leader is not None:
  553. path = layer.leader.get_layer_path()
  554. data.name = String(layer.name)
  555. data.size = Long(layer.get_population())
  556. data.size2 = Long(layer.get_population())
  557. data.capacity = Long(layer.get_capacity())
  558. data.child_population = Long(layer.in_quest_players())
  559. data.unk_long_0x0c = Long(0xc) # TODO: Reverse
  560. data.state = Byte(layer.get_state())
  561. data.layer_depth = Byte(layer.LAYER_DEPTH)
  562. data.layer_pathname = String(layer.get_pathname())
  563. if path is not None:
  564. data.layer_host = Binary(path.pack())
  565. # These fields are read when used in the NtcLayerInfoSet packet
  566. # Purpose is unknown
  567. # data.unk_binary_0x17 = Binary("test")
  568. # data.fallthrough_bug = FallthroughBug()
  569. return data
  570. class FriendData(PatData):
  571. FIELDS = (
  572. (0x01, "index"),
  573. (0x02, "capcom_id"),
  574. (0x03, "hunter_name"),
  575. )
  576. class BlackListUserData(FriendData):
  577. pass
  578. class UserStatusSet(PatData):
  579. FIELDS = (
  580. (0x01, "unk_byte_0x01"),
  581. (0x02, "unk_byte_0x02"),
  582. (0x03, "unk_byte_0x03"),
  583. (0x04, "unk_byte_0x04"),
  584. (0x05, "unk_byte_0x05"),
  585. (0x08, "unk_byte_0x08"),
  586. (0x09, "unk_byte_0x09"), # Byte(4) = Deny Friend Requests
  587. )
  588. class LayerSet(PatData):
  589. FIELDS = (
  590. (0x01, "unk_long_0x01"),
  591. (0x02, "unk_binary_0x02"),
  592. (0x03, "unk_string_0x03"),
  593. (0x05, "unk_binary_0x05"),
  594. (0x09, "unk_long_0x09"),
  595. (0x0a, "unk_long_0x0a"),
  596. (0x0c, "unk_long_0x0c"),
  597. (0x17, "unk_binary_0x17"),
  598. )
  599. class MediationListItem(PatData):
  600. FIELDS = (
  601. (0x01, "name"), # name or owner?
  602. (0x02, "index"),
  603. (0x03, "is_locked"), # 1 - locked, unlocked otherwise
  604. )
  605. class CircleInfo(PatData):
  606. FIELDS = (
  607. (0x01, "index"),
  608. (0x02, "unk_string_0x02"),
  609. (0x03, "has_password"),
  610. (0x04, "password"),
  611. (0x05, "party_members"),
  612. (0x06, "remarks"),
  613. (0x07, "player_count"),
  614. (0x08, "unk_long_0x08"),
  615. (0x09, "capacity"),
  616. (0x0a, "unk_long_0x0a"),
  617. (0x0b, "unk_long_0x0b"),
  618. (0x0c, "index2"),
  619. (0x0d, "leader_capcom_id"),
  620. (0x0e, "unk_byte_0x0e"),
  621. (0x0f, "not_joinable"),
  622. (0x10, "unk_byte_0x10"),
  623. )
  624. @staticmethod
  625. def pack_from(circle, circle_index):
  626. with circle.lock():
  627. circle_info = CircleInfo()
  628. circle_info.index = Long(circle_index)
  629. if not circle.is_empty():
  630. # circle_info.unk_string_0x02 = pati.String("192.168.23.1")
  631. if circle.has_password():
  632. circle_info.has_password = Byte(1)
  633. circle_info.password = String(circle.password)
  634. party_members = bytearray(0x88)
  635. for i, player in circle.players:
  636. start_offset = 0x10 * i
  637. end_offset = start_offset + 0x10
  638. # TODO: Shouldn't we use bytes instead?
  639. party_members[start_offset:end_offset] = \
  640. pad(player.capcom_id.encode('ascii'), 0x10)
  641. party_members[start_offset+0x40:end_offset+0x40] = \
  642. pad(player.hunter_name, 0x10)
  643. party_members[0x80+i] = player.hunter_info.weapon_type()
  644. # TODO: Discover flag meaning?
  645. party_members[0x84+i] = 0xff
  646. circle_info.party_members = Binary(party_members)
  647. if circle.remarks is not None:
  648. circle_info.remarks = String(circle.remarks)
  649. # Number of players currently in the quest
  650. circle_info.player_count = Long(len(circle.players))
  651. # circle_info.unk_long_0x08 = Long(3)
  652. circle_info.capacity = Long(circle.get_capacity())
  653. # circle_info.unk_long_0x0a = Long(5)
  654. # circle_info.unk_long_0x0b = Long(6)
  655. circle_info.index2 = Long(circle_index) # TODO: Verify this
  656. circle_info.leader_capcom_id = String(circle.leader.capcom_id)
  657. circle_info.unk_byte_0x0e = Byte(circle.unk_byte_0x0e)
  658. circle_info.not_joinable = Byte(int(not circle.is_joinable()))
  659. # circle_info.unk_byte_0x10 = pati.Byte(1)
  660. # TODO: Other optional fields
  661. optional_fields = [
  662. (1, circle.get_capacity()),
  663. (2, circle.quest_id)
  664. ]
  665. return circle_info.pack() + pack_optional_fields(optional_fields)
  666. class CircleUserData(PatData):
  667. FIELDS = (
  668. (0x01, "unk_binary_0x01"),
  669. (0x02, "unk_word_0x02"),
  670. (0x03, "is_standby"),
  671. (0x04, "player_index"),
  672. (0x05, "capcom_id"),
  673. (0x06, "hunter_name"),
  674. )
  675. class MessageInfo(PatData):
  676. FIELDS = (
  677. (0x01, "text_color"), # RGBA
  678. (0x02, "unk_long_0x02"), # Probably time related?
  679. (0x03, "sender_id"),
  680. (0x04, "sender_name"),
  681. )
  682. class LayerUserInfo(PatData):
  683. FIELDS = (
  684. (0x01, "capcom_id"),
  685. (0x02, "hunter_name"),
  686. (0x03, "layer_host"),
  687. (0x06, "unk_long_0x06"),
  688. (0x07, "stats"),
  689. )
  690. class LayerBinaryInfo(PatData):
  691. FIELDS = (
  692. (0x01, "unk_long_0x01"), # time related
  693. (0x02, "capcom_id"),
  694. (0x03, "hunter_name")
  695. )
  696. class LayerUserNum(PatData):
  697. FIELDS = (
  698. (0x01, "path"),
  699. (0x02, "population"),
  700. (0x03, "index"),
  701. (0x04, "unk_long_0x04"),
  702. (0x05, "max_population"),
  703. (0x06, "unk_long_0x06"),
  704. (0x07, "child_population"),
  705. )
  706. @staticmethod
  707. def pack_from(layer_data):
  708. # type: (LayerData) -> bytes
  709. data = LayerUserNum()
  710. data.path = layer_data.layer_host
  711. data.population = layer_data.size
  712. data.index = Long(unpack_word(layer_data.index))
  713. data.unk_long_0x04 = Long(0xAF00FA00)
  714. data.max_population = layer_data.capacity
  715. data.unk_long_0x06 = Long(0xBF00FB00)
  716. if "child_population" in layer_data:
  717. data.child_population = layer_data.child_population
  718. return data.pack()
  719. def patdata_extender(unpacker):
  720. """PatData classes must be defined above this function."""
  721. for name, value in globals().items():
  722. if not isinstance(value, type):
  723. continue # Not a valid class
  724. if not issubclass(value, PatData):
  725. continue
  726. unpacker.MAPPING[name] = (value.unpack, value.pack)
  727. return unpacker
  728. @patdata_extender
  729. class Unpacker(GenericUnpacker):
  730. """PAT item unpacker with PatData support."""
  731. MAPPING = {
  732. "struct": (struct.unpack_from, struct.pack),
  733. "lp_string": (unpack_lp_string, lp_string),
  734. "lp2_string": (unpack_lp2_string, lp2_string),
  735. "byte": (unpack_byte, pack_byte),
  736. "word": (unpack_word, pack_word),
  737. "long": (unpack_long, pack_long),
  738. "longlong": (unpack_longlong, pack_longlong),
  739. "string": (unpack_string, pack_string),
  740. "binary": (unpack_binary, pack_binary),
  741. "bytes": (unpack_bytes, pack_bytes),
  742. "optional_fields": (unpack_optional_fields,
  743. pack_optional_fields),
  744. "detailed_optional_fields": (unpack_detailed_optional_fields,
  745. pack_detailed_optional_fields)
  746. }
  747. class HunterSettings(object):
  748. """Helper for hunter settings.
  749. The game's recvNtcUserBinaryNotice function suggests that the maximum size
  750. is 0x100. However, some parts of the code suggest otherwise due to a
  751. `< 0x100` check.
  752. See the dispatched packet, case 0x8071, 0x803edc64 (RMHE08).
  753. """
  754. SIZE = 0x100
  755. def __init__(self):
  756. self.data = bytearray(self.SIZE)
  757. def unpack(self, data, length, offset=0):
  758. self.data[offset:offset+length] = data
  759. return self
  760. def rank(self):
  761. rank, = struct.unpack_from(">H", self.data, 0x00)
  762. return rank
  763. def weapon_type(self):
  764. weapon_type, = struct.unpack_from(">B", self.data, 0x05)
  765. return weapon_type
  766. def pack(self):
  767. return self.data
  768. def get_fmp_servers(session, first_index, count):
  769. assert first_index > 0, "Invalid list index"
  770. config = get_config("FMP")
  771. fmp_addr = get_ip(config["IP"])
  772. fmp_port = config["Port"]
  773. data = b""
  774. start = first_index - 1
  775. end = start + count
  776. servers = session.get_servers()[start:end]
  777. for i, server in enumerate(servers, first_index):
  778. fmp_data = FmpData()
  779. fmp_data.index = Long(i) # The server might be full, if zero
  780. server.addr = server.addr or fmp_addr
  781. server.port = server.port or fmp_port
  782. fmp_data.server_address = String(server.addr)
  783. fmp_data.server_port = Word(server.port)
  784. # Might produce invalid reads if too high
  785. # fmp_data.server_type = LongLong(i+0x10000000)
  786. # fmp_data.server_type = LongLong(i + (1<<32)) # OK
  787. fmp_data.server_type = LongLong(server.server_type)
  788. fmp_data.player_count = Long(server.get_population())
  789. fmp_data.player_capacity = Long(server.get_capacity())
  790. fmp_data.server_name = String(server.name)
  791. fmp_data.unk_string_0x0b = String("X")
  792. fmp_data.unk_long_0x0c = Long(0x12345678)
  793. data += fmp_data.pack()
  794. return data
  795. def get_layer_children(session, first_index, count, sibling=False):
  796. assert first_index > 0, "Invalid list index"
  797. data = b""
  798. start = first_index - 1
  799. end = start+count
  800. if not sibling:
  801. children = session.get_layer_children()[start:end]
  802. else:
  803. children = session.get_layer_sibling()[start:end]
  804. for i, child in enumerate(children, first_index):
  805. layer = LayerData.create_from(i, child)
  806. data += layer.pack()
  807. data += pack_optional_fields(child.optional_fields)
  808. return data
  809. def get_layer_sibling(session, first_index, count):
  810. return get_layer_children(session, first_index, count, True)
  811. def getDummyLayerData():
  812. layer = LayerData()
  813. layer.index = Word(1) # Index
  814. # layer.unk_custom_0x02 = Custom(b"")
  815. layer.name = String("LayerStart")
  816. # layer.unk_worddec_0x05 = Word(2) # City no longer exists message
  817. # Player in the city, displayed at the gate
  818. layer.size = Long(0)
  819. # layer.unk_long_0x07 = Long(1)
  820. # layer.unk_long_0x08 = Long(1)
  821. # Maximum number of players in the city, displayed at the gate
  822. layer.capacity = Long(4)
  823. # layer.unk_long_0x0a = Long(1)
  824. # In city population, displayed at the gate
  825. # layer.unk_long_0x0b = Long(0)
  826. # layer.unk_long_0x0c = Long(1)
  827. # layer.unk_word_0x0d = Word(1)
  828. layer.state = Byte(1)
  829. layer.positionInterval = Long(500)
  830. # layer.unk_long_0x11 = Long(1)
  831. # Might be needed to be >=1 to keep NetworkConnectionStable alive
  832. layer.unk_byte_0x12 = Byte(1)
  833. # layer.layer_depth = Byte(1)
  834. # layer.layer_pathname = String("UnkStart")
  835. # layer.unk_binary_0x17 = Binary(b"binStart")
  836. return layer
  837. def getHunterStats(hr=921, profile=b"Navaldeus",
  838. title=117, status=1, hr_limit=2, goal=35, seeking=23,
  839. server_type=3, weapon_type=10, weapon_id=11):
  840. """
  841. Offsets:
  842. - 0x00: Hunter Rank
  843. - 0x10~0x7c: Equipment
  844. - 0x9c: Profile description
  845. - 0xf2: Profile titles
  846. - 0xf3: Profile status
  847. - 0xf5: City's HR limit
  848. - 0xf6: City's goal
  849. - 0xf7: City's seeking
  850. - 0xf8: Server type
  851. """
  852. profile = to_bytearray(profile)
  853. if profile[-1] != b"\0":
  854. profile += b"\0"
  855. data = bytearray(0x100) # fuzz.repeat(fuzz.MSF_PATTERN, 0x100)
  856. def slot(type_id, equipment_id, slots=0):
  857. """Equipment slot / TODO: Handle gems"""
  858. return struct.pack(">BBHII", type_id, slots, equipment_id, 0, 0)
  859. data[:2] = struct.pack(">H", hr)
  860. # Weapon / Gun slots (Lance: Nega-Babylon)
  861. data[0x10:0x1c] = slot(weapon_type, weapon_id)
  862. data[0x1c:0x28] = b"\xff" * 0xc
  863. data[0x28:0x34] = b"\xff" * 0xc
  864. # Armors (Helios+ set)
  865. data[0x34:0x40] = slot(5, 111)
  866. data[0x40:0x4c] = slot(1, 116)
  867. data[0x4c:0x58] = slot(3, 115)
  868. data[0x58:0x64] = slot(2, 109)
  869. data[0x64:0x70] = slot(4, 113)
  870. data[0x70:0x7c] = slot(6, 7, slots=3)
  871. data[0x9c:0x9c+len(profile)] = profile
  872. data[0xf2] = title
  873. data[0xf3] = status
  874. data[0xf5] = hr_limit
  875. data[0xf6] = goal
  876. data[0xf7] = seeking
  877. data[0xf8] = server_type
  878. return data