├── .jshintrc ├── settings.json.example ├── package.json ├── lib ├── nonce.js ├── packetreceiver.js ├── crypto.js ├── client │ └── crypto.js ├── server │ └── crypto.js └── definitions.js ├── README.md ├── util ├── usage.js └── bytebuffer-sc.js ├── index.js ├── LICENSE └── enums └── emsg.js /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "strict": true, 3 | "node": true, 4 | "esversion": 6 5 | } 6 | -------------------------------------------------------------------------------- /settings.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "serverKey": "ac30dcbea27e213407519bc05be8e9d930e63f873858479946c144895fa3a26b" 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-cr-proxy", 3 | "version": "1.0.0", 4 | "description": "Clash Royale Proxy", 5 | "homepage": "https://github.com/royale-proxy/node-cr-proxy", 6 | "bugs": { 7 | "url": "https://github.com/royale-proxy/issues" 8 | }, 9 | "main": "index.js", 10 | "author": "Steve Rabouin", 11 | "license": "GNU GPLv3", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://bitbucket.org/royaletools/node-cr-proxy.git" 15 | }, 16 | "engines": { 17 | "node": ">=6.8.0" 18 | }, 19 | "dependencies": { 20 | "blake2": "^1.4.0", 21 | "bytebuffer": "^5.0.1", 22 | "command-line-args": "^4.0.2", 23 | "command-line-usage": "^4.0.0", 24 | "console-stamp": "^0.2.5", 25 | "cr-messages": "royale-proxy/cr-messages", 26 | "jsome": "^2.3.26", 27 | "tweetnacl": "^0.14.5" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/nonce.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var blake2 = require("blake2"); 4 | var nacl = require("tweetnacl"); 5 | 6 | class Nonce { 7 | constructor(arg) { 8 | if (!arg.clientKey) { 9 | if (arg.nonce) { 10 | this.buffer = arg.nonce; 11 | } else { 12 | this.buffer = new Buffer(nacl.randomBytes(nacl.box.nonceLength)); 13 | } 14 | } else { 15 | var b2 = blake2.createHash('blake2b', { digestLength: 24 }); 16 | if (arg.nonce) { 17 | b2.update(arg.nonce.getBuffer()); 18 | } 19 | 20 | b2.update(arg.clientKey); 21 | b2.update(arg.serverKey); 22 | 23 | this.buffer = b2.digest(); 24 | } 25 | } 26 | 27 | increment() { 28 | var integer; 29 | integer = this.buffer.readInt16LE(0); 30 | this.buffer.writeInt16LE(integer + 2, 0); 31 | } 32 | 33 | getBuffer() { 34 | return this.buffer; 35 | } 36 | } 37 | 38 | module.exports = Nonce; 39 | -------------------------------------------------------------------------------- /lib/packetreceiver.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class PacketReceiver { 4 | constructor() { 5 | this._buffer = null; 6 | this._packet = null; 7 | } 8 | 9 | packetize(data, callback) { 10 | var messageId, offset, payloadLength, ref, ref1, results; 11 | 12 | if (this._buffer) { 13 | this._buffer = Buffer.concat([this._buffer, data]); 14 | } else { 15 | this._buffer = data; 16 | } 17 | 18 | while (this._buffer && this._buffer.length) { 19 | if (this._packet && this._packet.length) { 20 | payloadLength = this._packet.readUIntBE(2, 3); 21 | 22 | if (this._buffer.length >= payloadLength) { 23 | if (this._packet) { 24 | this._packet = Buffer.concat([this._packet, this._buffer.slice(0, payloadLength)]); 25 | } else { 26 | this._packet = this._buffer.slice(0, payloadLength); 27 | } 28 | 29 | callback(this._packet); 30 | this._packet = null; 31 | 32 | this._buffer = this._buffer.slice(payloadLength); 33 | } else { 34 | break; 35 | } 36 | } else if (this._buffer.length >= 7) { 37 | this._packet = this._buffer.slice(0, 7); 38 | this._buffer = this._buffer.slice(7); 39 | } else { 40 | // we'll be coming back here soon, but looks like we went through current buffer without a full header yet 41 | break; 42 | } 43 | } 44 | } 45 | } 46 | 47 | module.exports = PacketReceiver; 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-cr-proxy 2 | [![clash royale](https://img.shields.io/badge/Clash%20Royale-1.9.2-brightred.svg?style=flat")](https://play.google.com/store/apps/details?id=com.supercell.clashroyale&hl=en) 3 | [![licence](https://img.shields.io/aur/license/yaourt.svg?style=flat)](https://github.com/royale-proxy/node-cr-proxy/blob/master/LICENSE) 4 | 5 | Clash Royale Proxy - Intercepts the traffic between your Clash Royale App and their servers, decrypts the protocol and decodes the messages. 6 | 7 | Don't like NodeJs, prefer python? Get the [python proxy](https://github.com/royale-proxy/cr-proxy). 8 | 9 | ## How to use it? 10 | 11 | ### Setting up the proxy server 12 | 13 | #### Prerequisites 14 | * Install [nodejs](https://nodejs.org/en) (>=6.8.0) 15 | * Install [node-gyp](https://github.com/nodejs/node-gyp) 16 | 17 | #### Clone the code 18 | 19 | `git clone https://github.com/royale-proxy/node-cr-proxy && cd node-cr-proxy` 20 | 21 | `npm install` 22 | 23 | `cp settings.json.example settings.json` / `copy settings.json.example settings.json` 24 | 25 | ### Setting up your device 26 | 27 | #### Android 28 | * Please see [cr-patcher](https://github.com/royale-proxy/cr-patcher) for instructions on how to get your apk ready to use the proxy. 29 | 30 | #### iPhone 31 | * We do not have a patcher at this time. 32 | 33 | #### Running the proxy 34 | 35 | `node index` 36 | 37 | `node index --verbose` will display the contents of the messages on the screen as well as show debug info when messages are missing/incomplete 38 | 39 | `node index --dump ./packets` will save decrypted packets into the packets folder with a format of messageId.bin (ex: 10101.bin) -- Make sure the folder exists. 40 | 41 | `node index --replay ./packets/10101.bin` will decode the 10101 packet using definitions, useful when trying to decode a new message 42 | 43 | `node index --help` will show you the command line help 44 | 45 | ## What's the status? 46 | 47 | This project has been abandoned 48 | 49 | -------------------------------------------------------------------------------- /lib/crypto.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var nacl = require("tweetnacl"); 4 | var blake2 = require("blake2"); 5 | var ByteBuffer = require("../util/bytebuffer-sc"); 6 | const Nonce = require("./nonce"); 7 | 8 | class Crypto { 9 | constructor(settings) { 10 | this.privateKey = null; 11 | this.serverKey = null; 12 | this.clientKey = null; 13 | this.sharedKey = null; 14 | this.decryptNonce = null; 15 | this.encryptNonce = null; 16 | this.sessionKey = null; 17 | } 18 | 19 | getSharedKey() { 20 | return this.sharedKey; 21 | } 22 | 23 | setSharedKey(sharedKey) { 24 | this.sharedKey = sharedKey; 25 | } 26 | 27 | getEncryptNonce() { 28 | return this.encryptNonce; 29 | } 30 | 31 | setEncryptNonce(nonce) { 32 | this.encryptNonce = new Nonce({ nonce: nonce }); 33 | } 34 | 35 | getDecryptNonce() { 36 | return this.decryptNonce; 37 | } 38 | 39 | setDecryptNonce(nonce) { 40 | this.decryptNonce = new Nonce({ nonce: nonce }); 41 | } 42 | 43 | setSessionKey(sessionKey) { 44 | this.sessionKey = sessionKey; 45 | } 46 | 47 | getSessionKey() { 48 | return this.sessionKey; 49 | } 50 | 51 | beforeNm(publicKey) { 52 | this.sharedKey = new Buffer(nacl.box.before(publicKey, this.privateKey)); 53 | } 54 | 55 | encrypt(message, nonce) { 56 | if (!nonce) { 57 | this.encryptNonce.increment(); 58 | nonce = this.encryptNonce; 59 | } 60 | 61 | return nacl.box.after(message, nonce.getBuffer(), this.sharedKey); 62 | } 63 | 64 | decrypt(cipherText, nonce) { 65 | var decrypted; 66 | if (!nonce) { 67 | this.decryptNonce.increment(); 68 | nonce = this.decryptNonce; 69 | } 70 | 71 | decrypted = nacl.box.open.after(cipherText, nonce.getBuffer(), this.sharedKey); 72 | 73 | if (decrypted) { 74 | return decrypted; 75 | } else { 76 | console.log('unable to decrypt message. exiting.'); 77 | process.exit(1); 78 | } 79 | } 80 | } 81 | 82 | module.exports = Crypto; 83 | -------------------------------------------------------------------------------- /lib/client/crypto.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var nacl = require("tweetnacl"); 4 | var blake2 = require("blake2"); 5 | var ByteBuffer = require("../../util/bytebuffer-sc"); 6 | var EMsg = require('../../enums/emsg'); 7 | const Crypto = require('../crypto.js'); 8 | const Nonce = require('../nonce.js'); 9 | 10 | class ClientCrypto extends Crypto { 11 | 12 | constructor(settings) { 13 | super(); 14 | var kp = nacl.box.keyPair(); 15 | 16 | this.privateKey = Buffer.from(kp.secretKey); 17 | this.clientKey = Buffer.from(kp.publicKey); 18 | this.serverKey = new Buffer(settings.serverKey, "hex"); 19 | this.beforeNm(this.serverKey); 20 | this.setEncryptNonce(); 21 | } 22 | 23 | setServer(server) { 24 | this.server = server; 25 | } 26 | 27 | decryptPacket(message) { 28 | if (message.messageType == EMsg.ServerHello || message.messageType == EMsg.LoginFailed) { 29 | var len = message.payload.readInt32BE(); 30 | this.setSessionKey(message.payload.slice(4, 4 + len)); 31 | message.decrypted = message.payload; 32 | } else if (message.messageType == EMsg.LoginOk) { 33 | var decrypted; 34 | var nonce = new Nonce({ clientKey: this.clientKey, serverKey: this.serverKey, nonce: this.encryptNonce }); 35 | 36 | message.decrypted = this.decrypt(message.payload, nonce); 37 | 38 | if (message.decrypted) { 39 | this.setDecryptNonce(Buffer.from(message.decrypted.slice(0, 24))); 40 | this.server.setEncryptNonce(Buffer.from(message.decrypted.slice(0, 24))); 41 | this.setSharedKey(Buffer.from(message.decrypted.slice(24, 56))); 42 | 43 | message.decrypted = message.decrypted.slice(56); 44 | } 45 | } else { 46 | message.decrypted = this.decrypt(message.payload); 47 | } 48 | } 49 | 50 | encryptPacket(message) { 51 | if (message.messageType == EMsg.ClientHello) { 52 | message.encrypted = message.decrypted; 53 | } else if (message.messageType == EMsg.Login) { 54 | var nonce = new Nonce({ clientKey: this.clientKey, serverKey: this.serverKey }); 55 | var toEncrypt = Buffer.concat([this.getSessionKey(), this.encryptNonce.getBuffer(), Buffer.from(message.decrypted)]); 56 | 57 | message.encrypted = Buffer.concat([this.clientKey, Buffer.from(this.encrypt(toEncrypt, nonce))]); 58 | } else { 59 | message.encrypted = this.encrypt(message.decrypted); 60 | } 61 | } 62 | } 63 | 64 | module.exports = ClientCrypto; 65 | -------------------------------------------------------------------------------- /lib/server/crypto.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var nacl = require("tweetnacl"); 4 | var blake2 = require("blake2"); 5 | var ByteBuffer = require("../../util/bytebuffer-sc"); 6 | var EMsg = require('../../enums/emsg'); 7 | const Crypto = require('../crypto.js'); 8 | const Nonce = require('../nonce.js'); 9 | 10 | class ServerCrypto extends Crypto { 11 | constructor(settings) { 12 | super(); 13 | this.privateKey = new Buffer("1891d401fadb51d25d3a9174d472a9f691a45b974285d47729c45c6538070d85", "hex"); 14 | this.serverKey = new Buffer("72f1a4a4c48e44da0c42310f800e96624e6dc6a641a9d41c3b5039d8dfadc27e", "hex"); 15 | } 16 | 17 | setClient(client) { 18 | this.client = client; 19 | } 20 | 21 | decryptPacket(message) { 22 | if (message.messageType == EMsg.ClientHello) { 23 | message.decrypted = message.payload; 24 | } else if (message.messageType == EMsg.Login) { 25 | 26 | this.clientKey = message.payload.slice(0, 32); 27 | var cipherText = message.payload.slice(32); 28 | 29 | this.beforeNm(this.clientKey, this); 30 | 31 | var nonce = new Nonce({ clientKey: this.clientKey, serverKey: this.serverKey }); 32 | 33 | message.decrypted = this.decrypt(cipherText, nonce); 34 | 35 | if (message.decrypted) { 36 | this.setSessionKey(Buffer.from(message.decrypted.slice(0, 24))); 37 | this.setDecryptNonce(Buffer.from(message.decrypted.slice(24, 48))); 38 | this.client.setEncryptNonce(Buffer.from(message.decrypted.slice(24, 48))); 39 | 40 | message.decrypted = message.decrypted.slice(48); 41 | } 42 | } else { 43 | message.decrypted = this.decrypt(message.payload); 44 | } 45 | } 46 | 47 | encryptPacket(message) { 48 | if (message.messageType == EMsg.ServerHello || message.messageType == EMsg.LoginFailed) { 49 | message.encrypted = message.decrypted; 50 | } else if (message.messageType == EMsg.LoginOk) { 51 | var nonce = new Nonce({ clientKey: this.clientKey, serverKey: this.serverKey, nonce: this.decryptNonce }); 52 | var toEncrypt = Buffer.concat([this.encryptNonce.getBuffer(), this.client.getSharedKey(), Buffer.from(message.decrypted)]); 53 | 54 | var cipherText = this.encrypt(toEncrypt, nonce); 55 | 56 | this.setSharedKey(this.client.getSharedKey()); // is this what I was missing, omg 57 | 58 | message.encrypted = cipherText; 59 | } else { 60 | message.encrypted = this.encrypt(message.decrypted); 61 | } 62 | } 63 | } 64 | 65 | module.exports = ServerCrypto; 66 | -------------------------------------------------------------------------------- /util/usage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var commandLineArgs = require('command-line-args'); 4 | var getUsage = require('command-line-usage'); 5 | var fs = require('fs'); 6 | 7 | function FileDetails(filename){ 8 | if (!(this instanceof FileDetails)) return new FileDetails(filename); 9 | this.filename = filename; 10 | this.exists = fs.existsSync(filename); 11 | } 12 | 13 | var optionDefinitions = [ 14 | { 15 | name: 'verbose', 16 | alias: 'v', 17 | description: 'Show debug log.', 18 | type: Boolean 19 | }, 20 | { 21 | name: 'dump', 22 | alias: 'd', 23 | typeLabel: '[underline]{folder}', 24 | description: 'Dump decrypted packets in specified folder', 25 | type: FileDetails 26 | }, 27 | { 28 | name: 'replay', 29 | alias: 'r', 30 | typeLabel: '[underline]{file}', 31 | description: 'Replay a dumped packet.', 32 | type: FileDetails, 33 | }, 34 | { 35 | name: 'help', 36 | alias: 'h', 37 | description: 'Print this usage guide.', 38 | type: Boolean 39 | } 40 | ]; 41 | 42 | var sections = [ 43 | { 44 | header: 'Royale Proxy', 45 | content: 'A simple Clash Royale proxy.' 46 | }, 47 | { 48 | header: 'Synopsis', 49 | content: [ 50 | '$ node index [[bold]{--verbose}] [[bold]{--dump} [underline]{./packets/}]', 51 | '$ node index --help' 52 | ] 53 | }, 54 | { 55 | header: 'Options', 56 | optionList: optionDefinitions 57 | }, 58 | { 59 | content: [ 60 | 'Visit us at [underline]{http://github.com/royale-proxy}', 61 | '', 62 | ' _______ __ _______ ', 63 | '| _ .-----.--.--.---.-| .-----| _ .----.-----.--.--.--.--.', 64 | '|. l | _ | | | _ | | -__|. 1 | _| _ |_ _| | |', 65 | '|. _ |_____|___ |___._|__|_____|. ____|__| |_____|__.__|___ |', 66 | '|: | | |_____| |: | |_____|', 67 | '|::.|:. | |::.| ', 68 | '`--- ---\' `---\' ' 69 | ], 70 | raw: true 71 | } 72 | ]; 73 | 74 | var options = commandLineArgs(optionDefinitions); 75 | var usage = getUsage(sections); 76 | 77 | if(options.help) { 78 | console.log(usage); 79 | process.exit(0); 80 | } 81 | 82 | if(options.dump === null || (options.dump && !options.dump.exists)) { 83 | console.error('Error: Specified path does not exist. Please check the path and try again.'); 84 | console.log(usage); 85 | process.exit(1); 86 | } 87 | 88 | if(options.replay === null || (options.replay && !options.replay.exists)) { 89 | console.error('Error: Specified filename does not exist. Please check the filename and try again.'); 90 | console.log(usage); 91 | process.exit(1); 92 | } 93 | 94 | module.exports.options = options; 95 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var net = require('net'); 4 | var fs = require('fs'); 5 | var util = require('util'); 6 | var path = require('path'); 7 | var jsome = require('jsome'); 8 | var options = require('./util/usage').options; 9 | var settings = require('./settings.json'); 10 | 11 | require('console-stamp')(console, 'yyyy-mm-dd HH:MM:ss'); 12 | 13 | var PacketReceiver = require('./lib/packetreceiver'); 14 | var ClientCrypto = require('./lib/client/crypto'); 15 | var ServerCrypto = require('./lib/server/crypto'); 16 | var Definitions = require('./lib/definitions'); 17 | var EMsg = require('./enums/emsg'); 18 | 19 | var definitions = new Definitions(options); 20 | var clients = {}; 21 | 22 | if(options.replay) { 23 | fs.readFile(options.replay.filename, {encoding: "binary"}, function(err, contents) { 24 | if(err) { 25 | return console.error(err); 26 | } 27 | var message = { 28 | messageType: parseInt(path.basename(options.replay.filename, ".bin")), 29 | decrypted: contents 30 | }; 31 | 32 | definitions.decode(message); 33 | if(message.decoded) { 34 | jsome(message.decoded); 35 | } 36 | }); 37 | } else { 38 | var server = net.createServer(); 39 | 40 | server.on('error', function(err) { 41 | if (err.code == 'EADDRINUSE') { 42 | console.log('Address in use, exiting...'); 43 | } else { 44 | console.log('Unknown error setting up proxy: ' + err); 45 | } 46 | 47 | process.exit(1); 48 | }); 49 | 50 | server.on('listening', function() { 51 | console.log('listening on ' + server.address().address + ':' + server.address().port); 52 | }); 53 | 54 | server.on('connection', function(socket) { 55 | var gameserver = new net.Socket(); 56 | socket.key = socket.remoteAddress + ":" + socket.remotePort; 57 | clients[socket.key] = socket; 58 | 59 | var clientPacketReceiver = new PacketReceiver(); 60 | var serverPacketReceiver = new PacketReceiver(); 61 | 62 | var clientCrypto = new ClientCrypto(settings); 63 | var serverCrypto = new ServerCrypto(settings); 64 | 65 | clientCrypto.setServer(serverCrypto); 66 | serverCrypto.setClient(clientCrypto); 67 | 68 | console.log('new client ' + socket.key + ' connected, establishing connection to game server'); 69 | 70 | gameserver.connect(9339, "game.clashroyaleapp.com", function() { 71 | console.log('Connected to game server on ' + gameserver.remoteAddress + ':' + gameserver.remotePort); 72 | }); 73 | 74 | gameserver.on("data", function(chunk) { 75 | serverPacketReceiver.packetize(chunk, function(packet) { 76 | var message = { 77 | 'messageType': packet.readUInt16BE(0), 78 | 'length': packet.readUIntBE(2, 3), 79 | 'version': packet.readUInt16BE(5), 80 | 'payload': packet.slice(7, packet.length) 81 | }; 82 | 83 | console.log('[SERVER] ' + (EMsg[message.messageType] ? EMsg[message.messageType] + ' [' + message.messageType + ']' : message.messageType)); 84 | 85 | clientCrypto.decryptPacket(message); 86 | 87 | if(options.dump) { 88 | fs.writeFile(options.dump.filename + "/" + message.messageType + ".bin", Buffer.from(message.decrypted), {encoding: "binary"}, function(err) { 89 | if(err) { 90 | console.error(err); 91 | } 92 | }); 93 | } 94 | 95 | definitions.decode(message); 96 | 97 | if(options.verbose && message.decoded && Object.keys(message.decoded).length) { 98 | jsome(message.decoded); 99 | } 100 | 101 | serverCrypto.encryptPacket(message); 102 | 103 | var header = Buffer.alloc(7); 104 | 105 | header.writeUInt16BE(message.messageType, 0); 106 | header.writeUIntBE(message.encrypted.length, 2, 3); 107 | header.writeUInt16BE(message.version, 5); 108 | 109 | clients[socket.key].write(Buffer.concat([header, Buffer.from(message.encrypted)])); 110 | }); 111 | }); 112 | 113 | gameserver.on("end", function() { 114 | console.log('Disconnected from game server'); 115 | }); 116 | 117 | clients[socket.key].on('data', function(chunk) { 118 | clientPacketReceiver.packetize(chunk, function(packet) { 119 | var message = { 120 | 'messageType': packet.readUInt16BE(0), 121 | 'length': packet.readUIntBE(2, 3), 122 | 'version': packet.readUInt16BE(5), 123 | 'payload': packet.slice(7, packet.length) 124 | }; 125 | 126 | console.log('[CLIENT] ' + (EMsg[message.messageType] ? EMsg[message.messageType] + ' [' + message.messageType + ']' : message.messageType)); 127 | 128 | serverCrypto.decryptPacket(message); 129 | 130 | if(options.dump) { 131 | fs.writeFile(options.dump.filename + "/" + message.messageType + ".bin", Buffer.from(message.decrypted), {encoding: "binary"}, function(err) { 132 | if(err) { 133 | return console.log(err); 134 | } 135 | }); 136 | } 137 | 138 | definitions.decode(message); 139 | 140 | if(options.verbose && message.decoded && Object.keys(message.decoded).length) { 141 | jsome(message.decoded); 142 | } 143 | 144 | clientCrypto.encryptPacket(message); 145 | 146 | var header = Buffer.alloc(7); 147 | 148 | header.writeUInt16BE(message.messageType, 0); 149 | header.writeUIntBE(message.encrypted.length, 2, 3); 150 | header.writeUInt16BE(message.version, 5); 151 | 152 | gameserver.write(Buffer.concat([header, Buffer.from(message.encrypted)])); 153 | }); 154 | }); 155 | 156 | clients[socket.key].on('end', function() { 157 | console.log('Client ' + socket.key + ' disconnected from proxy.'); 158 | delete clients[socket.key]; 159 | gameserver.end(); 160 | }); 161 | }); 162 | 163 | server.listen({ host: '0.0.0.0', port: 9339, exclusive: true }, function(err) { 164 | if (err) { 165 | console.log(err); 166 | } 167 | }); 168 | } 169 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /util/bytebuffer-sc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ByteBuffer = module.exports = require('bytebuffer'); 4 | 5 | /** 6 | * Reads a 32bit base 128 variable-length integer using supercell magic. 7 | * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes 8 | * written if omitted. 9 | * @returns {number|!{value: number, length: number}} The value read if offset is omitted, else the value read 10 | * and the actual number of bytes read. 11 | * @throws {Error} If it's not a valid varint. Has a property `truncated = true` if there is not enough data available 12 | * to fully decode the varint. 13 | * @expose 14 | */ 15 | ByteBuffer.prototype.readRrsInt32 = function(offset) { 16 | var relative = typeof offset === 'undefined'; 17 | if (relative) offset = this.offset; 18 | if (!this.noAssert) { 19 | if (typeof offset !== 'number' || offset % 1 !== 0) 20 | throw TypeError("Illegal offset: " + offset + " (not an integer)"); 21 | offset >>>= 0; 22 | if (offset < 0 || offset + 1 > this.buffer.length) 23 | throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 1 + ") <= " + this.buffer.length); 24 | } 25 | var c = 0, 26 | value = 0 >>> 0, 27 | seventh, 28 | msb, 29 | b; 30 | do { 31 | if (!this.noAssert && offset > this.limit) { 32 | var err = Error("Truncated"); 33 | err.truncated = true; 34 | throw err; 35 | } 36 | b = this.buffer[offset++]; 37 | 38 | if (c === 0) { 39 | seventh = (b & 0x40) >> 6; // save 7th bit 40 | msb = (b & 0x80) >> 7; // save msb 41 | b = b << 1; // rotate to the left 42 | b = b & ~(0x181); // clear 8th and 1st bit and 9th if any 43 | b = b | (msb << 7) | (seventh); // insert msb and 6th back in 44 | } 45 | 46 | value |= (b & 0x7f) << (7 * c); 47 | ++c; 48 | } while ((b & 0x80) !== 0); 49 | 50 | value = ((value >>> 1) ^ -(value & 1)) | 0; 51 | 52 | if (relative) { 53 | this.offset = offset; 54 | return value; 55 | } 56 | 57 | return { 58 | "value": value, 59 | "length": c 60 | }; 61 | }; 62 | 63 | /** 64 | * Writes a 32bit base 128 variable-length integer using supercell magic. 65 | * @param {number} value Value to write 66 | * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes 67 | * written if omitted. 68 | * @returns {!ByteBuffer|number} this if `offset` is omitted, else the actual number of bytes written 69 | * @expose 70 | */ 71 | ByteBuffer.prototype.writeRrsInt32 = function(value, offset) { 72 | var relative = typeof offset === 'undefined'; 73 | if (relative) offset = this.offset; 74 | if (!this.noAssert) { 75 | if (typeof value !== 'number' || value % 1 !== 0) 76 | throw TypeError("Illegal value: " + value + " (not an integer)"); 77 | value |= 0; 78 | if (typeof offset !== 'number' || offset % 1 !== 0) 79 | throw TypeError("Illegal offset: " + offset + " (not an integer)"); 80 | offset >>>= 0; 81 | if (offset < 0 || offset + 0 > this.buffer.length) 82 | throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.length); 83 | } 84 | var size = ByteBuffer.calculateVarint32(value), 85 | rotate = true, 86 | b; 87 | offset += size; 88 | var capacity10 = this.buffer.length; 89 | if (offset > capacity10) 90 | this.resize((capacity10 *= 2) > offset ? capacity10 : offset); 91 | offset -= size; 92 | 93 | value = (value << 1) ^ (value >> 31); 94 | 95 | value >>>= 0; 96 | while (value) { 97 | b = (value & 0x7f); 98 | if (value >= 0x80) 99 | b |= 0x80; 100 | if (rotate) { 101 | rotate = false; 102 | var lsb = b & 0x1; 103 | var msb = (b & 0x80) >> 7; 104 | b = b >> 1; // rotate to the right 105 | b = b & ~(0xC0); // clear 7th and 6th bit 106 | b = b | (msb << 7) | (lsb << 6); // insert msb and lsb back in 107 | } 108 | this.buffer[offset++] = b; 109 | value >>>= 7; 110 | } 111 | if (relative) { 112 | this.offset = offset; 113 | return this; 114 | } 115 | return size; 116 | }; 117 | 118 | /** 119 | * Reads a length as uint32 prefixed UTF8 encoded string. Supercell also has FF FF FF FF for len when string is empty so 120 | * we override it 121 | * @param {number=} offset Offset to read from. Will use and increase {@link ByteBuffer#offset} by the number of bytes 122 | * read if omitted. 123 | * @returns {string|!{string: string, length: number}} The string read if offset is omitted, else the string 124 | * read and the actual number of bytes read. 125 | * @expose 126 | * @see ByteBuffer#readVarint32 127 | */ 128 | ByteBuffer.prototype.readIString = function(offset) { 129 | var relative = typeof offset === 'undefined'; 130 | if (relative) offset = this.offset; 131 | if (!this.noAssert) { 132 | if (typeof offset !== 'number' || offset % 1 !== 0) 133 | throw TypeError("Illegal offset: " + offset + " (not an integer)"); 134 | offset >>>= 0; 135 | if (offset < 0 || offset + 4 > this.buffer.length) 136 | throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 4 + ") <= " + this.buffer.length); 137 | } 138 | var start = offset; 139 | var len = this.readUint32(offset); 140 | 141 | if (len == Math.pow(2, 32) - 1) { 142 | this.offset += 4; 143 | return ''; 144 | } else { 145 | var str = this.readUTF8String(len, ByteBuffer.METRICS_BYTES, offset += 4); 146 | offset += str.length; 147 | if (relative) { 148 | this.offset = offset; 149 | return str.string; 150 | } else { 151 | return { 152 | 'string': str.string, 153 | 'length': offset - start 154 | }; 155 | } 156 | } 157 | }; 158 | 159 | /** 160 | * Writes a length as uint32 prefixed UTF8 encoded string -- supercell wants FFFFFFFF instead of 00000000 when empty. 161 | * @param {string} str String to write 162 | * @param {number=} offset Offset to write to. Will use and increase {@link ByteBuffer#offset} by the number of bytes 163 | * written if omitted. 164 | * @returns {!ByteBuffer|number} `this` if `offset` is omitted, else the actual number of bytes written 165 | * @expose 166 | * @see ByteBuffer#writeVarint32 167 | */ 168 | ByteBuffer.prototype.writeIString = function(str, offset) { 169 | var relative = typeof offset === 'undefined'; 170 | if (relative) offset = this.offset; 171 | if (!this.noAssert) { 172 | if (typeof str !== 'string') 173 | throw TypeError("Illegal str: Not a string"); 174 | if (typeof offset !== 'number' || offset % 1 !== 0) 175 | throw TypeError("Illegal offset: " + offset + " (not an integer)"); 176 | offset >>>= 0; 177 | if (offset < 0 || offset + 0 > this.buffer.length) 178 | throw RangeError("Illegal offset: 0 <= " + offset + " (+" + 0 + ") <= " + this.buffer.length); 179 | } 180 | var start = offset, 181 | k; 182 | k = Buffer.byteLength(str, "utf8"); 183 | offset += 4 + k; 184 | var capacity13 = this.buffer.length; 185 | if (offset > capacity13) 186 | this.resize((capacity13 *= 2) > offset ? capacity13 : offset); 187 | offset -= 4 + k; 188 | if(k === 0) { // supercell wants FF ^_^ 189 | this.buffer[offset + 3] = 0xFF; 190 | this.buffer[offset + 2] = 0xFF; 191 | this.buffer[offset + 1] = 0xFF; 192 | this.buffer[offset] = 0xFF; 193 | } else if (this.littleEndian) { 194 | this.buffer[offset + 3] = (k >>> 24) & 0xFF; 195 | this.buffer[offset + 2] = (k >>> 16) & 0xFF; 196 | this.buffer[offset + 1] = (k >>> 8) & 0xFF; 197 | this.buffer[offset] = k & 0xFF; 198 | } else { 199 | this.buffer[offset] = (k >>> 24) & 0xFF; 200 | this.buffer[offset + 1] = (k >>> 16) & 0xFF; 201 | this.buffer[offset + 2] = (k >>> 8) & 0xFF; 202 | this.buffer[offset + 3] = k & 0xFF; 203 | } 204 | offset += 4; 205 | offset += this.buffer.write(str, offset, k, "utf8"); 206 | if (relative) { 207 | this.offset = offset; 208 | return this; 209 | } 210 | return offset - start; 211 | }; 212 | -------------------------------------------------------------------------------- /lib/definitions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const zlib = require('zlib'); 5 | 6 | var Long = require("long"); 7 | var ByteBuffer = require("../util/bytebuffer-sc"); 8 | var EMsg = require('../enums/emsg'); 9 | 10 | class Definitions { 11 | 12 | constructor(options) { 13 | var self = this; 14 | 15 | self.definitions = []; 16 | self.components = []; 17 | self.options = options; 18 | 19 | ['client', 'server', 'component'].forEach(function(folder) { 20 | fs.readdir('./node_modules/cr-messages/' + folder, (err, files) => { 21 | console.time('Loaded ' + folder + ' definitions in'); 22 | if (err) { 23 | console.log('error opening node-modules/cr-messages/' + folder + ': ' + err); 24 | process.exit(1); 25 | } 26 | 27 | files.forEach(file => { 28 | if(self.options.verbose) { 29 | console.log('loading ' + folder +'/' + file +'...'); 30 | } 31 | 32 | var json = JSON.parse(fs.readFileSync('./node_modules/cr-messages/' + folder + '/' + file, 'utf8')); 33 | 34 | if (json.id) { 35 | self.definitions[json.id] = json; 36 | } else { 37 | self.components[json.name] = json; 38 | 39 | if (json.extensions) { 40 | var extensions = []; 41 | 42 | for (var key in json.extensions) { 43 | extensions[json.extensions[key].id] = json.extensions[key]; 44 | } 45 | 46 | self.components[json.name].extensions = extensions; 47 | } 48 | } 49 | }); 50 | 51 | console.timeEnd('Loaded ' + folder + ' definitions in'); 52 | }); 53 | }); 54 | } 55 | 56 | decode_fields(reader, fields) { 57 | var unknown = 0; 58 | var decoded = {}; 59 | 60 | fields.forEach((field, index) => { 61 | var fieldType = field.type.substring(0); // creates a clone without reference 62 | 63 | if (!field.name) { 64 | field.name = "unknown_" + index; 65 | } 66 | 67 | if (fieldType.includes('?')) { 68 | if (Boolean(reader.readByte())) { 69 | fieldType = fieldType.substring(1); 70 | } else { 71 | reader.offset--; // we only peeked, multiple bools can be mixed together 72 | decoded[field.name] = false; 73 | return; 74 | } 75 | } 76 | 77 | if (fieldType.includes('[')) { 78 | var n = fieldType.substring(fieldType.indexOf('[') + 1, fieldType.indexOf(']')); 79 | fieldType = fieldType.substring(0, fieldType.indexOf('[')); 80 | 81 | // if n is specified, then we use it, otherwise we need to read how big the array is 82 | // may need to implement lenghtType, but seems unecessary, they are all RRSINT32 afaik 83 | if (n === '') { 84 | if(field.lengthType && field.lengthType == 'INT') { 85 | n = reader.readInt32(); 86 | } else { 87 | n = reader.readRrsInt32(); 88 | } 89 | } else { 90 | n = parseInt(n); 91 | } 92 | 93 | decoded[field.name] = []; 94 | 95 | for (var i = 0; i < n; i++) { 96 | decoded[field.name][i] = this.decode_field(reader, fieldType, field); 97 | } 98 | } else { 99 | decoded[field.name] = this.decode_field(reader, fieldType, field); 100 | } 101 | }); 102 | 103 | return decoded; 104 | } 105 | 106 | decode_field(reader, fieldType, field) { 107 | var decoded; 108 | 109 | if (fieldType == 'BYTE') { 110 | decoded = reader.readByte(); 111 | } else if (fieldType == 'SHORT') { 112 | decoded = reader.readInt16(); 113 | } else if (fieldType == 'BOOLEAN'){ 114 | decoded = Boolean(reader.readByte()); 115 | } else if (fieldType == 'INT') { 116 | decoded = reader.readInt32(); 117 | } else if (fieldType == 'INT32') { 118 | decoded = reader.readVarint32(); 119 | } else if (fieldType == 'RRSINT32') { 120 | decoded = reader.readRrsInt32(); 121 | } else if (fieldType == 'RRSLONG') { 122 | decoded = Long.fromValue({high: reader.readRrsInt32(), low: reader.readRrsInt32(), unsigned: false}); 123 | } else if (fieldType == 'LONG') { 124 | decoded = reader.readInt64(); 125 | } else if (fieldType == 'STRING') { 126 | decoded = reader.readIString(); 127 | } else if (fieldType == 'BITSET') { 128 | var bits = reader.readByte(); 129 | 130 | decoded = [ 131 | !!(bits & 0x01), 132 | !!(bits & 0x02), 133 | !!(bits & 0x04), 134 | !!(bits & 0x08), 135 | !!(bits & 0x10), 136 | !!(bits & 0x20), 137 | !!(bits & 0x40), 138 | !!(bits & 0x80) 139 | ]; 140 | 141 | if(field.bit) { 142 | decoded = decoded[field.bit]; 143 | } 144 | 145 | if(field.peek === true) { 146 | reader.offset--; 147 | } 148 | } else if (fieldType == 'SCID') { 149 | var hi = reader.readRrsInt32(); 150 | var lo; 151 | if(hi) { 152 | lo = reader.readRrsInt32(); 153 | decoded = hi * 1000000 + lo; 154 | } else { 155 | decoded = 0; 156 | } 157 | } else if (fieldType == 'ZIP_STRING') { 158 | var len = reader.readInt32() - 4; // it's prefixed with a INT32 of the unzipped length 159 | 160 | reader.LE(); // switch to little endian 161 | var zlength = reader.readInt32(); 162 | reader.BE(); // switch back to big endian 163 | 164 | if(reader.remaining() >= len) { 165 | decoded = zlib.unzipSync(reader.slice(reader.offset, reader.offset + len).toBuffer()).toString(); 166 | reader.offset = reader.offset + len; 167 | } else { 168 | decoded = false; 169 | console.log('Insufficient data to unzip field.'); 170 | } 171 | } else if (fieldType == 'IGNORE') { 172 | decoded = reader.remaining() + ' bytes have been ignored.'; 173 | reader.offset = reader.limit; 174 | } else if (this.components[fieldType]) { 175 | decoded = this.decode_fields(reader, this.components[fieldType].fields); 176 | if (this.components[fieldType].extensions !== undefined) { 177 | 178 | if (decoded.id !== undefined) { 179 | var extensionDef = this.components[fieldType].extensions.find(function(extension) { 180 | if (extension) { 181 | return extension.id == decoded.id; 182 | } else { 183 | return 0; 184 | } 185 | }); 186 | 187 | if (extensionDef) { 188 | decoded.payload = this.decode_fields(reader, extensionDef.fields); 189 | } else { 190 | console.warn('Error: Extensions of field type ' + fieldType + ' with id ' + decoded.id + ' is missing. (' + field.name + ').'); 191 | return false; 192 | } 193 | } else { 194 | console.warn('Warning: missing id for component ' + fieldType + ' (' + field.name + ').'); 195 | return false; 196 | } 197 | } 198 | } else { 199 | console.error('Error: field type ' + fieldType + ' does not exist. (' + field.name + '). Exiting.'); 200 | process.exit(1); 201 | } 202 | 203 | return decoded; 204 | } 205 | 206 | decode(message) { 207 | var reader = ByteBuffer.fromBinary(message.decrypted); 208 | 209 | if (this.definitions[message.messageType]) { 210 | message.decoded = {}; 211 | 212 | if (this.definitions[message.messageType].fields && this.definitions[message.messageType].fields.length) { 213 | message.decoded = this.decode_fields(reader, this.definitions[message.messageType].fields); 214 | } 215 | 216 | if (reader.remaining() && this.options.verbose) { 217 | console.warn(reader.remaining() + ' bytes remaining...'); 218 | reader.printDebug(); 219 | } 220 | } else { 221 | console.warn('Missing definition for ' + (EMsg[message.messageType] ? EMsg[message.messageType] : message.messageType)); 222 | if(this.options.verbose) { 223 | reader.printDebug(); 224 | } 225 | } 226 | } 227 | } 228 | 229 | module.exports = Definitions; 230 | -------------------------------------------------------------------------------- /enums/emsg.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @enum EMsg 3 | */ 4 | 5 | module.exports = { 6 | // Client Messages 7 | 'ClientHello': 10100, 8 | 'Login': 10101, 9 | 'ClientCapabilities': 10107, 10 | 'KeepAlive': 10108, 11 | 'AuthenticationCheck': 10112, 12 | 'SetDeviceToken': 10113, 13 | 'ResetAccount': 10116, 14 | 'ReportUser': 10117, 15 | 'AccountSwitched': 10118, 16 | 'UnlockAccount': 10121, 17 | 'AppleBillingRequest': 10150, 18 | 'GoogleBillingRequest': 10151, 19 | 'KunlunBillingRequest': 10159, 20 | 'ChangeAvatarName': 10212, 21 | 'AskForPlayingGamecenterFriends': 10512, 22 | 'AskForPlayingFacebookFriends': 10513, 23 | 'InboxOpened': 10905, 24 | 'UnbindFacebookAccount': 12211, 25 | 'RequestSectorState': 12903, 26 | 'SectorCommand': 12904, 27 | 'GetCurrentBattleReplayData': 12905, 28 | 'SendBattleEvent': 12951, 29 | 'GoHome': 14101, 30 | 'EndClientTurn': 14102, 31 | 'StartMission': 14104, 32 | 'HomeLogicStopped': 14105, 33 | 'CancelMatchmake': 14107, 34 | 'ChangeHomeName': 14108, 35 | 'VisitHome': 14113, 36 | 'HomeBattleReplay': 14114, 37 | 'HomeBattleReplayViewed': 14117, 38 | 'AcceptChallenge': 14120, 39 | 'CancelChallengeMessage': 14123, 40 | 'BindFacebookAccount': 14201, 41 | 'BindGamecenterAccount': 14212, 42 | 'BindGoogleServiceAccount': 14262, 43 | 'CreateAlliance': 14301, 44 | 'AskForAllianceData': 14302, 45 | 'AskForJoinableAlliancesList': 14303, 46 | 'AskForAllianceStream': 14304, 47 | 'JoinAlliance': 14305, 48 | 'ChangeAllianceMemberRole': 14306, 49 | 'KickAllianceMember': 14307, 50 | 'LeaveAlliance': 14308, 51 | 'DonateAllianceUnit': 14310, 52 | 'ChatToAllianceStream': 14315, 53 | 'ChangeAllianceSettings': 14316, 54 | 'RequestJoinAlliance': 14317, 55 | 'SelectSpellsFromCoOpen': 14318, 56 | 'OfferChestForCoOpen': 14319, 57 | 'RespondToAllianceJoinRequest': 14321, 58 | 'SendAllianceInvitation': 14322, 59 | 'JoinAllianceUsingInvitation': 14323, 60 | 'SearchAlliances': 14324, 61 | 'SendAllianceMail': 14330, 62 | 'AskForAllianceRankingList': 14401, 63 | 'AskForTVContent': 14402, 64 | 'AskForAvatarRankingList': 14403, 65 | 'AskForAvatarLocalRanking': 14404, 66 | 'AskForAvatarStream': 14405, 67 | 'AskForBattleReplayStream': 14406, 68 | 'AskForLastAvatarTournamentResults': 14408, 69 | 'RemoveAvatarStreamEntry': 14418, 70 | 'AvatarNameCheckRequest': 14600, 71 | 'LogicDeviceLinkCodeStatus': 16000, 72 | 'AskForJoinableTournaments': 16103, 73 | 'SearchTournaments': 16113, 74 | 75 | // Server Messages 76 | 'ServerHello': 20100, 77 | 'LoginFailed': 20103, 78 | 'LoginOk': 20104, 79 | 'FriendList': 20105, 80 | 'KeepAliveOk': 20108, 81 | 'ChatAccountBanStatus': 20118, 82 | 'BillingRequestFailed': 20121, 83 | 'UnlockAccountOk': 20132, 84 | 'UnlockAccountFailed': 20133, 85 | 'AppleBillingProcessedByServer': 20151, 86 | 'GoogleBillingProcessedByServer': 20152, 87 | 'KunlunBillingProcessedByServer': 20156, 88 | 'ShutdownStarted': 20161, 89 | 'AvatarNameChangeFailed': 20205, 90 | 'AvatarInGameStatusUpdated': 20206, 91 | 'AllianceOnlineStatusUpdated': 20207, 92 | 'BattleResult': 20225, 93 | 'AvatarNameCheckResponse': 20300, 94 | 'OpponentLeftMatchNotification': 20801, 95 | 'OpponentRejoinsMatchNotification': 20802, 96 | 'SectorHearbeat': 21902, 97 | 'SectorState': 21903, 98 | 'BattleEvent': 22952, 99 | 'PvpMatchmakeNotification': 22957, 100 | 'OwnHomeData': 24101, 101 | 'OwnAvatarData': 24102, 102 | 'OutOfSync': 24104, 103 | 'StopHomeLogic': 24106, 104 | 'MatchmakeInfo': 24107, 105 | 'MatchmakeFailed': 24108, 106 | 'AvailableServerCommand': 24111, 107 | 'UdpConnectionInfo': 24112, 108 | 'VisitedHomeData': 24113, 109 | 'HomeBattleReplayData': 24114, 110 | 'ServerError': 24115, 111 | 'HomeBattleReplayFailed': 24116, 112 | 'ChallengeFailed': 24121, 113 | 'CancelChallengeDone': 24124, 114 | 'CancelMatchmakeDone': 24125, 115 | 'FacebookAccountBound': 24201, 116 | 'FacebookAccountAlreadyBound': 24202, 117 | 'GamecenterAccountAlreadyBound': 24212, 118 | 'FacebookAccountUnbound': 24213, 119 | 'GoogleServiceAccountBound': 24261, 120 | 'GoogleServiceAccountAlreadyBound': 24262, 121 | 'AllianceData': 24301, 122 | 'AllianceJoinFailed': 24302, 123 | 'AllianceJoinOk': 24303, 124 | 'JoinableAllianceList': 24304, 125 | 'AllianceLeaveOk': 24305, 126 | 'ChangeAllianceMemberRoleOk': 24306, 127 | 'KickAllianceMemberOk': 24307, 128 | 'AllianceMember': 24308, 129 | 'AllianceMemberRemoved': 24309, 130 | 'AllianceList': 24310, 131 | 'AllianceStream': 24311, 132 | 'AllianceStreamEntry': 24312, 133 | 'AllianceStreamEntryRemoved': 24318, 134 | 'AllianceJoinRequestOk': 24319, 135 | 'AllianceJoinRequestFailed': 24320, 136 | 'AllianceInvitationSendFailed': 24321, 137 | 'AllianceInvitationSentOk': 24322, 138 | 'AllianceFullEntryUpdate': 24324, 139 | 'AllianceCreateFailed': 24332, 140 | 'AllianceChangeFailed': 24333, 141 | 'AllianceRankingList': 24401, 142 | 'AllianceLocalRankingList': 24402, 143 | 'AvatarRankingList': 24403, 144 | 'AvatarLocalRankingList': 24404, 145 | 'RoyalTVContent': 24405, 146 | 'LastAvatarTournamentResults': 24407, 147 | 'AvatarStream': 24411, 148 | 'AvatarStreamEntry': 24412, 149 | 'BattleReportStream': 24413, 150 | 'AvatarStreamEntryRemoved': 24418, 151 | 'InboxList': 24445, 152 | 'InboxGlobal': 24446, 153 | 'InboxCount': 24447, 154 | 'Disconnected': 25892, 155 | 'LogicDeviceLinkCodeResponse': 26002, 156 | 'LogicDeviceLinkNewDeviceLinked': 26003, 157 | 'LogicDeviceLinkCodeDeactivated': 26004, 158 | 'LogicDeviceLinkResponse': 26005, 159 | 'LogicDeviceLinkDone': 26007, 160 | 'LogicDeviceLinkError': 26008, 161 | 162 | // Value-to-name mapping for convenience 163 | 164 | // Client Messages 165 | '10100': 'ClientHello', 166 | '10101': 'Login', 167 | '10107': 'ClientCapabilities', 168 | '10108': 'KeepAlive', 169 | '10112': 'AuthenticationCheck', 170 | '10113': 'SetDeviceToken', 171 | '10116': 'ResetAccount', 172 | '10117': 'ReportUser', 173 | '10118': 'AccountSwitched', 174 | '10121': 'UnlockAccount', 175 | '10150': 'AppleBillingRequest', 176 | '10151': 'GoogleBillingRequest', 177 | '10159': 'KunlunBillingRequest', 178 | '10212': 'ChangeAvatarName', 179 | '10512': 'AskForPlayingGamecenterFriends', 180 | '10513': 'AskForPlayingFacebookFriends', 181 | '10905': 'InboxOpened', 182 | '12211': 'UnbindFacebookAccount', 183 | '12903': 'RequestSectorState', 184 | '12904': 'SectorCommand', 185 | '12905': 'GetCurrentBattleReplayData', 186 | '12951': 'SendBattleEvent', 187 | '14101': 'GoHome', 188 | '14102': 'EndClientTurn', 189 | '14104': 'StartMission', 190 | '14105': 'HomeLogicStopped', 191 | '14107': 'CancelMatchmake', 192 | '14108': 'ChangeHomeName', 193 | '14113': 'VisitHome', 194 | '14114': 'HomeBattleReplay', 195 | '14117': 'HomeBattleReplayViewed', 196 | '14120': 'AcceptChallenge', 197 | '14123': 'CancelChallengeMessage', 198 | '14201': 'BindFacebookAccount', 199 | '14212': 'BindGamecenterAccount', 200 | '14262': 'BindGoogleServiceAccount', 201 | '14301': 'CreateAlliance', 202 | '14302': 'AskForAllianceData', 203 | '14303': 'AskForJoinableAlliancesList', 204 | '14304': 'AskForAllianceStream', 205 | '14305': 'JoinAlliance', 206 | '14306': 'ChangeAllianceMemberRole', 207 | '14307': 'KickAllianceMember', 208 | '14308': 'LeaveAlliance', 209 | '14310': 'DonateAllianceUnit', 210 | '14315': 'ChatToAllianceStream', 211 | '14316': 'ChangeAllianceSettings', 212 | '14317': 'RequestJoinAlliance', 213 | '14318': 'SelectSpellsFromCoOpen', 214 | '14319': 'OfferChestForCoOpen', 215 | '14321': 'RespondToAllianceJoinRequest', 216 | '14322': 'SendAllianceInvitation', 217 | '14323': 'JoinAllianceUsingInvitation', 218 | '14324': 'SearchAlliances', 219 | '14330': 'SendAllianceMail', 220 | '14401': 'AskForAllianceRankingList', 221 | '14402': 'AskForTVContent', 222 | '14403': 'AskForAvatarRankingList', 223 | '14404': 'AskForAvatarLocalRanking', 224 | '14405': 'AskForAvatarStream', 225 | '14406': 'AskForBattleReplayStream', 226 | '14408': 'AskForLastAvatarTournamentResults', 227 | '14418': 'RemoveAvatarStreamEntry', 228 | '14600': 'AvatarNameCheckRequest', 229 | '16000': 'LogicDeviceLinkCodeStatus', 230 | '16103': 'AskForJoinableTournaments', 231 | '16113': 'SearchTournaments', 232 | 233 | // Server Messages 234 | '20100': 'ServerHello', 235 | '20103': 'LoginFailed', 236 | '20104': 'LoginOk', 237 | '20105': 'FriendList', 238 | '20108': 'KeepAliveServer', 239 | '20118': 'ChatAccountBanStatus', 240 | '20121': 'BillingRequestFailed', 241 | '20132': 'UnlockAccountOk', 242 | '20133': 'UnlockAccountFailed', 243 | '20151': 'AppleBillingProcessedByServer', 244 | '20152': 'GoogleBillingProcessedByServer', 245 | '20156': 'KunlunBillingProcessedByServer', 246 | '20161': 'ShutdownStarted', 247 | '20205': 'AvatarNameChangeFailed', 248 | '20206': 'AvatarInGameStatusUpdated', 249 | '20207': 'AllianceOnlineStatusUpdated', 250 | '20225': 'BattleResult', 251 | '20300': 'AvatarNameCheckResponse', 252 | '20801': 'OpponentLeftMatchNotification', 253 | '20802': 'OpponentRejoinsMatchNotification', 254 | '21902': 'SectorHearbeat', 255 | '21903': 'SectorState', 256 | '22952': 'BattleEvent', 257 | '22957': 'PvpMatchmakeNotification', 258 | '24101': 'OwnHomeData', 259 | '24102': 'OwnAvatarData', 260 | '24104': 'OutOfSync', 261 | '24106': 'StopHomeLogic', 262 | '24107': 'MatchmakeInfo', 263 | '24108': 'MatchmakeFailed', 264 | '24111': 'AvailableServerCommand', 265 | '24112': 'UdpConnectionInfo', 266 | '24113': 'VisitedHomeData', 267 | '24114': 'HomeBattleReplay', 268 | '24115': 'ServerError', 269 | '24116': 'HomeBattleReplayFailed', 270 | '24121': 'ChallengeFailed', 271 | '24124': 'CancelChallengeDone', 272 | '24125': 'CancelMatchmakeDone', 273 | '24201': 'FacebookAccountBound', 274 | '24202': 'FacebookAccountAlreadyBound', 275 | '24212': 'GamecenterAccountAlreadyBound', 276 | '24213': 'FacebookAccountUnbound', 277 | '24261': 'GoogleServiceAccountBound', 278 | '24262': 'GoogleServiceAccountAlreadyBound', 279 | '24301': 'AllianceData', 280 | '24302': 'AllianceJoinFailed', 281 | '24303': 'AllianceJoinOk', 282 | '24304': 'JoinableAllianceList', 283 | '24305': 'AllianceLeaveOk', 284 | '24306': 'ChangeAllianceMemberRoleOk', 285 | '24307': 'KickAllianceMemberOk', 286 | '24308': 'AllianceMember', 287 | '24309': 'AllianceMemberRemoved', 288 | '24310': 'AllianceList', 289 | '24311': 'AllianceStream', 290 | '24312': 'AllianceStreamEntry', 291 | '24318': 'AllianceStreamEntryRemoved', 292 | '24319': 'AllianceJoinRequestOk', 293 | '24320': 'AllianceJoinRequestFailed', 294 | '24321': 'AllianceInvitationSendFailed', 295 | '24322': 'AllianceInvitationSentOk', 296 | '24324': 'AllianceFullEntryUpdate', 297 | '24332': 'AllianceCreateFailed', 298 | '24333': 'AllianceChangeFailed', 299 | '24401': 'AllianceRankingList', 300 | '24402': 'AllianceLocalRankingList', 301 | '24403': 'AvatarRankingList', 302 | '24404': 'AvatarLocalRankingList', 303 | '24405': 'RoyalTVContent', 304 | '24407': 'LastAvatarTournamentResults', 305 | '24411': 'AvatarStream', 306 | '24412': 'AvatarStreamEntry', 307 | '24413': 'BattleReportStream', 308 | '24418': 'AvatarStreamEntryRemoved', 309 | '24445': 'InboxList', 310 | '24446': 'InboxGlobal', 311 | '24447': 'InboxCount', 312 | '25892': 'Disconnected', 313 | '26002': 'LogicDeviceLinkCodeResponse', 314 | '26003': 'LogicDeviceLinkNewDeviceLinked', 315 | '26004': 'LogicDeviceLinkCodeDeactivated', 316 | '26005': 'LogicDeviceLinkResponse', 317 | '26007': 'LogicDeviceLinkDone', 318 | '26008': 'LogicDeviceLinkError' 319 | }; 320 | --------------------------------------------------------------------------------