From f9a1b4c594b23506d3f2305f305572db55390a17 Mon Sep 17 00:00:00 2001 From: Ra Date: Fri, 11 Mar 2022 19:47:04 -0700 Subject: [PATCH] channel select, character select --- .gitignore | 3 +- __init__.py | 4 +- character.py | 41 +++++++++++++++++ client.py | 51 +++++++++++++++++++-- client_base.py | 2 +- packet_helper.py | 117 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 3 ++ 7 files changed, 215 insertions(+), 6 deletions(-) create mode 100644 character.py create mode 100644 packet_helper.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index 7c8fd25..1085b42 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ **/.git **/.vscode **/__pycache__ +**/.venv -test*.* \ No newline at end of file +test*.* diff --git a/__init__.py b/__init__.py index 53daca8..7d7db8e 100644 --- a/__init__.py +++ b/__init__.py @@ -1,5 +1,7 @@ -__all__ = "ClientBase", "Client", "Packet", "iPacket", "oPacket" +__all__ = "Character", "ClientBase", "Client", "Helpers", "Packet", "iPacket", "oPacket" +from .character import Character from .client_base import ClientBase from .client import Client from .packet import Packet, iPacket, oPacket +from .packet_helper import Helpers \ No newline at end of file diff --git a/character.py b/character.py new file mode 100644 index 0000000..5a67db6 --- /dev/null +++ b/character.py @@ -0,0 +1,41 @@ +from attrs import define + + +@define(auto_attribs=True, kw_only=True, init=True) +class Character(object): + idx: int + name: str + gender: int + skin: int + face: int + hair: int + level: int + job: int + strn: int + dex: int + intl: int + luk: int + hp: int + max_hp: int + mp: int + max_mp: int + sp: int + exp: int + fame: int + gach_exp: int + map_id: int + spawn: int + sub_job: int + pvp_exp: int + pvp_rank: int + battle_points: int + equips: list[tuple[int, int]] + weapon: int = 0 + rank: int = 0 + rank_move: int = 0 + job_rank: int = 0 + job_rank_move: int = 0 + + @classmethod + def fill(cls, values: dict): + return cls(**values) \ No newline at end of file diff --git a/client.py b/client.py index f41bebb..b6f43ba 100644 --- a/client.py +++ b/client.py @@ -1,15 +1,22 @@ from asyncio import sleep +from rich import print + +from .character import Character from .client_base import ClientBase -from .packet import iPacket, packet_handler, oPacket from .opcodes import RecvOps, SendOps +from .packet import iPacket, oPacket, packet_handler +from .packet_helper import Helpers class Client(ClientBase): + def __init__(self, loop=None, username=None, password=None): super().__init__(loop) self._username = username self._password = password + self._channels = [] + self._characters = [] async def begin(self): while self._loop.is_running() and self._sock.fileno(): @@ -52,8 +59,46 @@ class Client(ClientBase): print(f"Account ID: {account_id} | Gender: {gender} | GM 1: {gm_1} | GM 2: {gm_2} | Account Name: {account_name} | New Account: {new_account} | Shadow Ban: {shadow_ban} ") - opkt = oPacket(SendOps.SERVERLIST_REQUEST) - await self.send_packet(opkt) + await self.send_packet(oPacket(SendOps.SERVERLIST_REQUEST)) + + @packet_handler(RecvOps.SERVERLIST) + async def serverlist(self, ipkt: iPacket): + if world_id := ipkt.decode_byte() == 0xFF: + if not self._channels: + return + + await self.send_packet(iPacket(SendOps.CHARLIST_REQUEST)) + return + + world_name = ipkt.decode_string() + world_flag = ipkt.decode_byte() + event_message = ipkt.decode_string() + + print(world_id, world_name, world_flag, event_message) + + ipkt.decode_short() + ipkt.decode_short() + ipkt.decode_byte() + + channel_count = ipkt.decode_byte() + + for _ in range(channel_count): + self._channels.append({ + "name": ipkt.decode_string(), + "load": ipkt.decode_int(), + "world_id": ipkt.decode_byte(), + "channel_id": ipkt.decode_short() + }) + + ipkt.decode_short() + ipkt.decode_int() + + print(self._channels) + + @packet_handler(RecvOps.CHARLIST) + async def charlist(self, ipkt: iPacket): + self._characters = Helpers.character_entries(ipkt) + print(self._characters) async def do_login(self): opkt = oPacket(SendOps.LOGIN_PASSWORD) diff --git a/client_base.py b/client_base.py index 5acdb92..9727025 100644 --- a/client_base.py +++ b/client_base.py @@ -81,6 +81,7 @@ class ClientBase: except ConnectionResetError: print("Connection reset, attempting to reconnect..") self._create_sock() + self._buff = bytearray() del self._send_iv del self._recv_iv if self._action_task: @@ -96,7 +97,6 @@ class ClientBase: self._send_iv = MapleIV(begin_packet.decode_int()) self._recv_iv = MapleIV(begin_packet.decode_int()) self._locale = begin_packet.decode_byte() - # print(begin_packet) self._action_task = self._loop.create_task( getattr(self, "begin")() ) diff --git a/packet_helper.py b/packet_helper.py new file mode 100644 index 0000000..43b7396 --- /dev/null +++ b/packet_helper.py @@ -0,0 +1,117 @@ +from .character import Character +from .packet import iPacket + + +class Helpers: + + @staticmethod + def character_entries(ipkt: iPacket, view_all=None): + chars = [] + ipkt.decode_byte() + for i in range(ipkt.decode_byte()): + chars.append(Character.fill(Helpers.character_entry(ipkt, view_all))) + return chars + + @staticmethod + def character_entry(ipkt: iPacket, view_all=None): + stats = Helpers.character_stats(ipkt) + look = Helpers.character_look(ipkt) + look_useful = {"equips": look["equips"], "weapon": look["weapon"]} + + if view_all: + ipkt.decode_byte() + + rank = {} + if ranked := ipkt.decode_byte(): + rank |= { + "rank": ipkt.decode_int(), + "rank_move": ipkt.decode_int(), + "job_rank": ipkt.decode_int(), + "job_rank_move": ipkt.decode_int() + } + + return stats | look_useful | rank + + @staticmethod + def character_stats(ipkt: iPacket): + stats = { + "idx": ipkt.decode_int(), + "name": ipkt.decode_string(), + "gender": ipkt.decode_byte(), + "skin": ipkt.decode_byte(), + "face": ipkt.decode_int(), + "hair": ipkt.decode_int(), + "_": ipkt.seek(24), + "level": ipkt.decode_byte(), + "job": ipkt.decode_short(), + "strn": ipkt.decode_short(), + "dex": ipkt.decode_short(), + "intl": ipkt.decode_short(), + "luk": ipkt.decode_short(), + "hp": ipkt.decode_int(), + "max_hp": ipkt.decode_int(), + "mp": ipkt.decode_int(), + "max_mp": ipkt.decode_int(), + "sp": ipkt.decode_short(), + "exp": ipkt.decode_int(), + "fame": ipkt.decode_int(), + "gach_exp": ipkt.decode_int(), + "map_id": ipkt.decode_int(), + "spawn": ipkt.decode_byte(), + "_": ipkt.decode_int(), + "sub_job": ipkt.decode_short(), + } + + if stats["job"] in [3001, 3100, 3110, 3111, 3112]: + stats["demon_marking"] = ipkt.decode_int() + + stats["fatigue"] = ipkt.decode_byte() + ipkt.decode_int() # date + + for i in range(12): + if i // 6 == 1: + ipkt.decode_short() # today trait xp + else: + ipkt.decode_int() # total trait xp + + stats |= { + "pvp_exp": ipkt.decode_int(), + "pvp_rank": ipkt.decode_byte(), + "battle_points": ipkt.decode_int(), + } + ipkt.decode_byte() + ipkt.decode_int() + ipkt.decode_int() + ipkt.decode_int() + return stats + + @staticmethod + def character_look(ipkt: iPacket): + look = { + "gender": ipkt.decode_byte(), + "skin": ipkt.decode_byte(), + "face": ipkt.decode_int(), + "job": ipkt.decode_int(), + "mega": ipkt.decode_byte(), + "hair": ipkt.decode_int(), + "equips": [], + "cash_equips": [] + } + + while idx := ipkt.decode_byte() != 0xFF: + look["equips"].append((idx, ipkt.decode_int())) + + while idx := ipkt.decode_byte() != 0xFF: + look["cash_equips"].append((idx, ipkt.decode_int())) + + look |= { + "weapon": ipkt.decode_int(), + "mercedes_ears": ipkt.decode_byte() + } + + ipkt.seek(12) + + if marking := ipkt.decode_int(): + look["demon_marking"] = marking + + return look \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..53186c4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pycryptodome==3.14.1 +Pygments==2.11.2 +rich==12.0.0 \ No newline at end of file