diff options
author | Livio Recchia <recchialivio@libero.it> | 2020-02-10 23:06:34 +0100 |
---|---|---|
committer | Livio Recchia <recchialivio@libero.it> | 2020-02-10 23:06:34 +0100 |
commit | 9a13903a2f7d3a65fdf15a65fb59cccd622e2066 (patch) | |
tree | 9403b7dff39eb5e5d7fa0f79efb69b496add4c4b /net | |
parent | 11cc316b74d5f3f283413a33e7693b314741aa4a (diff) | |
download | manachat-9a13903a2f7d3a65fdf15a65fb59cccd622e2066.tar.gz manachat-9a13903a2f7d3a65fdf15a65fb59cccd622e2066.tar.bz2 manachat-9a13903a2f7d3a65fdf15a65fb59cccd622e2066.tar.xz manachat-9a13903a2f7d3a65fdf15a65fb59cccd622e2066.zip |
Initial commit
Diffstat (limited to 'net')
-rw-r--r-- | net/__init__.py | 11 | ||||
-rw-r--r-- | net/__init__.pyc | bin | 0 -> 556 bytes | |||
-rw-r--r-- | net/__pycache__/__init__.cpython-37.pyc | bin | 0 -> 430 bytes | |||
-rw-r--r-- | net/being.py | 63 | ||||
-rw-r--r-- | net/being.pyc | bin | 0 -> 3072 bytes | |||
-rw-r--r-- | net/charserv.py | 142 | ||||
-rw-r--r-- | net/charserv.pyc | bin | 0 -> 4913 bytes | |||
-rw-r--r-- | net/common.py | 106 | ||||
-rw-r--r-- | net/common.pyc | bin | 0 -> 4946 bytes | |||
-rw-r--r-- | net/dispatcher.py | 27 | ||||
-rw-r--r-- | net/dispatcher.pyc | bin | 0 -> 1230 bytes | |||
-rw-r--r-- | net/inventory.py | 51 | ||||
-rw-r--r-- | net/inventory.pyc | bin | 0 -> 1971 bytes | |||
-rw-r--r-- | net/item.py | 8 | ||||
-rw-r--r-- | net/item.pyc | bin | 0 -> 659 bytes | |||
-rw-r--r-- | net/loginsrv.py | 109 | ||||
-rw-r--r-- | net/loginsrv.pyc | bin | 0 -> 4403 bytes | |||
-rw-r--r-- | net/mapserv.py | 1216 | ||||
-rw-r--r-- | net/mapserv.pyc | bin | 0 -> 44223 bytes | |||
-rw-r--r-- | net/onlineusers.py | 86 | ||||
-rw-r--r-- | net/onlineusers.pyc | bin | 0 -> 3903 bytes | |||
-rw-r--r-- | net/packetlen.py | 28 | ||||
-rw-r--r-- | net/packetlen.pyc | bin | 0 -> 2218 bytes | |||
-rw-r--r-- | net/protocol.py | 105 | ||||
-rw-r--r-- | net/protocol.pyc | bin | 0 -> 3334 bytes | |||
-rw-r--r-- | net/stats.py | 44 | ||||
-rw-r--r-- | net/stats.pyc | bin | 0 -> 1198 bytes | |||
-rw-r--r-- | net/trade.py | 6 | ||||
-rw-r--r-- | net/trade.pyc | bin | 0 -> 433 bytes |
29 files changed, 2002 insertions, 0 deletions
diff --git a/net/__init__.py b/net/__init__.py new file mode 100644 index 0000000..55c1c56 --- /dev/null +++ b/net/__init__.py @@ -0,0 +1,11 @@ +import loginsrv + + +def login(host, port, username, password, charname): + loginsrv.connect(host, port) + + loginsrv.server.username = username + loginsrv.server.password = password + loginsrv.server.char_name = charname + + loginsrv.cmsg_server_version_request() diff --git a/net/__init__.pyc b/net/__init__.pyc Binary files differnew file mode 100644 index 0000000..6ec9624 --- /dev/null +++ b/net/__init__.pyc diff --git a/net/__pycache__/__init__.cpython-37.pyc b/net/__pycache__/__init__.cpython-37.pyc Binary files differnew file mode 100644 index 0000000..968d236 --- /dev/null +++ b/net/__pycache__/__init__.cpython-37.pyc diff --git a/net/being.py b/net/being.py new file mode 100644 index 0000000..ad7ad57 --- /dev/null +++ b/net/being.py @@ -0,0 +1,63 @@ +import monsterdb + + +def job_type(job): + if (job <= 25 or (job >= 4001 and job <= 4049)): + return "player" + elif (job >= 46 and job <= 1000): + return "npc" + elif (job > 1000 and job <= 2000): + return "monster" + elif (job == 45): + return "portal" + + +class Being: + def __init__(self, being_id, job): + self.id = being_id + self.job = job + self.speed = 0 + self.x = 0 + self.y = 0 + + if job_type(job) == "monster": + self._name = monsterdb.monster_db.get(job, "") + else: + self._name = "" + + @property + def name(self): + if len(self._name) > 0: + return self._name + return "{{ID:" + str(self.id) + "}}" + + @name.setter + def name(self, newname): + self._name = newname + + @property + def type(self): + return job_type(self.job) + + def __repr__(self): + return self.name + + +class BeingsCache(dict): + + def __init__(self, name_request_func, *args, **kwargs): + dict.__init__(self, *args, **kwargs) + self._name_request_func = name_request_func + + def findId(self, name, type_="player"): + for id_, being in self.iteritems(): + if being.name == name and being.type == type_: + return id_ + return -10 + + def findName(self, id_, job=1): + if id_ not in self: + self[id_] = Being(id_, job) + if job_type(job) in ("player", "npc"): + self._name_request_func(id_) + return self[id_].name diff --git a/net/being.pyc b/net/being.pyc Binary files differnew file mode 100644 index 0000000..32916bd --- /dev/null +++ b/net/being.pyc diff --git a/net/charserv.py b/net/charserv.py new file mode 100644 index 0000000..50f6f99 --- /dev/null +++ b/net/charserv.py @@ -0,0 +1,142 @@ + +from construct import * +from construct.protocols.layer3.ipv4 import IpAddress +import mapserv +import stats +from protocol import * +from utils import * +from common import * +from loggers import netlog + +server = None + + +def smsg_ignore(data): + pass + + +@extendable +def smsg_char_login(data): + netlog.info("SMSG_CHAR_LOGIN {}".format(data)) + + char_slot = -1 + for c in data.chars: + if c.name == server.char_name: + char_slot = c.slot + mapserv.player_money = c.money + mapserv.player_stats[stats.EXP] = c.exp + mapserv.player_stats[stats.MONEY] = c.money + mapserv.player_stats[stats.JOB] = c.job + mapserv.player_stats[stats.CHAR_POINTS] = c.charpoints + mapserv.player_stats[stats.HP] = c.hp + mapserv.player_stats[stats.MAX_HP] = c.max_hp + mapserv.player_stats[stats.MP] = c.mp + mapserv.player_stats[stats.MAX_MP] = c.max_mp + mapserv.player_stats[stats.WALK_SPEED] = c.speed + mapserv.player_stats[stats.LEVEL] = c.level + mapserv.player_stats[stats.SKILL_POINTS] = c.skillpoints + break + if char_slot < 0: + err_msg = "CharName {} not found".format(server.char_name) + netlog.error(err_msg) + server.close() + raise Exception(err_msg) + else: + cmsg_char_select(char_slot) + + +@extendable +def smsg_char_login_error(data): + err_msg = "SMSG_CHAR_LOGIN_ERROR (code={})".format(data.code) + netlog.error(err_msg) + server.close() + raise Exception(err_msg) + + +@extendable +def smsg_char_map_info(data): + netlog.info("SMSG_CHAR_MAP_INFO CID={} map={} address={} port={}".format( + data.char_id, data.map_name, data.address, data.port)) + server.close() + + mapserv.connect(data.address, data.port) + mapserv.server.char_name = server.char_name + mapserv.server.char_id = data.char_id + mapserv.player_pos['map'] = data.map_name + mapserv.cmsg_map_server_connect(server.account, data.char_id, + server.session1, server.session2, + server.gender) + + +protodef = { + 0x8000 : (smsg_ignore, Field("data", 2)), + 0x006b : (smsg_char_login, + Struct("data", + ULInt16("length"), + ULInt16("slots"), + Byte("version"), + # Probe("debug", show_stream=False, show_stack=False), + Padding(17), + Array(lambda ctx: (ctx["length"] - 24) / 106, + Struct("chars", + ULInt32("id"), + ULInt32("exp"), + ULInt32("money"), + ULInt32("job"), + ULInt32("job_level"), + Padding(20), + ULInt16("charpoints"), + ULInt16("hp"), + ULInt16("max_hp"), + ULInt16("mp"), + ULInt16("max_mp"), + ULInt16("speed"), + Padding(6), + ULInt16("level"), + ULInt16("skillpoints"), + Padding(12), + StringZ("name", 24), + Padding(6), + Byte("slot"), + Padding(1))))), + 0x006c : (smsg_char_login_error, + Struct("data", Byte("code"))), + 0x0071 : (smsg_char_map_info, + Struct("data", + ULInt32("char_id"), + StringZ("map_name", 16), + IpAddress("address"), + ULInt16("port"))), + 0x0081 : (mapserv.smsg_connection_problem, + Struct("data", Byte("code"))), +} + + +def cmsg_char_server_connect(account, session1, session2, proto, gender): + netlog.info(("CMSG_CHAR_SERVER_CONNECT account={} session1={} " + "session2={} proto={} gender={}").format( + account, session1, session2, proto, gender)) + + # save session data + server.account = account + server.session1 = session1 + server.session2 = session2 + server.gender = gender + + send_packet(server, CMSG_CHAR_SERVER_CONNECT, + (ULInt32("account"), account), + (ULInt32("session1"), session1), + (ULInt32("session2"), session2), + (ULInt16("proto"), proto), + (Gender("gender"), gender)) + + +def cmsg_char_select(slot): + netlog.info("CMSG_CHAR_SELECT slot={}".format(slot)) + send_packet(server, CMSG_CHAR_SELECT, (Byte("slot"), slot)) + + +def connect(host, port): + global server + #server = SocketWrapper(host=host, port=port, protodef=protodef) + server = SocketWrapper(host="52.174.196.146", port=port, protodef=protodef) diff --git a/net/charserv.pyc b/net/charserv.pyc Binary files differnew file mode 100644 index 0000000..1109add --- /dev/null +++ b/net/charserv.pyc diff --git a/net/common.py b/net/common.py new file mode 100644 index 0000000..a589b44 --- /dev/null +++ b/net/common.py @@ -0,0 +1,106 @@ +import time +import socket +import asyncore +from logging import DEBUG +from construct import Struct, ULInt16, String, Enum, Byte +from dispatcher import dispatch +from loggers import netlog + + +def StringZ(name, length, **kw): + kw['padchar'] = "\x00" + kw['paddir'] = "right" + return String(name, length, **kw) + + +def Gender(name): + return Enum(Byte(name), BOY=1, GIRL=0, UNSPECIFIED=2, OTHER=3) + + +class SocketWrapper(asyncore.dispatcher_with_send): + """ + socket wrapper with file-like read() and write() methods + """ + def __init__(self, host=None, port=0, + protodef={}, onerror=None, sock=None): + asyncore.dispatcher_with_send.__init__(self, sock) + self.read_buffer = '' + if sock is None: + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.setblocking(1) + self.socket.settimeout(0.7) + + self.protodef = protodef + self.raw = False + self.onerror = onerror + if protodef == {}: + netlog.warning("protodef is empty") + if host is not None: + self.connect((host, port)) + + def handle_read(self): + try: + self.read_buffer += self.recv(2) + except socket.error: + return + while len(self.read_buffer) > 0: + dispatch(self, self.protodef) + + def handle_error(self): + if self.onerror is not None: + self.onerror() + else: + raise + + def read(self, n=-1): + data = '' + if n < 0: + data = self.read_buffer + self.read_buffer = '' + else: + tries = 0 + while len(self.read_buffer) < n: + try: + self.read_buffer += self.recv(n - len(self.read_buffer)) + except socket.error as e: + tries += 1 + if tries < 10: + netlog.error("socket.error %s", e) + time.sleep(0.2) + else: + raise + + data = self.read_buffer[:n] + self.read_buffer = self.read_buffer[n:] + + if netlog.isEnabledFor(DEBUG): + netlog.debug("read " + + ":".join("{:02x}".format(ord(c)) for c in data)) + return data + + def write(self, data): + if netlog.isEnabledFor(DEBUG): + netlog.debug("write " + + ":".join("{:02x}".format(ord(c)) for c in data)) + if self.raw: + self.socket.sendall(data) + else: + self.send(data) + + +def send_packet(srv, opcode_, *fields): + class C: + opcode = opcode_ + + ms = [ULInt16("opcode")] + + for macro, value in fields: + setattr(C, macro.name, value) + ms.append(macro) + + d = Struct("packet", *ms) + d.build_stream(C, srv) + + +def distance(x1, y1, x2, y2): + return max(abs(x2 - x1), abs(y2 - y1)) diff --git a/net/common.pyc b/net/common.pyc Binary files differnew file mode 100644 index 0000000..3f457a5 --- /dev/null +++ b/net/common.pyc diff --git a/net/dispatcher.py b/net/dispatcher.py new file mode 100644 index 0000000..115212c --- /dev/null +++ b/net/dispatcher.py @@ -0,0 +1,27 @@ +from construct import Struct, ULInt16, MetaField +from loggers import netlog +from packetlen import packet_lengths + + +def dispatch(stream, protodef): + opcode = ULInt16("opcode").parse_stream(stream) + + if opcode in protodef: + func, macro = protodef[opcode] + data = macro.parse_stream(stream) + func(data) + else: + data = '' + pktlen = packet_lengths.get(opcode, -1) + + if pktlen > 0: + data = stream.read(pktlen - 2) + elif pktlen == -1: + datadef = Struct("data", + ULInt16("length"), + MetaField("ignore", + lambda ctx: ctx["length"] - 4)) + data = datadef.parse_stream(stream) + + netlog.warning('UNIMPLEMENTED opcode={:04x} data={}'.format( + opcode, data)) diff --git a/net/dispatcher.pyc b/net/dispatcher.pyc Binary files differnew file mode 100644 index 0000000..c671b01 --- /dev/null +++ b/net/dispatcher.pyc diff --git a/net/inventory.py b/net/inventory.py new file mode 100644 index 0000000..de927d7 --- /dev/null +++ b/net/inventory.py @@ -0,0 +1,51 @@ +import mapserv + + +def get_item_index(item_id): + for index, (id_, _) in mapserv.player_inventory.iteritems(): + if id_ == item_id: + return index + + return -10 + + +def remove_from_inventory(index, amount): + item_id, curr_amount = mapserv.player_inventory[index] + curr_amount -= amount + if curr_amount <= 0: + del mapserv.player_inventory[index] + else: + mapserv.player_inventory[index] = item_id, curr_amount + + +def add_to_inventory(index, item_id, amount): + if index not in mapserv.player_inventory: + mapserv.player_inventory[index] = item_id, amount + else: + _, curr_amount = mapserv.player_inventory[index] + mapserv.player_inventory[index] = item_id, curr_amount + amount + + +def get_storage_index(item_id): + for index, (id_, _) in mapserv.player_storage.iteritems(): + if id_ == item_id: + return index + + return -10 + + +def remove_from_storage(index, amount): + item_id, curr_amount = mapserv.player_storage[index] + curr_amount -= amount + if curr_amount <= 0: + del mapserv.player_storage[index] + else: + mapserv.player_storage[index] = item_id, curr_amount + + +def add_to_storage(index, item_id, amount): + if index not in mapserv.player_storage: + mapserv.player_storage[index] = item_id, amount + else: + _, curr_amount = mapserv.player_storage[index] + mapserv.player_storage[index] = item_id, curr_amount + amount diff --git a/net/inventory.pyc b/net/inventory.pyc Binary files differnew file mode 100644 index 0000000..85b5a74 --- /dev/null +++ b/net/inventory.pyc diff --git a/net/item.py b/net/item.py new file mode 100644 index 0000000..f82fa77 --- /dev/null +++ b/net/item.py @@ -0,0 +1,8 @@ + +class FloorItem: + def __init__(self, obj_id, item_type, amount, x, y): + self.id = obj_id + self.type = item_type + self.amount = amount + self.x = x + self.y = y diff --git a/net/item.pyc b/net/item.pyc Binary files differnew file mode 100644 index 0000000..9ceb6a7 --- /dev/null +++ b/net/item.pyc diff --git a/net/loginsrv.py b/net/loginsrv.py new file mode 100644 index 0000000..c9ebc0a --- /dev/null +++ b/net/loginsrv.py @@ -0,0 +1,109 @@ +from construct import * +from construct.protocols.layer3.ipv4 import IpAddress +import charserv +from protocol import * +from utils import * +from common import * +from loggers import netlog + +server = None + + +@extendable +def smsg_server_version(data): + netlog.info("SMSG_SERVER_VERSION {}.{}".format(data.hi, data.lo)) + cmsg_login_register(server.username, server.password) + + +@extendable +def smsg_update_host(data): + netlog.info("SMSG_UPDATE_HOST {}".format(data.host)) + + +@extendable +def smsg_login_data(data): + netlog.info("SMSG_LOGIN_DATA {}".format(data)) + server.close() + + charserv.connect(data.worlds[0].address, data.worlds[0].port) + charserv.server.char_name = server.char_name + charserv.cmsg_char_server_connect(data.account, data.session1, + data.session2, 1, data.gender) + + +@extendable +def smsg_login_error(data): + error_codes = { + 0: "Unregistered ID", + 1: "Wrong password", + 2: "Account expired", + 3: "Rejected from server", + 4: "Permban", + 5: "Client too old", + 6: "Temporary ban until {}".format(data.date), + 7: "Server overpopulated", + 9: "Username already taken", + 10: "Wrong name", + 11: "Incurrect email", + 99: "Username permanently erased" } + + err_msg = "SMSG_LOGIN_ERROR {}".format( + error_codes.get(data.code, "Unknown error")) + netlog.error(err_msg) + server.close() + raise Exception(err_msg) + + +protodef = { + 0x7531 : (smsg_server_version, + Struct("data", + ULInt32("hi"), + ULInt32("lo"))), + 0x0063 : (smsg_update_host, + Struct("data", + ULInt16("length"), + StringZ("host", + lambda ctx: ctx.length - 4))), + 0x0069 : (smsg_login_data, + Struct("data", + ULInt16("length"), + ULInt32("session1"), + ULInt32("account"), + ULInt32("session2"), + IpAddress("oldip"), + StringZ("lastlogin", 24), + Padding(2), + Gender("gender"), + Array(lambda ctx: (ctx.length - 47) / 32, + Struct("worlds", + IpAddress("address"), + ULInt16("port"), + StringZ("name", 20), + ULInt16("onlineusers"), + Padding(4))))), + + 0x006a : (smsg_login_error, + Struct("data", + Byte("code"), + StringZ("date", 20))) +} + + +def cmsg_server_version_request(): + netlog.info("CMSG_SERVER_VERSION_REQUEST") + ULInt16("opcode").build_stream(0x7530, server) + + +def cmsg_login_register(username, password): + netlog.info("CMSG_LOGIN_REGISTER username={} password={}".format( + username, password)) + send_packet(server, CMSG_LOGIN_REGISTER, + (ULInt32("clientversion"), 6), + (StringZ("username", 24), username), + (StringZ("password", 24), password), + (Byte("flags"), 3)) + + +def connect(host, port): + global server + server = SocketWrapper(host=host, port=port, protodef=protodef) diff --git a/net/loginsrv.pyc b/net/loginsrv.pyc Binary files differnew file mode 100644 index 0000000..eb52b86 --- /dev/null +++ b/net/loginsrv.pyc diff --git a/net/mapserv.py b/net/mapserv.py new file mode 100644 index 0000000..121c3a9 --- /dev/null +++ b/net/mapserv.py @@ -0,0 +1,1216 @@ + +import atexit +import time +from construct import * +from construct.protocols.layer3.ipv4 import IpAddress +from protocol import * +from common import * +from utils import * +from being import BeingsCache +from item import FloorItem +from inventory import (add_to_inventory, remove_from_inventory, + add_to_storage, remove_from_storage) +from trade import reset_trade_state +from loggers import netlog + +server = None +beings_cache = None +party_info = [] +party_members = {} +player_pos = {'map': 'unknown', 'x': 0, 'y': 0, 'dir': 0} +tick = 0 +last_whisper = {'to': '', 'msg': ''} +player_inventory = {} +player_storage = {} +player_stats = {} +player_skills = {} +player_money = 0 +player_attack_range = 0 +trade_state = {'items_give': [], 'items_get': [], + 'zeny_give': 0, 'zeny_get': 0} +floor_items = {} + +for s in range(255): + player_stats[s] = 0 + + +# -------------------------------------------------------------------- +def smsg_ignore(data): + pass + + +@extendable +def smsg_being_chat(data): + cached_name = beings_cache.findName(data.id) + real_name, _ = data.message.split(' : ', 1) + if real_name != cached_name: + cmsg_name_request(data.id) + netlog.info("SMSG_BEING_CHAT id={} msg={}".format(data.id, data.message)) + + +@extendable +def smsg_being_emotion(data): + beings_cache.findName(data.id) + netlog.info("SMSG_BEING_EMOTION {} : {}".format(data.id, data.emote)) + + +@extendable +def smsg_being_move(data): + global tick + tick = data.tick + beings_cache.findName(data.id, data.job) + beings_cache[data.id].x = data.coor_pair.dst_x + beings_cache[data.id].y = data.coor_pair.dst_y + netlog.info("SMSG_BEING_MOVE {}".format(data)) + + +@extendable +def smsg_being_action(data): + global tick + tick = data.tick + netlog.info("SMSG_BEING_ACTION {}".format(data)) + + +@extendable +def smsg_being_change_direction(data): + netlog.info("SMSG_BEING_CHANGE_DIRECTION {}".format(data)) + + +@extendable +def smsg_being_name_response(data): + try: + beings_cache[data.id].name = data.name + except KeyError: + pass + netlog.info("SMSG_BEING_NAME_RESPONSE id={} name={}".format( + data.id, data.name)) + + +@extendable +def smsg_being_remove(data): + try: + del beings_cache[data.id] + except KeyError: + pass + netlog.info("SMSG_BEING_REMOVE (id={}, deadflag={})".format( + data.id, data.deadflag)) + + +@extendable +def smsg_being_visible(data): + beings_cache.findName(data.id, data.job) + beings_cache[data.id].speed = data.speed + beings_cache[data.id].x = data.coor.x + beings_cache[data.id].y = data.coor.y + netlog.info("SMSG_BEING_VISIBLE {}".format(data)) + + +@extendable +def smsg_player_chat(data): + netlog.info("SMSG_PLAYER_CHAT {}".format(data.message)) + + +@extendable +def smsg_player_equipment(data): + netlog.info("SMSG_PLAYER_EQUIPMENT {}".format(data)) + for item in data.equipment: + player_inventory[item.index] = (item.id, 1) + + +@extendable +def smsg_player_inventory(data): + netlog.info("SMSG_PLAYER_INVENTORY {}".format(data)) + for item in data.inventory: + player_inventory[item.index] = (item.id, item.amount) + + +@extendable +def smsg_player_inventory_add(data): + netlog.info("SMSG_PLAYER_INVENTORY_ADD index={} id={} amount={}".format( + data.index, data.id, data.amount)) + add_to_inventory(data.index, data.id, data.amount) + + +@extendable +def smsg_player_inventory_remove(data): + netlog.info("SMSG_PLAYER_INVENTORY_REMOVE index={} amount={}".format( + data.index, data.amount)) + remove_from_inventory(data.index, data.amount) + + +@extendable +def smsg_player_inventory_use(data): + netlog.info("SMSG_PLAYER_INVENTORY_USE {}".format(data)) + if data.amount > 0: + player_inventory[data.index] = (data.item_id, data.amount) + else: + del player_inventory[data.index] + + +@extendable +def smsg_player_move(data): + netlog.info("SMSG_PLAYER_MOVE {}".format(data)) + global tick + tick = data.tick + beings_cache.findName(data.id, data.job) + beings_cache[data.id].x = data.coor_pair.dst_x + beings_cache[data.id].y = data.coor_pair.dst_y + + +@extendable +def smsg_player_stop(data): + netlog.info("SMSG_PLAYER_STOP id={} x={} y={}".format( + data.id, data.x, data.y)) + beings_cache.findName(data.id) + beings_cache[data.id].x = data.x + beings_cache[data.id].y = data.y + + +@extendable +def smsg_player_update(data): + netlog.info("SMSG_PLAYER_UPDATE_ {}".format(data)) + beings_cache.findName(data.id, data.job) + beings_cache[data.id].speed = data.speed + beings_cache[data.id].x = data.coor.x + beings_cache[data.id].y = data.coor.y + + +@extendable +def smsg_player_warp(data): + netlog.info("SMSG_PLAYER_WARP (map={}, x={}, y={}".format( + data.map, data.x, data.y)) + player_pos['map'] = data.map + player_pos['x'] = data.x + player_pos['y'] = data.y + beings_cache.clear() + + +@extendable +def smsg_ip_response(data): + netlog.info("SMSG_IP_RESPONSE id={} ip={}".format(data.id, data.ip)) + + +@extendable +def smsg_connection_problem(data): + error_codes = { + 0 : "Authentification failed", + 1 : "No servers available", + 2 : "Account already in use", + 3 : "Speed hack detected", + 8 : "Duplicated login", + } + msg = error_codes.get(data.code, 'code ' + str(data.code)) + netlog.error("SMSG_CONNECTION_PROBLEM %d", data.code) + raise Exception(msg) + + +@extendable +def smsg_gm_chat(data): + netlog.info("SMSG_GM_CHAT {}".format(data.message)) + + +@extendable +def smsg_party_info(data): + global party_info, party_members + party_info = data + for m in data.members: + party_members[m.id] = m.nick + netlog.info("SMSG_PARTY_INFO {}".format(data)) + + +@extendable +def smsg_party_chat(data): + netlog.info("SMSG_PARTY_CHAT {} : {}".format(data.id, data.message)) + + +@extendable +def smsg_trade_request(data): + netlog.info("SMSG_TRADE_REQUEST {}".format(data.nick)) + # cmsg_trade_response("DECLINE") + + +@extendable +def smsg_trade_response(data): + netlog.info("SMSG_TRADE_RESPONSE {}".format(data.code)) + + +@extendable +def smsg_trade_item_add(data): + netlog.info("SMSG_TRADE_ITEM_ADD id={} amount={}".format( + data.id, data.amount)) + if data.id == 0: + trade_state['zeny_get'] = data.amount + else: + trade_state['items_get'].append((data.id, data.amount)) + + +@extendable +def smsg_trade_item_add_response(data): + netlog.info(("SMSG_TRADE_ITEM_ADD_RESPONSE" + " index={} amount={} code={}").format( + data.index, data.amount, data.code)) + + index = data.index + amount = data.amount + code = data.code + + if code == 0 and amount > 0: + if index > 0: + item_id, _ = player_inventory[index] + remove_from_inventory(index, amount) + trade_state['items_give'].append((item_id, amount)) + elif index == 0: + trade_state['zeny_give'] = amount + + +@extendable +def smsg_trade_cancel(data): + netlog.info("SMSG_TRADE_CANCEL") + reset_trade_state(trade_state) + + +@extendable +def smsg_trade_ok(data): + netlog.info("SMSG_TRADE_OK who={}".format(data.who)) + + +@extendable +def smsg_trade_complete(data): + netlog.info("SMSG_TRADE_COMPLETE") + global player_money + player_money += trade_state['zeny_get'] - trade_state['zeny_give'] + # reset_trade_state(trade_state) + + +@extendable +def smsg_whisper(data): + netlog.info("SMSG_WHISPER {} : {}".format(data.nick, data.message)) + + +@extendable +def smsg_whisper_response(data): + m = {0: "OK", 1: "Recepient is offline"} + netlog.info("SMSG_WHISPER_RESPONSE {}".format(m.get(data.code, "error"))) + + +@extendable +def smsg_server_ping(data): + global tick + tick = data.tick + netlog.info("SMSG_SERVER_PING tick={}".format(data.tick)) + + +@extendable +def smsg_map_login_success(data): + netlog.info("SMSG_MAP_LOGIN_SUCCESS {}".format(data)) + global tick + tick = data.tick + player_pos['x'] = data.coor.x + player_pos['y'] = data.coor.y + player_pos['dir'] = data.coor.dir + + +@extendable +def smsg_walk_response(data): + global tick + tick = data.tick + player_pos['x'] = data.coor_pair.dst_x + player_pos['y'] = data.coor_pair.dst_y + netlog.info("SMSG_WALK_RESPONSE {}".format(data)) + + +@extendable +def smsg_item_visible(data): + netlog.info("SMSG_ITEM_VISIBLE {}".format(data)) + item = FloorItem(data.id, data.type, data.amount, data.x, data.y) + floor_items[data.id] = item + + +@extendable +def smsg_item_dropped(data): + netlog.info("SMSG_ITEM_DROPPED {}".format(data)) + item = FloorItem(data.id, data.type, data.amount, data.x, data.y) + floor_items[data.id] = item + + +@extendable +def smsg_item_remove(data): + netlog.info("SMSG_ITEM_REMOVE id={}".format(data.id)) + if data.id in floor_items: + del floor_items[data.id] + + +@extendable +def smsg_player_stat_update_x(data): + netlog.info("SMSG_PLAYER_STAT_UPDATE_X type={} value={}".format( + data.type, data.stat_value)) + player_stats[data.type] = data.stat_value + + +@extendable +def smsg_being_self_effect(data): + netlog.info("SMSG_BEING_SELF_EFFECT id={} effect={}".format( + data.id, data.effect)) + + +@extendable +def smsg_being_status_change(data): + netlog.info("SMSG_BEING_STATUS_CHANGE id={} status={} flag={}".format( + data.id, data.status, data.flag)) + + +@extendable +def smsg_player_status_change(data): + netlog.info("SMSG_PLAYER_STATUS_CHANGE {}".format(data)) + + +@extendable +def smsg_npc_message(data): + netlog.info("SMSG_NPC_MESSAGE id={} message={}".format( + data.id, data.message)) + + +@extendable +def smsg_npc_choice(data): + netlog.info("SMSG_NPC_CHOICE {}".format(data.select)) + + +@extendable +def smsg_npc_close(data): + netlog.info("SMSG_NPC_CLOSE id={}".format(data.id)) + + +@extendable +def smsg_npc_next(data): + netlog.info("SMSG_NPC_NEXT id={}".format(data.id)) + + +@extendable +def smsg_npc_int_input(data): + netlog.info("SMSG_NPC_INT_INPUT id={}".format(data.id)) + + +@extendable +def smsg_npc_str_input(data): + netlog.info("SMSG_NPC_STR_INPUT id={}".format(data.id)) + + +@extendable +def smsg_npc_command(data): + netlog.info("SMSG_NPC_COMMAND {}".format(data)) + + +@extendable +def smsg_npc_buy(data): + netlog.info("SMSG_NPC_BUY {}".format(data)) + + +@extendable +def smsg_player_skills(data): + netlog.info("SMSG_PLAYER_SKILLS {}".format(data)) + for skill in data.skills: + player_skills[skill.id] = skill.level + + +@extendable +def smsg_player_skill_up(data): + netlog.info("SMSG_PLAYER_SKILL_UP {}".format(data)) + player_skills.setdefault(data.id, data.level) + + +@extendable +def smsg_storage_status(data): + netlog.info("SMSG_STORAGE_STATUS used={} max_size={}".format( + data.used, data.max_size)) + + +@extendable +def smsg_storage_add(data): + netlog.info("SMSG_STORAGE_ADD {}".format(data)) + add_to_storage(data.index, data.id, data.amount) + + +@extendable +def smsg_storage_remove(data): + netlog.info("SMSG_STORAGE_REMOVE {}".format(data)) + remove_from_storage(data.index, data.amount) + + +@extendable +def smsg_storage_close(data): + netlog.info("SMSG_STORAGE_CLOSE") + + +@extendable +def smsg_storage_equip(data): + netlog.info("SMSG_STORAGE_EQUIP {}".format(data)) + for item in data.equipment: + player_storage[item.index] = (item.id, 1) + + +@extendable +def smsg_storage_items(data): + netlog.info("SMSG_STORAGE_ITEMS {}".format(data)) + for item in data.storage: + player_storage[item.index] = (item.id, item.amount) + + +@extendable +def smsg_player_attack_range(data): + netlog.info("SMSG_PLAYER_ATTACK_RANGE %d", data.range) + global player_attack_range + player_attack_range = data.range + + +@extendable +def smsg_player_arrow_message(data): + netlog.info("SMSG_PLAYER_ARROW_MESSAGE %d", data.code) + + +# -------------------------------------------------------------------- +protodef = { + 0x8000 : (smsg_ignore, Field("data", 2)), # ignore + 0x008a : (smsg_being_action, + Struct("data", + ULInt32("src_id"), + ULInt32("dst_id"), + ULInt32("tick"), + ULInt32("src_speed"), + ULInt32("dst_speed"), + ULInt16("damage"), + Padding(2), + Byte("type"), + Padding(2))), + 0x009c : (smsg_being_change_direction, + Struct("data", + ULInt32("id"), + Padding(2), + Byte("dir"))), + 0x00c3 : (smsg_ignore, Field("data", 6)), # being-change-looks + 0x01d7 : (smsg_ignore, Field("data", 9)), # being-change-looks2 + 0x008d : (smsg_being_chat, + Struct("data", + ULInt16("length"), + ULInt32("id"), + StringZ("message", lambda ctx: ctx.length - 8))), + 0x00c0 : (smsg_being_emotion, + Struct("data", + ULInt32("id"), + Byte("emote"))), + 0x007b : (smsg_being_move, + Struct("data", + ULInt32("id"), + ULInt16("speed"), + Padding(6), + ULInt16("job"), + Padding(6), + ULInt32("tick"), + Padding(10), + ULInt32("hp"), + ULInt32("max_hp"), + # Probe("debug", show_stream=False, show_stack=False), + Padding(6), + BitStruct("coor_pair", + BitField("src_x", 10), + BitField("src_y", 10), + BitField("dst_x", 10), + BitField("dst_y", 10)), + Padding(5))), + 0x0086 : (smsg_being_move, + Struct("data", + ULInt32("id"), + # Padding(10) + BitStruct("coor_pair", + BitField("src_x", 10), + BitField("src_y", 10), + BitField("dst_x", 10), + BitField("dst_y", 10)), + ULInt32("tick"))), + 0x0095 : (smsg_being_name_response, + Struct("data", + ULInt32("id"), + StringZ("name", 24))), + 0x0080 : (smsg_being_remove, + Struct("data", + ULInt32("id"), + Byte("deadflag"))), + 0x0148 : (smsg_ignore, Field("data", 6)), # being-resurrect + 0x019b : (smsg_being_self_effect, + Struct("data", + ULInt32("id"), + ULInt32("effect"))), + 0x007c : (smsg_ignore, Field("data", 39)), # spawn + 0x0196 : (smsg_being_status_change, + Struct("data", + ULInt16("status"), + ULInt32("id"), + Flag("flag"))), + 0x0078 : (smsg_being_visible, + Struct("data", + ULInt32("id"), + ULInt16("speed"), + Padding(6), + ULInt16("job"), + Padding(16), + ULInt32("hp"), + ULInt32("max_hp"), + Padding(6), + BitStruct("coor", + BitField("x", 10), + BitField("y", 10), + Nibble("dir")), + Padding(5))), + 0x013c : (smsg_ignore, Field("data", 2)), # arrow-equip + 0x013b : (smsg_player_arrow_message, + Struct("data", + ULInt16("code"))), + 0x013a : (smsg_player_attack_range, + Struct("data", + ULInt16("range"))), + 0x008e : (smsg_player_chat, + Struct("data", + ULInt16("length"), + StringZ("message", lambda ctx: ctx.length - 4))), + 0x00aa : (smsg_ignore, # player-equip + Struct("data", + ULInt16("index"), + ULInt16("type"), + Byte("flag"))), + 0x00a4 : (smsg_player_equipment, + Struct("data", + ULInt16("length"), + Array(lambda ctx: (ctx.length - 4) / 20, + Struct("equipment", + ULInt16("index"), + ULInt16("id"), + Padding(16))))), + 0x0195 : (smsg_ignore, Field("data", 100)), # guild-party-info + 0x01ee : (smsg_player_inventory, + Struct("data", + ULInt16("length"), + Array(lambda ctx: (ctx.length - 4) / 18, + Struct("inventory", + ULInt16("index"), + ULInt16("id"), + Padding(2), + ULInt16("amount"), + Padding(10))))), + 0x00a0 : (smsg_player_inventory_add, + Struct("data", + ULInt16("index"), + ULInt16("amount"), + ULInt16("id"), + Padding(15))), + 0x00af : (smsg_player_inventory_remove, + Struct("data", + ULInt16("index"), + ULInt16("amount"))), + 0x01c8 : (smsg_player_inventory_use, + Struct("data", + ULInt16("index"), + ULInt16("item_id"), + Padding(4), + ULInt16("amount"), + Byte("type"))), + 0x01da : (smsg_player_move, + Struct("data", + ULInt32("id"), + ULInt16("speed"), + Padding(6), + ULInt16("job"), + Padding(8), + ULInt32("tick"), + Padding(22), + BitStruct("coor_pair", + BitField("src_x", 10), + BitField("src_y", 10), + BitField("dst_x", 10), + BitField("dst_y", 10)), + Padding(5))), + 0x0139 : (smsg_ignore, Field("data", 14)), # player-move-to-attack + 0x010f : (smsg_player_skills, + Struct("data", + ULInt16("length"), + Array(lambda ctx: (ctx.length - 4) / 37, + Struct("skills", + ULInt16("id"), + Padding(4), + ULInt16("level"), + Padding(29))))), + 0x010e : (smsg_player_skill_up, + Struct("data", + ULInt16("id"), + ULInt16("level"), + Padding(5))), + 0x00b0 : (smsg_player_stat_update_x, + Struct("data", + ULInt16("type"), + ULInt32("stat_value"))), + 0x00b1 : (smsg_player_stat_update_x, + Struct("data", + ULInt16("type"), + ULInt32("stat_value"))), + 0x0141 : (smsg_player_stat_update_x, + Struct("data", + ULInt32("type"), + ULInt32("stat_value"), + ULInt32("bonus"))), + 0x00bc : (smsg_player_stat_update_x, + Struct("data", + ULInt16("type"), + Flag("ok"), + Byte("stat_value"))), + 0x00bd : (smsg_ignore, Field("data", 42)), # player-stat-update-5 + 0x00be : (smsg_player_stat_update_x, + Struct("data", + ULInt16("type"), + Byte("stat_value"))), + 0x0119 : (smsg_player_status_change, + Struct("data", + ULInt32("id"), + ULInt16("stun"), + ULInt16("effect"), + ULInt16("effect_hi"), + Padding(1))), + 0x0088 : (smsg_player_stop, + Struct("data", + ULInt32("id"), + ULInt16("x"), + ULInt16("y"))), + 0x00ac : (smsg_ignore, Field("data", 5)), # player-unequip + 0x01d8 : (smsg_player_update, + Struct("data", + ULInt32("id"), + ULInt16("speed"), + Padding(6), + ULInt16("job"), + Padding(30), + BitStruct("coor", + BitField("x", 10), + BitField("y", 10), + Nibble("dir")), + Padding(5))), + 0x01d9 : (smsg_player_update, + Struct("data", + ULInt32("id"), + ULInt16("speed"), + Padding(6), + ULInt16("job"), + Padding(30), + BitStruct("coor", + BitField("x", 10), + BitField("y", 10), + Nibble("dir")), + Padding(4))), + 0x0091 : (smsg_player_warp, + Struct("data", + StringZ("map", 16), + ULInt16("x"), + ULInt16("y"))), + 0x0020 : (smsg_ip_response, + Struct("data", + ULInt32("id"), + IpAddress("ip"))), + 0x019a : (smsg_ignore, Field("data", 12)), # pvp-set + 0x0081 : (smsg_connection_problem, + Struct("data", Byte("code"))), + 0x009a : (smsg_gm_chat, + Struct("data", + ULInt16("length"), + StringZ("message", lambda ctx: ctx.length - 4))), + 0x009e : (smsg_item_dropped, + Struct("data", + ULInt32("id"), + ULInt16("type"), + Byte("identify"), + ULInt16("x"), + ULInt16("y"), + Byte("sub_x"), + Byte("sub_y"), + ULInt16("amount"))), + 0x00a1 : (smsg_item_remove, + Struct("data", ULInt32("id"))), + 0x009d : (smsg_item_visible, + Struct("data", + ULInt32("id"), + ULInt16("type"), + Byte("identify"), + ULInt16("x"), + ULInt16("y"), + ULInt16("amount"), + Byte("sub_x"), + Byte("sub_y"))), + 0x00fb : (smsg_party_info, + Struct("data", + ULInt16("length"), + StringZ("name", 24), + Array(lambda ctx: (ctx.length - 28) / 46, + Struct("members", + ULInt32("id"), + StringZ("nick", 24), + StringZ("map", 16), + Flag("leader"), + Flag("online"))))), + 0x00fe : (smsg_ignore, Field("data", 28)), # party-invited + 0x0107 : (smsg_ignore, Field("data", 8)), # party-update-coords + 0x0106 : (smsg_ignore, Field("data", 8)), # party-update-hp + 0x0109 : (smsg_party_chat, + Struct("data", + ULInt16("length"), + ULInt32("id"), + # Probe("debug", show_stream=False, show_stack=False), + StringZ("message", lambda ctx: ctx.length - 8))), + 0x01b9 : (smsg_ignore, Field("data", 4)), # skill-cast-cancel + 0x013e : (smsg_ignore, Field("data", 22)), # skill-casting + 0x01de : (smsg_ignore, Field("data", 31)), # skill-damage + 0x011a : (smsg_ignore, Field("data", 13)), # skill-no-damage + 0x00e5 : (smsg_trade_request, + Struct("data", + StringZ("nick", 24))), + 0x00e7 : (smsg_trade_response, + Struct("data", Byte("code"))), + 0x00e9 : (smsg_trade_item_add, + Struct("data", + ULInt32("amount"), + ULInt16("id"), + Padding(11))), + 0x01b1 : (smsg_trade_item_add_response, + Struct("data", + ULInt16("index"), + ULInt16("amount"), + Byte("code"))), + 0x00ee : (smsg_trade_cancel, + StaticField("data", 0)), + 0x00f0 : (smsg_trade_complete, + StaticField("data", 1)), + 0x00ec : (smsg_trade_ok, + Struct("data", Byte("who"))), + 0x0097 : (smsg_whisper, + Struct("data", + ULInt16("length"), + StringZ("nick", 24), + StringZ("message", lambda ctx: ctx.length - 28))), + 0x0098 : (smsg_whisper_response, + Struct("data", Byte("code"))), + 0x0073 : (smsg_map_login_success, + Struct("data", + ULInt32("tick"), + # ULInt24("coor"), + BitStruct("coor", + BitField("x", 10), + BitField("y", 10), + Nibble("dir")), + Padding(2))), + 0x007f : (smsg_server_ping, + Struct("data", + ULInt32("tick"))), + 0x0087 : (smsg_walk_response, + Struct("data", + ULInt32("tick"), + BitStruct("coor_pair", + BitField("src_x", 10), + BitField("src_y", 10), + BitField("dst_x", 10), + BitField("dst_y", 10)), + Padding(1))), + 0x00b5 : (smsg_ignore, Field("data", 6)), # npc-next + 0x00b4 : (smsg_npc_message, + Struct("data", + ULInt16("length"), + ULInt32("id"), + StringZ("message", lambda ctx: ctx.length - 8))), + 0x00b7 : (smsg_npc_choice, + Struct("data", + ULInt16("length"), + ULInt32("id"), + StringZ("select", lambda ctx: ctx.length - 8))), + 0x00b6 : (smsg_npc_close, + Struct("data", + ULInt32("id"))), + 0x00b5 : (smsg_npc_next, + Struct("data", + ULInt32("id"))), + 0x0142 : (smsg_npc_int_input, + Struct("data", + ULInt32("id"))), + 0x01d4 : (smsg_npc_str_input, + Struct("data", + ULInt32("id"))), + 0x0212 : (smsg_npc_command, + Struct("data", + ULInt32("id"), + ULInt16("command"), + ULInt32("target_id"), + ULInt16("x"), + ULInt16("y"))), + 0x00c6 : (smsg_npc_buy, + Struct("data", + ULInt16("length"), + Array(lambda ctx: (ctx.length - 4) / 11, + Struct("items", + ULInt32("price"), + Padding(4), + ULInt16("type"), + ULInt32("id"))))), + 0x00f2 : (smsg_storage_status, + Struct("data", + ULInt16("used"), + ULInt16("max_size"))), + 0x00f4 : (smsg_storage_add, + Struct("data", + ULInt16("index"), + ULInt32("amount"), + ULInt16("id"), + Padding(11))), + 0x00f6 : (smsg_storage_remove, + Struct("data", + ULInt16("index"), + ULInt32("amount"))), + 0x00f8 : (smsg_storage_close, + Struct("data")), # ????? + 0x00a6 : (smsg_storage_equip, + Struct("data", + ULInt16("length"), + Array(lambda ctx: (ctx.length - 4) / 20, + Struct("equipment", + ULInt16("index"), + ULInt16("id"), + Padding(16))))), + 0x01f0 : (smsg_storage_items, + Struct("data", + ULInt16("length"), + Array(lambda ctx: (ctx.length - 4) / 18, + Struct("storage", + ULInt16("index"), + ULInt16("id"), + Padding(2), + ULInt16("amount"), + Padding(10))))), +} + + +# -------------------------------------------------------------------- +def cmsg_map_server_connect(account, char_id, session1, session2, gender): + netlog.info(("CMSG_MAP_SERVER_CONNECT account={} char_id={} " + "session1={} session2={} gender={}").format(account, + char_id, session1, session2, gender)) + + send_packet(server, CMSG_MAP_SERVER_CONNECT, + (ULInt32("account"), account), + (ULInt32("char_id"), char_id), + (ULInt32("session1"), session1), + (ULInt32("session2"), session2), + (Gender("gender"), gender)) + + +def cmsg_map_loaded(): + netlog.info("CMSG_MAP_LOADED") + ULInt16("opcode").build_stream(CMSG_MAP_LOADED, server) + + +def cmsg_map_server_ping(tick_=-1): + netlog.info("CMSG_MAP_SERVER_PING tick={}".format(tick)) + if tick_ < 0: + tick_ = tick + 1 + send_packet(server, CMSG_MAP_SERVER_PING, + (ULInt32("tick"), tick_)) + + +def cmsg_trade_response(answer): + if answer in ("OK", "ACCEPT", "YES", True): + answer = 3 + elif answer in ("DECLINE", "CANCEL", "NO", False): + answer = 4 + + s = {3: "ACCEPT", 4: "DECLINE"} + + netlog.info("CMSG_TRADE_RESPONSE {}".format(s[answer])) + send_packet(server, CMSG_TRADE_RESPONSE, + (Byte("answer"), answer)) + + +def cmsg_trade_request(player_id): + netlog.info("CMSG_TRADE_REQUEST id={}".format(player_id)) + send_packet(server, CMSG_TRADE_REQUEST, + (ULInt32("id"), player_id)) + + +def cmsg_trade_item_add_request(index, amount): + netlog.info("CMSG_TRADE_ITEM_ADD_REQUEST index={} amount={}".format( + index, amount)) + + # Workaround for TMWA, I'm pretty sure it has a bug related to + # not sending back the amount of GP player added to trade + if index == 0 and amount <= player_money: + trade_state['zeny_give'] = amount + + send_packet(server, CMSG_TRADE_ITEM_ADD_REQUEST, + (ULInt16("index"), index), + (ULInt32("amount"), amount)) + + +def cmsg_trade_ok(): + netlog.info("CMSG_TRADE_OK") + ULInt16("opcode").build_stream(CMSG_TRADE_OK, server) + + +def cmsg_trade_add_complete(): + netlog.info("CMSG_TRADE_ADD_COMPLETE") + ULInt16("opcode").build_stream(CMSG_TRADE_ADD_COMPLETE, server) + + +def cmsg_trade_cancel_request(): + netlog.info("CMSG_TRADE_CANCEL_REQUEST") + ULInt16("opcode").build_stream(CMSG_TRADE_CANCEL_REQUEST, server) + + +def cmsg_name_request(id_): + netlog.info("CMSG_NAME_REQUEST id={}".format(id_)) + send_packet(server, CMSG_NAME_REQUEST, + (ULInt32("id"), id_)) + + +def cmsg_chat_message(msg): + netlog.info("CMSG_CHAT_MESSAGE {}".format(msg)) + l = len(msg) + send_packet(server, CMSG_CHAT_MESSAGE, + (ULInt16("len"), l + 5), + (StringZ("msg", l + 1), msg)) + + +def cmsg_chat_whisper(to_, msg): + netlog.info("CMSG_CHAT_WHISPER to {} : {}".format(to_, msg)) + last_whisper['to'] = to_ + last_whisper['msg'] = msg + l = len(msg) + send_packet(server, CMSG_CHAT_WHISPER, + (ULInt16("len"), l + 29), + (StringZ("nick", 24), to_), + (StringZ("msg", l + 1), msg)) + + +def cmsg_party_message(msg): + netlog.info("CMSG_PARTY_MESSAGE {}".format(msg)) + l = len(msg) + send_packet(server, CMSG_PARTY_MESSAGE, + (ULInt16("len"), l + 4), + (String("msg", l), msg)) + + +def cmsg_player_change_dest(x_, y_): + netlog.info("CMSG_PLAYER_CHANGE_DEST x={} y={}".format(x_, y_)) + + class C: + opcode = CMSG_PLAYER_CHANGE_DEST + + class coor: + x = x_ + y = y_ + dir = 0 + + d = Struct("packet", + ULInt16("opcode"), + BitStruct("coor", BitField("x", 10), + BitField("y", 10), Nibble("dir"))) + + d.build_stream(C, server) + + +def cmsg_player_change_dir(new_dir): + netlog.info("CMSG_PLAYER_CHANGE_DIR {}".format(new_dir)) + send_packet(server, CMSG_PLAYER_CHANGE_DIR, + (ULInt16("unused"), 0), + (ULInt8("dir"), new_dir)) + + +def cmsg_player_change_act(id_, action): + netlog.info("CMSG_PLAYER_CHANGE_ACT id={} action={}".format(id_, action)) + send_packet(server, CMSG_PLAYER_CHANGE_ACT, + (ULInt32("id"), id_), + (Byte("action"), action)) + + +def cmsg_player_respawn(): + netlog.info("CMSG_PLAYER_RESPAWN") + send_packet(server, CMSG_PLAYER_RESPAWN, + (Byte("action"), 0)) + + +def cmsg_player_emote(emote): + netlog.info("CMSG_PLAYER_EMOTE {}".format(emote)) + send_packet(server, CMSG_PLAYER_EMOTE, + (Byte("emote"), emote)) + + +def cmsg_item_pickup(item_id): + netlog.info("CMSG_ITEM_PICKUP id={}".format(item_id)) + send_packet(server, CMSG_ITEM_PICKUP, + (ULInt32("id"), item_id)) + + +def cmsg_player_stop_attack(): + netlog.info("CMSG_PLAYER_STOP_ATTACK") + ULInt16("opcode").build_stream(CMSG_PLAYER_STOP_ATTACK, server) + + +def cmsg_player_equip(index): + netlog.info("CMSG_PLAYER_EQUIP index={}".format(index)) + send_packet(server, CMSG_PLAYER_EQUIP, + (ULInt16("index"), index), + (ULInt16("unused"), 0)) + + +def cmsg_player_unequip(index): + netlog.info("CMSG_PLAYER_UNEQUIP index={}".format(index)) + send_packet(server, CMSG_PLAYER_UNEQUIP, + (ULInt16("index"), index)) + + +def cmsg_player_inventory_use(index, item_id): + netlog.info("CMSG_PLAYER_INVENTORY_USE index={} id={}".format( + index, item_id)) + send_packet(server, CMSG_PLAYER_INVENTORY_USE, + (ULInt16("index"), index), + (ULInt32("id"), item_id)) + + +def cmsg_player_inventory_drop(index, amount): + netlog.info("CMSG_PLAYER_INVENTORY_DROP index={} amount={}".format( + index, amount)) + send_packet(server, CMSG_PLAYER_INVENTORY_DROP, + (ULInt16("index"), index), + (ULInt16("amount"), amount)) + + +# --------------- NPC --------------------- +def cmsg_npc_talk(npcId): + netlog.info("CMSG_NPC_TALK id={}".format(npcId)) + send_packet(server, CMSG_NPC_TALK, + (ULInt32("npcId"), npcId), + (Byte("unused"), 0)) + + +def cmsg_npc_next_request(npcId): + netlog.info("CMSG_NPC_NEXT_REQUEST id={}".format(npcId)) + send_packet(server, CMSG_NPC_NEXT_REQUEST, + (ULInt32("npcId"), npcId)) + + +def cmsg_npc_close(npcId): + netlog.info("CMSG_NPC_CLOSE id={}".format(npcId)) + send_packet(server, CMSG_NPC_CLOSE, + (ULInt32("npcId"), npcId)) + + +def cmsg_npc_list_choice(npcId, choice): + netlog.info("CMSG_NPC_LIST_CHOICE id={} choice={}".format(npcId, choice)) + send_packet(server, CMSG_NPC_LIST_CHOICE, + (ULInt32("npcId"), npcId), + (Byte("choice"), choice)) + + +def cmsg_npc_int_response(npcId, value): + netlog.info("CMSG_NPC_INT_RESPONSE id={} value={}".format(npcId, value)) + send_packet(server, CMSG_NPC_INT_RESPONSE, + (ULInt32("npcId"), npcId), + (SLInt32("value"), value)) + + +def cmsg_npc_str_response(npcId, value): + netlog.info("CMSG_NPC_STR_RESPONSE id={} value={}".format(npcId, value)) + l = len(value) + send_packet(server, CMSG_NPC_STR_RESPONSE, + (ULInt16("len"), l + 9), + (ULInt32("npcId"), npcId), + (StringZ("value", l + 1), value)) + + +def cmsg_npc_buy_sell_request(npcId, action): + netlog.info("CMSG_NPC_BUY_SELL_REQUEST id={} action={}".format( + npcId, action)) + send_packet(server, CMSG_NPC_BUY_SELL_REQUEST, + (ULInt32("npcId"), npcId), + (Byte("action"), action)) + + +def cmsg_npc_buy_request(itemId, amount): + netlog.info("CMSG_NPC_BUY_REQUEST itemId={} amount={}".format( + itemId, amount)) + send_packet(server, CMSG_NPC_BUY_REQUEST, + (ULInt16("len"), 8), + (ULInt16("amount"), amount), + (ULInt16("itemId"), itemId)) + + +def cmsg_npc_sell_request(index, amount): + netlog.info("CMSG_NPC_SELL_REQUEST index={} amount={}".format( + index, amount)) + send_packet(server, CMSG_NPC_SELL_REQUEST, + (ULInt16("len"), 8), + (ULInt16("index"), index), + (ULInt16("amount"), amount)) + + +# --------------- STATS, SKILLS -------------------------- +def cmsg_stat_update_request(stat, value): + netlog.info("CMSG_STAT_UPDATE_REQUEST stat={} value={}".format( + stat, value)) + send_packet(server, CMSG_STAT_UPDATE_REQUEST, + (ULInt16("stat"), stat), + (Byte("value"), value)) + + +def cmsg_skill_levelup_request(skillId): + netlog.info("CMSG_SKILL_LEVELUP_REQUEST skillId={}".format(skillId)) + send_packet(server, CMSG_SKILL_LEVELUP_REQUEST, + (ULInt16("id"), skillId)) + + +# ------------------- STORAGE ------------------------------- +def cmsg_move_to_storage(index, amount): + netlog.info("CMSG_MOVE_TO_STORAGE index={} amount={}".format( + index, amount)) + send_packet(server, CMSG_MOVE_TO_STORAGE, + (ULInt16("index"), index), + (ULInt32("amount"), amount)) + + +def cmsg_move_from_storage(index, amount): + netlog.info("CMSG_MOVE_FROM_STORAGE index={} amount={}".format( + index, amount)) + send_packet(server, CMSG_MOVE_FROM_STORAGE, + (ULInt16("index"), index), + (ULInt32("amount"), amount)) + + +def cmsg_storage_close(): + netlog.info("CMSG_CLOSE_STORAGE") + ULInt16("opcode").build_stream(CMSG_CLOSE_STORAGE, server) + + +# -------------------------------------------------------------------- + +last_ping_ts = 0 + + +def connect(host, port): + + def ping_logic(ts): + global last_ping_ts + if last_ping_ts and ts > last_ping_ts + 15: + last_ping_ts = time.time() + cmsg_map_server_ping() + + global server, beings_cache + beings_cache = BeingsCache(cmsg_name_request) + #server = SocketWrapper(host=host, port=port, protodef=protodef) + server = SocketWrapper(host="52.174.196.146", port=port, protodef=protodef) + + import logicmanager + logicmanager.logic_manager.add_logic(ping_logic) + + +@atexit.register +def cleanup(): + if server is not None: + server.close() diff --git a/net/mapserv.pyc b/net/mapserv.pyc Binary files differnew file mode 100644 index 0000000..0d7c757 --- /dev/null +++ b/net/mapserv.pyc diff --git a/net/onlineusers.py b/net/onlineusers.py new file mode 100644 index 0000000..92b5b0f --- /dev/null +++ b/net/onlineusers.py @@ -0,0 +1,86 @@ + +""" +Copyright 2015-2016, Joseph Botosh <rumly111@gmail.com> + +This file is part of tradey, a trading bot in The Mana World +see www.themanaworld.org + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation; either version 2 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. + +Additionally to the GPL, you are *strongly* encouraged to share any +modifications you do on these sources. +""" + +import urllib2 +import string +import threading +import time + +from common import netlog + + +class OnlineUsers(threading.Thread): + + def __init__(self, online_url='https://server.themanaworld.org/online-old.txt', + update_interval=60, refresh_hook=None): + self._active = True + self._timer = 0 + self._url = online_url + self._update_interval = update_interval + self.__lock = threading.Lock() + self.__online_users = [] + self.refresh_hook = refresh_hook + threading.Thread.__init__(self) + + @property + def online_users(self): + self.__lock.acquire(True) + users = self.__online_users[:] + self.__lock.release() + return users + + @staticmethod + def dl_online_list(url): + """ + Download online.txt, parse it, and return a list of online user nicks. + If error occurs, return empty list + """ + try: + data = urllib2.urlopen(url).read() + except urllib2.URLError, e: + netlog.error("urllib error: %s", e.message) + return [] + start = string.find(data, '------------------------------\n') + 31 + end = string.rfind(data, '\n\n') + s = data[start:end] + return map(lambda n: n[:-5].strip() if n.endswith('(GM) ') + else n.strip(), s.split('\n')) + + def run(self): + while self._active: + if (time.time() - self._timer) > self._update_interval: + users = self.dl_online_list(self._url) + + if self.refresh_hook: + self.refresh_hook(set(users), set(self.__online_users)) + + self.__lock.acquire(True) + self.__online_users = users + self.__lock.release() + self._timer = time.time() + else: + time.sleep(1.0) + + def stop(self): + self._active = False diff --git a/net/onlineusers.pyc b/net/onlineusers.pyc Binary files differnew file mode 100644 index 0000000..f22c505 --- /dev/null +++ b/net/onlineusers.pyc diff --git a/net/packetlen.py b/net/packetlen.py new file mode 100644 index 0000000..4c26271 --- /dev/null +++ b/net/packetlen.py @@ -0,0 +1,28 @@ +# autogenerated file from manaplus sources. + +packet_lengths = { + 0x0062: 3, 0x0063: -1, 0x0069: -1, 0x006a: 23, 0x006b: -1, 0x006c: 3, + 0x006d: 108, 0x006e: 3, 0x006f: 2, 0x0070: 3, 0x0071: 28, 0x0073: 11, + 0x0078: 54, 0x007b: 60, 0x007c: 41, 0x007f: 6, 0x0080: 7, 0x0081: 3, + 0x0086: 16, 0x0087: 12, 0x0088: 10, 0x008a: 29, 0x008d: -1, 0x008e: -1, + 0x0091: 22, 0x0092: 28, 0x0095: 30, 0x0097: -1, 0x0098: 3, 0x009a: -1, + 0x009c: 9, 0x009d: 17, 0x009e: 17, 0x00a0: 23, 0x00a1: 6, 0x00a4: -1, + 0x00a6: -1, 0x00a8: 7, 0x00aa: 7, 0x00ac: 7, 0x00af: 6, 0x00b0: 8, + 0x00b1: 8, 0x00b3: 3, 0x00b4: -1, 0x00b5: 6, 0x00b6: 6, 0x00b7: -1, + 0x00bc: 6, 0x00bd: 44, 0x00be: 5, 0x00c0: 7, 0x00c2: 6, 0x00c3: 8, + 0x00c4: 6, 0x00c6: -1, 0x00c7: -1, 0x00ca: 3, 0x00cb: 3, 0x00cd: 6, + 0x00d2: 4, 0x00e5: 26, 0x00e7: 3, 0x00e9: 19, 0x00ec: 3, 0x00ee: 2, + 0x00f0: 3, 0x00f2: 6, 0x00f4: 21, 0x00f6: 8, 0x00f8: 2, 0x00fa: 3, + 0x00fb: -1, 0x00fd: 27, 0x00fe: 30, 0x0101: 6, 0x0104: 79, 0x0105: 31, + 0x0106: 10, 0x0107: 10, 0x0109: -1, 0x010c: 6, 0x010e: 11, 0x010f: -1, + 0x0110: 10, 0x0119: 13, 0x011a: 15, 0x0139: 16, 0x013a: 4, 0x013b: 4, + 0x013c: 4, 0x013e: 24, 0x0141: 14, 0x0142: 6, 0x0148: 8, 0x014c: -1, + 0x014e: 6, 0x0152: -1, 0x0154: -1, 0x0156: -1, 0x015a: 66, 0x015c: 90, + 0x015e: 6, 0x0160: -1, 0x0162: -1, 0x0163: -1, 0x0166: -1, 0x0167: 3, + 0x0169: 3, 0x016a: 30, 0x016c: 43, 0x016d: 14, 0x016f: 182, 0x0171: 30, + 0x0173: 3, 0x0174: -1, 0x017f: -1, 0x0181: 3, 0x0184: 10, 0x018b: 4, + 0x0194: 30, 0x0195: 102, 0x0196: 9, 0x0199: 4, 0x019a: 14, 0x019b: 10, + 0x01b1: 7, 0x01b6: 114, 0x01b9: 6, 0x01c8: 13, 0x01d4: 6, 0x01d7: 11, + 0x01d8: 54, 0x01d9: 53, 0x01da: 60, 0x01de: 33, 0x01ee: -1, 0x01f0: -1, + 0x020c: 10, 0x0212: 16, 0x0214: 8, 0x0215: -1, 0x0225: -1, 0x0226: 10, + 0x0227: -1, 0x0228: -1, 0x0229: -1, 0x0230: -1, 0x7531: 10, } diff --git a/net/packetlen.pyc b/net/packetlen.pyc Binary files differnew file mode 100644 index 0000000..843e137 --- /dev/null +++ b/net/packetlen.pyc diff --git a/net/protocol.py b/net/protocol.py new file mode 100644 index 0000000..14fb462 --- /dev/null +++ b/net/protocol.py @@ -0,0 +1,105 @@ +SMSG_UPDATE_HOST = 0x0063 +SMSG_LOGIN_DATA = 0x0069 +SMSG_LOGIN_ERROR = 0x006a +SMSG_CHAR_LOGIN = 0x006b +SMSG_CHAR_LOGIN_ERROR = 0x006c +SMSG_QUEST_PLAYER_VARS = 0x0215 +SMSG_CHAR_MAP_INFO = 0x0071 +SMSG_MAP_LOGIN_SUCCESS = 0x0073 +CMSG_CHAR_SERVER_CONNECT = 0x0065 +CMSG_CHAR_SELECT = 0x0066 +CMSG_MAP_SERVER_CONNECT = 0x0072 +CMSG_CHAT_WHISPER = 0x0096 +CMSG_CHAT_MESSAGE = 0x008c +CMSG_MAP_LOADED = 0x007d +CMSG_CLIENT_QUIT = 0x018A + +SMSG_WHISPER = 0x0097 +SMSG_BEING_CHAT = 0x008d + +SMSG_PLAYER_CHAT = 0x008e +CMSG_PLAYER_CHANGE_ACT = 0x0089 +CMSG_PLAYER_RESPAWN = 0x00b2 + +SMSG_PLAYER_INVENTORY = 0x01ee +SMSG_PLAYER_INVENTORY_ADD = 0x00a0 +SMSG_PLAYER_INVENTORY_REMOVE = 0x00af +SMSG_PLAYER_EQUIPMENT = 0x00a4 +SMSG_PLAYER_STAT_UPDATE_1 = 0x00b0 +SMSG_PLAYER_STAT_UPDATE_2 = 0x00b1 + +SMSG_BEING_VISIBLE = 0x0078 +SMSG_BEING_MOVE = 0x007b +SMSG_BEING_REMOVE = 0x0080 +SMSG_PLAYER_MOVE = 0x01da +SMSG_PLAYER_WARP = 0x0091 +SMSG_PLAYER_UPDATE_1 = 0x01d8 +SMSG_PLAYER_UPDATE_2 = 0x01d9 +SMSG_BEING_NAME_RESPONSE = 0x0095 # Has to be requested +SMSG_BEING_ACTION = 0x008a + +CMSG_ITEM_PICKUP = 0x009f +CMSG_PLAYER_ATTACK = 0x0089 +CMSG_PLAYER_STOP_ATTACK = 0x0118 +CMSG_PLAYER_CHANGE_DIR = 0x009b +CMSG_PLAYER_CHANGE_DEST = 0x0085 +CMSG_PLAYER_EMOTE = 0x00bf +SMSG_WALK_RESPONSE = 0x0087 + +CMSG_PLAYER_INVENTORY_USE = 0x00a7 +CMSG_PLAYER_INVENTORY_DROP = 0x00a2 +CMSG_PLAYER_EQUIP = 0x00a9 +CMSG_PLAYER_UNEQUIP = 0x00ab + +CMSG_TRADE_REQUEST = 0x00e4 +CMSG_TRADE_RESPONSE = 0x00e6 +CMSG_TRADE_ITEM_ADD_REQUEST = 0x00e8 +CMSG_TRADE_CANCEL_REQUEST = 0x00ed +CMSG_TRADE_ADD_COMPLETE = 0x00eb +CMSG_TRADE_OK = 0x00ef + +SMSG_TRADE_REQUEST = 0x00e5 # Receiving a request to trade +SMSG_TRADE_RESPONSE = 0x00e7 +SMSG_TRADE_ITEM_ADD = 0x00e9 +SMSG_TRADE_ITEM_ADD_RESPONSE = 0x01b1 # Not standard eAthena! +SMSG_TRADE_OK = 0x00ec +SMSG_TRADE_CANCEL = 0x00ee +SMSG_TRADE_COMPLETE = 0x00f0 + +SMSG_ITEM_VISIBLE = 0x009d +SMSG_ITEM_DROPPED = 0x009e +SMSG_ITEM_REMOVE = 0x00a1 + +inventory_offset = 2 +storage_offset = 1 + +CMSG_SERVER_VERSION_REQUEST = 0x7530 +SMSG_SERVER_VERSION_RESPONSE = 0x7531 +CMSG_LOGIN_REGISTER = 0x0064 + +CMSG_MAP_SERVER_PING = 0x007e +CMSG_NAME_REQUEST = 0x0094 + +# party +SMSG_PARTY_CHAT = 0x0109 +CMSG_PARTY_MESSAGE = 0x0108 + +# NPC +CMSG_NPC_TALK = 0x0090 +CMSG_NPC_NEXT_REQUEST = 0x00b9 +CMSG_NPC_CLOSE = 0x0146 +CMSG_NPC_LIST_CHOICE = 0x00b8 +CMSG_NPC_INT_RESPONSE = 0x0143 +CMSG_NPC_STR_RESPONSE = 0x01d5 +CMSG_NPC_BUY_SELL_REQUEST = 0x00c5 +CMSG_NPC_BUY_REQUEST = 0x00c8 +CMSG_NPC_SELL_REQUEST = 0x00c9 + +# stats, skills +CMSG_STAT_UPDATE_REQUEST = 0x00bb +CMSG_SKILL_LEVELUP_REQUEST = 0x0112 + +# storage +CMSG_MOVE_TO_STORAGE = 0x00f3 +CMSG_MOVE_FROM_STORAGE = 0x00f5 +CMSG_CLOSE_STORAGE = 0x00f7 diff --git a/net/protocol.pyc b/net/protocol.pyc Binary files differnew file mode 100644 index 0000000..9b73f78 --- /dev/null +++ b/net/protocol.pyc diff --git a/net/stats.py b/net/stats.py new file mode 100644 index 0000000..009ac9b --- /dev/null +++ b/net/stats.py @@ -0,0 +1,44 @@ + +WALK_SPEED = 0 +EXP = 1 +JOB_EXP = 2 +KARMA = 3 +MANNER = 4 +HP = 5 +MAX_HP = 6 +MP = 7 +MAX_MP = 8 +CHAR_POINTS = 9 +LEVEL = 11 +SKILL_POINTS = 12 +STR = 13 +AGI = 14 +VIT = 15 +INT = 16 +DEX = 17 +LUK = 18 +MONEY = 20 +EXP_NEEDED = 22 +JOB_MOD = 23 +TOTAL_WEIGHT = 24 +MAX_WEIGHT = 25 +STR_NEEDED = 32 +AGI_NEEDED = 33 +VIT_NEEDED = 34 +INT_NEEDED = 35 +DEX_NEEDED = 36 +LUK_NEEDED = 37 +ATK = 41 +ATK_MOD = 42 +MATK = 43 +MATK_MOD = 44 +DEF = 45 +DEF_MOD = 46 +MDEF = 47 +MDEF_MOD = 48 +HIT = 49 +FLEE = 50 +FLEE_MOD = 51 +CRIT = 52 +ATTACK_DELAY = 53 +JOB = 55 diff --git a/net/stats.pyc b/net/stats.pyc Binary files differnew file mode 100644 index 0000000..aea5a45 --- /dev/null +++ b/net/stats.pyc diff --git a/net/trade.py b/net/trade.py new file mode 100644 index 0000000..c2982c7 --- /dev/null +++ b/net/trade.py @@ -0,0 +1,6 @@ + +def reset_trade_state(ts): + ts['zeny_give'] = 0 + ts['zeny_get'] = 0 + ts['items_give'] = [] + ts['items_get'] = [] diff --git a/net/trade.pyc b/net/trade.pyc Binary files differnew file mode 100644 index 0000000..f76375d --- /dev/null +++ b/net/trade.pyc |