├── noscrypto ├── __init__.py ├── tests │ ├── __init__.py │ └── tests.py ├── Client.py ├── Server.py └── Utils.py ├── setup.py ├── LICENSE ├── .gitignore └── README.md /noscrypto/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /noscrypto/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | from setuptools import setup 3 | 4 | # The directory containing this file 5 | HERE = pathlib.Path(__file__).parent 6 | 7 | # The text of the README file 8 | README = (HERE / "README.md").read_text() 9 | 10 | setup( 11 | name="noscrypto", 12 | version="0.2.0", 13 | description="A reverse engineered packet cryptography - encryption and decryption routines to emulate NosTale client or server", 14 | long_description=README, 15 | long_description_content_type="text/markdown", 16 | url="https://github.com/morsisko/NosCrypto", 17 | author="morsisko", 18 | license="MIT", 19 | classifiers=[ 20 | "License :: OSI Approved :: MIT License", 21 | "Programming Language :: Python", 22 | "Programming Language :: Python :: 3", 23 | "Programming Language :: Python :: 3.7", 24 | "Development Status :: 5 - Production/Stable", 25 | "Intended Audience :: Developers", 26 | "Topic :: Software Development :: Libraries", 27 | ], 28 | packages=["noscrypto", "noscrypto.tests"], 29 | ) 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 morsisko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /noscrypto/Client.py: -------------------------------------------------------------------------------- 1 | from noscrypto import Utils 2 | 3 | _ENCRYPTION_TABLE = [0x00, 0x20, 0x2D, 0x2E, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0xFF, 0x00] 4 | _DECRYPTION_TABLE = [0x00, 0x20, 0x2D, 0x2E, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x0A, 0x00] 5 | 6 | def LoginEncrypt(packet): 7 | output = [] 8 | 9 | if packet[-1] != 0xA: 10 | packet += b'\n' 11 | 12 | for b in packet: 13 | v = (b ^ 0xC3) + 0xF 14 | output.append(v & 0xFF) 15 | 16 | return bytes(output) 17 | 18 | def LoginDecrypt(packet): 19 | output = [] 20 | 21 | for b in packet: 22 | v = b - 0xF 23 | output.append(v & 0xFF) 24 | 25 | return bytes(output) 26 | 27 | def _WorldXor(packet, session, is_first_packet = False): 28 | output = [] 29 | 30 | if not is_first_packet: 31 | stype = (session >> 6) & 3 32 | else: 33 | stype = -1 34 | 35 | key = session & 0xFF 36 | 37 | for i in packet: 38 | if stype == 0: 39 | output.append((i + key + 0x40) & 0xFF) 40 | 41 | elif stype == 1: 42 | output.append((i - key - 0x40) & 0xFF) 43 | 44 | elif stype == 2: 45 | output.append(((i ^ 0xC3) + key + 0x40) & 0xFF) 46 | 47 | elif stype == 3: 48 | output.append(((i ^ 0xC3) - key - 0x40) & 0xFF) 49 | 50 | else: 51 | output.append((i + 0xF) & 0xFF) 52 | 53 | return bytes(output) 54 | 55 | def WorldEncrypt(packet, session, is_first_packet = False): 56 | packed = Utils._Pack(packet, _ENCRYPTION_TABLE) 57 | return _WorldXor(packed, session, is_first_packet) 58 | 59 | def WorldDecrypt(packet): 60 | return Utils._Unpack(packet, _DECRYPTION_TABLE) 61 | -------------------------------------------------------------------------------- /noscrypto/Server.py: -------------------------------------------------------------------------------- 1 | from noscrypto import Utils 2 | 3 | _ENCRYPTION_TABLE = [0x00, 0x20, 0x2D, 0x2E, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x0A, 0x00] 4 | _DECRYPTION_TABLE = [0x00, 0x20, 0x2D, 0x2E, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0xFF, 0x00] 5 | 6 | def LoginEncrypt(packet): 7 | output = [] 8 | 9 | if packet[-1] != 0xA: 10 | packet += b'\n' 11 | 12 | for b in packet: 13 | v = b + 0xF 14 | output.append(v & 0xFF) 15 | 16 | return bytes(output) 17 | 18 | def LoginDecrypt(packet): 19 | output = [] 20 | 21 | for b in packet: 22 | v = (b - 0xF) ^ 0xC3 23 | output.append(v & 0xFF) 24 | 25 | return bytes(output) 26 | 27 | def WorldEncrypt(packet): 28 | return Utils._Pack(packet, _ENCRYPTION_TABLE) 29 | 30 | def _WorldXor(packet, session, is_first_packet = False): 31 | output = [] 32 | 33 | if not is_first_packet: 34 | stype = (session >> 6) & 3 35 | else: 36 | stype = -1 37 | 38 | key = session & 0xFF 39 | 40 | for i in packet: 41 | if stype == 0: 42 | output.append((i - key - 0x40) & 0xFF) 43 | 44 | elif stype == 1: 45 | output.append((i + key + 0x40) & 0xFF) 46 | 47 | elif stype == 2: 48 | output.append(((i - key - 0x40) ^ 0xC3) & 0xFF) 49 | 50 | elif stype == 3: 51 | output.append(((i + key + 0x40) ^ 0xC3) & 0xFF) 52 | 53 | else: 54 | output.append((i - 0xF) & 0xFF) 55 | 56 | return bytes(output) 57 | 58 | def WorldDecrypt(packet, session, is_first_packet = False): 59 | xored = _WorldXor(packet, session, is_first_packet) 60 | return Utils._Unpack(xored, _DECRYPTION_TABLE) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /noscrypto/Utils.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | def _GetMaskPart(ch, charset): 4 | if ch == 0: 5 | return False 6 | 7 | return ch in charset 8 | 9 | def _GetMask(packet, charset): 10 | output = [] 11 | 12 | for ch in packet: 13 | if ch == 0x0: 14 | break 15 | 16 | output.append(_GetMaskPart(ch, charset)) 17 | 18 | 19 | return output 20 | 21 | def _CalcLenOfMask(start, mask, value): 22 | currentLen = 0 23 | for i in range(start, len(mask)): 24 | if mask[i] == value: 25 | currentLen += 1 26 | else: 27 | break 28 | 29 | 30 | return currentLen 31 | 32 | 33 | def _Pack(packet, chars_to_pack): 34 | output = [] 35 | mask = _GetMask(packet, chars_to_pack) 36 | pos = 0 37 | 38 | while len(mask) > pos: 39 | currentChunkLen = _CalcLenOfMask(pos, mask, False) 40 | 41 | for i in range(currentChunkLen): 42 | if pos > len(mask): 43 | break 44 | 45 | if i % 0x7E == 0: 46 | output.append(min(currentChunkLen - i, 0x7E)) 47 | 48 | output.append(packet[pos] ^ 0xFF) 49 | pos += 1 50 | 51 | currentChunkLen = _CalcLenOfMask(pos, mask, True) 52 | 53 | for i in range(currentChunkLen): 54 | if pos > len(mask): 55 | break 56 | 57 | if i % 0x7E == 0: 58 | output.append(min(currentChunkLen - i, 0x7E) | 0x80) 59 | 60 | currentValue = chars_to_pack.index(packet[pos]) 61 | 62 | if i % 2 == 0: 63 | output.append(currentValue << 4) 64 | 65 | else: 66 | output[-1] |= currentValue 67 | 68 | pos += 1 69 | 70 | output.append(0xFF) 71 | return bytes(output) 72 | 73 | def _Unpack(packet, chars_to_unpack): 74 | output = [] 75 | pos = 0 76 | 77 | while len(packet) > pos: 78 | if packet[pos] == 0xFF: 79 | break 80 | 81 | currentChunkLen = packet[pos] & 0x7F 82 | isPacked = packet[pos] & 0x80 83 | pos += 1 84 | 85 | 86 | if isPacked: 87 | for i in range(math.ceil(currentChunkLen / 2)): 88 | if pos >= len(packet): 89 | break 90 | 91 | twoChars = packet[pos] 92 | pos += 1 93 | 94 | leftChar = twoChars >> 4 95 | output.append(chars_to_unpack[leftChar]) 96 | 97 | rightChar = twoChars & 0xF 98 | if rightChar == 0: 99 | break 100 | 101 | output.append(chars_to_unpack[rightChar]) 102 | 103 | 104 | else: 105 | for i in range(currentChunkLen): 106 | if pos >= len(packet): 107 | break 108 | 109 | output.append(packet[pos] ^ 0xFF) 110 | pos += 1 111 | 112 | return bytes(output) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NosCrypto 2 | A reverse engineered packet cryptography - encryption and decryption routines to emulate NosTale client or server. 3 | 4 | # Abstract 5 | The goal of this project is to recreate an algorithm that is used to encrypt/decrypt net packets that are exchanged between NosTale client and server. I've seen many public cryptos but I think none of them were producing exactly the same output as real client or server (especially when it comes to special characters like `ąśżź` etc). The problem of a majority of those libraries is an incorrect mask generation algorithm. If you ever encounter output produced by this library that is different from the original produced by game client or server, please report it immediately. 6 | 7 | # Installation 8 | Please use pypi to install this library: 9 | 10 | `pip install noscrypto` 11 | 12 | # Tests 13 | Unit tests are available under `noscrypto/tests` 14 | 15 | # Usage 16 | All the functions accept `bytes` as input and return `bytes` as output. Keep in mind that each function should accept and output only single packet. It is extremely important when you are, for example decrypting world packets as a client, because sometime the server sends multiple packets in one call (they are splited by 0xFF, and you should decrypt the packets chunk by chunk) 17 | 18 | ## Client 19 | Functions can be imported like 20 | ```python 21 | from noscrypto import Client 22 | ``` 23 | 24 | ### Client.LoginEncrypt 25 | Encrypts your packet so the login server can read it. Automatically adds the `\n` character at the end of packet if not present. 26 | 27 | ```python 28 | >>> Client.LoginEncrypt("hey".encode("ascii")) 29 | b'\xba\xb5\xc9\xd8' 30 | ``` 31 | 32 | ### Client.LoginDecrypt 33 | Decrypts your login result packet, so you can read it 34 | 35 | ```python 36 | >>> Client.LoginDecrypt(b'\x75\x70\x78\x7B\x72\x2F\x44\x19').decode("ascii") 37 | 'failc 5\n' 38 | ``` 39 | 40 | ### Client.WorldEncrypt 41 | Encrypts your pakcet so the world server can read it. Beside `packet` you also need to supply your `session` (that one you received from login server). The last parameter `is_first_packet` (default `False`) must be set to `True` only if you are sending your first packet to the world server (the session packet) 42 | 43 | ```python 44 | >>> Client.WorldEncrypt("hey!".encode("ascii"), 1337, False) 45 | b'}\x10\x13\xffWx' 46 | ``` 47 | 48 | ### Client.WorldDecrypt 49 | Decrypts your wolrd packet sent by server, so you can read it 50 | 51 | ```python 52 | >>> Client.WorldDecrypt(b'\x04\x8C\x8B\x9E\x8B\x96\x16\x65\x16\x65\x1A\x41\xA4\x14\x15\x46\x8E\xFF') 53 | b'stat 221 221 60 60 0 1024\n' 54 | ``` 55 | 56 | ## Server 57 | Functions can be imported like 58 | ```python 59 | from noscrypto import Server 60 | ``` 61 | 62 | ### Server.LoginEncrypt 63 | Encrypts your packet so the client can read it. Automatically adds the `\n` character at the end of packet if not present. 64 | ```python 65 | >>> Server.LoginEncrypt("hello world".encode("ascii")) 66 | b'wt{{~/\x86~\x81{s\x19' 67 | ``` 68 | 69 | ### Server.LoginDecrypt 70 | Decrypts login packet from client, so you can read it 71 | ```python 72 | >>> Server.LoginDecrypt(b'\xba\xb5\xc9\xd8').decode("ascii") 73 | 'hey\n' 74 | ``` 75 | 76 | ### Server.WorldEncrypt 77 | Encrypt world packet, so the client can read it 78 | ```python 79 | >>> Server.WorldEncrypt("stat 221 221 60 60 0 1024\n".encode("ascii")) 80 | b'\x04\x8c\x8b\x9e\x8b\x96\x16e\x16e\x1aA\xa4\x14\x15F\x8e\xff' 81 | ``` 82 | ### Server.WorldDecrypt 83 | Decrypts client pakcet so the world server can read it. Beside `packet` you also need to supply client `session`. The last parameter `is_first_packet` (default `False`) must be set to `True` only if you didn't recv the first packet from client yet (the session packet). 84 | 85 | ```python 86 | >>> Server.WorldDecrypt(b'}\x10\x13\xffWx', 1337, False).decode("ascii") 87 | 'hey!' 88 | ``` 89 | -------------------------------------------------------------------------------- /noscrypto/tests/tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import string 3 | import random 4 | 5 | from noscrypto import Client, Server, Utils 6 | 7 | def getPseudoRandomString(length=20): 8 | return "".join([random.choice(string.printable) for i in range(length)]) 9 | 10 | class TestClientLogin(unittest.TestCase): 11 | def test_auto_endline(self): 12 | self.assertEqual(Client.LoginEncrypt(b"Test"), Client.LoginEncrypt(b"Test\n")) 13 | 14 | def test_real_loginpacket_encryption(self): 15 | self.assertEqual( 16 | Client.LoginEncrypt("NoS0575 571739 admin C7AD44CBAD762A5DA0A452F9E854FDC1E0E7A52A38015F23F3EAB1D80B931DD472634DFAC71CD34EBC35D16AB7FB8A90C81F975113D6C7538DC69DD8DE9077EC 6c38fd45-ef91-4054-82b8-9c96b4b0b209 00727371 0\x0B0.9.3.3134 0 9825E5FC051F9A17AEFE047A48F4C6F9\n".encode("ascii")), 17 | b"\x9C\xBB\x9F\x02\x05\x03\x05\xF2\x05\x03\x01\x03\xFF\x09\xF2\xB1\xB6\xBD\xB9\xBC\xF2\x8F\x03\x91\x96\x06\x06\x8F\x90\x91\x96\x03\x04\x00\x91\x05\x96\x91\x02\x91\x06\x05\x00\x94\x09\x95\x0A\x05\x06\x94\x96\x8F\x01\x95\x02\x95\x03\x91\x05\x00\x91\xFF\x0A\x02\x01\x05\x94\x00\xFF\x94\xFF\x95\x91\x90\x01\x96\x0A\x02\x90\x09\xFF\x01\x96\x96\x06\x03\x00\x04\xFF\x06\x96\x94\x91\x8F\x03\x01\x8F\x96\xFF\x06\x95\x90\x8F\xFF\x05\x96\x01\x04\x91\x90\x03\x94\x90\x0A\x91\x09\x02\x8F\x0A\x01\x94\x09\x03\x05\x01\x01\xFF\x96\x04\x8F\x03\x05\xFF\x0A\x96\x8F\x04\x09\x96\x96\x0A\x96\x95\x09\x02\x03\x03\x95\x8F\xF2\x04\xAF\xFF\x0A\xB4\xB6\x06\x05\xFD\xB5\xB4\x09\x01\xFD\x06\x02\x05\x06\xFD\x0A\x00\xB0\x0A\xFD\x09\xAF\x09\x04\xB0\x06\xB0\x02\xB0\x00\x02\x09\xF2\x02\x02\x03\x00\x03\xFF\x03\x01\xF2\x02\xD7\x02\xFC\x09\xFC\xFF\xFC\xFF\x01\xFF\x06\xF2\x02\xF2\x09\x0A\x00\x05\x95\x05\x94\x8F\x02\x05\x01\x94\x09\x91\x01\x03\x91\x95\x94\x95\x02\x06\x03\x91\x06\x0A\x94\x06\x8F\x04\x94\x09\xD8") 18 | 19 | def test_real_loginpacket_decryption(self): 20 | self.assertEqual(Client.LoginDecrypt(b"\x75\x70\x78\x7B\x72\x2F\x44\x19"), b"failc 5\n") 21 | 22 | 23 | class TestServerLogin(unittest.TestCase): 24 | def test_auto_endline(self): 25 | self.assertEqual(Server.LoginEncrypt(b"Test"), Server.LoginEncrypt(b"Test\n")) 26 | 27 | def test_real_loginpacket_decryption(self): 28 | self.assertEqual(Server.LoginDecrypt(b"\x9C\xBB\x9F\x02\x05\x03\x05\xF2\x05\x03\x01\x03\xFF\x09\xF2\xB1\xB6\xBD\xB9\xBC\xF2\x8F\x03\x91\x96\x06\x06\x8F\x90\x91\x96\x03\x04\x00\x91\x05\x96\x91\x02\x91\x06\x05\x00\x94\x09\x95\x0A\x05\x06\x94\x96\x8F\x01\x95\x02\x95\x03\x91\x05\x00\x91\xFF\x0A\x02\x01\x05\x94\x00\xFF\x94\xFF\x95\x91\x90\x01\x96\x0A\x02\x90\x09\xFF\x01\x96\x96\x06\x03\x00\x04\xFF\x06\x96\x94\x91\x8F\x03\x01\x8F\x96\xFF\x06\x95\x90\x8F\xFF\x05\x96\x01\x04\x91\x90\x03\x94\x90\x0A\x91\x09\x02\x8F\x0A\x01\x94\x09\x03\x05\x01\x01\xFF\x96\x04\x8F\x03\x05\xFF\x0A\x96\x8F\x04\x09\x96\x96\x0A\x96\x95\x09\x02\x03\x03\x95\x8F\xF2\x04\xAF\xFF\x0A\xB4\xB6\x06\x05\xFD\xB5\xB4\x09\x01\xFD\x06\x02\x05\x06\xFD\x0A\x00\xB0\x0A\xFD\x09\xAF\x09\x04\xB0\x06\xB0\x02\xB0\x00\x02\x09\xF2\x02\x02\x03\x00\x03\xFF\x03\x01\xF2\x02\xD7\x02\xFC\x09\xFC\xFF\xFC\xFF\x01\xFF\x06\xF2\x02\xF2\x09\x0A\x00\x05\x95\x05\x94\x8F\x02\x05\x01\x94\x09\x91\x01\x03\x91\x95\x94\x95\x02\x06\x03\x91\x06\x0A\x94\x06\x8F\x04\x94\x09\xD8"), 29 | "NoS0575 571739 admin C7AD44CBAD762A5DA0A452F9E854FDC1E0E7A52A38015F23F3EAB1D80B931DD472634DFAC71CD34EBC35D16AB7FB8A90C81F975113D6C7538DC69DD8DE9077EC 6c38fd45-ef91-4054-82b8-9c96b4b0b209 00727371 0\x0B0.9.3.3134 0 9825E5FC051F9A17AEFE047A48F4C6F9\n".encode("ascii")) 30 | 31 | def test_real_loginpacket_encryption(self): 32 | self.assertEqual(Server.LoginEncrypt("failc 5\n".encode("ascii")), b"\x75\x70\x78\x7B\x72\x2F\x44\x19") 33 | 34 | 35 | class TestClientServerLoginFlow(unittest.TestCase): 36 | def test_server_send_client_recv(self): 37 | s = getPseudoRandomString() + "\n" 38 | self.assertEqual(Client.LoginDecrypt(Server.LoginEncrypt(s.encode("ascii"))), s.encode("ascii")) 39 | 40 | def test_client_send_server_recv(self): 41 | s = getPseudoRandomString() + "\n" 42 | self.assertEqual(Server.LoginDecrypt(Client.LoginEncrypt(s.encode("ascii"))), s.encode("ascii")) 43 | 44 | class TestClientPackUnpack(unittest.TestCase): 45 | def test_client_mask_generator(self): 46 | packet = "14326 say dfskjda12312ąśąźżźżććżąąąśąąą2137dadaęóąśłżźćń;1122".encode("cp1250") 47 | generated_mask = "".join([str(int(i)) for i in Utils._GetMask(packet, Client._ENCRYPTION_TABLE)]) 48 | 49 | self.assertEqual(generated_mask, "1111110001000000011111000000000000000001111000000000000001111") 50 | 51 | def test_server_pack_client_unpack(self): 52 | self.assertEqual(Utils._Unpack(Utils._Pack("2137 say ąźć123pd".encode("cp1250"), Client._ENCRYPTION_TABLE), Client._DECRYPTION_TABLE), "2137 say ąźć123pd".encode("cp1250")) 53 | 54 | def test_real_client_pack(self): 55 | self.assertEqual(Utils._Pack("17535 walk 20 26 1 11".encode("ascii"), Client._ENCRYPTION_TABLE), b"\x86\x5B\x97\x91\x04\x88\x9E\x93\x94\x8B\x16\x41\x6A\x15\x15\x50\xFF") 56 | 57 | def test_real_client_pack2(self): 58 | self.assertEqual(Utils._Pack("48967 c_blist 0 0 0 0 0 0 0 0 17 185 302 882 942 999 1591 1592 4083 5065 5068 5069 5070 5206 5307 5361 5982 5991".encode("ascii"), Client._ENCRYPTION_TABLE), b"\x86\x8C\xDA\xB1\x07\x9C\xA0\x9D\x93\x96\x8C\x8B\xE4\x11\x41\x41\x41\x41\x41\x41\x41\x41\x5B\x15\xC9\x17\x46\x1C\xC6\x1D\x86\x1D\xDD\x15\x9D\x51\x59\xD6\x18\x4C\x71\x94\xA9\x19\x4A\xC1\x94\xAD\x19\x4B\x41\x96\x4A\x19\x74\xB1\x97\xA5\x19\xDC\x61\x9D\xD5\xFF") 59 | 60 | def test_real_client_pack_special_characters(self): 61 | self.assertEqual(Utils._Pack("14326 say dfskjda12312ąśąźżźżććżąąąśąąą2137dadaęóąśłżźćń;1122".encode("cp1250"), Client._ENCRYPTION_TABLE), 62 | b"\x86\x58\x76\xA1\x03\x8C\x9E\x86\x81\x10\x07\x9B\x99\x8C\x94\x95\x9B\x9E\x85\x56\x75\x60\x11\x46\x63\x46\x60\x40\x60\x40\x19\x19\x40\x46\x46\x46\x63\x46\x46\x46\x84\x65\x7B\x0E\x9B\x9E\x9B\x9E\x15\x0C\x46\x63\x4C\x40\x60\x19\x0E\xC4\x84\x55\x66\xFF") 63 | 64 | 65 | class TestClientWorld(unittest.TestCase): 66 | def test_real_client_encrypt(self): 67 | session = 53836 68 | packet = "17535 walk 20 26 1 11".encode("ascii") 69 | 70 | self.assertEqual(Client.WorldEncrypt(packet, session), b"\xFA\xCF\x0B\x05\x78\xFC\x12\x07\x08\xFF\x8A\xB5\xDE\x89\x89\xC4\x73") 71 | 72 | def test_real_client_encrypt2(self): 73 | session = 10685 74 | packet = "48967 c_blist 0 0 0 0 0 0 0 0 17 185 302 882 942 999 1591 1592 4083 5065 5068 5069 5070 5206 5307 5361 5982 5991".encode("ascii") 75 | 76 | self.assertEqual(Client.WorldEncrypt(packet, session), b"\x42\x4C\x16\x6F\xC1\x5C\x60\x5B\x4D\x52\x4C\x45\x24\xCF\x7F\x7F\x7F\x7F\x7F\x7F\x7F\x7F\x95\xD3\x07\xD1\x82\xDC\x02\xDB\x42\xDB\x1B\xD3\x5B\x8F\x97\x12\xD8\x8C\xAF\x54\x67\xD7\x86\xFF\x54\x6B\xD7\x85\x7F\x52\x86\xD7\xB4\x6F\x51\x63\xD7\x1C\x9F\x5B\x13\x39") 77 | 78 | def test_real_client_encypt_special_characters(self): 79 | session = 34353 80 | packet = "14326 say dfskjda12312ąśąźżźżććżąąąśąąą2137dadaęóąśłżźćń;1122".encode("cp1250") 81 | 82 | self.assertEqual(Client.WorldEncrypt(packet, session), b"\xF7\xC9\xE7\x12\x74\xFD\x0F\xF7\xF2\x81\x78\x0C\x0A\xFD\x05\x06\x0C\x0F\xF6\xC7\xE6\xD1\x82\xB7\xD4\xB7\xD1\xB1\xD1\xB1\x8A\x8A\xB1\xB7\xB7\xB7\xD4\xB7\xB7\xB7\xF5\xD6\xEC\x7F\x0C\x0F\x0C\x0F\x86\x7D\xB7\xD4\xBD\xB1\xD1\x8A\x7F\x35\xF5\xC6\xD7\x70") 83 | 84 | def test_real_client_decrypt(self): 85 | self.assertEqual(Client.WorldDecrypt(b"\x04\x8C\x8B\x9E\x8B\x96\x16\x65\x16\x65\x1A\x41\xA4\x14\x15\x46\x8E\xFF"), "stat 221 221 60 60 0 1024\n".encode("ascii")) 86 | 87 | class TestServerWorld(unittest.TestCase): 88 | def test_real_server_decrypt(self): 89 | session = 53836 90 | packet = b"\xFA\xCF\x0B\x05\x78\xFC\x12\x07\x08\xFF\x8A\xB5\xDE\x89\x89\xC4\x73" 91 | 92 | self.assertEqual(Server.WorldDecrypt(packet, session), "17535 walk 20 26 1 11".encode("ascii")) 93 | 94 | def test_real_server_encrypt2(self): 95 | session = 10685 96 | packet = b"\x42\x4C\x16\x6F\xC1\x5C\x60\x5B\x4D\x52\x4C\x45\x24\xCF\x7F\x7F\x7F\x7F\x7F\x7F\x7F\x7F\x95\xD3\x07\xD1\x82\xDC\x02\xDB\x42\xDB\x1B\xD3\x5B\x8F\x97\x12\xD8\x8C\xAF\x54\x67\xD7\x86\xFF\x54\x6B\xD7\x85\x7F\x52\x86\xD7\xB4\x6F\x51\x63\xD7\x1C\x9F\x5B\x13\x39" 97 | 98 | self.assertEqual(Server.WorldDecrypt(packet, session), "48967 c_blist 0 0 0 0 0 0 0 0 17 185 302 882 942 999 1591 1592 4083 5065 5068 5069 5070 5206 5307 5361 5982 5991".encode("ascii")) 99 | 100 | def test_real_server_decrypt_special_characters(self): 101 | session = 34353 102 | packet = b"\xF7\xC9\xE7\x12\x74\xFD\x0F\xF7\xF2\x81\x78\x0C\x0A\xFD\x05\x06\x0C\x0F\xF6\xC7\xE6\xD1\x82\xB7\xD4\xB7\xD1\xB1\xD1\xB1\x8A\x8A\xB1\xB7\xB7\xB7\xD4\xB7\xB7\xB7\xF5\xD6\xEC\x7F\x0C\x0F\x0C\x0F\x86\x7D\xB7\xD4\xBD\xB1\xD1\x8A\x7F\x35\xF5\xC6\xD7\x70" 103 | 104 | self.assertEqual(Server.WorldDecrypt(packet, session), "14326 say dfskjda12312ąśąźżźżććżąąąśąąą2137dadaęóąśłżźćń;1122".encode("cp1250")) 105 | 106 | def test_real_server_encrypt(self): 107 | self.assertEqual(Server.WorldEncrypt("stat 221 221 60 60 0 1024\n".encode("ascii")), b"\x04\x8C\x8B\x9E\x8B\x96\x16\x65\x16\x65\x1A\x41\xA4\x14\x15\x46\x8E\xFF") 108 | 109 | if __name__ == "__main__": 110 | unittest.main() --------------------------------------------------------------------------------