From 61b623c5d2812450542ea953cb730a5bd2b02c2f Mon Sep 17 00:00:00 2001 From: Ra Date: Wed, 9 Mar 2022 13:48:56 -0700 Subject: [PATCH] start repo --- .gitignore | 5 + __init__.py | 5 + __pycache__/__init__.cpython-310.pyc | Bin 0 -> 348 bytes __pycache__/clb.cpython-310.pyc | Bin 0 -> 3145 bytes __pycache__/client.cpython-310.pyc | Bin 0 -> 830 bytes __pycache__/client_base.cpython-310.pyc | Bin 0 -> 4113 bytes __pycache__/crypto.cpython-310.pyc | Bin 0 -> 6191 bytes __pycache__/opcodes.cpython-310.pyc | Bin 0 -> 19300 bytes __pycache__/packet.cpython-310.pyc | Bin 0 -> 6175 bytes __pycache__/tools.cpython-310.pyc | Bin 0 -> 4211 bytes client.py | 16 + client_base.py | 114 ++++ crypto.py | 238 ++++++++ opcodes.py | 725 ++++++++++++++++++++++++ packet.py | 176 ++++++ tools.py | 125 ++++ 16 files changed, 1404 insertions(+) create mode 100644 .gitignore create mode 100644 __init__.py create mode 100644 __pycache__/__init__.cpython-310.pyc create mode 100644 __pycache__/clb.cpython-310.pyc create mode 100644 __pycache__/client.cpython-310.pyc create mode 100644 __pycache__/client_base.cpython-310.pyc create mode 100644 __pycache__/crypto.cpython-310.pyc create mode 100644 __pycache__/opcodes.cpython-310.pyc create mode 100644 __pycache__/packet.cpython-310.pyc create mode 100644 __pycache__/tools.cpython-310.pyc create mode 100644 client.py create mode 100644 client_base.py create mode 100644 crypto.py create mode 100644 opcodes.py create mode 100644 packet.py create mode 100644 tools.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..78b4c93 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +**/.git +**/.vscode +**/__pyccache__ + +**/test*.* \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..53daca8 --- /dev/null +++ b/__init__.py @@ -0,0 +1,5 @@ +__all__ = "ClientBase", "Client", "Packet", "iPacket", "oPacket" + +from .client_base import ClientBase +from .client import Client +from .packet import Packet, iPacket, oPacket diff --git a/__pycache__/__init__.cpython-310.pyc b/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f1c52bf01047c5774da9603d01f5b18222f2356b GIT binary patch literal 348 zcmZ8b!Ait15KYoF?XJ6bKfzo)c~L~f^(KfYf`mXwn;c4$rnKFI{*f0yC^zxsFL-j& zDu@H~-kX8@hN7WnfM;iVXf#DK*JOMCBAcGBY*b_l;(wWOV6?E3r<(>)V z3sLw|l(5Sm5NAA(>z!+CH(lw`Mz-k4?sVPSDHhGI2#ff{aEjyMhtdZ;8#17QCddJ9 z`6?DlY3Gy@2lbq;Du|z5ku94cc?Ij|L;n(9(XdKs3BFEuXYvuuqrBJh+K%lMdU>N? uLa1~n>tL*G;nu#>vUZhJs_B|ZspI}Rmghd0y|WkT-LV9qXhSym5Pt&>-czpt literal 0 HcmV?d00001 diff --git a/__pycache__/clb.cpython-310.pyc b/__pycache__/clb.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c2b817e954e956bf238761f303c69eb621a0181d GIT binary patch literal 3145 zcmZuzTW{RP6`mP#xm+%HwYtV_)l_bq%ciZ`xOLhXL0~~v5+jamNKSwfDhMt~Nh|TP z9?m+^VgWY2B-*wr8ge zuaP>QlUkmIIU_QYX6kxw+VWay+iRzD-kg@#l1@7B&Fk;2WFcMj7CHNvMfJ#j&LaCv zcuN>HA_pS}qvfc%%ewAMXv4a-vbOa!&Q#exh?O4`>nC|WC_6Nk!z}A(2bJUoMt+#f ze5m?a?5jf=2T>91$7QWZ;&_0)>Kh;V_qVqmm(BZ+9_>7Q{Lp{A`KWB}KHU7&-+jEZ zweeZmgv8%|cyH^$#-B^8$iw4Uz2u~dS+*VpH2fsUq9m53RSnB}f6C+))8(c8SujZA zjko|`8_0g9Lb7M}5g6P>D}D(QGml5i6A_QZbLJW3PxNL~i!AiDs1BdlrE{-e3<4D% z#xkVurw{SeHqiNH$i{On@gR>_~Ppq*~ z+)(D%FbivI=hm2=*T)9T6cb~@30%8OE@8v2t&J-?{y;~|Kdi!4l=XZ7d_=KlhcKsy z*|NQ%lmYyS6yqay z02lm|@36nK`*6Z1Y{nIG8SA=+)>-}nd}#*qpm;?wxrUjapcN*Fc(%$%%QKo>S-;)2 zDC${|#=Z}?_-P&u6XIRpe=-b`YDWGT+eiYn-g)LULYpLC$VCtvtsr7ETiAx%*3uAy zYY!QH7p+(axl9u$5|QzoBT0fV#h=Wy=KEuDSh@bfJLM6=p?}v67-IDIk&*EiIo4*@Y(RfK+=4mBzaa+ZX#w+k=*?}L!63X?{ZWJ*pUe_7RWe!>i3_5E8;97uF*SD zaUf-If|Gi~{e7=ltqSuYkdLIGPbfjUqDl&r>hesqb)X3gH_?h!5W>67UBl%aaMRZY zhte@uMe3?-kV~cy@r2HQp%p&|0s4XcbI@}!VH5N|l@as2Hkr+Uu2p&SXZQp@0;z6eRbhdMXA8W|NA889dNO=fONyY@@&hnaEhkK_ zD%5spXne!|Awufr+yb~o%{4E%C2-4Hwu0|X?AZB+MbPYM0X^=MqZRaZ z^iA}X>`OSo(V=ROIrR-nd#!zO^D<10?XDQLZoj*J>(;IH+wZNv``-KgH*PZa@_Rg^ zCjBfFhrw}tXKmE@qRKuCC`mwx+VSM zFqcaH8gj2S72&eX4&-l0=3L0|Ya@S4g64&Q3Wua(t#8xtSkCL^^uLzG*?~GN4fI~a z&rz24ll)6>J`J+|V3^RAOQj~37ZAV>oE^CPFVuFI9GtOp?u$OGpt!epoccrWc#YqaUeO32$ zNvGnk>$;>mDCviyiXi^&ne2dgRwz#fDz6-G%6@oQ6$!d^^}+v7)6)K+y3aqPl2#BX QR@o}Q!Ckyb%7 literal 0 HcmV?d00001 diff --git a/__pycache__/client.cpython-310.pyc b/__pycache__/client.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3802b1bcdb5eccbc587e0512ca67baa292cc64a0 GIT binary patch literal 830 zcmZuv&1w`u5bo~j`N{4kESLw#Nt{crf`~*DSc5PL30{VQVRpJ*H{0`L_pIO&Az}70 zJm}Hq@Gbi4$ye}XRZrp%SWrd%ba#DURX4l4BLec=i|3yxA>T3hTQ~$qpazW~f+|wc zlv4DWVwIPA6;HW~J>^$H8dPB#QZgfg3x7+5FKO?kkq9nH9R7eFB&K$Btd_FwUKB>g z%CxsqM}wy1@Y+J?=!OF${SkmdWc*4Xe;)&iUo9{5K*h>k$bLokx0RFISjD%h5! zp5)TM3VJF$!MCKRX()Wa5!he=P$HgtK_B%Ss0qN_!dq~fCSMUVH&VUP5#*iu`S|!r z_S#5ovWp@+k!IC3ZT70zG|jxIv$7E~TNO&J>mn=F926NJwjZoNmy2bM4;dUUyWwmz z8SossQpq5FFUSwop?U<)E$v_rJ?x;P9lP>+`jK6e&-|Kwp{KBymq5bdX36FOPHkB) z4$jY#*}*X8*tdSGmvyJHyGEjxW8IDooX+n~d3pybDPOj$&hiHAAMP4Ijpc1qFDBp_ z10)SUti=Tx*UpW=UH@M< U8$BIBKf}M_T29E6PU*h)3$5I=_W%F@ literal 0 HcmV?d00001 diff --git a/__pycache__/client_base.cpython-310.pyc b/__pycache__/client_base.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7519801dfdc256584d4af5badb05440c007e784a GIT binary patch literal 4113 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 literal 0 HcmV?d00001 diff --git a/__pycache__/crypto.cpython-310.pyc b/__pycache__/crypto.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..76cb05259cb09a0b3b61696564ae952d53dc4213 GIT binary patch literal 6191 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 OZT;@0?mD+8Z9fX7s}TkO literal 0 HcmV?d00001 diff --git a/__pycache__/opcodes.cpython-310.pyc b/__pycache__/opcodes.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c24775531a1746e89ee1626cd711e9c78bed0a4 GIT binary patch literal 19300 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 F|3Aj=(}Vy3 literal 0 HcmV?d00001 diff --git a/__pycache__/tools.cpython-310.pyc b/__pycache__/tools.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..032f46a2babd0f9860db74bc9050faca5c547c24 GIT binary patch literal 4211 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 Q&> 8 & 255, + iv.value >> 16 & 255, + iv.value >> 24 & 255, + ] + + while remaining > 0: + for index in range(len(real_iv)): + real_iv[index] = iv_bytes[index % 4] # type: ignore + + if remaining < length: + length = remaining + + index = start + + while index < start + length: + sub = index - start + + if (sub % 16) == 0: + real_iv = AES.new(cls._user_key, + AES.MODE_ECB).encrypt(real_iv) + + buffer[index] ^= real_iv[sub % 16] + index += 1 + + start += length + remaining -= length + length = 0x5B4 + + iv.shuffle() + + return buffer + + @staticmethod + def get_header(iv, length, major_ver): + first = -(major_ver + 1) ^ iv.hiword + second = (first + 2**16) ^ length + return bytearray([ + first & 0xFF, first >> 8 & 0xFF, second & 0xFF, second >> 8 & 0xFF + ]) + + @staticmethod + def get_length(data): + return ((data[1] << 8) + data[0]) ^ ((data[3] << 8) + data[2]) + + +class MapleIV: + _shuffle = bytearray([ + 0xEC, 0x3F, 0x77, 0xA4, 0x45, 0xD0, 0x71, 0xBF, 0xB7, 0x98, 0x20, 0xFC, + 0x4B, 0xE9, 0xB3, 0xE1, 0x5C, 0x22, 0xF7, 0x0C, 0x44, 0x1B, 0x81, 0xBD, + 0x63, 0x8D, 0xD4, 0xC3, 0xF2, 0x10, 0x19, 0xE0, 0xFB, 0xA1, 0x6E, 0x66, + 0xEA, 0xAE, 0xD6, 0xCE, 0x06, 0x18, 0x4E, 0xEB, 0x78, 0x95, 0xDB, 0xBA, + 0xB6, 0x42, 0x7A, 0x2A, 0x83, 0x0B, 0x54, 0x67, 0x6D, 0xE8, 0x65, 0xE7, + 0x2F, 0x07, 0xF3, 0xAA, 0x27, 0x7B, 0x85, 0xB0, 0x26, 0xFD, 0x8B, 0xA9, + 0xFA, 0xBE, 0xA8, 0xD7, 0xCB, 0xCC, 0x92, 0xDA, 0xF9, 0x93, 0x60, 0x2D, + 0xDD, 0xD2, 0xA2, 0x9B, 0x39, 0x5F, 0x82, 0x21, 0x4C, 0x69, 0xF8, 0x31, + 0x87, 0xEE, 0x8E, 0xAD, 0x8C, 0x6A, 0xBC, 0xB5, 0x6B, 0x59, 0x13, 0xF1, + 0x04, 0x00, 0xF6, 0x5A, 0x35, 0x79, 0x48, 0x8F, 0x15, 0xCD, 0x97, 0x57, + 0x12, 0x3E, 0x37, 0xFF, 0x9D, 0x4F, 0x51, 0xF5, 0xA3, 0x70, 0xBB, 0x14, + 0x75, 0xC2, 0xB8, 0x72, 0xC0, 0xED, 0x7D, 0x68, 0xC9, 0x2E, 0x0D, 0x62, + 0x46, 0x17, 0x11, 0x4D, 0x6C, 0xC4, 0x7E, 0x53, 0xC1, 0x25, 0xC7, 0x9A, + 0x1C, 0x88, 0x58, 0x2C, 0x89, 0xDC, 0x02, 0x64, 0x40, 0x01, 0x5D, 0x38, + 0xA5, 0xE2, 0xAF, 0x55, 0xD5, 0xEF, 0x1A, 0x7C, 0xA7, 0x5B, 0xA6, 0x6F, + 0x86, 0x9F, 0x73, 0xE6, 0x0A, 0xDE, 0x2B, 0x99, 0x4A, 0x47, 0x9C, 0xDF, + 0x09, 0x76, 0x9E, 0x30, 0x0E, 0xE4, 0xB2, 0x94, 0xA0, 0x3B, 0x34, 0x1D, + 0x28, 0x0F, 0x36, 0xE3, 0x23, 0xB4, 0x03, 0xD8, 0x90, 0xC8, 0x3C, 0xFE, + 0x5E, 0x32, 0x24, 0x50, 0x1F, 0x3A, 0x43, 0x8A, 0x96, 0x41, 0x74, 0xAC, + 0x52, 0x33, 0xF0, 0xD9, 0x29, 0x80, 0xB1, 0x16, 0xD3, 0xAB, 0x91, 0xB9, + 0x84, 0x7F, 0x61, 0x1E, 0xCF, 0xC5, 0xD1, 0x56, 0x3D, 0xCA, 0xF4, 0x05, + 0xC6, 0xE5, 0x08, 0x49 + ]) + + def __init__(self, vector): + self.value = vector + + def __int__(self): + return self.value + + def __str__(self): + return self.value + + @property + def hiword(self): + return self.value >> 16 + + @property + def loword(self): + return self.value + + def shuffle(self): + seed = [0xF2, 0x53, 0x50, 0xC6] + p_iv = self.value + + for i in range(4): + temp_p_iv = (p_iv >> (8 * i)) & 0xFF + + a = seed[1] + b = a + b = self._shuffle[b & 0xFF] + b -= temp_p_iv + seed[0] += b + b = seed[2] + b ^= self._shuffle[int(temp_p_iv) & 0xFF] + a -= int(b) & 0xFF + seed[1] = a + a = seed[3] + b = a + a -= seed[0] & 0xFF + b = self._shuffle[b & 0xFF] + b += temp_p_iv + b ^= seed[2] + seed[2] = b & 0xFF + a += self._shuffle[temp_p_iv & 0xFF] & 0xFF + seed[3] = a + + c = seed[0] & 0xFF + c |= (seed[1] << 8) & 0xFFFF + c |= (seed[2] << 16) & 0xFFFFFF + c |= (seed[3] << 24) & 0xFFFFFFFF + + c = (c << 0x03) | (c >> 0x1D) + + seed[0] = c & 0xFF + seed[1] = (c >> 8) & 0xFFFF + seed[2] = (c >> 16) & 0xFFFFFF + seed[3] = (c >> 24) & 0xFFFFFFFF + + c = seed[0] & 0xFF + c |= (seed[1] << 8) & 0xFFFF + c |= (seed[2] << 16) & 0xFFFFFF + c |= (seed[3] << 24) & 0xFFFFFFFF + + self.value = c + + +def decrypt_transform(data): + for j in range(1, 7): + remember = 0 + data_length = len(data) & 0xFF + next_remember = 0 + if j % 2 == 0: + for i in range(len(data)): + cur = data[i] + cur = (cur - 0x48) & 0xFF + cur = ~cur & 0xFF + cur = roll_left(cur, data_length & 0xFF) + next_remember = cur + cur ^= remember + remember = next_remember + cur = (cur - data_length) & 0xFF + cur = roll_right(cur, 3) + data[i] = cur + data_length -= 1 + else: + for i in reversed(range(len(data))): + cur = data[i] + cur = roll_left(cur, 3) + cur ^= 0x13 + next_remember = cur + cur ^= remember + remember = next_remember + cur = (cur - data_length) & 0xFF + cur = roll_right(cur, 4) & 0xFF + data[i] = cur + data_length -= 1 + + return data + + +def encrypt_transform(data): + b = {str(i): 0 for i in range(len(data))} + cur = 0 + + for _ in range(3): + length = len(data) & 0xFF + xor_key = 0 + i = 0 + while i < len(data): + + cur = roll_left(data[i], 3) + cur = cur + length + cur = (cur ^ xor_key) & 0xFF + xor_key = cur + cur = ~roll_right(cur, length & 0xFF) & 0xFF + cur = (cur + 0x48) & 0xFF + data[i] = cur + b[str(i)] = cur + length -= 1 + i += 1 + + xor_key = 0 + length = len(data) & 0xFF + i = len(data) - 1 + + while i >= 0: + cur = roll_left(data[i], 4) + cur += length + cur = (cur ^ xor_key) & 0xFF + xor_key = cur + cur ^= 0x13 + cur = roll_right(cur, 3) + data[i] = cur + b[str(i)] = cur + length -= 1 + i -= 1 + + return bytearray([b[b_] for b_ in b]) + + +def roll_left(value, shift): + num = value << (shift % 8) + return (num & 0xFF) | (num >> 8) + + +def roll_right(value, shift): + num = (value << 8) >> (shift % 8) + return (num & 0xFF) | (num >> 8) diff --git a/opcodes.py b/opcodes.py new file mode 100644 index 0000000..08c1e7d --- /dev/null +++ b/opcodes.py @@ -0,0 +1,725 @@ +from enum import IntEnum + + +class RecvOps(IntEnum): + LOGIN_STATUS = 0x00 + SEND_LINK = 0x01 + LOGIN_SECOND = 0x02 + SERVERSTATUS = 0x03 + GENDER_SET = 0x04 + PIN_OPERATION = 0x06 + PIN_ASSIGNED = 0x07 + ALL_CHARLIST = 0x08 + SERVERLIST = 0x0A + CHARLIST = 0x0B + SERVER_IP = 0x0C + CHAR_NAME_RESPONSE = 0x0D + ADD_NEW_CHAR_ENTRY = 0x0E + DELETE_CHAR_RESPONSE = 0x0F + CHANGE_CHANNEL = 0x10 + PING = 0x11 + CS_USE = 0x12 + CHANNEL_SELECTED = 0x14 + RELOG_RESPONSE = 0x16 + ENABLE_RECOMMENDED = 0x1B + SEND_RECOMMENDED = 0x1C + SPECIAL_CREATION = 0x1E + SECONDPW_ERROR = 0x1F + + INVENTORY_OPERATION = 0x20 + INVENTORY_GROW = 0x21 + UPDATE_STATS = 0x22 + GIVE_BUFF = 0x23 + CANCEL_BUFF = 0x24 + TEMP_STATS = 0x25 + TEMP_STATS_RESET = 0x26 + UPDATE_SKILLS = 0x27 + SKILL_MEMORY = 0x28 + UPDATE_SKILL_TICK = 0x29 + FAME_RESPONSE = 0x2B + SHOW_STATUS_INFO = 0x2C + GAME_PATCHES = 0x2D + SHOW_NOTES = 0x2E + TROCK_LOCATIONS = 0x2F + LIE_DETECTOR = 0x30 + BOMB_LIE_DETECTOR = 0x31 + REPORT_RESPONSE = 0x33 + REPORT_TIME = 0x34 + REPORT_STATUS = 0x35 + UPDATE_MOUNT = 0x36 + SHOW_QUEST_COMPLETION = 0x37 + SEND_TITLE_BOX = 0x38 + USE_SKILL_BOOK = 0x39 + SP_RESET = 0x3A + AP_RESET = 0x3B + DISTRIBUTE_ITEM = 0x3D + EXPAND_CHARACTER_SLOTS = 0x3E + FINISH_SORT = 0x3F + FINISH_GATHER = 0x40 + REPORT_RESULT = 0x42 + TRADE_LIMIT = 0x44 + UPDATE_GENDER = 0x45 + BBS_OPERATION = 0x46 + CHAR_INFO = 0x49 + PARTY_OPERATION = 0x4A + MEMBER_SEARCH = 0x4B + PARTY_SEARCH = 0x4C + BOOK_INFO = 0x4D + EXPEDITION_OPERATION = 0x4F + BUDDYLIST = 0x50 + GUILD_OPERATION = 0x52 + ALLIANCE_OPERATION = 0x53 + SPAWN_PORTAL = 0x54 + MECH_PORTAL = 0x55 + ECHO_MESSAGE = 0x56 + SERVERMESSAGE = 0x58 + PIGMI_REWARD = 0x59 + OWL_OF_MINERVA = 0x5A + OWL_RESULT = 0x5B + ENGAGE_REQUEST = 0x5C + ENGAGE_RESULT = 0x5D + WEDDING_GIFT = 0x5E + WEDDING_MAP_TRANSFER = 0x5F + USE_CASH_PET_FOOD = 0x60 + YELLOW_CHAT = 0x61 + SHOP_DISCOUNT = 0x62 + CATCH_MOB = 0x63 + MAKE_PLAYER_NPC = 0x64 + PLAYER_NPC = 0x65 + DISABLE_NPC = 0x66 + GET_CARD = 0x67 + CARD_SET = 0x69 + BOOK_STATS = 0x6A + UPDATE_CODEX = 0x6B + CARD_DROPS = 0x6C + FAMILIAR_INFO = 0x6D + CHANGE_HOUR = 0x6F + RESET_MINIMAP = 0x70 + CONSULT_UPDATE = 0x71 + CLASS_UPDATE = 0x72 + WEB_BOARD_UPDATE = 0x73 + SESSION_VALUE = 0x74 + PARTY_VALUE = 0x75 + MAP_VALUE = 0x76 + EXP_BONUS = 0x78 + POTION_BONUS = 0x79 + SEND_PEDIGREE = 0x7A + OPEN_FAMILY = 0x7B + FAMILY_MESSAGE = 0x7C + FAMILY_INVITE = 0x7D + FAMILY_JUNIOR = 0x7E + SENIOR_MESSAGE = 0x7F + FAMILY = 0x80 + REP_INCREASE = 0x81 + FAMILY_LOGGEDIN = 0x82 + FAMILY_BUFF = 0x83 + FAMILY_USE_REQUEST = 0x84 + LEVEL_UPDATE = 0x85 + MARRIAGE_UPDATE = 0x86 + JOB_UPDATE = 0x87 + MAPLE_TV_MSG = 0x89 + AVATAR_MEGA_RESULT = 0x8A + AVATAR_MEGA = 0x8B + AVATAR_MEGA_REMOVE = 0x8C + CANCEL_NAME_CHANGE = 0x8D + CANCEL_WORLD_TRANSFER = 0x8E + CLOSE_HIRED_MERCHANT = 0x8F + GM_POLICE = 0x90 + TREASURE_BOX = 0x91 + NEW_YEAR_CARD = 0x92 + RANDOM_MORPH = 0x93 + CANCEL_NAME_CHANGE_2 = 0x94 + PENDANT_SLOT = 0x95 + FOLLOW_REQUEST = 0x96 + TOP_MSG = 0x97 + MID_MSG = 0x98 + CLEAR_MID_MSG = 0x99 + MAPLE_ADMIN_MSG = 0x9A + CAKE_VS_PIE_MSG = 0x9B + GM_STORY_BOARD = 0x9C + INVENTORY_FULL = 0x9D + UPDATE_JAGUAR = 0x9E + YOUR_INFORMATION = 0x9F + FIND_FRIEND = 0xA0 + VISITOR = 0xA1 + PINKBEAN_CHOCO = 0xA2 + PAM_SONG = 0xA3 + AUTO_CC_MSG = 0xA4 + DISALLOW_DELIVERY_QUEST = 0xA5 + ULTIMATE_EXPLORER = 0xA6 + PROFESSION_INFO = 0xA8 + UPDATE_IMP_TIME = 0xA9 + ITEM_POT = 0xAA + GIVE_CHARACTER_SKILL = 0xAE + MULUNG_DOJO_RANKING = 0xB1 + MULUNG_MESSAGE = 0xB4 + MAGIC_WHEEL_START = 0xB6 + MAGIC_WHEEL_RECEIVE = 0xB7 + + SKILL_MACRO = 0xB8 + WARP_TO_MAP = 0xB9 + MTS_OPEN = 0xBA + CS_OPEN = 0xBB + CHANGE_BACKGROUND = 0xBC + LOGIN_WELCOME = 0xBD + RESET_SCREEN = 0xBE + MAP_BLOCKED = 0xBF + SERVER_BLOCKED = 0xC0 + PARTY_BLOCKED = 0xC1 + SHOW_EQUIP_EFFECT = 0xC2 + MULTICHAT = 0xC3 + WHISPER = 0xC4 + SPOUSE_CHAT = 0xC5 + BOSS_ENV = 0xC7 + MOVE_ENV = 0xC8 + UPDATE_ENV = 0xC9 + MAP_EFFECT = 0xCB + CASH_SONG = 0xCC + GM_EFFECT = 0xCD + OX_QUIZ = 0xCE + GMEVENT_INSTRUCTIONS = 0xCF + CLOCK = 0xD0 + BOAT_MOVE = 0xD1 + BOAT_STATE = 0xD2 + SET_OBJECT_STATE = 0xD6 + STOP_CLOCK = 0xD7 + ARIANT_SCOREBOARD = 0xD8 + PYRAMID_UPDATE = 0xDA + PYRAMID_RESULT = 0xDB + QUICK_SLOT = 0xDC + MOVE_PLATFORM = 0xDD + PYRAMID_KILL_COUNT = 0xDF + PVP_INFO = 0xE1 + DIRECTION_STATUS = 0xE2 + GAIN_FORCE = 0xE3 + ACHIEVEMENT_RATIO = 0xE4 + PUBLIC_NPC = 0xE5 + SPAWN_PLAYER = 0xE6 + REMOVE_PLAYER_FROM_MAP = 0xE7 + CHATTEXT = 0xE8 + CHATTEXT_1 = 0xE9 + CHALKBOARD = 0xEA + UPDATE_CHAR_BOX = 0xEB + SHOW_CONSUME_EFFECT = 0xEC + SHOW_SCROLL_EFFECT = 0xED + SHOW_MAGNIFYING_EFFECT = 0xEF + SHOW_POTENTIAL_RESET = 0xF0 + SHOW_FIREWORKS_EFFECT = 0xF1 + SHOW_NEBULITE_EFFECT = 0xF2 + SHOW_FUSION_EFFECT = 0xF3 + PVP_ATTACK = 0xF4 + PVP_MIST = 0xF5 + PVP_COOL = 0xF7 + TESLA_TRIANGLE = 0xF8 + FOLLOW_EFFECT = 0xF9 + SHOW_PQ_REWARD = 0xFA + CRAFT_EFFECT = 0xFB + CRAFT_COMPLETE = 0xFC + HARVESTED = 0xFD + PLAYER_DAMAGED = 0xFF + NETT_PYRAMID = 0x100 + SET_PHASE = 0x101 + PAMS_SONG = 0x103 + SPAWN_PET = 0x104 + SPAWN_PET_2 = 0x106 + MOVE_PET = 0x107 + PET_CHAT = 0x108 + PET_NAMECHANGE = 0x109 + PET_EXCEPTION_LIST = 0x10A + PET_COMMAND = 0x10B + DRAGON_SPAWN = 0x10C + DRAGON_MOVE = 0x10D + DRAGON_REMOVE = 0x10E + ANDROID_SPAWN = 0x10F + ANDROID_MOVE = 0x110 + ANDROID_EMOTION = 0x111 + ANDROID_UPDATE = 0x112 + ANDROID_DEACTIVATED = 0x113 + SPAWN_FAMILIAR = 0x114 + MOVE_FAMILIAR = 0x115 + TOUCH_FAMILIAR = 0x116 + ATTACK_FAMILIAR = 0x117 + RENAME_FAMILIAR = 0x118 + SPAWN_FAMILIAR_2 = 0x119 + UPDATE_FAMILIAR = 0x11A + MOVE_PLAYER = 0x11C + CLOSE_RANGE_ATTACK = 0x11E + RANGED_ATTACK = 0x11F + MAGIC_ATTACK = 0x120 + ENERGY_ATTACK = 0x121 + SKILL_EFFECT = 0x122 + MOVE_ATTACK = 0x123 + CANCEL_SKILL_EFFECT = 0x124 + DAMAGE_PLAYER = 0x125 + FACIAL_EXPRESSION = 0x126 + SHOW_ITEM_EFFECT = 0x128 + SHOW_TITLE = 0x12A + SHOW_CHAIR = 0x12D + UPDATE_CHAR_LOOK = 0x12E + SHOW_FOREIGN_EFFECT = 0x12F + GIVE_FOREIGN_BUFF = 0x130 + CANCEL_FOREIGN_BUFF = 0x131 + UPDATE_PARTYMEMBER_HP = 0x132 + LOAD_GUILD_NAME = 0x133 + LOAD_GUILD_ICON = 0x134 + LOAD_TEAM = 0x135 + SHOW_HARVEST = 0x137 + PVP_HP = 0x138 + CANCEL_CHAIR = 0x13B + FACIAL_EXPRESSION_2 = 0x13C + SHOW_ITEM_GAIN_INCHAT = 0x13E + CURRENT_MAP_WARP = 0x13F + MESOBAG_SUCCESS = 0x141 + MESOBAG_FAILURE = 0x142 + R_MESOBAG_SUCCESS = 0x143 + R_MESOBAG_FAILURE = 0x144 + MAP_FADE = 0x145 + MAP_FADE_FORCE = 0x146 + UPDATE_QUEST_INFO = 0x147 + HP_DECREASE = 0x148 + PLAYER_HINT = 0x14A + PLAY_EVENT_SOUND = 0x14B + PLAY_MINIGAME_SOUND = 0x14C + MAKER_SKILL = 0x14D + OPEN_UI = 0x150 + OPEN_UI_OPTION = 0x152 + CYGNUS_INTRO_LOCK = 0x153 + CYGNUS_INTRO_ENABLE_UI = 0x154 + CYGNUS_INTRO_DISABLE_UI = 0x155 + SUMMON_HINT = 0x156 + SUMMON_HINT_MSG = 0x157 + ARAN_COMBO = 0x158 + ARAN_COMBO_RECHARGE = 0x159 + RANDOM_EMOTION = 0x15A + RADIO_SCHEDULE = 0x15D + OPEN_SKILL_GUIDE = 0x15E + NOTICE_MSG = 0x15F + GAME_MESSAGE = 0x160 + BUFF_ZONE_EFFECT = 0x162 + GO_CASHSHOP_SN = 0x163 + DAMAGE_METER = 0x164 + TIME_BOMB_ATTACK = 0x165 + FOLLOW_MOVE = 0x166 + FOLLOW_MSG = 0x167 + AP_SP_EVENT = 0x169 + QUEST_GUIDE_NPC = 0x16A + REGISTER_FAMILIAR = 0x171 + FAMILIAR_NAME_ERROR = 0x172 + CREATE_ULTIMATE = 0x173 + HARVEST_MESSAGE = 0x174 + SHOW_MAP_NAME = 0x175 + OPEN_BAG = 0x176 + DRAGON_BLINK = 0x177 + PVP_ICEGAGE = 0x178 + DIRECTION_INFO = 0x179 + REISSUE_MEDAL = 0x17A + PLAY_MOVIE = 0x17D + CAKE_VS_PIE = 0x17F + COOLDOWN = 0x181 + SPAWN_SUMMON = 0x183 + REMOVE_SUMMON = 0x184 + MOVE_SUMMON = 0x185 + SUMMON_ATTACK = 0x186 + PVP_SUMMON = 0x187 + SUMMON_SKILL = 0x189 + SUMMON_SKILL_2 = 0x18A + SUMMON_DELAY = 0x18B + DAMAGE_SUMMON = 0x18C + SPAWN_MONSTER = 0x18D + KILL_MONSTER = 0x18E + SPAWN_MONSTER_CONTROL = 0x18F + MONSTER_CRC_CHANGE = 0x19A + REMOVE_TALK_MONSTER = 0x1A3 + MOVE_MONSTER = 0x190 + MOVE_MONSTER_RESPONSE = 0x191 + APPLY_MONSTER_STATUS = 0x193 + CANCEL_MONSTER_STATUS = 0x194 + DAMAGE_MONSTER = 0x197 + SKILL_EFFECT_MOB = 0x198 + SHOW_MONSTER_HP = 0x19B + SHOW_MAGNET = 0x19C + ITEM_EFFECT_MOB = 0x19D + CATCH_MONSTER = 0x19E + MONSTER_PROPERTIES = 0x1A2 + TALK_MONSTER = 0x1A4 + CYGNUS_ATTACK = 0x1A8 + MONSTER_RESIST = 0x1A9 + MOB_TO_MOB_DAMAGE = 0x1AA + SPAWN_NPC = 0x1AC + REMOVE_NPC = 0x1AD + SPAWN_NPC_REQUEST_CONTROLLER = 0x1AF + NPC_ACTION = 0x1B0 + NPC_SPAWN_EFFECT = 0x1B1 + NPC_SCRIPTABLE = 0x1B6 + SPAWN_HIRED_MERCHANT = 0x1B8 + DESTROY_HIRED_MERCHANT = 0x1B9 + UPDATE_HIRED_MERCHANT = 0x1BA + DROP_ITEM_FROM_MAPOBJECT = 0x1BB + REMOVE_ITEM_FROM_MAP = 0x1BD + SPAWN_KITE_ERROR = 0x1BE + SPAWN_KITE = 0x1BF + DESTROY_KITE = 0x1C0 + SPAWN_MIST = 0x1C1 + REMOVE_MIST = 0x1C2 + SPAWN_DOOR = 0x1C3 + REMOVE_DOOR = 0x1C4 + MECH_DOOR_SPAWN = 0x1C5 + MECH_DOOR_REMOVE = 0x1C6 + REACTOR_HIT = 0x1C7 + REACTOR_MOVE = 0x1C8 + REACTOR_SPAWN = 0x1C9 + REACTOR_DESTROY = 0x1CB + SPAWN_EXTRACTOR = 0x1CC + REMOVE_EXTRACTOR = 0x1CD + ROLL_SNOWBALL = 0x1CE + HIT_SNOWBALL = 0x1CF + SNOWBALL_MESSAGE = 0x1D0 + LEFT_KNOCK_BACK = 0x1D1 + HIT_COCONUT = 0x1D2 + COCONUT_SCORE = 0x1D3 + MOVE_HEALER = 0x1D4 + PULLEY_STATE = 0x1D5 + MONSTER_CARNIVAL_START = 0x1D6 + MONSTER_CARNIVAL_OBTAINED_CP = 0x1D7 + MONSTER_CARNIVAL_STATS = 0x1D8 + MONSTER_CARNIVAL_SUMMON = 0x1DA + MONSTER_CARNIVAL_MESSAGE = 0x1DB + MONSTER_CARNIVAL_DIED = 0x1DC + MONSTER_CARNIVAL_LEAVE = 0x1DD + MONSTER_CARNIVAL_RESULT = 0x1DE + MONSTER_CARNIVAL_RANKING = 0x1DF + ARIANT_SCORE_UPDATE = 0x1E0 + SHEEP_RANCH_INFO = 0x1E2 + SHEEP_RANCH_CLOTHES = 0x1E3 + WITCH_TOWER = 0x1E4 + EXPEDITION_CHALLENGE = 0x1E5 + ZAKUM_SHRINE = 0x1E6 + CHAOS_ZAKUM_SHRINE = 0x1E7 + PVP_TYPE = 0x1E8 + PVP_TRANSFORM = 0x1E9 + PVP_DETAILS = 0x1EA + PVP_ENABLED = 0x1EB + PVP_SCORE = 0x1EC + PVP_RESULT = 0x1ED + PVP_TEAM = 0x1EE + PVP_SCOREBOARD = 0x1EF + PVP_POINTS = 0x1F1 + PVP_KILLED = 0x1F2 + PVP_MODE = 0x1F3 + PVP_ICEKNIGHT = 0x1F4 + HORNTAIL_SHRINE = 0x1FA + CAPTURE_FLAGS = 0x205 + CAPTURE_POSITION = 0x206 + CAPTURE_RESET = 0x207 + PINK_ZAKUM_SHRINE = 0x208 + NPC_TALK = 0x209 + OPEN_NPC_SHOP = 0x20A + CONFIRM_SHOP_TRANSACTION = 0x20B + OPEN_STORAGE = 0x20E + MERCH_ITEM_MSG = 0x20F + MERCH_ITEM_STORE = 0x210 + RPS_GAME = 0x211 + MESSENGER = 0x212 + PLAYER_INTERACTION = 0x213 + TOURNAMENT = 0x214 + TOURNAMENT_MATCH_TABLE = 0x215 + TOURNAMENT_SET_PRIZE = 0x216 + TOURNAMENT_UEW = 0x217 + TOURNAMENT_CHARACTERS = 0x218 + WEDDING_PROGRESS = 0x219 + WEDDING_CEREMONY_END = 0x21A + DUEY = 0x21B + KEYMAP = 0x22E + PET_AUTO_HP = 0x230 + PET_AUTO_MP = 0x231 + START_TV = 0x237 + REMOVE_TV = 0x238 + ENABLE_TV = 0x239 + + CS_CHARGE_CASH = 0x21C + CS_UPDATE = 0x21D + CS_OPERATION = 0x21E + CS_EXP_PURCHASE = 0x21F + GIFT_RESULT = 0x220 + CHANGE_NAME_CHECK = 0x221 + CHANGE_NAME_RESPONSE = 0x222 + CHAR_TRANSFER_WORLD = 0x224 + GACHAPON_STAMPS = 0x225 + FREE_CASH_ITEM = 0x226 + CS_SURPRISE = 0x227 + XMAS_SURPRISE = 0x228 + ONE_A_DAY = 0x22A + NX_SPEND_GIFT = 0x22C + + ALIEN_SOCKET_CREATOR = 0x247 + BATTLE_RECORD_DAMAGE_INFO = 0x24A + CALCULATE_REQUEST_RESULT = 0x24B + VICIOUS_HAMMER = 0x24F + LUCKY_LOGOUT_GIFT = 0x256 + + BOOSTER_PACK = 0x999 + GET_MTS_TOKENS = 0x999 + MTS_OPERATION = 0x999 + BOOSTER_FAMILIAR = 0x999 + BLOCK_PORTAL = 0x7FFE + NPC_CONFIRM = 0x7FFE + # PIN_ASSIGNED = 0x7FFE + RSA_KEY = 0x7FFE + LOGIN_AUTH = 0x7FFE + PET_FLAG_CHANGE = 0x7FFE + BUFF_BAR = 0x7FFE + GAME_POLL_REPLY = 0x7FFE + GAME_POLL_QUESTION = 0x7FFE + ENGLISH_QUIZ = 0x7FFE + FISHING_BOARD_UPDATE = 0x7FFE + BOAT_EFFECT = 0x7FFE + FISHING_CAUGHT = 0xFF + SIDEKICK_OPERATION = 0x7FFE + + +class SendOps(IntEnum): + PONG = 0x2E + + CLIENT_HELLO = 0x14 + LOGIN_PASSWORD = 0x15 + GUEST_LOGIN = 0x16 + CHARLIST_REQUEST = 0x19 + SERVERSTATUS_REQUEST = 0x1A + TOS = 0x1B #NOT NEEDED + SERVERLIST_REQUEST = 0x1F + REDISPLAY_SERVERLIST = 0x20 + VIEW_ALL_CHAR = 0x21 + PICK_ALL_CHAR = 0x22 + VIEW_SERVERLIST = 0x23 + CHAR_SELECT_NO_PIC = 0x27 + CHECK_CHAR_NAME = 0x29 + CREATE_CHAR = 0x2A + CREATE_ULTIMATE = 0x2C + DELETE_CHAR = 0x2D + CHAR_SELECT = 0x32 + AUTH_SECOND_PASSWORD = 0x33 + VIEW_REGISTER_PIC = 0x34 + VIEW_SELECT_PIC = 0x35 + CLIENT_START = 0x38 + CLIENT_FAILED = 0x39 + CLIENT_ERROR = 0x3B + ENABLE_LV50_CHAR = 0x3C + CREATE_LV50_CHAR = 0x3D + ENABLE_SPECIAL_CREATION = 0x3E + CREATE_SPECIAL_CHAR = 0x3F + + PLAYER_LOGGEDIN = 0x28 + CHANGE_MAP = 0x42 + CHANGE_CHANNEL = 0x43 + ENTER_CASH_SHOP = 0x44 + ENTER_PVP = 0x45 + ENTER_PVP_PARTY = 0x46 + LEAVE_PVP = 0x48 + MOVE_PLAYER = 0x49 + CANCEL_CHAIR = 0x4B + USE_CHAIR = 0x4C + CLOSE_RANGE_ATTACK = 0x4D + RANGED_ATTACK = 0x4E + MAGIC_ATTACK = 0x4F + PASSIVE_ENERGY = 0x50 + TAKE_DAMAGE = 0x52 + PVP_ATTACK = 0x53 + GENERAL_CHAT = 0x54 + CLOSE_CHALKBOARD = 0x55 + FACE_EXPRESSION = 0x56 + FACE_ANDROID = 0x57 + USE_ITEMEFFECT = 0x58 + WHEEL_OF_FORTUNE = 0x59 + USE_TITLE = 0x5A + CHANGE_SET = 0x61 + MONSTER_BOOK_DROPS = 0x64 + NPC_TALK = 0x66 + NPC_TALK_MORE = 0x68 + NPC_SHOP = 0x69 + STORAGE = 0x6A + USE_HIRED_MERCHANT = 0x6B + MERCH_ITEM_STORE = 0x6C + DUEY_ACTION = 0x6D + MECH_CANCEL = 0x6E + OWL = 0x70 + OWL_WARP = 0x71 + ITEM_SORT = 0x73 + ITEM_GATHER = 0x74 + ITEM_MOVE = 0x75 + MOVE_BAG = 0x76 + SWITCH_BAG = 0x77 + USE_ITEM = 0x79 + CANCEL_ITEM_EFFECT = 0x7A + USE_SUMMON_BAG = 0x7C + PET_FOOD = 0x7D + USE_MOUNT_FOOD = 0x7E + USE_SCRIPTED_NPC_ITEM = 0x7F + USE_RECIPE = 0x80 + USE_NEBULITE = 0x81 + USE_ALIEN_SOCKET = 0x82 + USE_ALIEN_SOCKET_RESPONSE = 0x83 + USE_NEBULITE_FUSION = 0x84 + USE_CASH_ITEM = 0x85 + USE_CATCH_ITEM = 0x999 + USE_SKILL_BOOK = 0x88 + USE_OWL_MINERVA = 0x8E + USE_TELE_ROCK = 0x8F + USE_RETURN_SCROLL = 0x90 + USE_UPGRADE_SCROLL = 0x91 + USE_FLAG_SCROLL = 0x92 + USE_EQUIP_SCROLL = 0x93 + USE_POTENTIAL_SCROLL = 0x95 + USE_BAG = 0x98 + USE_MAGNIFY_GLASS = 0x99 + DISTRIBUTE_AP = 0x9A + AUTO_ASSIGN_AP = 0x9B + HEAL_OVER_TIME = 0x9C + DISTRIBUTE_SP = 0x9F + SPECIAL_MOVE = 0xA0 + CANCEL_BUFF = 0xA1 + SKILL_EFFECT = 0xA2 + MESO_DROP = 0xA3 + GIVE_FAME = 0xA4 + CHAR_INFO_REQUEST = 0xA6 + SPAWN_PET = 0xA7 + GET_BOOK_INFO = 0xA8 + USE_FAMILIAR = 0xAA + SPAWN_FAMILIAR = 0xAB + RENAME_FAMILIAR = 0xAC + CANCEL_DEBUFF = 0xAE + CHANGE_MAP_SPECIAL = 0xAF + USE_INNER_PORTAL = 0xB1 + TROCK_ADD_MAP = 0xB2 + REPORT = 0x999 + QUEST_ACTION = 0xB8 + REISSUE_MEDAL = 0xB9 + SKILL_MACRO = 0xBC + REWARD_ITEM = 0xBE + ITEM_MAKER = 0x999 + REPAIR_ALL = 0xC4 + REPAIR = 0xC5 + SOLOMON = 0xC6 + GACH_EXP = 0xC7 + FOLLOW_REQUEST = 0xC8 + PQ_REWARD = 0xCA + FOLLOW_REPLY = 0xCC + AUTO_FOLLOW_REPLY = 0x999 + USE_TREASUER_CHEST = 0x999 + PROFESSION_INFO = 0xD2 + USE_POT = 0xD3 + CLEAR_POT = 0xD4 + FEED_POT = 0xD5 + CURE_POT = 0xD6 + REWARD_POT = 0xD7 + USE_COSMETIC = 0x999 + PVP_RESPAWN = 0xD8 + GAIN_FORCE = 0xDA + PARTYCHAT = 0xDD + WHISPER = 0xDE + SPOUSE_CHAT = 0xDF + MESSENGER = 0xE0 + PLAYER_INTERACTION = 0xE1 + PARTY_OPERATION = 0xE2 + DENY_PARTY_REQUEST = 0xE3 + ALLOW_PARTY_INVITE = 0xE4 + EXPEDITION_OPERATION = 0xE5 + EXPEDITION_LISTING = 0xE6 + GUILD_OPERATION = 0xE7 + DENY_GUILD_REQUEST = 0xE8 + ADMIN_COMMAND = 0xE9 #TODO ADD TO SOURCE + ADMIN_LOG = 0xEA #TODO ADD TO SOURCE + BUDDYLIST_MODIFY = 0xEB + NOTE_ACTION = 0xEC + USE_DOOR = 0xEE + USE_MECH_DOOR = 0xEF + CHANGE_KEYMAP = 0xF1 + RPS_GAME = 0xF2 + RING_ACTION = 0xF3 + WEDDING_ACTION = 0xF4 + ALLIANCE_OPERATION = 0xF8 + DENY_ALLIANCE_REQUEST = 0xF9 + REQUEST_FAMILY = 0xFA + OPEN_FAMILY = 0xFB + FAMILY_OPERATION = 0xFC + DELETE_JUNIOR = 0xFD + DELETE_SENIOR = 0xFE + ACCEPT_FAMILY = 0xFF + USE_FAMILY = 0x100 + FAMILY_PRECEPT = 0x101 + FAMILY_SUMMON = 0x102 + BBS_OPERATION = 0x104 + ENTER_MTS = 0x105 + NEW_YEAR_CARD = 0x108 + XMAS_SURPRISE = 0x10A + TWIN_DRAGON_EGG = 0x10B + ARAN_COMBO = 0x10E + TRANSFORM_PLAYER = 0x999 + CYGNUS_SUMMON = 0x999 + CRAFT_DONE = 0x110 + CRAFT_EFFECT = 0x111 + CRAFT_MAKE = 0x112 + CHANGE_ROOM_CHANNEL = 0x116 + EVENT_CARD = 0x117 + YOUR_INFORMATION = 0x11B + FIND_FRIEND = 0x11C + PINKBEAN_CHOCO_OPEN = 0x11E + PINKBEAN_CHOCO_SUMMON = 0x11F + MOVE_PET = 0x123 + PET_CHAT = 0x124 + PET_COMMAND = 0x125 + PET_LOOT = 0x126 + PET_AUTO_POT = 0x127 + PET_IGNORE = 0x128 + MOVE_SUMMON = 0x12B + SUMMON_ATTACK = 0x12C + DAMAGE_SUMMON = 0x12D + SUB_SUMMON = 0x12E + REMOVE_SUMMON = 0x12F + PVP_SUMMON = 0x130 + MOVE_DRAGON = 0x133 + MOVE_ANDROID = 0x137 + MOVE_FAMILIAR = 0x13B + TOUCH_FAMILIAR = 0x13C + ATTACK_FAMILIAR = 0x13D + REVEAL_FAMILIAR = 0x13E + QUICK_SLOT = 0x140 + #SKILL_MEMORY = 0x141 INT + PAM_SONG = 0x145 + MOVE_LIFE = 0x14F + AUTO_AGGRO = 0x150 + FRIENDLY_DAMAGE = 0x153 + MONSTER_BOMB = 0x154 + HYPNOTIZE_DMG = 0x155 + MOB_BOMB = 0x157 + MOB_NODE = 0x158 + DISPLAY_NODE = 0x159 + NPC_ACTION = 0x15F + ITEM_PICKUP = 0x164 + DAMAGE_REACTOR = 0x167 + CLICK_REACTOR = 0x168 + TOUCH_REACTOR = 0x169 + MAKE_EXTRACTOR = 0x999 + UPDATE_ENV = 0x16B + SNOWBALL = 0x999 + LEFT_KNOCK_BACK = 0x999 + COCONUT = 0x999 + MONSTER_CARNIVAL = 0x17B + SHIP_OBJECT = 0x999 + PARTY_SEARCH_START = 0x184 + PARTY_SEARCH_STOP = 0x185 + START_HARVEST = 0x189 + STOP_HARVEST = 0x18A + PUBLIC_NPC = 0x18C + CS_UPDATE = 0x192 + BUY_CS_ITEM = 0x193 + COUPON_CODE = 0x194 + UPDATE_QUEST = 0x999 + QUEST_ITEM = 0x999 + USE_ITEM_QUEST = 0x999 + VICIOUS_HAMMER = 0x1AB + TOUCHING_MTS = 0x999 + MTS_TAB = 0x999 + + PYRAMID_BUY_ITEM = 0x17E + CLASS_COMPETITION = 0x1A6 + MAGIC_WHEEL = 0x1BD \ No newline at end of file diff --git a/packet.py b/packet.py new file mode 100644 index 0000000..a505a6d --- /dev/null +++ b/packet.py @@ -0,0 +1,176 @@ +from enum import Enum, IntEnum +from io import BytesIO +from struct import pack, unpack + +from .opcodes import RecvOps, SendOps +from .tools import to_string + + +class ByteBuffer(BytesIO): + """Base class for packet write and read operations""" + + def encode(self, _bytes): + self.write(_bytes) + return self + + def encode_byte(self, value): + if isinstance(value, Enum): + value = value.value + + self.write(bytes([value])) + return self + + def encode_short(self, value): + self.write(pack("H", value)) + return self + + def encode_int(self, value): + self.write(pack("I", value)) + return self + + def encode_long(self, value): + self.write(pack("Q", value)) + return self + + def encode_buffer(self, buffer): + self.write(buffer) + return self + + def skip(self, count): + self.write(bytes(count)) + return self + + def encode_string(self, string): + self.write(pack("H", len(string))) + + for ch in string: + self.write(ch.encode()) + + return self + + def encode_fixed_string(self, string, length=13): + for i in range(length): + if i < len(string): + self.write(string[i].encode()) + continue + + self.encode_byte(0) + + return self + + def encode_hex_string(self, string): + string = string.strip(" -") + self.write(bytes.fromhex(string)) + return self + + def decode_byte(self): + return self.read(1)[0] + + def decode_bool(self): + return bool(self.decode_byte()) + + def decode_short(self): + return unpack("H", self.read(2))[0] + + def decode_int(self): + return unpack("I", self.read(4))[0] + + def decode_long(self): + return unpack("Q", self.read(8))[0] + + def decode_buffer(self, size): + return self.read(size) + + def decode_string(self): + length = self.decode_short() + string = "" + + for _ in range(length): + string += self.read(1).decode() + + return string + + +class Packet(ByteBuffer): + """Packet class use in all send / recv opertions + + Parameters + ---------- + data: bytes + The initial data to load into the packet + op_code: :class:`OpCodes` + OpCode used to encode the first short onto the packet + op_codes: :class:`OpCodes` + Which enum to try to get the op_code from + + """ + + _op_codes: type[IntEnum] + + def __init__(self, data=None, op_code=None, raw=False): + self.op_code: IntEnum | int + + if not data: + data = bytearray() + + if isinstance(data, IntEnum): + if op_code and isinstance(op_code, bytearray): + op_code, data = data, op_code + else: + op_code, data = data, bytearray() + + super().__init__(data) + + @property + def name(self): + if isinstance(self.op_code, IntEnum): + return self.op_code.name + + return self.op_code + + def to_array(self): + return self.getvalue() + + def to_string(self): + return to_string(self.getvalue()) + + def __len__(self): + return len(self.getvalue()) + + @property + def length(self): + return len(self.getvalue()) + + +class iPacket(Packet): + _op_codes = RecvOps + + def __init__(self, data=None): + super().__init__(data=data) + self.op_code = self._op_codes(self.decode_short()) + + +class oPacket(Packet): + _op_codes = SendOps + + def __init__(self, op_code=None): + super().__init__(op_code=op_code) + self.op_code = op_code.value if hasattr(op_code, "value") else op_code + if self.op_code: + self.encode_short(self.op_code) + + +class PacketHandler: + + def __init__(self, name, callback, op_code=None, **kwargs): + self.name = name + self.callback = callback + self.op_code = op_code + + +def packet_handler(op_code=None): + + def wrap(func): + return PacketHandler(func.__name__, func, op_code=op_code) + + return wrap diff --git a/tools.py b/tools.py new file mode 100644 index 0000000..a144d23 --- /dev/null +++ b/tools.py @@ -0,0 +1,125 @@ +from asyncio import sleep + +from dataclasses import dataclass, is_dataclass +from random import randint + + +class TagPoint: + + def __init__(self, x=0, y=0): + self.x = x + self.y = y + + def __str__(self): + return f"{self.x},{self.y}" + + +class Random: + + def __init__(self): + self.seed_1 = randint(1, 2**31 - 1) + self.seed_2 = randint(1, 2**31 - 1) + self.seed_3 = randint(1, 2**31 - 1) + + def encode(self, packet): + packet.encode_int(self.seed_1) + packet.encode_int(self.seed_2) + packet.encode_int(self.seed_3) + + +def find(predicate, seq): + for element in seq: + if predicate(element): + return element + return None + + +def get(iterable, **attrs): + + def predicate(elem): + for attr, val in attrs.items(): + nested = attr.split("__") + obj = elem + for attribute in nested: + obj = getattr(obj, attribute) + + if obj != val: + return False + return True + + return find(predicate, iterable) + + +def filter_out_to(func, iters, out): + new = [] + + for item in iters: + if func(item): + new.append(item) + else: + out.append(item) + + return new + + +def first_or_default(list_, f): + return next((val for val in list_ if f(val)), None) + + +def fix_dict_keys(dict_): + copy = dict(dict_) + + for key, value in copy.items(): + if key.isdigit(): + value = dict_.pop(key) + key = int(key) + + if isinstance(value, dict): + dict_[key] = fix_dict_keys(value) + + else: + dict_[key] = value + + return dict_ + + +def to_string(bytes_): + return " ".join([ + bytes_.hex()[i:i + 2].upper() for i in range(0, len(bytes_.hex()), 2) + ]) + + +async def wakeup(): + while True: + await sleep(0.01) + + +def nested_dataclass(*args, **kwargs): + + def wrapper(cls): + cls = dataclass(cls, **kwargs) + original_init = cls.__init__ + + def __init__(self, *args, **kwargs): + for name, value in kwargs.items(): + field_type = cls.__annotations__.get(name, None) + + if is_dataclass(field_type) and isinstance(value, dict): + new_obj = field_type(**value) + kwargs[name] = new_obj + + original_init(self, *args, **kwargs) + + cls.__init__ = __init__ + return cls + + return wrapper(args[0]) if args else wrapper + + +class Manager(list): + + def get(self, search): + return first_or_default(self, search) + + def first_or_default(self, func): + return next((val for val in self if func(val)), None)