channel select, character select

This commit is contained in:
Ra 2022-03-11 19:47:04 -07:00
parent 2c3f0c65bb
commit f9a1b4c594
7 changed files with 215 additions and 6 deletions

3
.gitignore vendored
View File

@ -1,5 +1,6 @@
**/.git
**/.vscode
**/__pycache__
**/.venv
test*.*
test*.*

View File

@ -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

41
character.py Normal file
View File

@ -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)

View File

@ -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)

View File

@ -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")()
)

117
packet_helper.py Normal file
View File

@ -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

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
pycryptodome==3.14.1
Pygments==2.11.2
rich==12.0.0