channel select, character select
This commit is contained in:
parent
2c3f0c65bb
commit
f9a1b4c594
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,5 +1,6 @@
|
|||||||
**/.git
|
**/.git
|
||||||
**/.vscode
|
**/.vscode
|
||||||
**/__pycache__
|
**/__pycache__
|
||||||
|
**/.venv
|
||||||
|
|
||||||
test*.*
|
test*.*
|
||||||
|
@ -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_base import ClientBase
|
||||||
from .client import Client
|
from .client import Client
|
||||||
from .packet import Packet, iPacket, oPacket
|
from .packet import Packet, iPacket, oPacket
|
||||||
|
from .packet_helper import Helpers
|
41
character.py
Normal file
41
character.py
Normal 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)
|
51
client.py
51
client.py
@ -1,15 +1,22 @@
|
|||||||
from asyncio import sleep
|
from asyncio import sleep
|
||||||
|
|
||||||
|
from rich import print
|
||||||
|
|
||||||
|
from .character import Character
|
||||||
from .client_base import ClientBase
|
from .client_base import ClientBase
|
||||||
from .packet import iPacket, packet_handler, oPacket
|
|
||||||
from .opcodes import RecvOps, SendOps
|
from .opcodes import RecvOps, SendOps
|
||||||
|
from .packet import iPacket, oPacket, packet_handler
|
||||||
|
from .packet_helper import Helpers
|
||||||
|
|
||||||
|
|
||||||
class Client(ClientBase):
|
class Client(ClientBase):
|
||||||
|
|
||||||
def __init__(self, loop=None, username=None, password=None):
|
def __init__(self, loop=None, username=None, password=None):
|
||||||
super().__init__(loop)
|
super().__init__(loop)
|
||||||
self._username = username
|
self._username = username
|
||||||
self._password = password
|
self._password = password
|
||||||
|
self._channels = []
|
||||||
|
self._characters = []
|
||||||
|
|
||||||
async def begin(self):
|
async def begin(self):
|
||||||
while self._loop.is_running() and self._sock.fileno():
|
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} ")
|
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(oPacket(SendOps.SERVERLIST_REQUEST))
|
||||||
await self.send_packet(opkt)
|
|
||||||
|
@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):
|
async def do_login(self):
|
||||||
opkt = oPacket(SendOps.LOGIN_PASSWORD)
|
opkt = oPacket(SendOps.LOGIN_PASSWORD)
|
||||||
|
@ -81,6 +81,7 @@ class ClientBase:
|
|||||||
except ConnectionResetError:
|
except ConnectionResetError:
|
||||||
print("Connection reset, attempting to reconnect..")
|
print("Connection reset, attempting to reconnect..")
|
||||||
self._create_sock()
|
self._create_sock()
|
||||||
|
self._buff = bytearray()
|
||||||
del self._send_iv
|
del self._send_iv
|
||||||
del self._recv_iv
|
del self._recv_iv
|
||||||
if self._action_task:
|
if self._action_task:
|
||||||
@ -96,7 +97,6 @@ class ClientBase:
|
|||||||
self._send_iv = MapleIV(begin_packet.decode_int())
|
self._send_iv = MapleIV(begin_packet.decode_int())
|
||||||
self._recv_iv = MapleIV(begin_packet.decode_int())
|
self._recv_iv = MapleIV(begin_packet.decode_int())
|
||||||
self._locale = begin_packet.decode_byte()
|
self._locale = begin_packet.decode_byte()
|
||||||
# print(begin_packet)
|
|
||||||
self._action_task = self._loop.create_task(
|
self._action_task = self._loop.create_task(
|
||||||
getattr(self, "begin")()
|
getattr(self, "begin")()
|
||||||
)
|
)
|
||||||
|
117
packet_helper.py
Normal file
117
packet_helper.py
Normal 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
3
requirements.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pycryptodome==3.14.1
|
||||||
|
Pygments==2.11.2
|
||||||
|
rich==12.0.0
|
Loading…
Reference in New Issue
Block a user