summaryrefslogtreecommitdiff
path: root/net
diff options
context:
space:
mode:
authorLivio Recchia <recchialivio@libero.it>2020-02-10 23:06:34 +0100
committerLivio Recchia <recchialivio@libero.it>2020-02-10 23:06:34 +0100
commit9a13903a2f7d3a65fdf15a65fb59cccd622e2066 (patch)
tree9403b7dff39eb5e5d7fa0f79efb69b496add4c4b /net
parent11cc316b74d5f3f283413a33e7693b314741aa4a (diff)
downloadmanachat-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__.py11
-rw-r--r--net/__init__.pycbin0 -> 556 bytes
-rw-r--r--net/__pycache__/__init__.cpython-37.pycbin0 -> 430 bytes
-rw-r--r--net/being.py63
-rw-r--r--net/being.pycbin0 -> 3072 bytes
-rw-r--r--net/charserv.py142
-rw-r--r--net/charserv.pycbin0 -> 4913 bytes
-rw-r--r--net/common.py106
-rw-r--r--net/common.pycbin0 -> 4946 bytes
-rw-r--r--net/dispatcher.py27
-rw-r--r--net/dispatcher.pycbin0 -> 1230 bytes
-rw-r--r--net/inventory.py51
-rw-r--r--net/inventory.pycbin0 -> 1971 bytes
-rw-r--r--net/item.py8
-rw-r--r--net/item.pycbin0 -> 659 bytes
-rw-r--r--net/loginsrv.py109
-rw-r--r--net/loginsrv.pycbin0 -> 4403 bytes
-rw-r--r--net/mapserv.py1216
-rw-r--r--net/mapserv.pycbin0 -> 44223 bytes
-rw-r--r--net/onlineusers.py86
-rw-r--r--net/onlineusers.pycbin0 -> 3903 bytes
-rw-r--r--net/packetlen.py28
-rw-r--r--net/packetlen.pycbin0 -> 2218 bytes
-rw-r--r--net/protocol.py105
-rw-r--r--net/protocol.pycbin0 -> 3334 bytes
-rw-r--r--net/stats.py44
-rw-r--r--net/stats.pycbin0 -> 1198 bytes
-rw-r--r--net/trade.py6
-rw-r--r--net/trade.pycbin0 -> 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
new file mode 100644
index 0000000..6ec9624
--- /dev/null
+++ b/net/__init__.pyc
Binary files differ
diff --git a/net/__pycache__/__init__.cpython-37.pyc b/net/__pycache__/__init__.cpython-37.pyc
new file mode 100644
index 0000000..968d236
--- /dev/null
+++ b/net/__pycache__/__init__.cpython-37.pyc
Binary files differ
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
new file mode 100644
index 0000000..32916bd
--- /dev/null
+++ b/net/being.pyc
Binary files differ
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
new file mode 100644
index 0000000..1109add
--- /dev/null
+++ b/net/charserv.pyc
Binary files differ
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
new file mode 100644
index 0000000..3f457a5
--- /dev/null
+++ b/net/common.pyc
Binary files differ
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
new file mode 100644
index 0000000..c671b01
--- /dev/null
+++ b/net/dispatcher.pyc
Binary files differ
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
new file mode 100644
index 0000000..85b5a74
--- /dev/null
+++ b/net/inventory.pyc
Binary files differ
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
new file mode 100644
index 0000000..9ceb6a7
--- /dev/null
+++ b/net/item.pyc
Binary files differ
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
new file mode 100644
index 0000000..eb52b86
--- /dev/null
+++ b/net/loginsrv.pyc
Binary files differ
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
new file mode 100644
index 0000000..0d7c757
--- /dev/null
+++ b/net/mapserv.pyc
Binary files differ
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
new file mode 100644
index 0000000..f22c505
--- /dev/null
+++ b/net/onlineusers.pyc
Binary files differ
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
new file mode 100644
index 0000000..843e137
--- /dev/null
+++ b/net/packetlen.pyc
Binary files differ
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
new file mode 100644
index 0000000..9b73f78
--- /dev/null
+++ b/net/protocol.pyc
Binary files differ
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
new file mode 100644
index 0000000..aea5a45
--- /dev/null
+++ b/net/stats.pyc
Binary files differ
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
new file mode 100644
index 0000000..f76375d
--- /dev/null
+++ b/net/trade.pyc
Binary files differ