├── .gitignore ├── .gitmodules ├── README.md ├── coc ├── __init__.py ├── client │ ├── __init__.py │ ├── crypto.py │ ├── endpoint.py │ ├── factory.py │ └── protocol.py ├── crypto.py ├── hexdump.py ├── protocol.py ├── proxyconfig.py ├── replay.py └── server │ ├── __init__.py │ ├── crypto.py │ ├── endpoint.py │ ├── factory.py │ └── protocol.py ├── config.json.example ├── proxy.py ├── replay.py └── scripts ├── sc_decompress.sh └── varint.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.pyc 3 | .project 4 | .pydevproject 5 | *.iml 6 | *.swp 7 | *.json 8 | replay 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "coc/message"] 2 | path = coc/message 3 | url = https://github.com/royale-proxy/cr-messages-python.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Warning:** With version 8.212.12, Supercell has started banning accounts for the use of third party software. I am unsure what, if any, checks are in place that might reveal the use of this proxy, so continue use at your own risk. See [here](http://supercell.com/en/safe-and-fair-play/) for more info. 2 | 3 | # cr-proxy 4 | 5 | Run with: 6 | 7 | python3.5 proxy.py 8 | 9 | ## Installation 10 | 11 | 1. Install dependencies. 12 | 2. Patch the [Clash Royale APK](https://apkpure.com/clash-royale/com.supercell.clashroyale) with [cr-patcher](https://github.com/royale-proxy/cr-patcher) and install. 13 | 14 | ## Dependencies 15 | 16 | Install `twisted` with: 17 | 18 | python3.5 -m pip install twisted 19 | 20 | Install `pynacl` with: 21 | 22 | python3.5 -m pip install pynacl 23 | 24 | Install `pyblake2` with: 25 | 26 | python3.5 -m pip install pyblake2 27 | -------------------------------------------------------------------------------- /coc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/royale-proxy/cr-proxy/b7538badf033a26d12094029c6f6d92f65ebb816/coc/__init__.py -------------------------------------------------------------------------------- /coc/client/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/royale-proxy/cr-proxy/b7538badf033a26d12094029c6f6d92f65ebb816/coc/client/__init__.py -------------------------------------------------------------------------------- /coc/client/crypto.py: -------------------------------------------------------------------------------- 1 | from nacl.public import PublicKey 2 | from coc.crypto import CoCCrypto, CoCNonce 3 | 4 | 5 | class CoCClientCrypto(CoCCrypto): 6 | 7 | def __init__(self, factory): 8 | self._factory = factory 9 | self.keypair() 10 | self._serverkey = PublicKey(bytes.fromhex("ac30dcbea27e213407519bc05be8e9d930e63f873858479946c144895fa3a26b")) 11 | self.beforenm(self.serverkey) 12 | 13 | @property 14 | def serverkey(self): 15 | return bytes(self._serverkey) 16 | 17 | @property 18 | def clientkey(self): 19 | return bytes(self._pk) 20 | 21 | def decryptPacket(self, packet): 22 | messageid = int.from_bytes(packet[:2], byteorder="big") 23 | unknown = int.from_bytes(packet[5:7], byteorder="big") 24 | payload = packet[7:] 25 | if messageid == 20100 or (messageid == 20103 and not self.session_key): # ServerHandshake or LoginFailed 26 | if messageid == 20100: 27 | self.session_key = self.server.session_key = packet[-24:] 28 | return messageid, unknown, payload 29 | elif messageid in {20103, 20104}: # LoginFailed or LoginOk 30 | nonce = CoCNonce(nonce=self.encrypt_nonce, clientkey=self.clientkey, serverkey=self.serverkey) 31 | ciphertext = payload 32 | try: 33 | message = self.decrypt(ciphertext, bytes(nonce)) 34 | except ValueError: 35 | print("Failed to decrypt the message (client, {}).".format(messageid)) 36 | self.server.loseConnection() 37 | return False 38 | else: 39 | self.decrypt_nonce = self.server.encrypt_nonce = message[:24] 40 | self.k = message[24:56] 41 | return messageid, unknown, message[56:] 42 | else: 43 | ciphertext = payload 44 | message = self.decrypt(ciphertext) 45 | return messageid, unknown, message 46 | 47 | def encryptPacket(self, messageid, unknown, payload): 48 | if messageid == 10100: 49 | return messageid, unknown, payload 50 | elif messageid == 10101: 51 | nonce = CoCNonce(clientkey=self.clientkey, serverkey=self.serverkey) 52 | message = self.session_key + self.encrypt_nonce + payload 53 | ciphertext = self.encrypt(message, nonce) 54 | return messageid, unknown, self.clientkey + ciphertext 55 | else: 56 | message = payload 57 | return messageid, unknown, self.encrypt(message) 58 | -------------------------------------------------------------------------------- /coc/client/endpoint.py: -------------------------------------------------------------------------------- 1 | from twisted.internet.endpoints import TCP4ClientEndpoint 2 | 3 | 4 | class CoCClientEndpoint(TCP4ClientEndpoint): 5 | 6 | @property 7 | def host(self): 8 | return self._host 9 | 10 | @property 11 | def port(self): 12 | return self._port 13 | -------------------------------------------------------------------------------- /coc/client/factory.py: -------------------------------------------------------------------------------- 1 | from twisted.internet.protocol import ClientFactory 2 | from coc.client.protocol import CoCClientProtocol 3 | 4 | 5 | class CoCClientFactory(ClientFactory): 6 | 7 | def __init__(self, server): 8 | self.server = server 9 | 10 | def buildProtocol(self, addr): 11 | return CoCClientProtocol(self) 12 | -------------------------------------------------------------------------------- /coc/client/protocol.py: -------------------------------------------------------------------------------- 1 | from coc.protocol import CoCProtocol 2 | from coc.client.crypto import CoCClientCrypto 3 | 4 | 5 | class CoCClientProtocol(CoCClientCrypto, CoCProtocol): 6 | 7 | def __init__(self, factory): 8 | super(CoCClientProtocol, self).__init__(factory) 9 | self.factory.server.client = self 10 | self.server = self.factory.server 11 | self.decoder = self.server.decoder 12 | self.encoder = self.server.encoder 13 | 14 | def connectionMade(self): 15 | super(CoCClientProtocol, self).connectionMade() 16 | print("connected to {}:{} ...".format(self.peer.host, self.peer.port)) 17 | 18 | def packetDecrypted(self, messageid, version, payload): 19 | self.decodePacket(messageid, version, payload) 20 | self.server.sendPacket(messageid, version, payload) 21 | 22 | def connectionLost(self, reason): 23 | print("connection to {}:{} closed ...".format(self.peer.host, self.peer.port)) 24 | self.server.transport.loseConnection() 25 | -------------------------------------------------------------------------------- /coc/crypto.py: -------------------------------------------------------------------------------- 1 | from pyblake2 import blake2b 2 | import nacl.utils 3 | from nacl.public import Box, PrivateKey, PublicKey 4 | 5 | 6 | class CoCCrypto: 7 | _sk = None 8 | _pk = None 9 | _k = None 10 | _encrypt_nonce = None 11 | _decrypt_nonce = None 12 | 13 | session_key = None 14 | 15 | @property 16 | def pk(self): 17 | return bytes(self._pk) 18 | 19 | @property 20 | def k(self): 21 | return bytes(self._k) 22 | 23 | @k.setter 24 | def k(self, k): 25 | self._k = Box.decode(k) 26 | 27 | @property 28 | def encrypt_nonce(self): 29 | return bytes(self._encrypt_nonce) 30 | 31 | @encrypt_nonce.setter 32 | def encrypt_nonce(self, encrypt_nonce): 33 | self._encrypt_nonce = CoCNonce(nonce=encrypt_nonce) 34 | 35 | @property 36 | def decrypt_nonce(self): 37 | return bytes(self._decrypt_nonce) 38 | 39 | @decrypt_nonce.setter 40 | def decrypt_nonce(self, decrypt_nonce): 41 | self._decrypt_nonce = CoCNonce(nonce=decrypt_nonce) 42 | 43 | def keypair(self): 44 | self._sk = PrivateKey.generate() 45 | self._pk = self._sk.public_key 46 | 47 | def beforenm(self, pk): 48 | pk = PublicKey(bytes(pk)) 49 | self._k = Box(self._sk, pk) 50 | 51 | def encrypt(self, message, nonce=None): 52 | if not nonce: 53 | self._encrypt_nonce.increment() 54 | nonce = self.encrypt_nonce 55 | return self._k.encrypt(message, bytes(nonce))[24:] 56 | 57 | def decrypt(self, ciphertext, nonce=None): 58 | if not nonce: 59 | self._decrypt_nonce.increment() 60 | nonce = self.decrypt_nonce 61 | return self._k.decrypt(ciphertext, bytes(nonce)) 62 | 63 | def encryptPacket(self, messageid, unknown, payload): 64 | raise NotImplementedError 65 | 66 | def decryptPacket(self, packet): 67 | raise NotImplementedError 68 | 69 | 70 | class CoCNonce: 71 | 72 | def __init__(self, nonce=None, clientkey=None, serverkey=None): 73 | if not clientkey: 74 | if nonce: 75 | self._nonce = nonce 76 | else: 77 | self._nonce = nacl.utils.random(Box.NONCE_SIZE) 78 | else: 79 | b2 = blake2b(digest_size=24) 80 | if nonce: 81 | b2.update(bytes(nonce)) 82 | b2.update(bytes(clientkey)) 83 | b2.update(bytes(serverkey)) 84 | self._nonce = b2.digest() 85 | 86 | def __bytes__(self): 87 | return self._nonce 88 | 89 | def __len__(self): 90 | return len(self._nonce) 91 | 92 | def increment(self): 93 | self._nonce = (int.from_bytes(self._nonce, byteorder="little") + 2).to_bytes(Box.NONCE_SIZE, byteorder="little") 94 | -------------------------------------------------------------------------------- /coc/hexdump.py: -------------------------------------------------------------------------------- 1 | # https://gist.github.com/ImmortalPC/c340564823f283fe530b 2 | 3 | def hexdump( src, length=16, sep='.' ): 4 | ''' 5 | @brief Return {src} in hex dump. 6 | @param[in] length {Int} Nb Bytes by row. 7 | @param[in] sep {Char} For the text part, {sep} will be used for non ASCII char. 8 | @return {Str} The hexdump 9 | @note Full support for python2 and python3 ! 10 | ''' 11 | result = []; 12 | 13 | # Python3 support 14 | try: 15 | xrange(0,1); 16 | except NameError: 17 | xrange = range; 18 | 19 | for i in xrange(0, len(src), length): 20 | subSrc = src[i:i+length]; 21 | hexa = ''; 22 | isMiddle = False; 23 | for h in xrange(0,len(subSrc)): 24 | if h == length/2: 25 | hexa += ' '; 26 | h = subSrc[h]; 27 | if not isinstance(h, int): 28 | h = ord(h); 29 | h = hex(h).replace('0x',''); 30 | if len(h) == 1: 31 | h = '0'+h; 32 | hexa += h+' '; 33 | hexa = hexa.strip(' '); 34 | text = ''; 35 | for c in subSrc: 36 | if not isinstance(c, int): 37 | c = ord(c); 38 | if 0x20 <= c < 0x7F: 39 | text += chr(c); 40 | else: 41 | text += sep; 42 | result.append(('%08X: %-'+str(length*(2+1)+1)+'s |%s|') % (i, hexa, text)); 43 | 44 | return '\n'.join(result); -------------------------------------------------------------------------------- /coc/protocol.py: -------------------------------------------------------------------------------- 1 | from twisted.internet import protocol 2 | import traceback 3 | import os 4 | from coc.hexdump import hexdump 5 | from coc.proxyconfig import ProxyConfig 6 | from coc.replay import Replay 7 | 8 | class CoCPacketReceiver(protocol.Protocol): 9 | 10 | _buffer = b"" 11 | _packet = b"" 12 | 13 | def dataReceived(self, data): 14 | self._buffer += data 15 | while self._buffer: 16 | if self._packet: 17 | payload_length = int.from_bytes(self._packet[2:5], byteorder="big") 18 | if len(self._buffer) >= payload_length: 19 | self._packet += self._buffer[:payload_length] 20 | self.packetReceived(self._packet) 21 | self._packet = b"" 22 | self._buffer = self._buffer[payload_length:] 23 | else: 24 | break 25 | elif len(self._buffer) >= 7: 26 | self._packet = self._buffer[:7] 27 | self._buffer = self._buffer[7:] 28 | 29 | def packetReceived(self, packet): 30 | raise NotImplementedError 31 | 32 | 33 | class CoCProtocol(CoCPacketReceiver): 34 | 35 | _peer = None 36 | factory = None 37 | server = None 38 | client = None 39 | 40 | def __init__(self, factory): 41 | self._factory = factory 42 | 43 | @property 44 | def factory(self): 45 | return self._factory 46 | 47 | @property 48 | def peer(self): 49 | return self._peer 50 | 51 | def connectionMade(self): 52 | self._peer = self.transport.getPeer() 53 | 54 | def packetReceived(self, packet): 55 | decrypted = self.decryptPacket(packet) 56 | if decrypted: 57 | Replay.save(*decrypted) 58 | self.packetDecrypted(*decrypted) 59 | 60 | def decodePacket(self, messageid, version, payload): 61 | try: 62 | decoded = self.decoder.decode(messageid, version, payload) 63 | if messageid==24114: 64 | print(hexdump(messageid.to_bytes(2, byteorder="big") + len(payload).to_bytes(3, byteorder="big") + version.to_bytes(2, byteorder="big") + payload)) 65 | 66 | except (KeyError, IndexError, NotImplementedError) as e: 67 | print(messageid, "Error:", e) 68 | print(messageid, "payload length: {} version: {}".format(len(payload), version)) 69 | print(hexdump(messageid.to_bytes(2, byteorder="big") + len(payload).to_bytes(3, byteorder="big") + version.to_bytes(2, byteorder="big") + payload)) 70 | else: 71 | self.decoder.dump(decoded) 72 | 73 | def decryptPacket(self, packet): 74 | raise NotImplementedError 75 | 76 | def packetDecrypted(self, messageid, version, payload): 77 | raise NotImplementedError 78 | 79 | def sendPacket(self, messageid, unknown, payload): 80 | encrypted = self.encryptPacket(messageid, unknown, payload) 81 | if encrypted: 82 | (messageid, unknown, payload) = encrypted 83 | packet = (messageid.to_bytes(2, byteorder="big") + len(payload).to_bytes(3, byteorder="big") + unknown.to_bytes(2, byteorder="big") + payload) 84 | self.transport.write(packet) 85 | 86 | def encryptPacket(self, messageid, unknown, payload): 87 | raise NotImplementedError 88 | -------------------------------------------------------------------------------- /coc/proxyconfig.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from distutils.command.config import config 4 | 5 | 6 | class ProxyConfig: 7 | config = None 8 | 9 | def __init__(self, config_file): 10 | ProxyConfig.start(config_file) 11 | 12 | @staticmethod 13 | def start(config_file): 14 | if os.path.exists(config_file): 15 | with open(config_file, 'r') as fh: 16 | ProxyConfig.config = json.load(fh) 17 | else: 18 | raise FileNotFoundError("Define a config.json file") 19 | 20 | @staticmethod 21 | def get_replay_directory(): 22 | if ProxyConfig.config and 'replayDirectory' in ProxyConfig.config: 23 | return ProxyConfig.config['replayDirectory']; 24 | return None -------------------------------------------------------------------------------- /coc/replay.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | from glob import glob 4 | from coc.proxyconfig import ProxyConfig 5 | 6 | class Replay: 7 | message_index_file = "message.index" 8 | message_index = 1 9 | 10 | def __init__(self): 11 | Replay.start() 12 | 13 | @staticmethod 14 | def start(): 15 | if ProxyConfig.get_replay_directory() and not os.path.exists(ProxyConfig.get_replay_directory()): 16 | os.makedirs(ProxyConfig.get_replay_directory()) 17 | Replay.read_message_index() 18 | 19 | @staticmethod 20 | def read_message_index(): 21 | if not ProxyConfig.get_replay_directory(): 22 | return 23 | Replay.message_index_filepath = "{}/{}".format(ProxyConfig.get_replay_directory(), Replay.message_index_file) 24 | if not os.path.exists(Replay.message_index_filepath): 25 | return 26 | with open(Replay.message_index_filepath, 'r') as fh: 27 | index = fh.read() 28 | Replay.message_index = int(index) 29 | 30 | @staticmethod 31 | def save_message_index(): 32 | if not ProxyConfig.get_replay_directory(): 33 | return 34 | if not os.path.exists(ProxyConfig.get_replay_directory()): 35 | return 36 | target = open("{}/{}".format(ProxyConfig.get_replay_directory(), Replay.message_index_file), 'w') 37 | target.write(str(Replay.message_index)) 38 | target.close() 39 | 40 | @staticmethod 41 | def get_next_message_index(): 42 | current = Replay.message_index 43 | Replay.message_index += 1 44 | return current 45 | 46 | @staticmethod 47 | def save(messageid, version, payload): 48 | if not ProxyConfig.get_replay_directory(): 49 | return 50 | if not os.path.exists(ProxyConfig.get_replay_directory()): 51 | os.makedirs(ProxyConfig.get_replay_directory()) 52 | 53 | target = open("{}/{}-{}.{}".format(ProxyConfig.get_replay_directory(), str(Replay.get_next_message_index()).zfill(4), str(messageid), "bin"), "wb") 54 | target.write(messageid.to_bytes(2, byteorder="big")) 55 | target.write(len(payload).to_bytes(3, byteorder="big")) 56 | target.write(version.to_bytes(2, byteorder="big")) 57 | target.write(payload) 58 | target.close() 59 | Replay.save_message_index() 60 | 61 | @staticmethod 62 | def read(message_index): 63 | if not ProxyConfig.get_replay_directory(): 64 | return 65 | glob_string = "{}/{}-*".format(ProxyConfig.get_replay_directory(), str(Replay.message_index).zfill(4)) 66 | files = glob(glob_string) 67 | if len(files) != 1: 68 | return 69 | 70 | file_path = files[0] 71 | with open(file_path, 'rb') as fh: 72 | message = fh.read() 73 | return message 74 | -------------------------------------------------------------------------------- /coc/server/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/royale-proxy/cr-proxy/b7538badf033a26d12094029c6f6d92f65ebb816/coc/server/__init__.py -------------------------------------------------------------------------------- /coc/server/crypto.py: -------------------------------------------------------------------------------- 1 | from nacl.public import PrivateKey, PublicKey 2 | from nacl.exceptions import CryptoError 3 | from coc.crypto import CoCCrypto, CoCNonce 4 | from coc.hexdump import hexdump 5 | 6 | class CoCServerCrypto(CoCCrypto): 7 | 8 | def __init__(self, factory): 9 | self._factory = factory 10 | self._sk = PrivateKey(bytes.fromhex("1891d401fadb51d25d3a9174d472a9f691a45b974285d47729c45c6538070d85")) 11 | self._pk = self._sk.public_key 12 | self._clientkey = None 13 | 14 | @property 15 | def serverkey(self): 16 | return bytes(self._pk) 17 | 18 | @property 19 | def clientkey(self): 20 | return bytes(self._clientkey) 21 | 22 | @clientkey.setter 23 | def clientkey(self, pk): 24 | self._clientkey = PublicKey(pk) 25 | 26 | def decryptPacket(self, packet): 27 | messageid = int.from_bytes(packet[:2], byteorder="big") 28 | unknown = int.from_bytes(packet[5:7], byteorder="big") 29 | payload = packet[7:] 30 | if messageid == 10100: # ClientHandshake 31 | return messageid, unknown, payload 32 | elif messageid == 10101: # Login 33 | self.clientkey = payload[:32] 34 | self.beforenm(self.clientkey) 35 | nonce = CoCNonce(clientkey=self.clientkey, serverkey=self.serverkey) 36 | ciphertext = payload[32:] 37 | try: 38 | message = self.decrypt(ciphertext, nonce) 39 | except CryptoError: 40 | print("Failed to decrypt the message (server, {}).".format(messageid)) 41 | self.transport.loseConnection() 42 | return False 43 | else: 44 | self.decrypt_nonce = self.client.encrypt_nonce = message[24:48] 45 | print("Client nonce") 46 | print(hexdump(message[24:48])) 47 | return messageid, unknown, message[48:] 48 | else: 49 | ciphertext = payload 50 | message = self.decrypt(ciphertext) 51 | return messageid, unknown, message 52 | 53 | def encryptPacket(self, messageid, unknown, payload): 54 | if messageid == 20100 or (messageid == 20103 and not self.session_key): 55 | return messageid, unknown, payload 56 | elif messageid in {20103, 20104}: 57 | nonce = CoCNonce(nonce=self.decrypt_nonce, clientkey=self.clientkey, serverkey=self.serverkey) 58 | message = self.encrypt_nonce + self.client.k + payload 59 | ciphertext = self.encrypt(message, nonce) 60 | self.k = self.client.k 61 | return messageid, unknown, ciphertext 62 | else: 63 | message = payload 64 | return messageid, unknown, self.encrypt(message) 65 | -------------------------------------------------------------------------------- /coc/server/endpoint.py: -------------------------------------------------------------------------------- 1 | from twisted.internet.endpoints import TCP4ServerEndpoint 2 | 3 | 4 | class CoCServerEndpoint(TCP4ServerEndpoint): 5 | 6 | @property 7 | def interface(self): 8 | if not self._interface: 9 | return "0.0.0.0" 10 | else: 11 | return self._interface 12 | 13 | @property 14 | def port(self): 15 | return self._port 16 | -------------------------------------------------------------------------------- /coc/server/factory.py: -------------------------------------------------------------------------------- 1 | from twisted.internet.protocol import Factory 2 | from coc.server.protocol import CoCServerProtocol 3 | from coc.message.encoder import CoCMessageEncoder 4 | from coc.message.decoder import CoCMessageDecoder 5 | 6 | 7 | class CoCServerFactory(Factory): 8 | def __init__(self, client_endpoint, definitions): 9 | self.client_endpoint = client_endpoint 10 | self.encoder = CoCMessageEncoder(definitions) 11 | self.decoder = CoCMessageDecoder(definitions) 12 | 13 | def buildProtocol(self, endpoint): 14 | return CoCServerProtocol(self) 15 | -------------------------------------------------------------------------------- /coc/server/protocol.py: -------------------------------------------------------------------------------- 1 | from twisted.internet import reactor 2 | from coc.protocol import CoCProtocol 3 | from coc.server.crypto import CoCServerCrypto 4 | from coc.client.factory import CoCClientFactory 5 | 6 | 7 | class CoCServerProtocol(CoCServerCrypto, CoCProtocol): 8 | 9 | client = None 10 | 11 | def __init__(self, factory): 12 | super(CoCServerProtocol, self).__init__(factory) 13 | self.factory.server = self 14 | self.decoder = self.factory.decoder 15 | self.encoder = self.factory.encoder 16 | 17 | def connectionMade(self): 18 | super(CoCServerProtocol, self).connectionMade() 19 | print("connection from {}:{} ...".format(self.peer.host, self.peer.port)) 20 | self.factory.client_endpoint.connect(CoCClientFactory(self)) 21 | 22 | def packetDecrypted(self, messageid, version, payload): 23 | if not self.client: 24 | reactor.callLater(0.25, self.packetDecrypted, messageid, version, payload) 25 | return 26 | self.decodePacket(messageid, version, payload) 27 | self.client.sendPacket(messageid, version, payload) 28 | 29 | def connectionLost(self, reason): 30 | print("connection from {}:{} closed ...".format(self.peer.host, self.peer.port)) 31 | if self.client: 32 | self.client.transport.loseConnection() 33 | -------------------------------------------------------------------------------- /config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "replayDirectory": "replay" 3 | } 4 | -------------------------------------------------------------------------------- /proxy.py: -------------------------------------------------------------------------------- 1 | from twisted.internet import reactor 2 | from coc.message.definitions import CoCMessageDefinitions 3 | from coc.server.endpoint import CoCServerEndpoint 4 | from coc.server.factory import CoCServerFactory 5 | from coc.client.endpoint import CoCClientEndpoint 6 | from coc.proxyconfig import ProxyConfig 7 | from coc.replay import Replay 8 | 9 | config_file = "config.json" 10 | 11 | if __name__ == "__main__": 12 | 13 | ProxyConfig.start(config_file) 14 | Replay.start() 15 | definitions = CoCMessageDefinitions.read() 16 | client_endpoint = CoCClientEndpoint(reactor, "game.clashroyaleapp.com", 9339) 17 | server_endpoint = CoCServerEndpoint(reactor, 9339) 18 | server_endpoint.listen(CoCServerFactory(client_endpoint, definitions)) 19 | 20 | print("listening on {}:{} ...".format(server_endpoint.interface, server_endpoint.port)) 21 | 22 | reactor.run() 23 | -------------------------------------------------------------------------------- /replay.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import glob 3 | import os 4 | from twisted.internet import reactor 5 | from coc.message.definitions import CoCMessageDefinitions 6 | from coc.server.endpoint import CoCServerEndpoint 7 | from coc.server.factory import CoCServerFactory 8 | from coc.client.endpoint import CoCClientEndpoint 9 | from coc.proxyconfig import ProxyConfig 10 | from coc.message.decoder import CoCMessageDecoder 11 | from coc.replay import Replay 12 | 13 | config_file = "config.json" 14 | 15 | def get_filepath_by_message_index(message_index): 16 | if not ProxyConfig.get_replay_directory(): 17 | return None 18 | glob_string = "{}/{}-*.bin".format(ProxyConfig.get_replay_directory(), str(message_index).zfill(4)) 19 | matches = glob.glob(glob_string) 20 | if len(matches) == 1: 21 | return matches[0] 22 | return None 23 | 24 | def start(): 25 | ProxyConfig.start(config_file) 26 | Replay.start() 27 | 28 | if __name__ == "__main__": 29 | start() 30 | 31 | 32 | message_index = sys.argv[1] 33 | filepath = get_filepath_by_message_index(message_index) 34 | 35 | print ("found message {}".format(filepath)) 36 | 37 | definitions = CoCMessageDefinitions.read() 38 | decoder = CoCMessageDecoder(definitions) 39 | decoded = decoder.decodeFile(filepath) 40 | decoder.dump(decoded) -------------------------------------------------------------------------------- /scripts/sc_decompress.sh: -------------------------------------------------------------------------------- 1 | ( 2 | dd if="$1" bs=1 skip=26 count=9 3 | dd if=/dev/zero bs=1 count=4 4 | dd if="$1" bs=1 skip=35 5 | ) | lzma -dc 6 | -------------------------------------------------------------------------------- /scripts/varint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """Varint encoder/decoder 4 | 5 | varints are a common encoding for variable length integer data, used in 6 | libraries such as sqlite, protobuf, v8, and more. 7 | 8 | Here's a quick and dirty module to help avoid reimplementing the same thing 9 | over and over again. 10 | """ 11 | 12 | # byte-oriented StringIO was moved to io.BytesIO in py3k 13 | try: 14 | from io import BytesIO 15 | except ImportError: 16 | from StringIO import StringIO as BytesIO 17 | 18 | import sys 19 | import binascii 20 | 21 | if sys.version > '3': 22 | def _byte(b): 23 | return bytes((b, )) 24 | else: 25 | def _byte(b): 26 | return chr(b) 27 | 28 | def sevenBitRotateLeft(byte): 29 | n = int.from_bytes(byte, byteorder='big') 30 | seventh = (n & 0x40) >> 6 # save 7th bit 31 | msb = (n & 0x80) >> 7 # save msb 32 | n = n << 1 # rotate to the left 33 | n = n & ~(0x181) # clear 8th and 1st bit and 9th if any 34 | n = n | (msb << 7) | (seventh) # insert msb and 6th back in 35 | return bytes([n]) 36 | 37 | def sevenBitRotateRight(byte): 38 | n = int.from_bytes(byte, byteorder='big') 39 | lsb = n & 0x1 # save lsb 40 | msb = (n & 0x80) >> 7 # save msb 41 | n = n >> 1 # rotate to the right 42 | n = n & ~(0xC0) # clear 7th and 6th bit 43 | n = n | (msb << 7) | (lsb << 6) # insert msb and lsb back in 44 | return bytes([n]) 45 | 46 | def encode(number, isRr): 47 | """Pack `number` into varint bytes""" 48 | buf = b'' 49 | while True: 50 | towrite = number & 0x7f 51 | number >>= 7 52 | if number: 53 | tmp = _byte(towrite | 0x80) 54 | if isRr and not buf: 55 | tmp = sevenBitRotateRight(tmp) 56 | buf += tmp 57 | else: 58 | buf += _byte(towrite) 59 | break 60 | return buf 61 | 62 | def decode_stream(stream, isRr): 63 | """Read a varint from `stream`""" 64 | shift = 0 65 | result = 0 66 | while True: 67 | byte = _read_one(stream) 68 | if isRr and shift == 0: 69 | byte = sevenBitRotateLeft(byte) 70 | i = ord(byte) 71 | result |= (i & 0x7f) << shift 72 | shift += 7 73 | if not (i & 0x80): 74 | break 75 | 76 | return result 77 | 78 | def decode_bytes(buf, isRr): 79 | """Read a varint from from `buf` bytes""" 80 | return decode_stream(BytesIO(buf), isRr) 81 | 82 | 83 | def _read_one(stream): 84 | """Read a byte from the file (as an integer) 85 | 86 | raises EOFError if the stream ends while reading bytes. 87 | """ 88 | c = stream.read(1) 89 | if c == '': 90 | raise EOFError("Unexpected EOF while reading bytes") 91 | return c 92 | 93 | def decimalToHex(int32): 94 | sint32 = (int32 << 1) ^ (int32 >> 31) 95 | 96 | int32Bytes = encode(int32, False) 97 | sint32Bytes = encode(sint32, False) 98 | print ("int32={} bytes={}".format(int32, binascii.hexlify(int32Bytes))) 99 | print ("sint32={} bytes={}".format(sint32, binascii.hexlify(sint32Bytes))) 100 | rrsint32Bytes = encode(sint32, True) 101 | print ("rrsint32={} bytes={}".format(sint32, binascii.hexlify(rrsint32Bytes))) 102 | 103 | def hexToDecimal(hexStr): 104 | decBytes = binascii.unhexlify(hexStr) 105 | n = decode_bytes(decBytes, False) 106 | rrn = decode_bytes(decBytes, True) 107 | print ("int32={}".format(n)) 108 | sint32 = (((n) >> 1) ^ (-((n) & 1))) 109 | print ("sint32={}".format(sint32)) 110 | rrsint32 = (((rrn) >> 1) ^ (-((rrn) & 1))) 111 | print ("rrsint32={}".format(rrsint32)) 112 | print ("int32 bin={}".format(bin(n))) 113 | 114 | def printHelp(): 115 | print(""" 116 | varint.py 117 | Outputs hex encoded byte values of the decimal as required by int32, 118 | sint32, and rrsint32 119 | 120 | varint.py 0x 121 | Outputs decimal values decoded by int32, sint32, and rrsint32 122 | """) 123 | 124 | def main(): 125 | if len(sys.argv) < 2: 126 | printHelp() 127 | exit() 128 | 129 | arg = sys.argv[1]; 130 | if (arg.startswith("0x")): 131 | hexToDecimal(arg[2:]) 132 | else: 133 | int32 = int(arg) 134 | decimalToHex(int32) 135 | 136 | if __name__ == "__main__": 137 | main() 138 | --------------------------------------------------------------------------------