├── .gitignore ├── Changelog.md ├── Gruntfile.js ├── LICENSE ├── Readme.md ├── bin ├── extract.js ├── heroprotocol.js └── query.js ├── examples ├── details.js └── replay.js ├── index.js ├── lib ├── Replay.js ├── data.js ├── decoders.js ├── protocol29406.js ├── protocol30414.js ├── protocol30509.js ├── protocol30829.js ├── protocol30948.js ├── protocol31090.js ├── protocol31360.js ├── protocol31566.js ├── protocol31726.js ├── protocol31948.js ├── protocol32120.js ├── protocol32253.js ├── protocol32455.js ├── protocol32524.js ├── protocol33182.js ├── protocol33353.js ├── protocol33684.js ├── protocol34053.js ├── protocol34190.js ├── protocol34659.js ├── protocol34846.js ├── protocol35360.js ├── protocol35529.js ├── protocol35634.js ├── protocol35702.js ├── protocol36144.js ├── protocol36280.js ├── protocol36359.js ├── protocol36536.js ├── protocol36693.js ├── protocol37069.js ├── protocol37117.js ├── protocol37274.js ├── protocol37351.js ├── protocol37569.js ├── protocol37795.js ├── protocol38236.js ├── protocol38500.js ├── protocol38593.js ├── protocol38793.js ├── protocol39015.js ├── protocol39153.js ├── protocol39271.js ├── protocol39445.js ├── protocol39595.js ├── protocol39709.js ├── protocol39951.js ├── protocol40087.js ├── protocol40322.js ├── protocol40336.js └── protocol40431.js ├── package.json └── reference ├── header.md ├── replay.details.md ├── replay.game.events.md ├── replay.initdata.md ├── replay.message.events.md └── replay.tracker.events.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.StormReplay 2 | *.json 3 | replays 4 | dist 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 23 | .grunt 24 | 25 | # node-waf configuration 26 | .lock-wscript 27 | 28 | # Compiled binary addons (http://nodejs.org/api/addons.html) 29 | build/Release 30 | 31 | # Dependency directory 32 | node_modules 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | # 0.2.1 - 2016-01-31 4 | 5 | - Support NodeJS v4.x.x 6 | - Properly parse string Buffer in events 7 | 8 | # 0.2.0 - 2016-01-31 9 | 10 | - `heroprotocol.js` API streamlined. 11 | - Every protocol ported, including PTR patch 16.0 protocol. 12 | - Added Changelog and Todo. 13 | - Switch to javascript strict mode. 14 | 15 | # 0.1.2 - 2016-01-28 16 | 17 | - Breaking bug correction where `heroprotocol.js` required a local dependency instead of using the npm one 18 | - Initial reference for `replay.message.events` and `replay.message.tracker`. 19 | - `data.js` collection of replay constants. 20 | 21 | # 0.1.1 - 2016-01-27 22 | 23 | - Inital reference for replay `header`, `replay.details` and `replay.initdata`. 24 | - `bin/extract.js` replay bulk extraction tool. 25 | - Basic replay abstraction. 26 | 27 | # 0.1.0 - 2016-01-26 28 | 29 | - Initial release. 30 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | // Project configuration. 4 | grunt.initConfig({ 5 | pkg: grunt.file.readJSON('package.json'), 6 | watch: { 7 | files: ['*.js', 'bin/*.js', 'examples/*.js', 'lib/*.js', 'test/*.js' ], 8 | tasks: ['jshint'] 9 | }, 10 | jshint: { 11 | all: ['*.js', 'bin/*.js', 'examples/*.js', 'lib/*.js', 'test/*.js' ], 12 | options: { 13 | esversion: 6, 14 | node: true, 15 | force: true 16 | } 17 | }, 18 | concurrent: { 19 | options: { 20 | "logConcurrentOutput": true 21 | }, 22 | lint: { 23 | tasks: ['jshint', 'watch'] 24 | } 25 | }, 26 | simplemocha: { 27 | options: { 28 | globals: [], // Add whitelisted globals here 29 | timeout: 3000, 30 | ignoreLeaks: false, 31 | ui: 'bdd', 32 | reporter: 'spec' 33 | }, 34 | all: { src: ['test/**/*.js'] } 35 | } 36 | }); 37 | 38 | // Load plugins/tasks 39 | grunt.loadNpmTasks('grunt-contrib-jshint'); 40 | grunt.loadNpmTasks('grunt-contrib-watch'); 41 | grunt.loadNpmTasks('grunt-concurrent'); 42 | grunt.loadNpmTasks('grunt-simple-mocha'); 43 | 44 | // Grunt task(s). 45 | grunt.registerTask('default', ['concurrent:lint']); 46 | grunt.registerTask('test', ['simplemocha']); 47 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Mathieu Merdy 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | ======================================================================== 16 | 17 | Copyright (c) 2015 Blizzard Entertainment 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # heroprotocoljs 2 | 3 | heroprotocoljs is a Javascript port of [heroprotocol](https://github.com/Blizzard/heroprotocol). It is a library and standalone tool to decode Heroes of the Storm replay files into Javascript data structures. 4 | 5 | Currently heroprotocoljs can decode these structures and events: 6 | 7 | - replay header 8 | - game details 9 | - replay init data 10 | - game events 11 | - message events 12 | - tracker events 13 | 14 | heroprotocoljs can be used as a base-build-specific library to decode binary blobs, or it can be run as a standalone tool to pretty print information from supported replay files. 15 | 16 | Note that heroprotocoljs does not expose game balance information or provide any kind of high level analysis of replays; it's meant 17 | to be just the first tool in the chain for your data mining application. 18 | 19 | ## Installation 20 | 21 | npm install -S heroprotocoljs 22 | 23 | ## Usage 24 | 25 | ### As a library 26 | 27 | An usage example is provided in the "example" folder. It displays the map name and players name. 28 | 29 | const heroprotocol = require('../'); 30 | 31 | const file = process.argv[2]; 32 | 33 | const details = heroprotocol.get(heroprotocol.DETAILS, file); 34 | 35 | if (details) { 36 | const players = details.m_playerList.map(player => player.m_name.toString()); 37 | 38 | console.log('Map:', details.m_title); 39 | console.log('Players:', players.sort()); 40 | } 41 | 42 | Output: 43 | 44 | Map: Battlefield of Eternity 45 | Players: […] 46 | 47 | ### As a command line tool 48 | 49 | $ bin/heroprotocol.js map.StormReplay -H --json 50 | 51 | Outputs the `map.StormReplay` replay header in JSON format. 52 | 53 | usage: heroprotocol.js replayFile [--help] [--gameevents] [--messageevents] 54 | [--trackerevents] [--attributeevents] [--header] [--details] [--initdata] 55 | [--stats] [--json] 56 | 57 | Options: 58 | -h, --help show this help [boolean] 59 | -H, --header parse protocol header [boolean] 60 | -d, --details parse protocol details [boolean] 61 | -i, --initdata parse protocol initdata [boolean] 62 | -g, --gameevents parse protocol gameevents [boolean] 63 | -m, --messageevents parse protocol messageevents [boolean] 64 | -t, --trackerevents parse protocol trackerevents [boolean] 65 | -a, --attributeevents parse protocol attributeevents [boolean] 66 | -s, --stats print SPlayerStatsEvent [boolean] 67 | --json prints in JSON format [boolean] 68 | 69 | To extract everything, you can use the extraction tool provided: 70 | 71 | $ node bin/extract.js map.StormReplay extractionDir/ --pretty 72 | 73 | Extracts `map.StormReplay` in the `extractionDir` directory in prettified JSON. 74 | 75 | usage: extract.js file|dir ... outdir [-h] [-p] [-r] [-v] 76 | 77 | Options: 78 | -h, --help show this help [boolean] 79 | 80 | -p, --pretty prettifies the json [boolean] 81 | 82 | -r, --recursive scans input folders for replays recursively [boolean] 83 | 84 | -v, --verbose prints additional info [boolean] 85 | 86 | ## Data reference 87 | 88 | The following files are in the archive and supported by the library: 89 | 90 | - replay.details (see `reference/replay.details.md` for details) 91 | - replay.initdata (see `reference/replay.initdata.md` for details) 92 | - replay.game.events 93 | - replay.message.events (see `reference/replay.message.events.md` for details) 94 | - replay.tracker.events (see `reference/replay.tracker.events.md` for details) 95 | - replay.attributes.events 96 | 97 | Also accessible is the replay header (see `reference/header.md` for details). 98 | 99 | The following files are in the archive but not supported by this port nor the original library yet: 100 | 101 | - replay.load.info 102 | - replay.resumable.events 103 | - replay.server.battlelobby 104 | - replay.smartcam.events 105 | - replay.sync.events 106 | - replay.sync.history 107 | 108 | ## Supported Versions 109 | 110 | heroprotocoljs supports all protocols avalaible in the original library and can read all replays from retail versions of the game, up to and including patch 16.0. The plan is to port all future versions as they become available. 111 | 112 | ### How it works 113 | 114 | Heroes of the Storm replay files are MPQ archives. heroprotocol uses the mpyq library to read and extract the binary content out of the archive. It then parses the binary content into data structures containing the replay information. 115 | 116 | The three main files are: 117 | 118 | - decoders.js: Contains the binary structures decoders. They are the same for all versions of the game. 119 | - protocol#####.js: Contains the data structures description of a specific public release of the game. 120 | - heroprotocol.js: Entry point. Exports the ReplayDecoder and provides the CLI. 121 | 122 | heroprotocol.js starts by loading the earliest protocol available, protocol29406.js and uses it to parse the replay header. It then reads the replay build version in the header and loads the associated protocol#####.js containing the correct data structures to parse the full replay. 123 | 124 | ## Plans 125 | 126 | What I always wanted for personal use is a bulk analyzer that takes all your replays and give you stats, so that's my end goal for now. If it goes well I'd like to provide a website for anyone to see their stats whithout having to install this tool. Along the way I hope I can provide tools to read individual or multiple replays and all information associated in a friendlier way than Blizzard's data structures. I would also like to build a reference for those data structure. Finally I would like to explore the possibility of using this library directory in the browser, as while hard it's certainly feasible and would help third-party tool creation greatly. 127 | 128 | Any small or big contribution appreciated whether in code, documentation, feedback or feature request. 129 | 130 | ## Acknowledgements 131 | 132 | Blizzard Entertainment for making the awesome Heroes of the Storm game and releasing the original [heroprotocol](https://github.com/Blizzard/heroprotocol) tool. 133 | 134 | The standalone tool uses a javascript port of [mpyq](https://github.com/arkx/mpyq/) to read mopaq files. 135 | 136 | ## License 137 | 138 | Copyright (c) 2016, Mathieu Merdy 139 | 140 | --- 141 | 142 | Copyright (c) 2015 Blizzard Entertainment 143 | 144 | --- 145 | 146 | Open sourced under the ISC license and MIT license. See the included LICENSE file for more information. 147 | -------------------------------------------------------------------------------- /bin/extract.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | $ node extract.js file|dir ... outdir [-h] [-p] [-r] [-v] 4 | 5 | */ 6 | "use strict"; 7 | 8 | const startTime = Date.now(); 9 | 10 | const fs = require('fs'); 11 | const _path = require('path'); 12 | const yargs = require('yargs'); 13 | const heroprotocol = require('../'); 14 | 15 | const args = yargs.usage('usage: extract.js file|dir ... outdir [-h] [-p] [-r] [-v]\n\nExtracts replays to the given directory or current directory of none is specified. Extracs the full replay by default.') 16 | .option('h', { alias: 'help', type: 'boolean', desc: 'show this help' }) 17 | .option('p', { alias: 'pretty', type: 'boolean', desc: 'prettifies the json' }) 18 | .option('r', { alias: 'recursive', type: 'boolean', desc: 'scans input folders for replays recursively' }) 19 | .option('s', { alias: 'skip-existing', type: 'boolean', desc: 'skips extraction of already existing files' }) 20 | .option('H', { alias: 'header', type: 'boolean', desc: 'extract protocol header' }) 21 | .option('d', { alias: 'details', type: 'boolean', desc: 'extract protocol details' }) 22 | .option('i', { alias: 'initdata', type: 'boolean', desc: 'extract protocol initdata' }) 23 | .option('g', { alias: 'gameevents', type: 'boolean', desc: 'extract game events' }) 24 | .option('m', { alias: 'messageevents', type: 'boolean', desc: 'extract message events' }) 25 | .option('t', { alias: 'trackerevents', type: 'boolean', desc: 'extract tracker events' }) 26 | .option('a', { alias: 'attributeevents', type: 'boolean', desc: 'extract attribute events' }) 27 | .option('v', { alias: 'verbose', type: 'boolean', desc: 'prints additional info' }) 28 | .argv; 29 | const spacing = args.pretty ? ' ' : undefined; 30 | const files = [heroprotocol.HEADER, heroprotocol.DETAILS, heroprotocol.INITDATA, 31 | heroprotocol.GAME_EVENTS, heroprotocol.MESSAGE_EVENTS, 32 | heroprotocol.TRACKER_EVENTS, heroprotocol.ATTRIBUTES_EVENTS]; 33 | 34 | const protocols = new Set(); // required protocols not yet implemented 35 | let paths = args._; 36 | var extractDir = process.cwd(); // defaults the extraction directory to the current execution directory 37 | var extractionDone = 0, extractionFailed = 0; 38 | var i = 0; 39 | 40 | function usage() { 41 | console.log('usage: extract.js file|dir ... [outdir] [-r]'); 42 | console.log(); 43 | console.log('\tfile|dir\tone or several .StormReplay files or directories containing them'); 44 | console.log('\toutdir\t\tdirectory in which to extract the replays'); 45 | console.log('\t-r\t\tsearch the directories for replay files recursively'); 46 | } 47 | 48 | function getPaths(paths, top) { 49 | return new Promise((resolve, reject) => { 50 | // if last element of several is not a replay, it's the output directory 51 | const cwd = process.cwd(); 52 | const last = paths[paths.length - 1]; 53 | if (top && paths.length > 1 && !last.endsWith('.StormReplay')) { 54 | extractDir = _path.isAbsolute(last) ? last : _path.join(cwd, last); 55 | paths.pop(); 56 | } 57 | 58 | // resolve all paths 59 | Promise.all(paths.map(path => { 60 | return new Promise((resolve, reject) => { 61 | fs.stat(path, (err, stats) => { 62 | if (err) return resolve(null); 63 | 64 | if (stats.isDirectory()) { 65 | fs.readdir(path, (err, files) => { 66 | if (err) return reject(null); 67 | 68 | if (args.r || top) { 69 | getPaths(files.map(file => _path.join(path, file))).then(resolve); 70 | } else { 71 | resolve(null); 72 | } 73 | }); 74 | } else { 75 | // only extract files with the .StormReplay extension 76 | if (_path.extname(path) === '.StormReplay') return resolve(_path.join(cwd, path)); 77 | else resolve(null); 78 | } 79 | }); 80 | }); 81 | })).then(paths => resolve(paths.reduce((a, b) => b ? a.concat(b) : a, []))); 82 | }); 83 | } 84 | 85 | function sort(obj) { 86 | const ret = {}; 87 | Object.keys(obj).sort().forEach(key => { 88 | const value = obj[key]; 89 | if (!value) { 90 | ret[key] = value; 91 | } else if (Array.isArray(value)) { 92 | ret[key] = value.map(item => { 93 | if (item && !Array.isArray(item) && typeof item === 'object') 94 | return sort(item); 95 | else 96 | return item; 97 | }); 98 | } else if (typeof value === 'object') { 99 | ret[key] = sort(value); 100 | } else { 101 | ret[key] = value; 102 | } 103 | }); 104 | return ret; 105 | } 106 | 107 | function writeFile(archive, file, dir) { 108 | return new Promise((resolve, reject) => { 109 | const path = `${_path.join(dir, file)}.json`; 110 | fs.stat(path, (err, stats) => { 111 | if (args.s && !err) { 112 | return resolve(true); 113 | } 114 | 115 | const data = archive.get(file); 116 | fs.writeFile(path, JSON.stringify(args.pretty ? sort(data) : data, undefined, spacing), (err) => { 117 | if (err) console.log(err); 118 | resolve(err ? false : true); 119 | }); 120 | }); 121 | }); 122 | } 123 | 124 | function extractReplay(path) { 125 | return new Promise((resolve, reject) => { 126 | const archive = heroprotocol.open(path); 127 | const basename = _path.basename(path, _path.extname(path)); 128 | const dir = _path.join(extractDir, basename); 129 | 130 | if (archive instanceof Error || !archive.protocol) { 131 | if (args.v) console.log('Extraction failed:', path); 132 | if (archive.baseBuild) protocols.add(archive.baseBuild); 133 | extractionFailed += 1; 134 | return resolve(false); 135 | } 136 | 137 | fs.mkdir(dir, err => { 138 | if (err && err.code !== 'EEXIST') { 139 | if (args.v) console.log('Failed to create extraction directory:', dir); 140 | extractionFailed += 1; 141 | return resolve(false); 142 | } 143 | 144 | Promise.all(readFiles.map(file => writeFile(archive, file, dir))).then(() => { 145 | extractionDone += 1; 146 | resolve(true); 147 | }); 148 | }); 149 | }); 150 | } 151 | 152 | if (args.h) { 153 | yargs.showHelp(); 154 | return process.exit(0); 155 | } 156 | 157 | if (!args._[0]) { 158 | yargs.showHelp(); 159 | return process.exit(1); 160 | } 161 | 162 | let readFiles = []; 163 | 164 | if (args.H) 165 | readFiles.push(heroprotocol.HEADER); 166 | if (args.d) 167 | readFiles.push(heroprotocol.DETAILS); 168 | if (args.i) 169 | readFiles.push(heroprotocol.INITDATA); 170 | if (args.g) 171 | readFiles.push(heroprotocol.GAME_EVENTS); 172 | if (args.m) 173 | readFiles.push(heroprotocol.MESSAGE_EVENTS); 174 | if (args.t) 175 | readFiles.push(heroprotocol.TRACKER_EVENTS); 176 | if (args.a) 177 | readFiles.push(heroprotocol.ATTRIBUTES_EVENTS); 178 | if (readFiles.length === 0) 179 | readFiles = files; 180 | 181 | getPaths(paths, true) 182 | .then(paths => { 183 | if (paths.length === 0) { 184 | yargs.showHelp(); 185 | process.exit(1); 186 | } 187 | 188 | return Promise.all(paths.map(extractReplay)); 189 | }) 190 | .then(() => { 191 | if (args.v) { 192 | const endTime = Date.now(); 193 | const extracTime = endTime - startTime; 194 | 195 | console.log(); 196 | console.log('extraction completed in ', (extracTime / 1000) + 's'); 197 | console.log('--------------------'); 198 | console.log('extracted:', extractionDone); 199 | console.log('failed:', extractionFailed); 200 | if (protocols.size > 0) { 201 | console.log(); 202 | console.log('missing protocols:', Array.from(protocols).join(', ')); 203 | } 204 | } 205 | }) 206 | .catch(err => { 207 | console.log(err.stack); 208 | }); 209 | -------------------------------------------------------------------------------- /bin/heroprotocol.js: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright (c) 2015 Blizzard Entertainment 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | */ 22 | "use strict"; 23 | 24 | const heroprotocol = require('../'); 25 | 26 | class EventLogger { 27 | constructor() { 28 | this.eventStats = {}; 29 | } 30 | 31 | log(event) { 32 | function sort(obj) { 33 | const ret = {}; 34 | Object.keys(obj).sort().forEach(key => { 35 | const value = obj[key]; 36 | if (!value) { 37 | ret[key] = value; 38 | } else if (Array.isArray(value)) { 39 | ret[key] = value.map(item => { 40 | if (item && !Array.isArray(item) && typeof item === 'object') 41 | return sort(item); 42 | else 43 | return item; 44 | }); 45 | } else if (typeof value === 'object') { 46 | ret[key] = sort(value); 47 | } else { 48 | ret[key] = value; 49 | } 50 | }); 51 | return ret; 52 | } 53 | 54 | let stat; 55 | 56 | if (event && event._event && event._bits) { 57 | stat = this.eventStats[event._event] || [0, 0]; 58 | stat[0] += 1; 59 | stat[1] += event._bits; 60 | this.eventStats[event._event] = stat; 61 | } 62 | 63 | event = sort(event); 64 | console.log(args.json ? JSON.stringify(event, undefined, ' ') : event); 65 | } 66 | 67 | logStats() { 68 | // order events by bits parsed and output them from most to least 69 | Object.keys(this.eventStats).sort((b, a) => { 70 | return this.eventStats[b][1] > this.eventStats[a][1]; 71 | }).forEach(name => { 72 | const stat = this.eventStats[name]; 73 | console.log(`${name}, ${stat[0]}, ${stat[1]}`); 74 | }); 75 | } 76 | } 77 | 78 | const yargs = require('yargs') 79 | .usage('usage: heroprotocol.js replayFile [--help] [--header] [--details] [--initdata] [--gameevents] [--messageevents] [--trackerevents] [--attributeevents] [--stats]') 80 | .demand(1) 81 | .option('h', { alias: 'help', type: 'boolean', desc: 'show this help' }) 82 | .option('H', { alias: 'header', type: 'boolean', desc: 'print protocol header' }) 83 | .option('d', { alias: 'details', type: 'boolean', desc: 'print protocol details' }) 84 | .option('i', { alias: 'initdata', type: 'boolean', desc: 'print protocol initdata' }) 85 | .option('g', { alias: 'gameevents', type: 'boolean', desc: 'print game events' }) 86 | .option('m', { alias: 'messageevents', type: 'boolean', desc: 'print message events' }) 87 | .option('t', { alias: 'trackerevents', type: 'boolean', desc: 'print tracker events' }) 88 | .option('a', { alias: 'attributeevents', type: 'boolean', desc: 'print attribute events' }) 89 | .option('s', { alias: 'stats', type: 'boolean', desc: 'print stats' }) 90 | .option('json', { type: 'boolean', desc: 'prints in JSON format' }); 91 | const args = yargs.argv; 92 | 93 | if (args.help) { 94 | yargs.showHelp(); 95 | process.exit(); 96 | } 97 | 98 | const archive = heroprotocol.open(args._[0]); 99 | 100 | if (archive instanceof Error) { 101 | console.log(archive.error); 102 | process.exit(1); 103 | } 104 | 105 | const logger = new EventLogger(); 106 | 107 | if (args.header) { 108 | logger.log(archive.data[heroprotocol.HEADER]); 109 | } 110 | 111 | if (!archive.protocol) { 112 | console.log('Unsupported base build:', archive.baseBuild); 113 | process.exit(1); 114 | } 115 | 116 | if (args.details) { 117 | logger.log(archive.get(heroprotocol.DETAILS)); 118 | } 119 | 120 | if (args.initdata) { 121 | const data = archive.get(heroprotocol.INITDATA); 122 | logger.log(data.m_syncLobbyState.m_gameDescription.m_cacheHandles); 123 | logger.log(data); 124 | } 125 | 126 | if (args.gameevents) { 127 | logger.log(archive.get(heroprotocol.GAME_EVENTS)); 128 | } 129 | 130 | if (args.messageevents) { 131 | logger.log(archive.get(heroprotocol.MESSAGE_EVENTS)); 132 | } 133 | 134 | if (args.trackerevents) { 135 | logger.log(archive.get(heroprotocol.TRACKER_EVENTS)); 136 | } 137 | 138 | if (args.attributeevents) { 139 | logger.log(archive.get(heroprotocol.ATTRIBUTES_EVENTS)); 140 | } 141 | 142 | if (args.stats) { 143 | logger.logStats(); 144 | } 145 | -------------------------------------------------------------------------------- /bin/query.js: -------------------------------------------------------------------------------- 1 | /* 2 | Get the names of all players that are not bots: 3 | 4 | $ node bin/query.js replays/ -f details -q "m_playerList[m_toon.m_id != 0].m_name" 5 | 6 | Get the internal name of every unit that is born: 7 | 8 | $ node bin/query.js replays/ -f tracker.events -q "[_event = NNet.Replay.Tracker.SUnitBornEvent].m_unitTypeName" 9 | */ 10 | 11 | "use strict"; 12 | 13 | const fs = require('fs'); 14 | const heroprotocol = require('../'); 15 | 16 | const yargs = 17 | require('yargs') 18 | .usage('usage: query.js file.StormReplay -f archiveFile').demand(1) 19 | .option('file', { alias: 'f', desc: 'archive file on which to permorm the query', choices: [ 20 | 'header', 'details', 'initdata', 'game.events', 21 | 'message.events', 'tracker.events', 'attributes.events' 22 | ], demand: true }) 23 | .option('query', { alias: 'q', desc: 'path in the file to query', demand: true }); 24 | 25 | const args = yargs.argv; 26 | 27 | if (!args._[0]) { 28 | yargs.showHelp(); 29 | process.exit(1); 30 | } 31 | 32 | function getFiles(path) { 33 | return new Promise((resolve, reject) => { 34 | fs.stat(path, (err, stats) => { 35 | if (err) return reject(err); 36 | 37 | if (stats.isDirectory()) { 38 | fs.readdir(path, (err, files) => { 39 | resolve(files.filter(file => file.endsWith('.StormReplay')).map(file => `${path}/${file}`)); 40 | }); 41 | } else if (!path.endsWith('.StormReplay')) { 42 | reject(new Error('File must be a .StormReplay')); 43 | } else { 44 | resolve([path]); 45 | } 46 | }); 47 | }); 48 | } 49 | 50 | const opquery = /(.*?)(=|!=)(.*)/; 51 | function filter(ar, filter) { 52 | const match = filter.match(opquery); 53 | if (!match) { 54 | console.log('Unsupported filter:', filter); 55 | process.exit(1); 56 | } 57 | 58 | const left = match[1].trim(), operand = match[2].trim(), right = match[3].trim(); 59 | 60 | return ar.filter(item => { 61 | const value = find(item, left); 62 | if (operand === '=') { 63 | return value == right; 64 | } else if (operand === '!=') { 65 | return value != right; 66 | } 67 | }); 68 | } 69 | 70 | const subquery = /(?:\[(.*?)\])/; 71 | function find(obj, query, map) { 72 | const match = query.match(subquery); 73 | let value = obj, part, sub, next; 74 | 75 | if (match) { 76 | part = query.slice(0, match.index); 77 | sub = match[1]; 78 | next = query.slice(match.index + sub.length + 3); 79 | } else if(query.indexOf('.') > -1) { 80 | part = query.slice(0, query.indexOf('.')); 81 | next = query.slice(query.indexOf('.') + 1); 82 | } else { 83 | part = query; 84 | } 85 | 86 | value = map ? value : value[part]; 87 | if (sub) { 88 | value = filter(value, sub); 89 | } 90 | 91 | if (next) { 92 | if (sub) value = value.map(item => { 93 | return find(item, next); 94 | }); 95 | else value = find(value, next); 96 | } 97 | 98 | return value; 99 | } 100 | 101 | getFiles(args._[0]).then(paths => { 102 | Promise.all(paths.map(path => { 103 | return new Promise((resolve, reject) => { 104 | const archive = heroprotocol.open(path); 105 | 106 | if (archive instanceof Error) { 107 | console.log(archive); 108 | process.exit(1); 109 | } 110 | 111 | let data; 112 | if (args.f === 'header') { 113 | data = archive.get('header'); 114 | } else { 115 | data = archive.get(`replay.${args.f}`); 116 | } 117 | 118 | if (data) { 119 | resolve(find(data, args.q, args.f.includes('events'))); 120 | } else { 121 | resolve([]); 122 | } 123 | }); 124 | })).then(values => { 125 | values = values.reduce((a, b) => a.concat(b), []); 126 | console.log(JSON.stringify(Array.from(new Set(values)).sort(), undefined, ' ')); 127 | }, err => { 128 | console.log(err); 129 | }); 130 | }, err => { 131 | console.log(err); 132 | }); 133 | -------------------------------------------------------------------------------- /examples/details.js: -------------------------------------------------------------------------------- 1 | const heroprotocol = require('../'); 2 | 3 | const file = process.argv[2]; 4 | 5 | const details = heroprotocol.get(heroprotocol.DETAILS, file); 6 | 7 | if (details) { 8 | const players = details.m_playerList.map(player => player.m_name.toString()); 9 | 10 | console.log('Map:', details.m_title); 11 | console.log('Players:', players.sort()); 12 | } 13 | -------------------------------------------------------------------------------- /examples/replay.js: -------------------------------------------------------------------------------- 1 | const Replay = require('../lib/Replay').Replay; 2 | 3 | var replay = new Replay(process.argv[2]); 4 | if (replay) { 5 | replay.print(); 6 | } 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const MPQArchive = exports.MPQArchive = require('mpyqjs/mpyq').MPQArchive; 6 | const protocol29406 = exports.protocol = require('./lib/protocol29406'); 7 | 8 | const version = exports.version = require('./package.json').version; 9 | 10 | // parsable parts 11 | const HEADER = exports.HEADER = 'header'; 12 | const DETAILS = exports.DETAILS = 'replay.details'; 13 | const INITDATA = exports.INITDATA = 'replay.initdata'; 14 | const GAME_EVENTS = exports.GAME_EVENTS = 'replay.game.events'; 15 | const MESSAGE_EVENTS = exports.MESSAGE_EVENTS = 'replay.message.events'; 16 | const TRACKER_EVENTS = exports.TRACKER_EVENTS = 'replay.tracker.events'; 17 | const ATTRIBUTES_EVENTS = exports.ATTRIBUTES_EVENTS = 'replay.attributes.events'; 18 | 19 | const decoderMap = { 20 | [HEADER]: 'decodeReplayHeader', 21 | [DETAILS]: 'decodeReplayDetails', 22 | [INITDATA]: 'decodeReplayInitdata', 23 | [GAME_EVENTS]: 'decodeReplayGameEvents', 24 | [MESSAGE_EVENTS]: 'decodeReplayMessageEvents', 25 | [TRACKER_EVENTS]: 'decodeReplayTrackerEvents', 26 | [ATTRIBUTES_EVENTS]: 'decodeReplayAttributesEvents' 27 | }; 28 | 29 | const parseStrings = function parseStrings(data) { 30 | if (!data) return data; 31 | else if (data instanceof Buffer) return data.toString(); 32 | else if (Array.isArray(data)) return data.map(item => parseStrings(item)); 33 | else if (typeof data === 'object') { 34 | for (let key in data) { 35 | data[key] = parseStrings(data[key]); 36 | } 37 | } 38 | return data; 39 | }; 40 | 41 | let lastUsed; 42 | 43 | exports.open = function (file) { 44 | let archive, header; 45 | 46 | // TODO - should we check if the user is trying to open the lastUsed file 47 | // and return the cache or assume they know what they're doing? Need usecase. 48 | 49 | if (typeof file === 'string') { 50 | try { 51 | if (!path.isAbsolute(file)) { 52 | file = path.join(process.cwd(), file); 53 | } 54 | archive = new MPQArchive(file); 55 | archive.filename = file; 56 | } catch (err) { 57 | archive = err; 58 | } 59 | } else if (file instanceof MPQArchive) { 60 | // TODO - need to check what happens when instanciating an MPQArchive with 61 | // invalid path and setup an error accordingly 62 | archive = file; 63 | } else { 64 | archive = new Error('Unsupported parameter: ${file}'); 65 | } 66 | 67 | if (archive instanceof Error) return archive; 68 | lastUsed = archive; 69 | 70 | // parse header 71 | archive.data = {}; 72 | header = archive.data[HEADER] = parseStrings(protocol29406.decodeReplayHeader(archive.header.userDataHeader.content)); 73 | // The header's baseBuild determines which protocol to use 74 | archive.baseBuild = header.m_version.m_baseBuild; 75 | 76 | try { 77 | archive.protocol = require(`./lib/protocol${archive.baseBuild}`); 78 | } catch (err) { 79 | archive.error = err; 80 | } 81 | 82 | archive.get = function (file) { 83 | return exports.get(file, archive); 84 | }; 85 | 86 | 87 | return archive; 88 | }; 89 | 90 | // returns the content of a file in a replay archive 91 | exports.get = function (archiveFile, archive) { 92 | let data; 93 | if (!lastUsed || !(archive instanceof MPQArchive) || archive !== lastUsed.filename) { 94 | archive = exports.open(archive); 95 | } else { 96 | lastUsed = archive; 97 | } 98 | 99 | if (archive instanceof Error) { 100 | return data; 101 | } 102 | 103 | if (archive.data[archiveFile]) { 104 | data = archive.data[archiveFile]; 105 | } else { 106 | if (archive.protocol) { 107 | if ([DETAILS, INITDATA, ATTRIBUTES_EVENTS].indexOf(archiveFile) > -1) { 108 | data = archive.data[archiveFile] = 109 | parseStrings(archive.protocol[decoderMap[archiveFile]]( 110 | archive.readFile(archiveFile) 111 | )); 112 | } else if ([GAME_EVENTS, MESSAGE_EVENTS, TRACKER_EVENTS].indexOf(archiveFile) > -1) { 113 | // protocol function to call is a generator 114 | data = archive.data[archiveFile] = []; 115 | for (let event of archive.protocol[decoderMap[archiveFile]](archive.readFile(archiveFile))) { 116 | data.push(parseStrings(event)); 117 | } 118 | } 119 | } 120 | } 121 | 122 | return data; 123 | }; 124 | 125 | /** 126 | * parses a basic MPQ header 127 | * @function 128 | * @param {buffer} buffer - Header content from MPQ archive 129 | * @returns {object} Header information from file 130 | */ 131 | exports.parseHeader = function (buffer) { 132 | return parseStrings(protocol29406.decodeReplayHeader(buffer)); 133 | }; 134 | 135 | /** 136 | * parses a buffer based on a given build 137 | * @function 138 | * @param {string} filename - Name of the file to assist in parsing 139 | * @param {buffer} buffer - Binary file contents from MPQ archive 140 | * @param {string} build - Build in which to parse the contents 141 | * @returns {object} File contents 142 | */ 143 | exports.parseFile = function (filename, buffer, build) { 144 | let data, protocol; 145 | 146 | try { 147 | protocol = require(`./lib/protocol${build}`); 148 | } catch (err) { 149 | return undefined; 150 | } 151 | 152 | if ([DETAILS, INITDATA, ATTRIBUTES_EVENTS].indexOf(filename) > -1) { 153 | data = parseStrings(protocol[decoderMap[filename]](buffer)); 154 | } else if ([GAME_EVENTS, MESSAGE_EVENTS, TRACKER_EVENTS].indexOf(filename) > -1) { 155 | data = []; 156 | for (let event of protocol[decoderMap[filename]](buffer)) { 157 | data.push(parseStrings(event)); 158 | } 159 | } 160 | 161 | return data; 162 | }; 163 | -------------------------------------------------------------------------------- /lib/Replay.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Todo: 4 | - take locale into account 5 | 6 | */ 7 | 8 | "use strict"; 9 | 10 | const heroprotocol = require('../'); 11 | const data = require('./data'); 12 | 13 | // Main replay object 14 | const Replay = exports.Replay = class { 15 | constructor(file) { 16 | this.archive = heroprotocol.open(file); 17 | if (this.archive instanceof Error || !this.archive.protocol) return null; 18 | 19 | const details = this.archive.get(heroprotocol.DETAILS); 20 | const initdata = this.archive.get(heroprotocol.INITDATA); 21 | 22 | // map 23 | this.map = new HeroesMap(details); 24 | 25 | // players 26 | this.players = details.m_playerList.map((data, index) => { 27 | return new Player( 28 | data, { 29 | m_lobbyState: initdata.m_syncLobbyState.m_lobbyState.m_slots[index], 30 | m_userInitialData: initdata.m_syncLobbyState.m_userInitialData[index] 31 | }); 32 | }); 33 | 34 | this.teams = { 35 | blue: this.players.filter(player => player.blue).map(player => player.name), 36 | red: this.players.filter(player => player.red).map(player => player.name) 37 | }; 38 | } 39 | 40 | print() { 41 | this.map.print(); 42 | 43 | console.log(); 44 | console.log('Teams'); 45 | console.log('====='); 46 | console.log('Blue:', this.teams.blue.join(', ')); 47 | console.log('Red:', this.teams.red.join(', ')); 48 | console.log(); 49 | 50 | console.log('Players'); 51 | console.log('======='); 52 | this.players.forEach(player => player.print()); 53 | } 54 | 55 | serialize() { 56 | return { 57 | map: this.map, 58 | players: this.players.map(player => player.serialize()), 59 | teams: this.teams 60 | }; 61 | } 62 | }; 63 | 64 | // Map 65 | const HeroesMap = class { 66 | constructor(details) { 67 | this.name = details.m_title; 68 | } 69 | 70 | print() { 71 | console.log('Map'); 72 | console.log('==='); 73 | console.log('name:', this.name); 74 | } 75 | 76 | serialize() { 77 | return { 78 | name: this.name 79 | }; 80 | } 81 | }; 82 | 83 | // Player 84 | const Player = class { 85 | constructor(details, initdata) { 86 | this.index = details.m_workingSetSlotId; // index from 0 to 9 in the replay players arrays 87 | 88 | this.uid = details.m_toon.m_id; // unique id 89 | this.realm = details.m_toon.m_realm; 90 | this.region = details.m_toon.m_region; 91 | this.toonHandle = initdata.m_lobbyState.m_toonHandle; 92 | this.internalName = initdata.m_lobbyState.m_hero; 93 | this.name = details.m_name; 94 | 95 | this.hero = details.m_hero; 96 | this.skin = initdata.m_lobbyState.m_skin; 97 | this.mount = initdata.m_lobbyState.m_mount; 98 | 99 | // put color in data.js? 100 | this.color = details.m_color.m_r === 255 ? 'red' : 'blue'; 101 | this.blue = this.color === 'blue'; 102 | this.red = this.color === 'red'; 103 | this.teamId = details.m_teamId; 104 | 105 | this.silenced = initdata.m_lobbyState.m_hasSilencePenalty; 106 | } 107 | 108 | print() { 109 | console.log(); 110 | console.log('Name:', this.name); 111 | console.log('------'); 112 | console.log('Toon handle:', this.toonHandle); 113 | console.log('Region:', data.regions[this.region]); 114 | console.log('Realm:', data.realms[this.realm]); 115 | console.log('Hero:', this.hero); 116 | console.log('Team:', this.color); 117 | console.log('Skin:', this.skin); 118 | console.log('Mount:', this.mount); 119 | } 120 | 121 | serialize() { 122 | return { 123 | name: this.name, 124 | toonHandle: this.toonHandle, 125 | region: this.region, 126 | realm: this.realm, 127 | hero: this.hero, 128 | color: this.color 129 | }; 130 | } 131 | }; 132 | -------------------------------------------------------------------------------- /lib/data.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // intentional order for data mapping from replay information 4 | exports.realms = [ 5 | undefined, // either undefined if realm notation starts at 1 or maybe the PTR, should confirm with ptr replay 6 | 'live' 7 | ]; 8 | 9 | // intentional order for data mapping from replay information 10 | exports.regions = [ 11 | undefined, 12 | undefined, 13 | 'Europe' 14 | ]; 15 | 16 | // ordered alphabetically 17 | exports.heroes = [ 18 | { 19 | name: 'Abathur' 20 | }, 21 | { 22 | name: 'Jaina' 23 | }, 24 | { 25 | name: 'Johanna', 26 | altNames: ['Crusader'] 27 | }, 28 | { 29 | name: "Kael'thas", 30 | altNames: ['Kaelthas'] 31 | }, 32 | { 33 | name: 'Kerrigan' 34 | }, 35 | { 36 | name: 'Leoric' 37 | }, 38 | { 39 | name: 'Muradin' 40 | }, 41 | { 42 | name: 'Sylvanas' 43 | }, 44 | { 45 | name: 'Valla ', 46 | altNames: ['DemonHunter'] 47 | } 48 | ]; 49 | 50 | exports.mounts = []; 51 | 52 | exports.maps = []; 53 | 54 | // see to complete http://us.battle.net/heroes/en/search?k=Patch%20Notes&f=article 55 | exports.builds = { 56 | 39951: { 57 | live: { 58 | link: 'http://us.battle.net/heroes/en/blog/19993879/heroes-of-the-storm-patch-notes-january-12-2016-1-12-2016', 59 | patch: '15.5', 60 | from: '2016-01-12', 61 | to: '2016-01-27' 62 | } 63 | }, 64 | 40987: { 65 | live: { 66 | link: 'http://us.battle.net/heroes/en/blog/19996517/heroes-of-the-storm-balance-update-notes-january-20-2016-1-20-2016', 67 | patch: '15.6', 68 | from: '2016-01-20', 69 | to: '2016-01-27' 70 | } 71 | }, 72 | 40322: { 73 | live: { 74 | link: 'http://us.battle.net/heroes/en/blog/20021838', 75 | patch: '15.7', 76 | from: '2016-01-27', 77 | to: '2016-02-02' 78 | } 79 | }, 80 | 40336: { 81 | ptr: { 82 | link: 'http://us.battle.net/heroes/en/blog/19998381', 83 | patch: '16.0', 84 | from: '2016-01-26', 85 | to: '2016-02-02' 86 | } 87 | }, 88 | 40431: { 89 | live: { 90 | link: 'http://us.battle.net/heroes/en/blog/19995509', 91 | patch: '16.0', 92 | from: '2016-02-02' 93 | } 94 | } 95 | }; 96 | -------------------------------------------------------------------------------- /lib/decoders.js: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright (c) 2015 Blizzard Entertainment 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | */ 22 | "use strict"; 23 | 24 | function TruncateError(message) { 25 | this.name = 'TruncateError'; 26 | this.message = message || 'truncate error'; 27 | Error.captureStackTrace(this); 28 | } 29 | TruncateError.prototype = Object.create(Error.prototype); 30 | TruncateError.prototype.constructor = TruncateError; 31 | exports.TruncateError = TruncateError; 32 | 33 | function CorruptedError(message) { 34 | this.name = 'CorruptedError'; 35 | this.message = message || 'corrupted error'; 36 | Error.captureStackTrace(this); 37 | } 38 | CorruptedError.prototype = Object.create(Error.prototype); 39 | CorruptedError.prototype.constructor = CorruptedError; 40 | exports.CorruptedError = CorruptedError; 41 | 42 | 43 | function BitPackedBuffer(contents, endian) { 44 | if (!endian) endian = 'big'; 45 | 46 | this._data = contents || []; 47 | this._used = 0; 48 | this._next = null; 49 | this._nextbits = 0; 50 | this._bigendian = endian === 'big'; 51 | } 52 | exports.BitPackedBuffer = BitPackedBuffer; 53 | 54 | BitPackedBuffer.prototype.toString = function() { 55 | console.log(this._data); 56 | return 'buffer(' + 57 | (this._nextbits && this._next || 0).toString(16) + '/' + this._nextbits + 58 | ',[' + this._used + ']=' + ((this._used < this._data.length) ? this._data.readUInt8(this._used).toString(16) : '--') + 59 | ')'; 60 | }; 61 | 62 | BitPackedBuffer.prototype.done = function() { 63 | return this._nextbits === 0 && this._used >= this._data.length; 64 | }; 65 | 66 | BitPackedBuffer.prototype.usedBits = function() { 67 | return this._used * 8 - this._nextbits; 68 | }; 69 | 70 | BitPackedBuffer.prototype.byteAlign = function() { 71 | this._nextbits = 0; 72 | }; 73 | 74 | BitPackedBuffer.prototype.readAlignedBytes = function(bytes) { 75 | this.byteAlign(); 76 | var data = this._data.slice(this._used, this._used + bytes); 77 | this._used += bytes; 78 | if (data.length !== bytes) { 79 | throw new TruncateError(this.toString()); 80 | } 81 | return data; 82 | }; 83 | 84 | BitPackedBuffer.prototype.readBits = function(bits) { 85 | var result = 0; 86 | var resultbits = 0; 87 | 88 | while (resultbits !== bits) { 89 | if (this._nextbits === 0) { 90 | if (this.done()) throw new TruncateError(this.toString()); 91 | this._next = this._data.readUInt8(this._used); 92 | this._used += 1; 93 | this._nextbits = 8; 94 | } 95 | 96 | var copybits = Math.min(bits - resultbits, this._nextbits); 97 | var copy = this._next & ((1 << copybits) - 1); 98 | 99 | if (this._bigendian) 100 | result |= copy << (bits - resultbits - copybits); 101 | else 102 | result |= copy << resultbits; 103 | 104 | this._next >>= copybits; 105 | this._nextbits -= copybits; 106 | resultbits += copybits; 107 | } 108 | 109 | return result; 110 | }; 111 | 112 | BitPackedBuffer.prototype.readUnalignedBytes = function(bytes) { 113 | // not sure, to test 114 | var buff = new Buffer(bytes); 115 | for (var i = 0; i < bytes; i += 1) { 116 | buff.writeUInt8(this.read(8)); 117 | } 118 | return buff.toString(); // should maybe return buffer instead of string? 119 | }; 120 | 121 | 122 | function BitPackedDecoder(contents, typeinfos) { 123 | this._buffer = new BitPackedBuffer(contents); 124 | this._typeinfos = typeinfos; 125 | } 126 | exports.BitPackedDecoder = BitPackedDecoder; 127 | 128 | BitPackedDecoder.prototype.toString = function() { 129 | return this._buffer.toString(); 130 | }; 131 | 132 | BitPackedDecoder.prototype.instance = function(typeid) { 133 | if (typeid >= this._typeinfos.length) throw new CorruptedError(this.toString()); 134 | var typeinfo = this._typeinfos[typeid]; 135 | return this[typeinfo[0]].apply(this, typeinfo[1]); 136 | }; 137 | 138 | BitPackedDecoder.prototype.byteAlign = function() { 139 | this._buffer.byteAlign(); 140 | }; 141 | 142 | BitPackedDecoder.prototype.done = function() { 143 | return this._buffer.done(); 144 | }; 145 | 146 | BitPackedDecoder.prototype.usedBits = function() { 147 | return this._buffer.usedBits(); 148 | }; 149 | 150 | BitPackedDecoder.prototype._array = function(bounds, typeid) { 151 | var length = this._int(bounds); 152 | var ar = []; 153 | for (var i = 0; i < length; i += 1) { 154 | ar[i] = this.instance(typeid); 155 | } 156 | return ar; 157 | }; 158 | 159 | BitPackedDecoder.prototype._bitarray = function(bounds) { 160 | var length = this._int(bounds); 161 | return [length, this._buffer.readBits(length)]; 162 | }; 163 | 164 | BitPackedDecoder.prototype._blob = function(bounds) { 165 | var length = this._int(bounds); 166 | return this._buffer.readAlignedBytes(length); 167 | }; 168 | 169 | BitPackedDecoder.prototype._bool = function() { 170 | return this._int([0, 1]) !== 0; 171 | }; 172 | 173 | BitPackedDecoder.prototype._choice = function(bounds, fields) { 174 | var tag = this._int(bounds); 175 | var field = fields[tag]; 176 | if (!field) throw new CorruptedError(this.toString()); 177 | var ret = {}; 178 | ret[field[0]] = this.instance(field[1]); 179 | return ret; 180 | }; 181 | 182 | BitPackedDecoder.prototype._fourcc = function() { 183 | return this._buffer.readUnalignedBytes(4); 184 | }; 185 | 186 | BitPackedDecoder.prototype._int = function(bounds) { 187 | var value = bounds[0] + this._buffer.readBits(bounds[1]); 188 | return value; 189 | }; 190 | 191 | BitPackedDecoder.prototype._null = function() { 192 | return null; 193 | }; 194 | 195 | BitPackedDecoder.prototype._optional = function(typeid) { 196 | var exists = this._bool(); 197 | return exists ? this.instance(typeid) : null; 198 | }; 199 | 200 | BitPackedDecoder.prototype._real32 = function() { 201 | return this._buffer.readUnalignedBytes(4).readFloatBE(0); 202 | }; 203 | 204 | BitPackedDecoder.prototype._real64 = function() { 205 | return this._buffer.readUnalignedBytes(8).readDoubleBE(0); 206 | }; 207 | 208 | BitPackedDecoder.prototype._struct = function(fields) { 209 | var result = {}; 210 | 211 | fields.forEach(field => { 212 | if (field[0] === '__parent') { 213 | var parent = this.instance(field[1]); 214 | if (parent && typeof parent === 'object' && !Array.isArray(parent)) { 215 | result = Object.assign(result, parent); 216 | } else if (fields.length === 0) { 217 | result = parent; 218 | } else { 219 | result[field[0]] = parent; 220 | } 221 | } else { 222 | result[field[0]] = this.instance(field[1]); 223 | } 224 | }); 225 | 226 | return result; 227 | }; 228 | 229 | 230 | function VersionDecoder(contents, typeinfos) { 231 | this._buffer = new BitPackedBuffer(contents); 232 | this._typeinfos = typeinfos; 233 | } 234 | exports.VersionDecoder = VersionDecoder; 235 | 236 | VersionDecoder.prototype.toString = function() { 237 | return this._buffer.toString(); 238 | }; 239 | 240 | VersionDecoder.prototype.instance = function(typeid) { 241 | if (typeid >= this._typeinfos.length) throw new CorruptedError(this.toString()); 242 | 243 | var typeinfo = this._typeinfos[typeid]; 244 | return this[typeinfo[0]].apply(this, typeinfo[1]); 245 | }; 246 | 247 | VersionDecoder.prototype.byteAlign = function() { 248 | this._buffer.byteAlign(); 249 | }; 250 | 251 | VersionDecoder.prototype.done = function() { 252 | return this._buffer.done(); 253 | }; 254 | 255 | VersionDecoder.prototype.usedBits = function() { 256 | return this._buffer.usedBits(); 257 | }; 258 | 259 | VersionDecoder.prototype._expectSkip = function(expected) { 260 | var r = this._buffer.readBits(8); 261 | if (r !== expected) throw new CorruptedError(this.toString()); 262 | }; 263 | 264 | VersionDecoder.prototype._vint = function() { 265 | var b = this._buffer.readBits(8); 266 | var negative = b & 1; 267 | var result = (b >> 1) & 0x3f; 268 | var bits = 6; 269 | 270 | while ((b & 0x80) !== 0) { 271 | b = this._buffer.readBits(8); 272 | result |= (b & 0x7f) << bits; 273 | bits += 7; 274 | } 275 | 276 | return negative ? -result : result; 277 | }; 278 | 279 | VersionDecoder.prototype._array = function(bounds, typeid) { 280 | this._expectSkip(0); 281 | var length = this._vint(); 282 | var ar = []; 283 | for (var i = 0; i < length; i += 1) { 284 | ar[i] = this.instance(typeid); 285 | } 286 | return ar; 287 | }; 288 | 289 | VersionDecoder.prototype._bitarray = function(bounds) { 290 | this._expectSkip(1); 291 | var length = this._vint(); 292 | return [length, this._buffer.readAlignedBytes((length + 7) / 8)]; 293 | }; 294 | 295 | VersionDecoder.prototype._blob = function(bounds) { 296 | this._expectSkip(2); 297 | var length = this._vint(); 298 | return this._buffer.readAlignedBytes(length); 299 | }; 300 | 301 | VersionDecoder.prototype._bool = function() { 302 | this._expectSkip(6); 303 | return this._buffer.readBits(8) !== 0; 304 | }; 305 | 306 | VersionDecoder.prototype._choice = function(bounds, fields) { 307 | this._expectSkip(3); 308 | var tag = this._vint(); 309 | var field = fields[tag]; 310 | if (!field) { 311 | this._skipInstance(); 312 | return {}; 313 | } 314 | var ret = {}; 315 | ret[field[0]] = this.instance(field[1]); 316 | return ret; 317 | }; 318 | 319 | VersionDecoder.prototype._fourcc = function() { 320 | this._expectSkip(7); 321 | return this._buffer.readAlignedBytes(4); 322 | }; 323 | 324 | VersionDecoder.prototype._int = function() { 325 | this._expectSkip(9); 326 | return this._vint(); 327 | }; 328 | 329 | VersionDecoder.prototype._null = function() { 330 | return null; 331 | }; 332 | 333 | VersionDecoder.prototype._optional = function(typeid) { 334 | this._expectSkip(4); 335 | var exists = this._buffer.readBits(8) !== 0; 336 | return exists ? this.instance(typeid) : null; 337 | }; 338 | 339 | VersionDecoder.prototype._real32 = function() { 340 | this._expectSkip(7); 341 | return this._buffer.readAlignedBytes(4).readFloatBE(0); 342 | }; 343 | 344 | VersionDecoder.prototype._real64 = function() { 345 | this._expectSkip(8); 346 | return this._buffer.readAlignedBytes(8).readDoubleBE(0); 347 | }; 348 | 349 | VersionDecoder.prototype._struct = function(fields) { 350 | function matchTag(tag) { 351 | return function (field) { 352 | return tag === field[2]; 353 | }; 354 | } 355 | this._expectSkip(5); 356 | 357 | var result = {}; 358 | var length = this._vint(); 359 | 360 | for (var i = 0; i < length; i += 1) { 361 | var tag = this._vint(); 362 | var field = fields.find(matchTag(tag)); 363 | 364 | if (field) { 365 | if (field[0] === '__parent') { 366 | var parent = this.instance(field[1]); 367 | if (parent && typeof parent === 'object' && !Array.isArray(parent)) { 368 | result = Object.assign(result, parent); 369 | } else if (fields.length === 0) { 370 | result = parent; 371 | } else { 372 | result[field[0]] = parent; 373 | } 374 | } else { 375 | result[field[0]] = this.instance(field[1]); 376 | } 377 | } else { 378 | this._skipInstance(); 379 | } 380 | } 381 | 382 | return result; 383 | }; 384 | 385 | VersionDecoder.prototype._skipInstance = function() { 386 | var skip = this._buffer.readBits(8), length, exists, i, tag; 387 | 388 | if (skip === 0) { // array 389 | length = this._vint(); 390 | for (i = 0; i < length; i += 1) { 391 | this._skipInstance(); 392 | } 393 | } else if (skip === 1) { // bitblob 394 | length = this._vint(); 395 | this._buffer.readAlignedBytes((length + 7) / 8); 396 | } else if (skip === 2) { // blob 397 | length = this._vint(); 398 | this._buffer.readAlignedBytes(length); 399 | } else if (skip === 3) { // choice 400 | tag = this._vint(); 401 | this._skipInstance(); 402 | } else if (skip === 4) { // optional 403 | exists = this._buffer.readBits(8) !== 0; 404 | if (exists) this._skipInstance(); 405 | } else if (skip === 5) { // struct 406 | length = this._vint(); 407 | for (i = 0; i < length; i += 1) { 408 | tag = this._vint(); 409 | this._skipInstance(); 410 | } 411 | } else if (skip === 6) { // u8 412 | this._buffer.readAlignedBytes(1); 413 | } else if (skip === 7) { // u32 414 | this._buffer.readAlignedBytes(4); 415 | } else if (skip === 8) { // u64 416 | this._buffer.readAlignedBytes(8); 417 | } else if (skip === 9) { // vint 418 | this._vint(); 419 | } 420 | }; 421 | -------------------------------------------------------------------------------- /lib/protocol29406.js: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright (c) 2015 Blizzard Entertainment 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | */ 22 | "use strict"; 23 | 24 | exports.version = 29406; 25 | 26 | const decoders = require('./decoders'); 27 | const BitPackedDecoder = decoders.BitPackedDecoder; 28 | const VersionDecoder = decoders.VersionDecoder; 29 | 30 | 31 | // Decoding instructions for each protocol type. 32 | const typeinfos = [ 33 | ['_int', [[0, 7]]], //0 34 | ['_int', [[0, 4]]], //1 35 | ['_int', [[0, 5]]], //2 36 | ['_int', [[0, 6]]], //3 37 | ['_int', [[0, 14]]], //4 38 | ['_int', [[0, 22]]], //5 39 | ['_int', [[0, 32]]], //6 40 | ['_choice', [[0, 2], { 0: ['m_uint6', 3], 1: ['m_uint14', 4], 2: ['m_uint22', 5], 3: ['m_uint32', 6]}]], //7 41 | ['_struct', [[['m_userId', 2, -1]]]], //8 42 | ['_blob', [[0, 8]]], //9 43 | ['_int', [[0, 8]]], //10 44 | ['_struct', [[['m_flags', 10, 0], ['m_major', 10, 1], ['m_minor', 10, 2], ['m_revision', 10, 3], ['m_build', 6, 4], ['m_baseBuild', 6, 5]]]], //11 45 | ['_int', [[0, 3]]], //12 46 | ['_bool', []], //13 47 | ['_array', [[16, 0], 10]], //14 48 | ['_optional', [14]], //15 49 | ['_struct', [[['m_data', 15, 0]]]], //16 50 | ['_struct', [[['m_signature', 9, 0], ['m_version', 11, 1], ['m_type', 12, 2], ['m_elapsedGameLoops', 6, 3], ['m_useScaledTime', 13, 4], ['m_ngdpRootKey', 16, 5], ['m_dataBuildNum', 6, 6]]]], //17 51 | ['_fourcc', []], //18 52 | ['_blob', [[0, 7]]], //19 53 | ['_int', [[0, 64]]], //20 54 | ['_struct', [[['m_region', 10, 0], ['m_programId', 18, 1], ['m_realm', 6, 2], ['m_name', 19, 3], ['m_id', 20, 4]]]], //21 55 | ['_struct', [[['m_a', 10, 0], ['m_r', 10, 1], ['m_g', 10, 2], ['m_b', 10, 3]]]], //22 56 | ['_int', [[0, 2]]], //23 57 | ['_optional', [10]], //24 58 | ['_struct', [[['m_name', 9, 0], ['m_toon', 21, 1], ['m_race', 9, 2], ['m_color', 22, 3], ['m_control', 10, 4], ['m_teamId', 1, 5], ['m_handicap', 0, 6], ['m_observe', 23, 7], ['m_result', 23, 8], ['m_workingSetSlotId', 24, 9], ['m_hero', 9, 10]]]], //25 59 | ['_array', [[0, 5], 25]], //26 60 | ['_optional', [26]], //27 61 | ['_blob', [[0, 10]]], //28 62 | ['_blob', [[0, 11]]], //29 63 | ['_struct', [[['m_file', 29, 0]]]], //30 64 | ['_optional', [13]], //31 65 | ['_int', [[-9223372036854775808, 64]]], //32 66 | ['_blob', [[0, 12]]], //33 67 | ['_blob', [[40, 0]]], //34 68 | ['_array', [[0, 6], 34]], //35 69 | ['_optional', [35]], //36 70 | ['_array', [[0, 6], 29]], //37 71 | ['_optional', [37]], //38 72 | ['_struct', [[['m_playerList', 27, 0], ['m_title', 28, 1], ['m_difficulty', 9, 2], ['m_thumbnail', 30, 3], ['m_isBlizzardMap', 13, 4], ['m_restartAsTransitionMap', 31, 16], ['m_timeUTC', 32, 5], ['m_timeLocalOffset', 32, 6], ['m_description', 33, 7], ['m_imageFilePath', 29, 8], ['m_campaignIndex', 10, 15], ['m_mapFileName', 29, 9], ['m_cacheHandles', 36, 10], ['m_miniSave', 13, 11], ['m_gameSpeed', 12, 12], ['m_defaultDifficulty', 3, 13], ['m_modPaths', 38, 14]]]], //39 73 | ['_optional', [9]], //40 74 | ['_optional', [34]], //41 75 | ['_optional', [6]], //42 76 | ['_struct', [[['m_race', 24, -1]]]], //43 77 | ['_struct', [[['m_team', 24, -1]]]], //44 78 | ['_blob', [[0, 9]]], //45 79 | ['_struct', [[['m_name', 9, -18], ['m_clanTag', 40, -17], ['m_clanLogo', 41, -16], ['m_highestLeague', 24, -15], ['m_combinedRaceLevels', 42, -14], ['m_randomSeed', 6, -13], ['m_racePreference', 43, -12], ['m_teamPreference', 44, -11], ['m_testMap', 13, -10], ['m_testAuto', 13, -9], ['m_examine', 13, -8], ['m_customInterface', 13, -7], ['m_testType', 6, -6], ['m_observe', 23, -5], ['m_hero', 45, -4], ['m_skin', 45, -3], ['m_mount', 45, -2], ['m_toonHandle', 19, -1]]]], //46 80 | ['_array', [[0, 5], 46]], //47 81 | ['_struct', [[['m_lockTeams', 13, -12], ['m_teamsTogether', 13, -11], ['m_advancedSharedControl', 13, -10], ['m_randomRaces', 13, -9], ['m_battleNet', 13, -8], ['m_amm', 13, -7], ['m_competitive', 13, -6], ['m_noVictoryOrDefeat', 13, -5], ['m_fog', 23, -4], ['m_observers', 23, -3], ['m_userDifficulty', 23, -2], ['m_clientDebugFlags', 20, -1]]]], //48 82 | ['_int', [[1, 4]]], //49 83 | ['_int', [[1, 8]]], //50 84 | ['_bitarray', [[0, 6]]], //51 85 | ['_bitarray', [[0, 8]]], //52 86 | ['_bitarray', [[0, 2]]], //53 87 | ['_bitarray', [[0, 7]]], //54 88 | ['_struct', [[['m_allowedColors', 51, -6], ['m_allowedRaces', 52, -5], ['m_allowedDifficulty', 51, -4], ['m_allowedControls', 52, -3], ['m_allowedObserveTypes', 53, -2], ['m_allowedAIBuilds', 54, -1]]]], //55 89 | ['_array', [[0, 5], 55]], //56 90 | ['_struct', [[['m_randomValue', 6, -26], ['m_gameCacheName', 28, -25], ['m_gameOptions', 48, -24], ['m_gameSpeed', 12, -23], ['m_gameType', 12, -22], ['m_maxUsers', 2, -21], ['m_maxObservers', 2, -20], ['m_maxPlayers', 2, -19], ['m_maxTeams', 49, -18], ['m_maxColors', 3, -17], ['m_maxRaces', 50, -16], ['m_maxControls', 10, -15], ['m_mapSizeX', 10, -14], ['m_mapSizeY', 10, -13], ['m_mapFileSyncChecksum', 6, -12], ['m_mapFileName', 29, -11], ['m_mapAuthorName', 9, -10], ['m_modFileSyncChecksum', 6, -9], ['m_slotDescriptions', 56, -8], ['m_defaultDifficulty', 3, -7], ['m_defaultAIBuild', 0, -6], ['m_cacheHandles', 35, -5], ['m_hasExtensionMod', 13, -4], ['m_isBlizzardMap', 13, -3], ['m_isPremadeFFA', 13, -2], ['m_isCoopMode', 13, -1]]]], //57 91 | ['_optional', [1]], //58 92 | ['_optional', [2]], //59 93 | ['_struct', [[['m_color', 59, -1]]]], //60 94 | ['_array', [[0, 17], 6]], //61 95 | ['_array', [[0, 9], 6]], //62 96 | ['_struct', [[['m_control', 10, -16], ['m_userId', 58, -15], ['m_teamId', 1, -14], ['m_colorPref', 60, -13], ['m_racePref', 43, -12], ['m_difficulty', 3, -11], ['m_aiBuild', 0, -10], ['m_handicap', 0, -9], ['m_observe', 23, -8], ['m_hero', 45, -7], ['m_skin', 45, -6], ['m_mount', 45, -5], ['m_workingSetSlotId', 24, -4], ['m_rewards', 61, -3], ['m_toonHandle', 19, -2], ['m_licenses', 62, -1]]]], //63 97 | ['_array', [[0, 5], 63]], //64 98 | ['_struct', [[['m_phase', 12, -10], ['m_maxUsers', 2, -9], ['m_maxObservers', 2, -8], ['m_slots', 64, -7], ['m_randomSeed', 6, -6], ['m_hostUserId', 58, -5], ['m_isSinglePlayer', 13, -4], ['m_gameDuration', 6, -3], ['m_defaultDifficulty', 3, -2], ['m_defaultAIBuild', 0, -1]]]], //65 99 | ['_struct', [[['m_userInitialData', 47, -3], ['m_gameDescription', 57, -2], ['m_lobbyState', 65, -1]]]], //66 100 | ['_struct', [[['m_syncLobbyState', 66, -1]]]], //67 101 | ['_struct', [[['m_name', 19, -1]]]], //68 102 | ['_blob', [[0, 6]]], //69 103 | ['_struct', [[['m_name', 69, -1]]]], //70 104 | ['_struct', [[['m_name', 69, -3], ['m_type', 6, -2], ['m_data', 19, -1]]]], //71 105 | ['_struct', [[['m_type', 6, -3], ['m_name', 69, -2], ['m_data', 33, -1]]]], //72 106 | ['_array', [[0, 5], 10]], //73 107 | ['_struct', [[['m_signature', 73, -2], ['m_toonHandle', 19, -1]]]], //74 108 | ['_struct', [[['m_gameFullyDownloaded', 13, -11], ['m_developmentCheatsEnabled', 13, -10], ['m_multiplayerCheatsEnabled', 13, -9], ['m_syncChecksummingEnabled', 13, -8], ['m_isMapToMapTransition', 13, -7], ['m_startingRally', 13, -6], ['m_debugPauseEnabled', 13, -5], ['m_platformMac', 13, -4], ['m_baseBuildNum', 6, -3], ['m_buildNum', 6, -2], ['m_versionFlags', 6, -1]]]], //75 109 | ['_struct', [[]]], //76 110 | ['_int', [[0, 16]]], //77 111 | ['_struct', [[['x', 77, -2], ['y', 77, -1]]]], //78 112 | ['_struct', [[['m_which', 12, -2], ['m_target', 78, -1]]]], //79 113 | ['_struct', [[['m_fileName', 29, -5], ['m_automatic', 13, -4], ['m_overwrite', 13, -3], ['m_name', 9, -2], ['m_description', 28, -1]]]], //80 114 | ['_int', [[-2147483648, 32]]], //81 115 | ['_struct', [[['x', 81, -2], ['y', 81, -1]]]], //82 116 | ['_struct', [[['m_point', 82, -4], ['m_time', 81, -3], ['m_verb', 28, -2], ['m_arguments', 28, -1]]]], //83 117 | ['_struct', [[['m_data', 83, -1]]]], //84 118 | ['_int', [[0, 21]]], //85 119 | ['_struct', [[['m_abilLink', 77, -3], ['m_abilCmdIndex', 2, -2], ['m_abilCmdData', 24, -1]]]], //86 120 | ['_optional', [86]], //87 121 | ['_null', []], //88 122 | ['_int', [[0, 20]]], //89 123 | ['_struct', [[['x', 89, -3], ['y', 89, -2], ['z', 81, -1]]]], //90 124 | ['_struct', [[['m_targetUnitFlags', 77, -7], ['m_timer', 10, -6], ['m_tag', 6, -5], ['m_snapshotUnitLink', 77, -4], ['m_snapshotControlPlayerId', 58, -3], ['m_snapshotUpkeepPlayerId', 58, -2], ['m_snapshotPoint', 90, -1]]]], //91 125 | ['_choice', [[0, 2], { 0: ['None', 88], 1: ['TargetPoint', 90], 2: ['TargetUnit', 91], 3: ['Data', 6]}]], //92 126 | ['_struct', [[['m_cmdFlags', 85, -5], ['m_abil', 87, -4], ['m_data', 92, -3], ['m_otherUnit', 42, -2], ['m_unitGroup', 42, -1]]]], //93 127 | ['_int', [[0, 9]]], //94 128 | ['_bitarray', [[0, 9]]], //95 129 | ['_array', [[0, 9], 94]], //96 130 | ['_choice', [[0, 2], { 0: ['None', 88], 1: ['Mask', 95], 2: ['OneIndices', 96], 3: ['ZeroIndices', 96]}]], //97 131 | ['_struct', [[['m_unitLink', 77, -4], ['m_subgroupPriority', 10, -3], ['m_intraSubgroupPriority', 10, -2], ['m_count', 94, -1]]]], //98 132 | ['_array', [[0, 9], 98]], //99 133 | ['_struct', [[['m_subgroupIndex', 94, -4], ['m_removeMask', 97, -3], ['m_addSubgroups', 99, -2], ['m_addUnitTags', 62, -1]]]], //100 134 | ['_struct', [[['m_controlGroupId', 1, -2], ['m_delta', 100, -1]]]], //101 135 | ['_struct', [[['m_controlGroupIndex', 1, -3], ['m_controlGroupUpdate', 23, -2], ['m_mask', 97, -1]]]], //102 136 | ['_struct', [[['m_count', 94, -6], ['m_subgroupCount', 94, -5], ['m_activeSubgroupIndex', 94, -4], ['m_unitTagsChecksum', 6, -3], ['m_subgroupIndicesChecksum', 6, -2], ['m_subgroupsChecksum', 6, -1]]]], //103 137 | ['_struct', [[['m_controlGroupId', 1, -2], ['m_selectionSyncData', 103, -1]]]], //104 138 | ['_array', [[0, 3], 81]], //105 139 | ['_struct', [[['m_recipientId', 1, -2], ['m_resources', 105, -1]]]], //106 140 | ['_struct', [[['m_chatMessage', 28, -1]]]], //107 141 | ['_int', [[-128, 8]]], //108 142 | ['_struct', [[['x', 81, -3], ['y', 81, -2], ['z', 81, -1]]]], //109 143 | ['_struct', [[['m_beacon', 108, -9], ['m_ally', 108, -8], ['m_flags', 108, -7], ['m_build', 108, -6], ['m_targetUnitTag', 6, -5], ['m_targetUnitSnapshotUnitLink', 77, -4], ['m_targetUnitSnapshotUpkeepPlayerId', 108, -3], ['m_targetUnitSnapshotControlPlayerId', 108, -2], ['m_targetPoint', 109, -1]]]], //110 144 | ['_struct', [[['m_speed', 12, -1]]]], //111 145 | ['_struct', [[['m_delta', 108, -1]]]], //112 146 | ['_struct', [[['m_point', 82, -4], ['m_unit', 6, -3], ['m_pingedMinimap', 13, -2], ['m_option', 81, -1]]]], //113 147 | ['_struct', [[['m_verb', 28, -2], ['m_arguments', 28, -1]]]], //114 148 | ['_struct', [[['m_alliance', 6, -2], ['m_control', 6, -1]]]], //115 149 | ['_struct', [[['m_unitTag', 6, -1]]]], //116 150 | ['_struct', [[['m_unitTag', 6, -2], ['m_flags', 10, -1]]]], //117 151 | ['_struct', [[['m_conversationId', 81, -2], ['m_replyId', 81, -1]]]], //118 152 | ['_optional', [19]], //119 153 | ['_struct', [[['m_gameUserId', 1, -6], ['m_observe', 23, -5], ['m_name', 9, -4], ['m_toonHandle', 119, -3], ['m_clanTag', 40, -2], ['m_clanLogo', 41, -1]]]], //120 154 | ['_array', [[0, 5], 120]], //121 155 | ['_int', [[0, 1]]], //122 156 | ['_struct', [[['m_userInfos', 121, -2], ['m_method', 122, -1]]]], //123 157 | ['_struct', [[['m_purchaseItemId', 81, -1]]]], //124 158 | ['_struct', [[['m_difficultyLevel', 81, -1]]]], //125 159 | ['_choice', [[0, 3], { 0: ['None', 88], 1: ['Checked', 13], 2: ['ValueChanged', 6], 3: ['SelectionChanged', 81], 4: ['TextChanged', 29], 5: ['MouseButton', 6]}]], //126 160 | ['_struct', [[['m_controlId', 81, -3], ['m_eventType', 81, -2], ['m_eventData', 126, -1]]]], //127 161 | ['_struct', [[['m_soundHash', 6, -2], ['m_length', 6, -1]]]], //128 162 | ['_array', [[0, 7], 6]], //129 163 | ['_struct', [[['m_soundHash', 129, -2], ['m_length', 129, -1]]]], //130 164 | ['_struct', [[['m_syncInfo', 130, -1]]]], //131 165 | ['_struct', [[['m_queryId', 77, -3], ['m_lengthMs', 6, -2], ['m_finishGameLoop', 6, -1]]]], //132 166 | ['_struct', [[['m_queryId', 77, -2], ['m_lengthMs', 6, -1]]]], //133 167 | ['_struct', [[['m_animWaitQueryId', 77, -1]]]], //134 168 | ['_struct', [[['m_sound', 6, -1]]]], //135 169 | ['_struct', [[['m_transmissionId', 81, -2], ['m_thread', 6, -1]]]], //136 170 | ['_struct', [[['m_transmissionId', 81, -1]]]], //137 171 | ['_optional', [78]], //138 172 | ['_optional', [77]], //139 173 | ['_optional', [108]], //140 174 | ['_struct', [[['m_target', 138, -5], ['m_distance', 139, -4], ['m_pitch', 139, -3], ['m_yaw', 139, -2], ['m_reason', 140, -1]]]], //141 175 | ['_struct', [[['m_skipType', 122, -1]]]], //142 176 | ['_int', [[0, 11]]], //143 177 | ['_struct', [[['x', 143, -2], ['y', 143, -1]]]], //144 178 | ['_struct', [[['m_button', 6, -5], ['m_down', 13, -4], ['m_posUI', 144, -3], ['m_posWorld', 90, -2], ['m_flags', 108, -1]]]], //145 179 | ['_struct', [[['m_posUI', 144, -3], ['m_posWorld', 90, -2], ['m_flags', 108, -1]]]], //146 180 | ['_struct', [[['m_achievementLink', 77, -1]]]], //147 181 | ['_struct', [[['m_abilLink', 77, -3], ['m_abilCmdIndex', 2, -2], ['m_state', 108, -1]]]], //148 182 | ['_struct', [[['m_soundtrack', 6, -1]]]], //149 183 | ['_struct', [[['m_planetId', 81, -1]]]], //150 184 | ['_struct', [[['m_key', 108, -2], ['m_flags', 108, -1]]]], //151 185 | ['_struct', [[['m_resources', 105, -1]]]], //152 186 | ['_struct', [[['m_fulfillRequestId', 81, -1]]]], //153 187 | ['_struct', [[['m_cancelRequestId', 81, -1]]]], //154 188 | ['_struct', [[['m_researchItemId', 81, -1]]]], //155 189 | ['_struct', [[['m_mercenaryId', 81, -1]]]], //156 190 | ['_struct', [[['m_battleReportId', 81, -2], ['m_difficultyLevel', 81, -1]]]], //157 191 | ['_struct', [[['m_battleReportId', 81, -1]]]], //158 192 | ['_int', [[0, 19]]], //159 193 | ['_struct', [[['m_decrementMs', 159, -1]]]], //160 194 | ['_struct', [[['m_portraitId', 81, -1]]]], //161 195 | ['_struct', [[['m_functionName', 19, -1]]]], //162 196 | ['_struct', [[['m_result', 81, -1]]]], //163 197 | ['_struct', [[['m_gameMenuItemIndex', 81, -1]]]], //164 198 | ['_struct', [[['m_purchaseCategoryId', 81, -1]]]], //165 199 | ['_struct', [[['m_button', 77, -1]]]], //166 200 | ['_struct', [[['m_cutsceneId', 81, -2], ['m_bookmarkName', 19, -1]]]], //167 201 | ['_struct', [[['m_cutsceneId', 81, -1]]]], //168 202 | ['_struct', [[['m_cutsceneId', 81, -3], ['m_conversationLine', 19, -2], ['m_altConversationLine', 19, -1]]]], //169 203 | ['_struct', [[['m_cutsceneId', 81, -2], ['m_conversationLine', 19, -1]]]], //170 204 | ['_struct', [[['m_observe', 23, -5], ['m_name', 9, -4], ['m_toonHandle', 119, -3], ['m_clanTag', 40, -2], ['m_clanLogo', 41, -1]]]], //171 205 | ['_struct', [[['m_state', 23, -1]]]], //172 206 | ['_struct', [[['m_target', 90, -1]]]], //173 207 | ['_struct', [[['m_target', 91, -1]]]], //174 208 | ['_struct', [[['m_catalog', 10, -4], ['m_entry', 77, -3], ['m_field', 9, -2], ['m_value', 9, -1]]]], //175 209 | ['_struct', [[['m_heroLink', 77, -4], ['m_talentLink', 77, -3], ['m_tier', 6, -2], ['m_column', 6, -1]]]], //176 210 | ['_struct', [[['m_talent', 176, -1]]]], //177 211 | ['_struct', [[['m_recipient', 12, -2], ['m_string', 29, -1]]]], //178 212 | ['_struct', [[['m_recipient', 12, -2], ['m_point', 82, -1]]]], //179 213 | ['_struct', [[['m_progress', 81, -1]]]], //180 214 | ['_struct', [[['m_status', 23, -1]]]], //181 215 | ['_struct', [[['m_scoreValueMineralsCurrent', 81, 0], ['m_scoreValueVespeneCurrent', 81, 1], ['m_scoreValueMineralsCollectionRate', 81, 2], ['m_scoreValueVespeneCollectionRate', 81, 3], ['m_scoreValueWorkersActiveCount', 81, 4], ['m_scoreValueMineralsUsedInProgressArmy', 81, 5], ['m_scoreValueMineralsUsedInProgressEconomy', 81, 6], ['m_scoreValueMineralsUsedInProgressTechnology', 81, 7], ['m_scoreValueVespeneUsedInProgressArmy', 81, 8], ['m_scoreValueVespeneUsedInProgressEconomy', 81, 9], ['m_scoreValueVespeneUsedInProgressTechnology', 81, 10], ['m_scoreValueMineralsUsedCurrentArmy', 81, 11], ['m_scoreValueMineralsUsedCurrentEconomy', 81, 12], ['m_scoreValueMineralsUsedCurrentTechnology', 81, 13], ['m_scoreValueVespeneUsedCurrentArmy', 81, 14], ['m_scoreValueVespeneUsedCurrentEconomy', 81, 15], ['m_scoreValueVespeneUsedCurrentTechnology', 81, 16], ['m_scoreValueMineralsLostArmy', 81, 17], ['m_scoreValueMineralsLostEconomy', 81, 18], ['m_scoreValueMineralsLostTechnology', 81, 19], ['m_scoreValueVespeneLostArmy', 81, 20], ['m_scoreValueVespeneLostEconomy', 81, 21], ['m_scoreValueVespeneLostTechnology', 81, 22], ['m_scoreValueMineralsKilledArmy', 81, 23], ['m_scoreValueMineralsKilledEconomy', 81, 24], ['m_scoreValueMineralsKilledTechnology', 81, 25], ['m_scoreValueVespeneKilledArmy', 81, 26], ['m_scoreValueVespeneKilledEconomy', 81, 27], ['m_scoreValueVespeneKilledTechnology', 81, 28], ['m_scoreValueFoodUsed', 81, 29], ['m_scoreValueFoodMade', 81, 30], ['m_scoreValueMineralsUsedActiveForces', 81, 31], ['m_scoreValueVespeneUsedActiveForces', 81, 32], ['m_scoreValueMineralsFriendlyFireArmy', 81, 33], ['m_scoreValueMineralsFriendlyFireEconomy', 81, 34], ['m_scoreValueMineralsFriendlyFireTechnology', 81, 35], ['m_scoreValueVespeneFriendlyFireArmy', 81, 36], ['m_scoreValueVespeneFriendlyFireEconomy', 81, 37], ['m_scoreValueVespeneFriendlyFireTechnology', 81, 38]]]], //182 216 | ['_struct', [[['m_playerId', 1, 0], ['m_stats', 182, 1]]]], //183 217 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1], ['m_unitTypeName', 28, 2], ['m_controlPlayerId', 1, 3], ['m_upkeepPlayerId', 1, 4], ['m_x', 10, 5], ['m_y', 10, 6]]]], //184 218 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1], ['m_killerPlayerId', 58, 2], ['m_x', 10, 3], ['m_y', 10, 4], ['m_killerUnitTagIndex', 42, 5], ['m_killerUnitTagRecycle', 42, 6]]]], //185 219 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1], ['m_controlPlayerId', 1, 2], ['m_upkeepPlayerId', 1, 3]]]], //186 220 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1], ['m_unitTypeName', 28, 2]]]], //187 221 | ['_struct', [[['m_playerId', 1, 0], ['m_upgradeTypeName', 28, 1], ['m_count', 81, 2]]]], //188 222 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1]]]], //189 223 | ['_array', [[0, 10], 81]], //190 224 | ['_struct', [[['m_firstUnitIndex', 6, 0], ['m_items', 190, 1]]]], //191 225 | ['_struct', [[['m_playerId', 1, 0], ['m_type', 6, 1], ['m_userId', 42, 2], ['m_slotId', 42, 3]]]] //192 226 | ]; 227 | 228 | // Map from protocol NNet.Game.*Event eventid to [typeid, name] 229 | const game_event_types = { 230 | 5: [76, 'NNet.Game.SUserFinishedLoadingSyncEvent'], 231 | 7: [75, 'NNet.Game.SUserOptionsEvent'], 232 | 9: [68, 'NNet.Game.SBankFileEvent'], 233 | 10: [70, 'NNet.Game.SBankSectionEvent'], 234 | 11: [71, 'NNet.Game.SBankKeyEvent'], 235 | 12: [72, 'NNet.Game.SBankValueEvent'], 236 | 13: [74, 'NNet.Game.SBankSignatureEvent'], 237 | 14: [79, 'NNet.Game.SCameraSaveEvent'], 238 | 21: [80, 'NNet.Game.SSaveGameEvent'], 239 | 22: [76, 'NNet.Game.SSaveGameDoneEvent'], 240 | 23: [76, 'NNet.Game.SLoadGameDoneEvent'], 241 | 26: [84, 'NNet.Game.SGameCheatEvent'], 242 | 27: [93, 'NNet.Game.SCmdEvent'], 243 | 28: [101, 'NNet.Game.SSelectionDeltaEvent'], 244 | 29: [102, 'NNet.Game.SControlGroupUpdateEvent'], 245 | 30: [104, 'NNet.Game.SSelectionSyncCheckEvent'], 246 | 31: [106, 'NNet.Game.SResourceTradeEvent'], 247 | 32: [107, 'NNet.Game.STriggerChatMessageEvent'], 248 | 33: [110, 'NNet.Game.SAICommunicateEvent'], 249 | 34: [111, 'NNet.Game.SSetAbsoluteGameSpeedEvent'], 250 | 35: [112, 'NNet.Game.SAddAbsoluteGameSpeedEvent'], 251 | 36: [113, 'NNet.Game.STriggerPingEvent'], 252 | 37: [114, 'NNet.Game.SBroadcastCheatEvent'], 253 | 38: [115, 'NNet.Game.SAllianceEvent'], 254 | 39: [116, 'NNet.Game.SUnitClickEvent'], 255 | 40: [117, 'NNet.Game.SUnitHighlightEvent'], 256 | 41: [118, 'NNet.Game.STriggerReplySelectedEvent'], 257 | 43: [123, 'NNet.Game.SHijackReplayGameEvent'], 258 | 44: [76, 'NNet.Game.STriggerSkippedEvent'], 259 | 45: [128, 'NNet.Game.STriggerSoundLengthQueryEvent'], 260 | 46: [135, 'NNet.Game.STriggerSoundOffsetEvent'], 261 | 47: [136, 'NNet.Game.STriggerTransmissionOffsetEvent'], 262 | 48: [137, 'NNet.Game.STriggerTransmissionCompleteEvent'], 263 | 49: [141, 'NNet.Game.SCameraUpdateEvent'], 264 | 50: [76, 'NNet.Game.STriggerAbortMissionEvent'], 265 | 51: [124, 'NNet.Game.STriggerPurchaseMadeEvent'], 266 | 52: [76, 'NNet.Game.STriggerPurchaseExitEvent'], 267 | 53: [125, 'NNet.Game.STriggerPlanetMissionLaunchedEvent'], 268 | 54: [76, 'NNet.Game.STriggerPlanetPanelCanceledEvent'], 269 | 55: [127, 'NNet.Game.STriggerDialogControlEvent'], 270 | 56: [131, 'NNet.Game.STriggerSoundLengthSyncEvent'], 271 | 57: [142, 'NNet.Game.STriggerConversationSkippedEvent'], 272 | 58: [145, 'NNet.Game.STriggerMouseClickedEvent'], 273 | 59: [146, 'NNet.Game.STriggerMouseMovedEvent'], 274 | 60: [147, 'NNet.Game.SAchievementAwardedEvent'], 275 | 62: [148, 'NNet.Game.STriggerTargetModeUpdateEvent'], 276 | 63: [76, 'NNet.Game.STriggerPlanetPanelReplayEvent'], 277 | 64: [149, 'NNet.Game.STriggerSoundtrackDoneEvent'], 278 | 65: [150, 'NNet.Game.STriggerPlanetMissionSelectedEvent'], 279 | 66: [151, 'NNet.Game.STriggerKeyPressedEvent'], 280 | 67: [162, 'NNet.Game.STriggerMovieFunctionEvent'], 281 | 68: [76, 'NNet.Game.STriggerPlanetPanelBirthCompleteEvent'], 282 | 69: [76, 'NNet.Game.STriggerPlanetPanelDeathCompleteEvent'], 283 | 70: [152, 'NNet.Game.SResourceRequestEvent'], 284 | 71: [153, 'NNet.Game.SResourceRequestFulfillEvent'], 285 | 72: [154, 'NNet.Game.SResourceRequestCancelEvent'], 286 | 73: [76, 'NNet.Game.STriggerResearchPanelExitEvent'], 287 | 74: [76, 'NNet.Game.STriggerResearchPanelPurchaseEvent'], 288 | 75: [155, 'NNet.Game.STriggerResearchPanelSelectionChangedEvent'], 289 | 77: [76, 'NNet.Game.STriggerMercenaryPanelExitEvent'], 290 | 78: [76, 'NNet.Game.STriggerMercenaryPanelPurchaseEvent'], 291 | 79: [156, 'NNet.Game.STriggerMercenaryPanelSelectionChangedEvent'], 292 | 80: [76, 'NNet.Game.STriggerVictoryPanelExitEvent'], 293 | 81: [76, 'NNet.Game.STriggerBattleReportPanelExitEvent'], 294 | 82: [157, 'NNet.Game.STriggerBattleReportPanelPlayMissionEvent'], 295 | 83: [158, 'NNet.Game.STriggerBattleReportPanelPlaySceneEvent'], 296 | 84: [158, 'NNet.Game.STriggerBattleReportPanelSelectionChangedEvent'], 297 | 85: [125, 'NNet.Game.STriggerVictoryPanelPlayMissionAgainEvent'], 298 | 86: [76, 'NNet.Game.STriggerMovieStartedEvent'], 299 | 87: [76, 'NNet.Game.STriggerMovieFinishedEvent'], 300 | 88: [160, 'NNet.Game.SDecrementGameTimeRemainingEvent'], 301 | 89: [161, 'NNet.Game.STriggerPortraitLoadedEvent'], 302 | 90: [163, 'NNet.Game.STriggerCustomDialogDismissedEvent'], 303 | 91: [164, 'NNet.Game.STriggerGameMenuItemSelectedEvent'], 304 | 93: [124, 'NNet.Game.STriggerPurchasePanelSelectedPurchaseItemChangedEvent'], 305 | 94: [165, 'NNet.Game.STriggerPurchasePanelSelectedPurchaseCategoryChangedEvent'], 306 | 95: [166, 'NNet.Game.STriggerButtonPressedEvent'], 307 | 96: [76, 'NNet.Game.STriggerGameCreditsFinishedEvent'], 308 | 97: [167, 'NNet.Game.STriggerCutsceneBookmarkFiredEvent'], 309 | 98: [168, 'NNet.Game.STriggerCutsceneEndSceneFiredEvent'], 310 | 99: [169, 'NNet.Game.STriggerCutsceneConversationLineEvent'], 311 | 100: [170, 'NNet.Game.STriggerCutsceneConversationLineMissingEvent'], 312 | 101: [76, 'NNet.Game.SGameUserLeaveEvent'], 313 | 102: [171, 'NNet.Game.SGameUserJoinEvent'], 314 | 103: [172, 'NNet.Game.SCommandManagerStateEvent'], 315 | 104: [173, 'NNet.Game.SCommandManagerTargetPointEvent'], 316 | 105: [174, 'NNet.Game.SCommandManagerTargetUnitEvent'], 317 | 106: [132, 'NNet.Game.STriggerAnimLengthQueryByNameEvent'], 318 | 107: [133, 'NNet.Game.STriggerAnimLengthQueryByPropsEvent'], 319 | 108: [134, 'NNet.Game.STriggerAnimOffsetEvent'], 320 | 109: [175, 'NNet.Game.SCatalogModifyEvent'], 321 | 110: [177, 'NNet.Game.SHeroTalentSelectedEvent'] 322 | }; 323 | 324 | // The typeid of the NNet.Game.EEventId enum. 325 | const game_eventid_typeid = 0; 326 | 327 | // Map from protocol NNet.Game.*Message eventid to [typeid, name] 328 | const message_event_types = { 329 | 0: [178, 'NNet.Game.SChatMessage'], 330 | 1: [179, 'NNet.Game.SPingMessage'], 331 | 2: [180, 'NNet.Game.SLoadingProgressMessage'], 332 | 3: [76, 'NNet.Game.SServerPingMessage'], 333 | 4: [181, 'NNet.Game.SReconnectNotifyMessage'] 334 | }; 335 | 336 | // The typeid of the NNet.Game.EMessageId enum. 337 | const message_eventid_typeid = 1; 338 | 339 | // Map from protocol NNet.Replay.Tracker.*Event eventid to [typeid, name] 340 | const tracker_event_types = { 341 | 0: [183, 'NNet.Replay.Tracker.SPlayerStatsEvent'], 342 | 1: [184, 'NNet.Replay.Tracker.SUnitBornEvent'], 343 | 2: [185, 'NNet.Replay.Tracker.SUnitDiedEvent'], 344 | 3: [186, 'NNet.Replay.Tracker.SUnitOwnerChangeEvent'], 345 | 4: [187, 'NNet.Replay.Tracker.SUnitTypeChangeEvent'], 346 | 5: [188, 'NNet.Replay.Tracker.SUpgradeEvent'], 347 | 6: [184, 'NNet.Replay.Tracker.SUnitInitEvent'], 348 | 7: [189, 'NNet.Replay.Tracker.SUnitDoneEvent'], 349 | 8: [191, 'NNet.Replay.Tracker.SUnitPositionsEvent'], 350 | 9: [192, 'NNet.Replay.Tracker.SPlayerSetupEvent'] 351 | }; 352 | 353 | // The typeid of the NNet.Replay.Tracker.EEventId enum. 354 | const tracker_eventid_typeid = 2; 355 | 356 | // The typeid of NNet.SVarUint32 (the type used to encode gameloop deltas). 357 | const svaruint32_typeid = 7; 358 | 359 | // The typeid of NNet.Replay.SGameUserId (the type used to encode player ids). 360 | const replay_userid_typeid = 8; 361 | 362 | // The typeid of NNet.Replay.SHeader (the type used to store replay game version and length). 363 | const replay_header_typeid = 17; 364 | 365 | // The typeid of NNet.Game.SDetails (the type used to store overall replay details). 366 | const game_details_typeid = 39; 367 | 368 | // The typeid of NNet.Replay.SInitData (the type used to store the inital lobby). 369 | const replay_initdata_typeid = 67; 370 | 371 | // not sure if correct port 372 | function _varuint32Value(value) { 373 | // Returns the numeric value from a SVarUint32 instance. 374 | return value[Object.keys(value)[0]]; 375 | } 376 | 377 | function* _decode_event_stream(decoder, eventidTypeid, eventTypes, decodeUserId) { 378 | // Decodes events prefixed with a gameloop and possibly userid 379 | var gameloop = 0; 380 | while (!decoder.done()) { 381 | var startBits = decoder.usedBits(); 382 | 383 | // decode the gameloop delta before each event 384 | var delta = _varuint32Value(decoder.instance(svaruint32_typeid)); 385 | gameloop += delta; 386 | 387 | // decode the userid before each event 388 | var userid = (decodeUserId === true) ? decoder.instance(replay_userid_typeid) : undefined; 389 | 390 | // decode the event id 391 | var eventid = decoder.instance(eventidTypeid); 392 | var eventType = eventTypes[eventid] || [null, null]; 393 | var typeid = eventType[0]; 394 | var typename = eventType[1]; 395 | if (typeid === null) throw new decoders.CorruptedError('eventid(' + eventid + ') at ' + decoder.toString()); 396 | 397 | // decode the event struct instance 398 | var event = decoder.instance(typeid); 399 | event._event = typename; 400 | event._eventid = eventid; 401 | 402 | // insert gameloop and userid 403 | event._gameloop = gameloop; 404 | if (decodeUserId) event._userid = userid; 405 | 406 | // the next event is byte aligned 407 | decoder.byteAlign(); 408 | 409 | // insert bits used in stream 410 | event._bits = decoder.usedBits() - startBits; 411 | 412 | yield event; 413 | } 414 | } 415 | 416 | exports.decodeReplayGameEvents = function* (contents) { 417 | // Decodes and yields each game event from the contents byte string. 418 | const decoder = new BitPackedDecoder(contents, typeinfos); 419 | for (let event of _decode_event_stream(decoder, game_eventid_typeid, game_event_types, true)) 420 | yield event; 421 | }; 422 | 423 | exports.decodeReplayMessageEvents = function* (contents) { 424 | // Decodes and yields each message event from the contents byte string. 425 | const decoder = new BitPackedDecoder(contents, typeinfos); 426 | for (let event of _decode_event_stream(decoder, message_eventid_typeid, message_event_types, true)) 427 | yield event; 428 | }; 429 | 430 | exports.decodeReplayTrackerEvents = function* (contents) { 431 | // Decodes and yields each tracker event from the contents byte string. 432 | const decoder = new VersionDecoder(contents, typeinfos); 433 | for (let event of _decode_event_stream(decoder, tracker_eventid_typeid, tracker_event_types, false)) 434 | yield event; 435 | }; 436 | 437 | exports.decodeReplayHeader = function(contents) { 438 | // Decodes and return the replay header from the contents byte string. 439 | const decoder = new VersionDecoder(contents, typeinfos); 440 | return decoder.instance(replay_header_typeid); 441 | }; 442 | 443 | exports.decodeReplayDetails = function(contents) { 444 | // Decodes and returns the game details from the contents byte string. 445 | const decoder = new VersionDecoder(contents, typeinfos); 446 | return decoder.instance(game_details_typeid); 447 | }; 448 | 449 | exports.decodeReplayInitdata = function(contents) { 450 | // Decodes and return the replay init data from the contents byte string. 451 | const decoder = new BitPackedDecoder(contents, typeinfos); 452 | return decoder.instance(replay_initdata_typeid); 453 | }; 454 | 455 | exports.decodeReplayAttributesEvents = function (contents) { 456 | // Decodes and yields each attribute from the contents byte string. 457 | const buffer = new decoders.BitPackedBuffer(contents, 'little'); 458 | const attributes = {}; 459 | 460 | if (!buffer.done()) { 461 | attributes.source = buffer.readBits(8); 462 | attributes.mapNameSpace = buffer.readBits(32); 463 | var count = buffer.readBits(32); 464 | attributes.scopes = {}; 465 | 466 | while (!buffer.done()) { 467 | var value = {}; 468 | value.namespace = buffer.readBits(32); 469 | var attrid = value.attrid = buffer.readBits(32); 470 | var scope = buffer.readBits(8); 471 | value.value = buffer.readAlignedBytes(4).reverse(); 472 | while (value.value[0] === 0) value.value = value.value.slice(1); 473 | while (value.value[value.value.length - 1] === 0) value.value = value.value.slice(0, -1); 474 | if (!attributes.scopes[scope]) 475 | attributes.scopes[scope] = {}; 476 | if (!attributes.scopes[scope][attrid]) 477 | attributes.scopes[scope][attrid] = []; 478 | attributes.scopes[scope][attrid].push(value); 479 | } 480 | } 481 | 482 | return attributes; 483 | }; 484 | 485 | exports.unitTag = function(unitTagIndex, unitTagRecycle) { 486 | return (unitTagIndex << 18) + unitTagRecycle; 487 | }; 488 | 489 | exports.unitTagIndex = function(unitTag) { 490 | return (unitTag >> 18) & 0x00003FFF; 491 | }; 492 | 493 | exports.unitTagRecycle = function(unitTag) { 494 | return unitTag & 0x0003FFFF; 495 | }; 496 | -------------------------------------------------------------------------------- /lib/protocol30414.js: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright (c) 2015 Blizzard Entertainment 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | */ 22 | "use strict"; 23 | 24 | exports.version = 30414; 25 | 26 | const decoders = require('./decoders'); 27 | const BitPackedDecoder = decoders.BitPackedDecoder; 28 | const VersionDecoder = decoders.VersionDecoder; 29 | 30 | 31 | // Decoding instructions for each protocol type. 32 | const typeinfos = [ 33 | ['_int', [[0, 7]]], //0 34 | ['_int', [[0, 4]]], //1 35 | ['_int', [[0, 5]]], //2 36 | ['_int', [[0, 6]]], //3 37 | ['_int', [[0, 14]]], //4 38 | ['_int', [[0, 22]]], //5 39 | ['_int', [[0, 32]]], //6 40 | ['_choice', [[0, 2], { 0: ['m_uint6', 3], 1: ['m_uint14', 4], 2: ['m_uint22', 5], 3: ['m_uint32', 6]}]], //7 41 | ['_struct', [[['m_userId', 2, -1]]]], //8 42 | ['_blob', [[0, 8]]], //9 43 | ['_int', [[0, 8]]], //10 44 | ['_struct', [[['m_flags', 10, 0], ['m_major', 10, 1], ['m_minor', 10, 2], ['m_revision', 10, 3], ['m_build', 6, 4], ['m_baseBuild', 6, 5]]]], //11 45 | ['_int', [[0, 3]]], //12 46 | ['_bool', []], //13 47 | ['_array', [[16, 0], 10]], //14 48 | ['_optional', [14]], //15 49 | ['_struct', [[['m_data', 15, 0]]]], //16 50 | ['_struct', [[['m_signature', 9, 0], ['m_version', 11, 1], ['m_type', 12, 2], ['m_elapsedGameLoops', 6, 3], ['m_useScaledTime', 13, 4], ['m_ngdpRootKey', 16, 5], ['m_dataBuildNum', 6, 6]]]], //17 51 | ['_fourcc', []], //18 52 | ['_blob', [[0, 7]]], //19 53 | ['_int', [[0, 64]]], //20 54 | ['_struct', [[['m_region', 10, 0], ['m_programId', 18, 1], ['m_realm', 6, 2], ['m_name', 19, 3], ['m_id', 20, 4]]]], //21 55 | ['_struct', [[['m_a', 10, 0], ['m_r', 10, 1], ['m_g', 10, 2], ['m_b', 10, 3]]]], //22 56 | ['_int', [[0, 2]]], //23 57 | ['_optional', [10]], //24 58 | ['_struct', [[['m_name', 9, 0], ['m_toon', 21, 1], ['m_race', 9, 2], ['m_color', 22, 3], ['m_control', 10, 4], ['m_teamId', 1, 5], ['m_handicap', 0, 6], ['m_observe', 23, 7], ['m_result', 23, 8], ['m_workingSetSlotId', 24, 9], ['m_hero', 9, 10]]]], //25 59 | ['_array', [[0, 5], 25]], //26 60 | ['_optional', [26]], //27 61 | ['_blob', [[0, 10]]], //28 62 | ['_blob', [[0, 11]]], //29 63 | ['_struct', [[['m_file', 29, 0]]]], //30 64 | ['_optional', [13]], //31 65 | ['_int', [[-9223372036854775808, 64]]], //32 66 | ['_blob', [[0, 12]]], //33 67 | ['_blob', [[40, 0]]], //34 68 | ['_array', [[0, 6], 34]], //35 69 | ['_optional', [35]], //36 70 | ['_array', [[0, 6], 29]], //37 71 | ['_optional', [37]], //38 72 | ['_struct', [[['m_playerList', 27, 0], ['m_title', 28, 1], ['m_difficulty', 9, 2], ['m_thumbnail', 30, 3], ['m_isBlizzardMap', 13, 4], ['m_restartAsTransitionMap', 31, 16], ['m_timeUTC', 32, 5], ['m_timeLocalOffset', 32, 6], ['m_description', 33, 7], ['m_imageFilePath', 29, 8], ['m_campaignIndex', 10, 15], ['m_mapFileName', 29, 9], ['m_cacheHandles', 36, 10], ['m_miniSave', 13, 11], ['m_gameSpeed', 12, 12], ['m_defaultDifficulty', 3, 13], ['m_modPaths', 38, 14]]]], //39 73 | ['_optional', [9]], //40 74 | ['_optional', [34]], //41 75 | ['_optional', [6]], //42 76 | ['_struct', [[['m_race', 24, -1]]]], //43 77 | ['_struct', [[['m_team', 24, -1]]]], //44 78 | ['_blob', [[0, 9]]], //45 79 | ['_struct', [[['m_name', 9, -18], ['m_clanTag', 40, -17], ['m_clanLogo', 41, -16], ['m_highestLeague', 24, -15], ['m_combinedRaceLevels', 42, -14], ['m_randomSeed', 6, -13], ['m_racePreference', 43, -12], ['m_teamPreference', 44, -11], ['m_testMap', 13, -10], ['m_testAuto', 13, -9], ['m_examine', 13, -8], ['m_customInterface', 13, -7], ['m_testType', 6, -6], ['m_observe', 23, -5], ['m_hero', 45, -4], ['m_skin', 45, -3], ['m_mount', 45, -2], ['m_toonHandle', 19, -1]]]], //46 80 | ['_array', [[0, 5], 46]], //47 81 | ['_struct', [[['m_lockTeams', 13, -14], ['m_teamsTogether', 13, -13], ['m_advancedSharedControl', 13, -12], ['m_randomRaces', 13, -11], ['m_battleNet', 13, -10], ['m_amm', 13, -9], ['m_competitive', 13, -8], ['m_practice', 13, -7], ['m_cooperative', 13, -6], ['m_noVictoryOrDefeat', 13, -5], ['m_fog', 23, -4], ['m_observers', 23, -3], ['m_userDifficulty', 23, -2], ['m_clientDebugFlags', 20, -1]]]], //48 82 | ['_int', [[1, 4]]], //49 83 | ['_int', [[1, 8]]], //50 84 | ['_bitarray', [[0, 6]]], //51 85 | ['_bitarray', [[0, 8]]], //52 86 | ['_bitarray', [[0, 2]]], //53 87 | ['_bitarray', [[0, 7]]], //54 88 | ['_struct', [[['m_allowedColors', 51, -6], ['m_allowedRaces', 52, -5], ['m_allowedDifficulty', 51, -4], ['m_allowedControls', 52, -3], ['m_allowedObserveTypes', 53, -2], ['m_allowedAIBuilds', 54, -1]]]], //55 89 | ['_array', [[0, 5], 55]], //56 90 | ['_struct', [[['m_randomValue', 6, -26], ['m_gameCacheName', 28, -25], ['m_gameOptions', 48, -24], ['m_gameSpeed', 12, -23], ['m_gameType', 12, -22], ['m_maxUsers', 2, -21], ['m_maxObservers', 2, -20], ['m_maxPlayers', 2, -19], ['m_maxTeams', 49, -18], ['m_maxColors', 3, -17], ['m_maxRaces', 50, -16], ['m_maxControls', 10, -15], ['m_mapSizeX', 10, -14], ['m_mapSizeY', 10, -13], ['m_mapFileSyncChecksum', 6, -12], ['m_mapFileName', 29, -11], ['m_mapAuthorName', 9, -10], ['m_modFileSyncChecksum', 6, -9], ['m_slotDescriptions', 56, -8], ['m_defaultDifficulty', 3, -7], ['m_defaultAIBuild', 0, -6], ['m_cacheHandles', 35, -5], ['m_hasExtensionMod', 13, -4], ['m_isBlizzardMap', 13, -3], ['m_isPremadeFFA', 13, -2], ['m_isCoopMode', 13, -1]]]], //57 91 | ['_optional', [1]], //58 92 | ['_optional', [2]], //59 93 | ['_struct', [[['m_color', 59, -1]]]], //60 94 | ['_array', [[0, 17], 6]], //61 95 | ['_array', [[0, 9], 6]], //62 96 | ['_struct', [[['m_control', 10, -16], ['m_userId', 58, -15], ['m_teamId', 1, -14], ['m_colorPref', 60, -13], ['m_racePref', 43, -12], ['m_difficulty', 3, -11], ['m_aiBuild', 0, -10], ['m_handicap', 0, -9], ['m_observe', 23, -8], ['m_hero', 45, -7], ['m_skin', 45, -6], ['m_mount', 45, -5], ['m_workingSetSlotId', 24, -4], ['m_rewards', 61, -3], ['m_toonHandle', 19, -2], ['m_licenses', 62, -1]]]], //63 97 | ['_array', [[0, 5], 63]], //64 98 | ['_struct', [[['m_phase', 12, -10], ['m_maxUsers', 2, -9], ['m_maxObservers', 2, -8], ['m_slots', 64, -7], ['m_randomSeed', 6, -6], ['m_hostUserId', 58, -5], ['m_isSinglePlayer', 13, -4], ['m_gameDuration', 6, -3], ['m_defaultDifficulty', 3, -2], ['m_defaultAIBuild', 0, -1]]]], //65 99 | ['_struct', [[['m_userInitialData', 47, -3], ['m_gameDescription', 57, -2], ['m_lobbyState', 65, -1]]]], //66 100 | ['_struct', [[['m_syncLobbyState', 66, -1]]]], //67 101 | ['_struct', [[['m_name', 19, -1]]]], //68 102 | ['_blob', [[0, 6]]], //69 103 | ['_struct', [[['m_name', 69, -1]]]], //70 104 | ['_struct', [[['m_name', 69, -3], ['m_type', 6, -2], ['m_data', 19, -1]]]], //71 105 | ['_struct', [[['m_type', 6, -3], ['m_name', 69, -2], ['m_data', 33, -1]]]], //72 106 | ['_array', [[0, 5], 10]], //73 107 | ['_struct', [[['m_signature', 73, -2], ['m_toonHandle', 19, -1]]]], //74 108 | ['_struct', [[['m_gameFullyDownloaded', 13, -14], ['m_developmentCheatsEnabled', 13, -13], ['m_multiplayerCheatsEnabled', 13, -12], ['m_syncChecksummingEnabled', 13, -11], ['m_isMapToMapTransition', 13, -10], ['m_startingRally', 13, -9], ['m_debugPauseEnabled', 13, -8], ['m_useGalaxyAsserts', 13, -7], ['m_platformMac', 13, -6], ['m_cameraFollow', 13, -5], ['m_baseBuildNum', 6, -4], ['m_buildNum', 6, -3], ['m_versionFlags', 6, -2], ['m_hotkeyProfile', 45, -1]]]], //75 109 | ['_struct', [[]]], //76 110 | ['_int', [[0, 16]]], //77 111 | ['_struct', [[['x', 77, -2], ['y', 77, -1]]]], //78 112 | ['_struct', [[['m_which', 12, -2], ['m_target', 78, -1]]]], //79 113 | ['_struct', [[['m_fileName', 29, -5], ['m_automatic', 13, -4], ['m_overwrite', 13, -3], ['m_name', 9, -2], ['m_description', 28, -1]]]], //80 114 | ['_int', [[-2147483648, 32]]], //81 115 | ['_struct', [[['x', 81, -2], ['y', 81, -1]]]], //82 116 | ['_struct', [[['m_point', 82, -4], ['m_time', 81, -3], ['m_verb', 28, -2], ['m_arguments', 28, -1]]]], //83 117 | ['_struct', [[['m_data', 83, -1]]]], //84 118 | ['_int', [[0, 21]]], //85 119 | ['_struct', [[['m_abilLink', 77, -3], ['m_abilCmdIndex', 2, -2], ['m_abilCmdData', 24, -1]]]], //86 120 | ['_optional', [86]], //87 121 | ['_null', []], //88 122 | ['_int', [[0, 20]]], //89 123 | ['_struct', [[['x', 89, -3], ['y', 89, -2], ['z', 81, -1]]]], //90 124 | ['_struct', [[['m_targetUnitFlags', 77, -7], ['m_timer', 10, -6], ['m_tag', 6, -5], ['m_snapshotUnitLink', 77, -4], ['m_snapshotControlPlayerId', 58, -3], ['m_snapshotUpkeepPlayerId', 58, -2], ['m_snapshotPoint', 90, -1]]]], //91 125 | ['_choice', [[0, 2], { 0: ['None', 88], 1: ['TargetPoint', 90], 2: ['TargetUnit', 91], 3: ['Data', 6]}]], //92 126 | ['_struct', [[['m_cmdFlags', 85, -5], ['m_abil', 87, -4], ['m_data', 92, -3], ['m_otherUnit', 42, -2], ['m_unitGroup', 42, -1]]]], //93 127 | ['_int', [[0, 9]]], //94 128 | ['_bitarray', [[0, 9]]], //95 129 | ['_array', [[0, 9], 94]], //96 130 | ['_choice', [[0, 2], { 0: ['None', 88], 1: ['Mask', 95], 2: ['OneIndices', 96], 3: ['ZeroIndices', 96]}]], //97 131 | ['_struct', [[['m_unitLink', 77, -4], ['m_subgroupPriority', 10, -3], ['m_intraSubgroupPriority', 10, -2], ['m_count', 94, -1]]]], //98 132 | ['_array', [[0, 9], 98]], //99 133 | ['_struct', [[['m_subgroupIndex', 94, -4], ['m_removeMask', 97, -3], ['m_addSubgroups', 99, -2], ['m_addUnitTags', 62, -1]]]], //100 134 | ['_struct', [[['m_controlGroupId', 1, -2], ['m_delta', 100, -1]]]], //101 135 | ['_struct', [[['m_controlGroupIndex', 1, -3], ['m_controlGroupUpdate', 23, -2], ['m_mask', 97, -1]]]], //102 136 | ['_struct', [[['m_count', 94, -6], ['m_subgroupCount', 94, -5], ['m_activeSubgroupIndex', 94, -4], ['m_unitTagsChecksum', 6, -3], ['m_subgroupIndicesChecksum', 6, -2], ['m_subgroupsChecksum', 6, -1]]]], //103 137 | ['_struct', [[['m_controlGroupId', 1, -2], ['m_selectionSyncData', 103, -1]]]], //104 138 | ['_array', [[0, 3], 81]], //105 139 | ['_struct', [[['m_recipientId', 1, -2], ['m_resources', 105, -1]]]], //106 140 | ['_struct', [[['m_chatMessage', 28, -1]]]], //107 141 | ['_int', [[-128, 8]]], //108 142 | ['_struct', [[['x', 81, -3], ['y', 81, -2], ['z', 81, -1]]]], //109 143 | ['_struct', [[['m_beacon', 108, -9], ['m_ally', 108, -8], ['m_flags', 108, -7], ['m_build', 108, -6], ['m_targetUnitTag', 6, -5], ['m_targetUnitSnapshotUnitLink', 77, -4], ['m_targetUnitSnapshotUpkeepPlayerId', 108, -3], ['m_targetUnitSnapshotControlPlayerId', 108, -2], ['m_targetPoint', 109, -1]]]], //110 144 | ['_struct', [[['m_speed', 12, -1]]]], //111 145 | ['_struct', [[['m_delta', 108, -1]]]], //112 146 | ['_struct', [[['m_point', 82, -4], ['m_unit', 6, -3], ['m_pingedMinimap', 13, -2], ['m_option', 81, -1]]]], //113 147 | ['_struct', [[['m_verb', 28, -2], ['m_arguments', 28, -1]]]], //114 148 | ['_struct', [[['m_alliance', 6, -2], ['m_control', 6, -1]]]], //115 149 | ['_struct', [[['m_unitTag', 6, -1]]]], //116 150 | ['_struct', [[['m_unitTag', 6, -2], ['m_flags', 10, -1]]]], //117 151 | ['_struct', [[['m_conversationId', 81, -2], ['m_replyId', 81, -1]]]], //118 152 | ['_optional', [19]], //119 153 | ['_struct', [[['m_gameUserId', 1, -6], ['m_observe', 23, -5], ['m_name', 9, -4], ['m_toonHandle', 119, -3], ['m_clanTag', 40, -2], ['m_clanLogo', 41, -1]]]], //120 154 | ['_array', [[0, 5], 120]], //121 155 | ['_int', [[0, 1]]], //122 156 | ['_struct', [[['m_userInfos', 121, -2], ['m_method', 122, -1]]]], //123 157 | ['_struct', [[['m_purchaseItemId', 81, -1]]]], //124 158 | ['_struct', [[['m_difficultyLevel', 81, -1]]]], //125 159 | ['_choice', [[0, 3], { 0: ['None', 88], 1: ['Checked', 13], 2: ['ValueChanged', 6], 3: ['SelectionChanged', 81], 4: ['TextChanged', 29], 5: ['MouseButton', 6]}]], //126 160 | ['_struct', [[['m_controlId', 81, -3], ['m_eventType', 81, -2], ['m_eventData', 126, -1]]]], //127 161 | ['_struct', [[['m_soundHash', 6, -2], ['m_length', 6, -1]]]], //128 162 | ['_array', [[0, 7], 6]], //129 163 | ['_struct', [[['m_soundHash', 129, -2], ['m_length', 129, -1]]]], //130 164 | ['_struct', [[['m_syncInfo', 130, -1]]]], //131 165 | ['_struct', [[['m_queryId', 77, -3], ['m_lengthMs', 6, -2], ['m_finishGameLoop', 6, -1]]]], //132 166 | ['_struct', [[['m_queryId', 77, -2], ['m_lengthMs', 6, -1]]]], //133 167 | ['_struct', [[['m_animWaitQueryId', 77, -1]]]], //134 168 | ['_struct', [[['m_sound', 6, -1]]]], //135 169 | ['_struct', [[['m_transmissionId', 81, -2], ['m_thread', 6, -1]]]], //136 170 | ['_struct', [[['m_transmissionId', 81, -1]]]], //137 171 | ['_optional', [78]], //138 172 | ['_optional', [77]], //139 173 | ['_optional', [108]], //140 174 | ['_struct', [[['m_target', 138, -6], ['m_distance', 139, -5], ['m_pitch', 139, -4], ['m_yaw', 139, -3], ['m_reason', 140, -2], ['m_follow', 13, -1]]]], //141 175 | ['_struct', [[['m_skipType', 122, -1]]]], //142 176 | ['_int', [[0, 11]]], //143 177 | ['_struct', [[['x', 143, -2], ['y', 143, -1]]]], //144 178 | ['_struct', [[['m_button', 6, -5], ['m_down', 13, -4], ['m_posUI', 144, -3], ['m_posWorld', 90, -2], ['m_flags', 108, -1]]]], //145 179 | ['_struct', [[['m_posUI', 144, -3], ['m_posWorld', 90, -2], ['m_flags', 108, -1]]]], //146 180 | ['_struct', [[['m_achievementLink', 77, -1]]]], //147 181 | ['_struct', [[['m_hotkey', 6, -2], ['m_down', 13, -1]]]], //148 182 | ['_struct', [[['m_abilLink', 77, -3], ['m_abilCmdIndex', 2, -2], ['m_state', 108, -1]]]], //149 183 | ['_struct', [[['m_soundtrack', 6, -1]]]], //150 184 | ['_struct', [[['m_planetId', 81, -1]]]], //151 185 | ['_struct', [[['m_key', 108, -2], ['m_flags', 108, -1]]]], //152 186 | ['_struct', [[['m_resources', 105, -1]]]], //153 187 | ['_struct', [[['m_fulfillRequestId', 81, -1]]]], //154 188 | ['_struct', [[['m_cancelRequestId', 81, -1]]]], //155 189 | ['_struct', [[['m_researchItemId', 81, -1]]]], //156 190 | ['_struct', [[['m_mercenaryId', 81, -1]]]], //157 191 | ['_struct', [[['m_battleReportId', 81, -2], ['m_difficultyLevel', 81, -1]]]], //158 192 | ['_struct', [[['m_battleReportId', 81, -1]]]], //159 193 | ['_int', [[0, 19]]], //160 194 | ['_struct', [[['m_decrementMs', 160, -1]]]], //161 195 | ['_struct', [[['m_portraitId', 81, -1]]]], //162 196 | ['_struct', [[['m_functionName', 19, -1]]]], //163 197 | ['_struct', [[['m_result', 81, -1]]]], //164 198 | ['_struct', [[['m_gameMenuItemIndex', 81, -1]]]], //165 199 | ['_struct', [[['m_purchaseCategoryId', 81, -1]]]], //166 200 | ['_struct', [[['m_button', 77, -1]]]], //167 201 | ['_struct', [[['m_cutsceneId', 81, -2], ['m_bookmarkName', 19, -1]]]], //168 202 | ['_struct', [[['m_cutsceneId', 81, -1]]]], //169 203 | ['_struct', [[['m_cutsceneId', 81, -3], ['m_conversationLine', 19, -2], ['m_altConversationLine', 19, -1]]]], //170 204 | ['_struct', [[['m_cutsceneId', 81, -2], ['m_conversationLine', 19, -1]]]], //171 205 | ['_struct', [[['m_observe', 23, -6], ['m_name', 9, -5], ['m_toonHandle', 119, -4], ['m_clanTag', 40, -3], ['m_clanLogo', 41, -2], ['m_hijack', 13, -1]]]], //172 206 | ['_struct', [[['m_state', 23, -1]]]], //173 207 | ['_struct', [[['m_managed', 13, -3], ['m_target', 90, -2], ['m_unitGroup', 42, -1]]]], //174 208 | ['_struct', [[['m_managed', 13, -3], ['m_target', 91, -2], ['m_unitGroup', 42, -1]]]], //175 209 | ['_struct', [[['m_catalog', 10, -4], ['m_entry', 77, -3], ['m_field', 9, -2], ['m_value', 9, -1]]]], //176 210 | ['_struct', [[['m_index', 6, -1]]]], //177 211 | ['_struct', [[['m_recipient', 12, -2], ['m_string', 29, -1]]]], //178 212 | ['_struct', [[['m_recipient', 12, -2], ['m_point', 82, -1]]]], //179 213 | ['_struct', [[['m_progress', 81, -1]]]], //180 214 | ['_struct', [[['m_status', 23, -1]]]], //181 215 | ['_struct', [[['m_scoreValueMineralsCurrent', 81, 0], ['m_scoreValueVespeneCurrent', 81, 1], ['m_scoreValueMineralsCollectionRate', 81, 2], ['m_scoreValueVespeneCollectionRate', 81, 3], ['m_scoreValueWorkersActiveCount', 81, 4], ['m_scoreValueMineralsUsedInProgressArmy', 81, 5], ['m_scoreValueMineralsUsedInProgressEconomy', 81, 6], ['m_scoreValueMineralsUsedInProgressTechnology', 81, 7], ['m_scoreValueVespeneUsedInProgressArmy', 81, 8], ['m_scoreValueVespeneUsedInProgressEconomy', 81, 9], ['m_scoreValueVespeneUsedInProgressTechnology', 81, 10], ['m_scoreValueMineralsUsedCurrentArmy', 81, 11], ['m_scoreValueMineralsUsedCurrentEconomy', 81, 12], ['m_scoreValueMineralsUsedCurrentTechnology', 81, 13], ['m_scoreValueVespeneUsedCurrentArmy', 81, 14], ['m_scoreValueVespeneUsedCurrentEconomy', 81, 15], ['m_scoreValueVespeneUsedCurrentTechnology', 81, 16], ['m_scoreValueMineralsLostArmy', 81, 17], ['m_scoreValueMineralsLostEconomy', 81, 18], ['m_scoreValueMineralsLostTechnology', 81, 19], ['m_scoreValueVespeneLostArmy', 81, 20], ['m_scoreValueVespeneLostEconomy', 81, 21], ['m_scoreValueVespeneLostTechnology', 81, 22], ['m_scoreValueMineralsKilledArmy', 81, 23], ['m_scoreValueMineralsKilledEconomy', 81, 24], ['m_scoreValueMineralsKilledTechnology', 81, 25], ['m_scoreValueVespeneKilledArmy', 81, 26], ['m_scoreValueVespeneKilledEconomy', 81, 27], ['m_scoreValueVespeneKilledTechnology', 81, 28], ['m_scoreValueFoodUsed', 81, 29], ['m_scoreValueFoodMade', 81, 30], ['m_scoreValueMineralsUsedActiveForces', 81, 31], ['m_scoreValueVespeneUsedActiveForces', 81, 32], ['m_scoreValueMineralsFriendlyFireArmy', 81, 33], ['m_scoreValueMineralsFriendlyFireEconomy', 81, 34], ['m_scoreValueMineralsFriendlyFireTechnology', 81, 35], ['m_scoreValueVespeneFriendlyFireArmy', 81, 36], ['m_scoreValueVespeneFriendlyFireEconomy', 81, 37], ['m_scoreValueVespeneFriendlyFireTechnology', 81, 38]]]], //182 216 | ['_struct', [[['m_playerId', 1, 0], ['m_stats', 182, 1]]]], //183 217 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1], ['m_unitTypeName', 28, 2], ['m_controlPlayerId', 1, 3], ['m_upkeepPlayerId', 1, 4], ['m_x', 10, 5], ['m_y', 10, 6]]]], //184 218 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1], ['m_killerPlayerId', 58, 2], ['m_x', 10, 3], ['m_y', 10, 4], ['m_killerUnitTagIndex', 42, 5], ['m_killerUnitTagRecycle', 42, 6]]]], //185 219 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1], ['m_controlPlayerId', 1, 2], ['m_upkeepPlayerId', 1, 3]]]], //186 220 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1], ['m_unitTypeName', 28, 2]]]], //187 221 | ['_struct', [[['m_playerId', 1, 0], ['m_upgradeTypeName', 28, 1], ['m_count', 81, 2]]]], //188 222 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1]]]], //189 223 | ['_array', [[0, 10], 81]], //190 224 | ['_struct', [[['m_firstUnitIndex', 6, 0], ['m_items', 190, 1]]]], //191 225 | ['_struct', [[['m_playerId', 1, 0], ['m_type', 6, 1], ['m_userId', 42, 2], ['m_slotId', 42, 3]]]] //192 226 | ]; 227 | 228 | // Map from protocol NNet.Game.*Event eventid to [typeid, name] 229 | const game_event_types = { 230 | 5: [76, 'NNet.Game.SUserFinishedLoadingSyncEvent'], 231 | 7: [75, 'NNet.Game.SUserOptionsEvent'], 232 | 9: [68, 'NNet.Game.SBankFileEvent'], 233 | 10: [70, 'NNet.Game.SBankSectionEvent'], 234 | 11: [71, 'NNet.Game.SBankKeyEvent'], 235 | 12: [72, 'NNet.Game.SBankValueEvent'], 236 | 13: [74, 'NNet.Game.SBankSignatureEvent'], 237 | 14: [79, 'NNet.Game.SCameraSaveEvent'], 238 | 21: [80, 'NNet.Game.SSaveGameEvent'], 239 | 22: [76, 'NNet.Game.SSaveGameDoneEvent'], 240 | 23: [76, 'NNet.Game.SLoadGameDoneEvent'], 241 | 26: [84, 'NNet.Game.SGameCheatEvent'], 242 | 27: [93, 'NNet.Game.SCmdEvent'], 243 | 28: [101, 'NNet.Game.SSelectionDeltaEvent'], 244 | 29: [102, 'NNet.Game.SControlGroupUpdateEvent'], 245 | 30: [104, 'NNet.Game.SSelectionSyncCheckEvent'], 246 | 31: [106, 'NNet.Game.SResourceTradeEvent'], 247 | 32: [107, 'NNet.Game.STriggerChatMessageEvent'], 248 | 33: [110, 'NNet.Game.SAICommunicateEvent'], 249 | 34: [111, 'NNet.Game.SSetAbsoluteGameSpeedEvent'], 250 | 35: [112, 'NNet.Game.SAddAbsoluteGameSpeedEvent'], 251 | 36: [113, 'NNet.Game.STriggerPingEvent'], 252 | 37: [114, 'NNet.Game.SBroadcastCheatEvent'], 253 | 38: [115, 'NNet.Game.SAllianceEvent'], 254 | 39: [116, 'NNet.Game.SUnitClickEvent'], 255 | 40: [117, 'NNet.Game.SUnitHighlightEvent'], 256 | 41: [118, 'NNet.Game.STriggerReplySelectedEvent'], 257 | 43: [123, 'NNet.Game.SHijackReplayGameEvent'], 258 | 44: [76, 'NNet.Game.STriggerSkippedEvent'], 259 | 45: [128, 'NNet.Game.STriggerSoundLengthQueryEvent'], 260 | 46: [135, 'NNet.Game.STriggerSoundOffsetEvent'], 261 | 47: [136, 'NNet.Game.STriggerTransmissionOffsetEvent'], 262 | 48: [137, 'NNet.Game.STriggerTransmissionCompleteEvent'], 263 | 49: [141, 'NNet.Game.SCameraUpdateEvent'], 264 | 50: [76, 'NNet.Game.STriggerAbortMissionEvent'], 265 | 51: [124, 'NNet.Game.STriggerPurchaseMadeEvent'], 266 | 52: [76, 'NNet.Game.STriggerPurchaseExitEvent'], 267 | 53: [125, 'NNet.Game.STriggerPlanetMissionLaunchedEvent'], 268 | 54: [76, 'NNet.Game.STriggerPlanetPanelCanceledEvent'], 269 | 55: [127, 'NNet.Game.STriggerDialogControlEvent'], 270 | 56: [131, 'NNet.Game.STriggerSoundLengthSyncEvent'], 271 | 57: [142, 'NNet.Game.STriggerConversationSkippedEvent'], 272 | 58: [145, 'NNet.Game.STriggerMouseClickedEvent'], 273 | 59: [146, 'NNet.Game.STriggerMouseMovedEvent'], 274 | 60: [147, 'NNet.Game.SAchievementAwardedEvent'], 275 | 61: [148, 'NNet.Game.STriggerHotkeyPressedEvent'], 276 | 62: [149, 'NNet.Game.STriggerTargetModeUpdateEvent'], 277 | 63: [76, 'NNet.Game.STriggerPlanetPanelReplayEvent'], 278 | 64: [150, 'NNet.Game.STriggerSoundtrackDoneEvent'], 279 | 65: [151, 'NNet.Game.STriggerPlanetMissionSelectedEvent'], 280 | 66: [152, 'NNet.Game.STriggerKeyPressedEvent'], 281 | 67: [163, 'NNet.Game.STriggerMovieFunctionEvent'], 282 | 68: [76, 'NNet.Game.STriggerPlanetPanelBirthCompleteEvent'], 283 | 69: [76, 'NNet.Game.STriggerPlanetPanelDeathCompleteEvent'], 284 | 70: [153, 'NNet.Game.SResourceRequestEvent'], 285 | 71: [154, 'NNet.Game.SResourceRequestFulfillEvent'], 286 | 72: [155, 'NNet.Game.SResourceRequestCancelEvent'], 287 | 73: [76, 'NNet.Game.STriggerResearchPanelExitEvent'], 288 | 74: [76, 'NNet.Game.STriggerResearchPanelPurchaseEvent'], 289 | 75: [156, 'NNet.Game.STriggerResearchPanelSelectionChangedEvent'], 290 | 77: [76, 'NNet.Game.STriggerMercenaryPanelExitEvent'], 291 | 78: [76, 'NNet.Game.STriggerMercenaryPanelPurchaseEvent'], 292 | 79: [157, 'NNet.Game.STriggerMercenaryPanelSelectionChangedEvent'], 293 | 80: [76, 'NNet.Game.STriggerVictoryPanelExitEvent'], 294 | 81: [76, 'NNet.Game.STriggerBattleReportPanelExitEvent'], 295 | 82: [158, 'NNet.Game.STriggerBattleReportPanelPlayMissionEvent'], 296 | 83: [159, 'NNet.Game.STriggerBattleReportPanelPlaySceneEvent'], 297 | 84: [159, 'NNet.Game.STriggerBattleReportPanelSelectionChangedEvent'], 298 | 85: [125, 'NNet.Game.STriggerVictoryPanelPlayMissionAgainEvent'], 299 | 86: [76, 'NNet.Game.STriggerMovieStartedEvent'], 300 | 87: [76, 'NNet.Game.STriggerMovieFinishedEvent'], 301 | 88: [161, 'NNet.Game.SDecrementGameTimeRemainingEvent'], 302 | 89: [162, 'NNet.Game.STriggerPortraitLoadedEvent'], 303 | 90: [164, 'NNet.Game.STriggerCustomDialogDismissedEvent'], 304 | 91: [165, 'NNet.Game.STriggerGameMenuItemSelectedEvent'], 305 | 93: [124, 'NNet.Game.STriggerPurchasePanelSelectedPurchaseItemChangedEvent'], 306 | 94: [166, 'NNet.Game.STriggerPurchasePanelSelectedPurchaseCategoryChangedEvent'], 307 | 95: [167, 'NNet.Game.STriggerButtonPressedEvent'], 308 | 96: [76, 'NNet.Game.STriggerGameCreditsFinishedEvent'], 309 | 97: [168, 'NNet.Game.STriggerCutsceneBookmarkFiredEvent'], 310 | 98: [169, 'NNet.Game.STriggerCutsceneEndSceneFiredEvent'], 311 | 99: [170, 'NNet.Game.STriggerCutsceneConversationLineEvent'], 312 | 100: [171, 'NNet.Game.STriggerCutsceneConversationLineMissingEvent'], 313 | 101: [76, 'NNet.Game.SGameUserLeaveEvent'], 314 | 102: [172, 'NNet.Game.SGameUserJoinEvent'], 315 | 103: [173, 'NNet.Game.SCommandManagerStateEvent'], 316 | 104: [174, 'NNet.Game.SCmdUpdateTargetPointEvent'], 317 | 105: [175, 'NNet.Game.SCmdUpdateTargetUnitEvent'], 318 | 106: [132, 'NNet.Game.STriggerAnimLengthQueryByNameEvent'], 319 | 107: [133, 'NNet.Game.STriggerAnimLengthQueryByPropsEvent'], 320 | 108: [134, 'NNet.Game.STriggerAnimOffsetEvent'], 321 | 109: [176, 'NNet.Game.SCatalogModifyEvent'], 322 | 110: [177, 'NNet.Game.SHeroTalentSelectedEvent'] 323 | }; 324 | 325 | // The typeid of the NNet.Game.EEventId enum. 326 | const game_eventid_typeid = 0; 327 | 328 | // Map from protocol NNet.Game.*Message eventid to [typeid, name] 329 | const message_event_types = { 330 | 0: [178, 'NNet.Game.SChatMessage'], 331 | 1: [179, 'NNet.Game.SPingMessage'], 332 | 2: [180, 'NNet.Game.SLoadingProgressMessage'], 333 | 3: [76, 'NNet.Game.SServerPingMessage'], 334 | 4: [181, 'NNet.Game.SReconnectNotifyMessage'] 335 | }; 336 | 337 | // The typeid of the NNet.Game.EMessageId enum. 338 | const message_eventid_typeid = 1; 339 | 340 | // Map from protocol NNet.Replay.Tracker.*Event eventid to [typeid, name] 341 | const tracker_event_types = { 342 | 0: [183, 'NNet.Replay.Tracker.SPlayerStatsEvent'], 343 | 1: [184, 'NNet.Replay.Tracker.SUnitBornEvent'], 344 | 2: [185, 'NNet.Replay.Tracker.SUnitDiedEvent'], 345 | 3: [186, 'NNet.Replay.Tracker.SUnitOwnerChangeEvent'], 346 | 4: [187, 'NNet.Replay.Tracker.SUnitTypeChangeEvent'], 347 | 5: [188, 'NNet.Replay.Tracker.SUpgradeEvent'], 348 | 6: [184, 'NNet.Replay.Tracker.SUnitInitEvent'], 349 | 7: [189, 'NNet.Replay.Tracker.SUnitDoneEvent'], 350 | 8: [191, 'NNet.Replay.Tracker.SUnitPositionsEvent'], 351 | 9: [192, 'NNet.Replay.Tracker.SPlayerSetupEvent'] 352 | }; 353 | 354 | // The typeid of the NNet.Replay.Tracker.EEventId enum. 355 | const tracker_eventid_typeid = 2; 356 | 357 | // The typeid of NNet.SVarUint32 (the type used to encode gameloop deltas). 358 | const svaruint32_typeid = 7; 359 | 360 | // The typeid of NNet.Replay.SGameUserId (the type used to encode player ids). 361 | const replay_userid_typeid = 8; 362 | 363 | // The typeid of NNet.Replay.SHeader (the type used to store replay game version and length). 364 | const replay_header_typeid = 17; 365 | 366 | // The typeid of NNet.Game.SDetails (the type used to store overall replay details). 367 | const game_details_typeid = 39; 368 | 369 | // The typeid of NNet.Replay.SInitData (the type used to store the inital lobby). 370 | const replay_initdata_typeid = 67; 371 | 372 | // not sure if correct port 373 | function _varuint32Value(value) { 374 | // Returns the numeric value from a SVarUint32 instance. 375 | return value[Object.keys(value)[0]]; 376 | } 377 | 378 | function* _decode_event_stream(decoder, eventidTypeid, eventTypes, decodeUserId) { 379 | // Decodes events prefixed with a gameloop and possibly userid 380 | var gameloop = 0; 381 | while (!decoder.done()) { 382 | var startBits = decoder.usedBits(); 383 | 384 | // decode the gameloop delta before each event 385 | var delta = _varuint32Value(decoder.instance(svaruint32_typeid)); 386 | gameloop += delta; 387 | 388 | // decode the userid before each event 389 | var userid = (decodeUserId === true) ? decoder.instance(replay_userid_typeid) : undefined; 390 | 391 | // decode the event id 392 | var eventid = decoder.instance(eventidTypeid); 393 | var eventType = eventTypes[eventid] || [null, null]; 394 | var typeid = eventType[0]; 395 | var typename = eventType[1]; 396 | if (typeid === null) throw new decoders.CorruptedError('eventid(' + eventid + ') at ' + decoder.toString()); 397 | 398 | // decode the event struct instance 399 | var event = decoder.instance(typeid); 400 | event._event = typename; 401 | event._eventid = eventid; 402 | 403 | // insert gameloop and userid 404 | event._gameloop = gameloop; 405 | if (decodeUserId) event._userid = userid; 406 | 407 | // the next event is byte aligned 408 | decoder.byteAlign(); 409 | 410 | // insert bits used in stream 411 | event._bits = decoder.usedBits() - startBits; 412 | 413 | yield event; 414 | } 415 | } 416 | 417 | exports.decodeReplayGameEvents = function* (contents) { 418 | // Decodes and yields each game event from the contents byte string. 419 | const decoder = new BitPackedDecoder(contents, typeinfos); 420 | for (let event of _decode_event_stream(decoder, game_eventid_typeid, game_event_types, true)) 421 | yield event; 422 | }; 423 | 424 | exports.decodeReplayMessageEvents = function* (contents) { 425 | // Decodes and yields each message event from the contents byte string. 426 | const decoder = new BitPackedDecoder(contents, typeinfos); 427 | for (let event of _decode_event_stream(decoder, message_eventid_typeid, message_event_types, true)) 428 | yield event; 429 | }; 430 | 431 | exports.decodeReplayTrackerEvents = function* (contents) { 432 | // Decodes and yields each tracker event from the contents byte string. 433 | const decoder = new VersionDecoder(contents, typeinfos); 434 | for (let event of _decode_event_stream(decoder, tracker_eventid_typeid, tracker_event_types, false)) 435 | yield event; 436 | }; 437 | 438 | exports.decodeReplayHeader = function(contents) { 439 | // Decodes and return the replay header from the contents byte string. 440 | const decoder = new VersionDecoder(contents, typeinfos); 441 | return decoder.instance(replay_header_typeid); 442 | }; 443 | 444 | exports.decodeReplayDetails = function(contents) { 445 | // Decodes and returns the game details from the contents byte string. 446 | const decoder = new VersionDecoder(contents, typeinfos); 447 | return decoder.instance(game_details_typeid); 448 | }; 449 | 450 | exports.decodeReplayInitdata = function(contents) { 451 | // Decodes and return the replay init data from the contents byte string. 452 | const decoder = new BitPackedDecoder(contents, typeinfos); 453 | return decoder.instance(replay_initdata_typeid); 454 | }; 455 | 456 | exports.decodeReplayAttributesEvents = function (contents) { 457 | // Decodes and yields each attribute from the contents byte string. 458 | const buffer = new decoders.BitPackedBuffer(contents, 'little'); 459 | const attributes = {}; 460 | 461 | if (!buffer.done()) { 462 | attributes.source = buffer.readBits(8); 463 | attributes.mapNameSpace = buffer.readBits(32); 464 | var count = buffer.readBits(32); 465 | attributes.scopes = {}; 466 | 467 | while (!buffer.done()) { 468 | var value = {}; 469 | value.namespace = buffer.readBits(32); 470 | var attrid = value.attrid = buffer.readBits(32); 471 | var scope = buffer.readBits(8); 472 | value.value = buffer.readAlignedBytes(4).reverse(); 473 | while (value.value[0] === 0) value.value = value.value.slice(1); 474 | while (value.value[value.value.length - 1] === 0) value.value = value.value.slice(0, -1); 475 | if (!attributes.scopes[scope]) 476 | attributes.scopes[scope] = {}; 477 | if (!attributes.scopes[scope][attrid]) 478 | attributes.scopes[scope][attrid] = []; 479 | attributes.scopes[scope][attrid].push(value); 480 | } 481 | } 482 | 483 | return attributes; 484 | }; 485 | 486 | exports.unitTag = function(unitTagIndex, unitTagRecycle) { 487 | return (unitTagIndex << 18) + unitTagRecycle; 488 | }; 489 | 490 | exports.unitTagIndex = function(unitTag) { 491 | return (unitTag >> 18) & 0x00003FFF; 492 | }; 493 | 494 | exports.unitTagRecycle = function(unitTag) { 495 | return unitTag & 0x0003FFFF; 496 | }; 497 | -------------------------------------------------------------------------------- /lib/protocol30509.js: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright (c) 2015 Blizzard Entertainment 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | */ 22 | "use strict"; 23 | 24 | exports.version = 30509; 25 | 26 | const decoders = require('./decoders'); 27 | const BitPackedDecoder = decoders.BitPackedDecoder; 28 | const VersionDecoder = decoders.VersionDecoder; 29 | 30 | 31 | // Decoding instructions for each protocol type. 32 | const typeinfos = [ 33 | ['_int', [[0, 7]]], //0 34 | ['_int', [[0, 4]]], //1 35 | ['_int', [[0, 5]]], //2 36 | ['_int', [[0, 6]]], //3 37 | ['_int', [[0, 14]]], //4 38 | ['_int', [[0, 22]]], //5 39 | ['_int', [[0, 32]]], //6 40 | ['_choice', [[0, 2], { 0: ['m_uint6', 3], 1: ['m_uint14', 4], 2: ['m_uint22', 5], 3: ['m_uint32', 6]}]], //7 41 | ['_struct', [[['m_userId', 2, -1]]]], //8 42 | ['_blob', [[0, 8]]], //9 43 | ['_int', [[0, 8]]], //10 44 | ['_struct', [[['m_flags', 10, 0], ['m_major', 10, 1], ['m_minor', 10, 2], ['m_revision', 10, 3], ['m_build', 6, 4], ['m_baseBuild', 6, 5]]]], //11 45 | ['_int', [[0, 3]]], //12 46 | ['_bool', []], //13 47 | ['_array', [[16, 0], 10]], //14 48 | ['_optional', [14]], //15 49 | ['_struct', [[['m_data', 15, 0]]]], //16 50 | ['_struct', [[['m_signature', 9, 0], ['m_version', 11, 1], ['m_type', 12, 2], ['m_elapsedGameLoops', 6, 3], ['m_useScaledTime', 13, 4], ['m_ngdpRootKey', 16, 5], ['m_dataBuildNum', 6, 6]]]], //17 51 | ['_fourcc', []], //18 52 | ['_blob', [[0, 7]]], //19 53 | ['_int', [[0, 64]]], //20 54 | ['_struct', [[['m_region', 10, 0], ['m_programId', 18, 1], ['m_realm', 6, 2], ['m_name', 19, 3], ['m_id', 20, 4]]]], //21 55 | ['_struct', [[['m_a', 10, 0], ['m_r', 10, 1], ['m_g', 10, 2], ['m_b', 10, 3]]]], //22 56 | ['_int', [[0, 2]]], //23 57 | ['_optional', [10]], //24 58 | ['_struct', [[['m_name', 9, 0], ['m_toon', 21, 1], ['m_race', 9, 2], ['m_color', 22, 3], ['m_control', 10, 4], ['m_teamId', 1, 5], ['m_handicap', 0, 6], ['m_observe', 23, 7], ['m_result', 23, 8], ['m_workingSetSlotId', 24, 9], ['m_hero', 9, 10]]]], //25 59 | ['_array', [[0, 5], 25]], //26 60 | ['_optional', [26]], //27 61 | ['_blob', [[0, 10]]], //28 62 | ['_blob', [[0, 11]]], //29 63 | ['_struct', [[['m_file', 29, 0]]]], //30 64 | ['_optional', [13]], //31 65 | ['_int', [[-9223372036854775808, 64]]], //32 66 | ['_blob', [[0, 12]]], //33 67 | ['_blob', [[40, 0]]], //34 68 | ['_array', [[0, 6], 34]], //35 69 | ['_optional', [35]], //36 70 | ['_array', [[0, 6], 29]], //37 71 | ['_optional', [37]], //38 72 | ['_struct', [[['m_playerList', 27, 0], ['m_title', 28, 1], ['m_difficulty', 9, 2], ['m_thumbnail', 30, 3], ['m_isBlizzardMap', 13, 4], ['m_restartAsTransitionMap', 31, 16], ['m_timeUTC', 32, 5], ['m_timeLocalOffset', 32, 6], ['m_description', 33, 7], ['m_imageFilePath', 29, 8], ['m_campaignIndex', 10, 15], ['m_mapFileName', 29, 9], ['m_cacheHandles', 36, 10], ['m_miniSave', 13, 11], ['m_gameSpeed', 12, 12], ['m_defaultDifficulty', 3, 13], ['m_modPaths', 38, 14]]]], //39 73 | ['_optional', [9]], //40 74 | ['_optional', [34]], //41 75 | ['_optional', [6]], //42 76 | ['_struct', [[['m_race', 24, -1]]]], //43 77 | ['_struct', [[['m_team', 24, -1]]]], //44 78 | ['_blob', [[0, 9]]], //45 79 | ['_struct', [[['m_name', 9, -18], ['m_clanTag', 40, -17], ['m_clanLogo', 41, -16], ['m_highestLeague', 24, -15], ['m_combinedRaceLevels', 42, -14], ['m_randomSeed', 6, -13], ['m_racePreference', 43, -12], ['m_teamPreference', 44, -11], ['m_testMap', 13, -10], ['m_testAuto', 13, -9], ['m_examine', 13, -8], ['m_customInterface', 13, -7], ['m_testType', 6, -6], ['m_observe', 23, -5], ['m_hero', 45, -4], ['m_skin', 45, -3], ['m_mount', 45, -2], ['m_toonHandle', 19, -1]]]], //46 80 | ['_array', [[0, 5], 46]], //47 81 | ['_struct', [[['m_lockTeams', 13, -14], ['m_teamsTogether', 13, -13], ['m_advancedSharedControl', 13, -12], ['m_randomRaces', 13, -11], ['m_battleNet', 13, -10], ['m_amm', 13, -9], ['m_competitive', 13, -8], ['m_practice', 13, -7], ['m_cooperative', 13, -6], ['m_noVictoryOrDefeat', 13, -5], ['m_fog', 23, -4], ['m_observers', 23, -3], ['m_userDifficulty', 23, -2], ['m_clientDebugFlags', 20, -1]]]], //48 82 | ['_int', [[1, 4]]], //49 83 | ['_int', [[1, 8]]], //50 84 | ['_bitarray', [[0, 6]]], //51 85 | ['_bitarray', [[0, 8]]], //52 86 | ['_bitarray', [[0, 2]]], //53 87 | ['_bitarray', [[0, 7]]], //54 88 | ['_struct', [[['m_allowedColors', 51, -6], ['m_allowedRaces', 52, -5], ['m_allowedDifficulty', 51, -4], ['m_allowedControls', 52, -3], ['m_allowedObserveTypes', 53, -2], ['m_allowedAIBuilds', 54, -1]]]], //55 89 | ['_array', [[0, 5], 55]], //56 90 | ['_struct', [[['m_randomValue', 6, -26], ['m_gameCacheName', 28, -25], ['m_gameOptions', 48, -24], ['m_gameSpeed', 12, -23], ['m_gameType', 12, -22], ['m_maxUsers', 2, -21], ['m_maxObservers', 2, -20], ['m_maxPlayers', 2, -19], ['m_maxTeams', 49, -18], ['m_maxColors', 3, -17], ['m_maxRaces', 50, -16], ['m_maxControls', 10, -15], ['m_mapSizeX', 10, -14], ['m_mapSizeY', 10, -13], ['m_mapFileSyncChecksum', 6, -12], ['m_mapFileName', 29, -11], ['m_mapAuthorName', 9, -10], ['m_modFileSyncChecksum', 6, -9], ['m_slotDescriptions', 56, -8], ['m_defaultDifficulty', 3, -7], ['m_defaultAIBuild', 0, -6], ['m_cacheHandles', 35, -5], ['m_hasExtensionMod', 13, -4], ['m_isBlizzardMap', 13, -3], ['m_isPremadeFFA', 13, -2], ['m_isCoopMode', 13, -1]]]], //57 91 | ['_optional', [1]], //58 92 | ['_optional', [2]], //59 93 | ['_struct', [[['m_color', 59, -1]]]], //60 94 | ['_array', [[0, 17], 6]], //61 95 | ['_array', [[0, 9], 6]], //62 96 | ['_struct', [[['m_control', 10, -16], ['m_userId', 58, -15], ['m_teamId', 1, -14], ['m_colorPref', 60, -13], ['m_racePref', 43, -12], ['m_difficulty', 3, -11], ['m_aiBuild', 0, -10], ['m_handicap', 0, -9], ['m_observe', 23, -8], ['m_hero', 45, -7], ['m_skin', 45, -6], ['m_mount', 45, -5], ['m_workingSetSlotId', 24, -4], ['m_rewards', 61, -3], ['m_toonHandle', 19, -2], ['m_licenses', 62, -1]]]], //63 97 | ['_array', [[0, 5], 63]], //64 98 | ['_struct', [[['m_phase', 12, -10], ['m_maxUsers', 2, -9], ['m_maxObservers', 2, -8], ['m_slots', 64, -7], ['m_randomSeed', 6, -6], ['m_hostUserId', 58, -5], ['m_isSinglePlayer', 13, -4], ['m_gameDuration', 6, -3], ['m_defaultDifficulty', 3, -2], ['m_defaultAIBuild', 0, -1]]]], //65 99 | ['_struct', [[['m_userInitialData', 47, -3], ['m_gameDescription', 57, -2], ['m_lobbyState', 65, -1]]]], //66 100 | ['_struct', [[['m_syncLobbyState', 66, -1]]]], //67 101 | ['_struct', [[['m_name', 19, -1]]]], //68 102 | ['_blob', [[0, 6]]], //69 103 | ['_struct', [[['m_name', 69, -1]]]], //70 104 | ['_struct', [[['m_name', 69, -3], ['m_type', 6, -2], ['m_data', 19, -1]]]], //71 105 | ['_struct', [[['m_type', 6, -3], ['m_name', 69, -2], ['m_data', 33, -1]]]], //72 106 | ['_array', [[0, 5], 10]], //73 107 | ['_struct', [[['m_signature', 73, -2], ['m_toonHandle', 19, -1]]]], //74 108 | ['_struct', [[['m_gameFullyDownloaded', 13, -14], ['m_developmentCheatsEnabled', 13, -13], ['m_multiplayerCheatsEnabled', 13, -12], ['m_syncChecksummingEnabled', 13, -11], ['m_isMapToMapTransition', 13, -10], ['m_startingRally', 13, -9], ['m_debugPauseEnabled', 13, -8], ['m_useGalaxyAsserts', 13, -7], ['m_platformMac', 13, -6], ['m_cameraFollow', 13, -5], ['m_baseBuildNum', 6, -4], ['m_buildNum', 6, -3], ['m_versionFlags', 6, -2], ['m_hotkeyProfile', 45, -1]]]], //75 109 | ['_struct', [[]]], //76 110 | ['_int', [[0, 16]]], //77 111 | ['_struct', [[['x', 77, -2], ['y', 77, -1]]]], //78 112 | ['_struct', [[['m_which', 12, -2], ['m_target', 78, -1]]]], //79 113 | ['_struct', [[['m_fileName', 29, -5], ['m_automatic', 13, -4], ['m_overwrite', 13, -3], ['m_name', 9, -2], ['m_description', 28, -1]]]], //80 114 | ['_int', [[-2147483648, 32]]], //81 115 | ['_struct', [[['x', 81, -2], ['y', 81, -1]]]], //82 116 | ['_struct', [[['m_point', 82, -4], ['m_time', 81, -3], ['m_verb', 28, -2], ['m_arguments', 28, -1]]]], //83 117 | ['_struct', [[['m_data', 83, -1]]]], //84 118 | ['_int', [[0, 21]]], //85 119 | ['_struct', [[['m_abilLink', 77, -3], ['m_abilCmdIndex', 2, -2], ['m_abilCmdData', 24, -1]]]], //86 120 | ['_optional', [86]], //87 121 | ['_null', []], //88 122 | ['_int', [[0, 20]]], //89 123 | ['_struct', [[['x', 89, -3], ['y', 89, -2], ['z', 81, -1]]]], //90 124 | ['_struct', [[['m_targetUnitFlags', 77, -7], ['m_timer', 10, -6], ['m_tag', 6, -5], ['m_snapshotUnitLink', 77, -4], ['m_snapshotControlPlayerId', 58, -3], ['m_snapshotUpkeepPlayerId', 58, -2], ['m_snapshotPoint', 90, -1]]]], //91 125 | ['_choice', [[0, 2], { 0: ['None', 88], 1: ['TargetPoint', 90], 2: ['TargetUnit', 91], 3: ['Data', 6]}]], //92 126 | ['_struct', [[['m_cmdFlags', 85, -5], ['m_abil', 87, -4], ['m_data', 92, -3], ['m_otherUnit', 42, -2], ['m_unitGroup', 42, -1]]]], //93 127 | ['_int', [[0, 9]]], //94 128 | ['_bitarray', [[0, 9]]], //95 129 | ['_array', [[0, 9], 94]], //96 130 | ['_choice', [[0, 2], { 0: ['None', 88], 1: ['Mask', 95], 2: ['OneIndices', 96], 3: ['ZeroIndices', 96]}]], //97 131 | ['_struct', [[['m_unitLink', 77, -4], ['m_subgroupPriority', 10, -3], ['m_intraSubgroupPriority', 10, -2], ['m_count', 94, -1]]]], //98 132 | ['_array', [[0, 9], 98]], //99 133 | ['_struct', [[['m_subgroupIndex', 94, -4], ['m_removeMask', 97, -3], ['m_addSubgroups', 99, -2], ['m_addUnitTags', 62, -1]]]], //100 134 | ['_struct', [[['m_controlGroupId', 1, -2], ['m_delta', 100, -1]]]], //101 135 | ['_struct', [[['m_controlGroupIndex', 1, -3], ['m_controlGroupUpdate', 23, -2], ['m_mask', 97, -1]]]], //102 136 | ['_struct', [[['m_count', 94, -6], ['m_subgroupCount', 94, -5], ['m_activeSubgroupIndex', 94, -4], ['m_unitTagsChecksum', 6, -3], ['m_subgroupIndicesChecksum', 6, -2], ['m_subgroupsChecksum', 6, -1]]]], //103 137 | ['_struct', [[['m_controlGroupId', 1, -2], ['m_selectionSyncData', 103, -1]]]], //104 138 | ['_array', [[0, 3], 81]], //105 139 | ['_struct', [[['m_recipientId', 1, -2], ['m_resources', 105, -1]]]], //106 140 | ['_struct', [[['m_chatMessage', 28, -1]]]], //107 141 | ['_int', [[-128, 8]]], //108 142 | ['_struct', [[['x', 81, -3], ['y', 81, -2], ['z', 81, -1]]]], //109 143 | ['_struct', [[['m_beacon', 108, -9], ['m_ally', 108, -8], ['m_flags', 108, -7], ['m_build', 108, -6], ['m_targetUnitTag', 6, -5], ['m_targetUnitSnapshotUnitLink', 77, -4], ['m_targetUnitSnapshotUpkeepPlayerId', 108, -3], ['m_targetUnitSnapshotControlPlayerId', 108, -2], ['m_targetPoint', 109, -1]]]], //110 144 | ['_struct', [[['m_speed', 12, -1]]]], //111 145 | ['_struct', [[['m_delta', 108, -1]]]], //112 146 | ['_struct', [[['m_point', 82, -4], ['m_unit', 6, -3], ['m_pingedMinimap', 13, -2], ['m_option', 81, -1]]]], //113 147 | ['_struct', [[['m_verb', 28, -2], ['m_arguments', 28, -1]]]], //114 148 | ['_struct', [[['m_alliance', 6, -2], ['m_control', 6, -1]]]], //115 149 | ['_struct', [[['m_unitTag', 6, -1]]]], //116 150 | ['_struct', [[['m_unitTag', 6, -2], ['m_flags', 10, -1]]]], //117 151 | ['_struct', [[['m_conversationId', 81, -2], ['m_replyId', 81, -1]]]], //118 152 | ['_optional', [19]], //119 153 | ['_struct', [[['m_gameUserId', 1, -6], ['m_observe', 23, -5], ['m_name', 9, -4], ['m_toonHandle', 119, -3], ['m_clanTag', 40, -2], ['m_clanLogo', 41, -1]]]], //120 154 | ['_array', [[0, 5], 120]], //121 155 | ['_int', [[0, 1]]], //122 156 | ['_struct', [[['m_userInfos', 121, -2], ['m_method', 122, -1]]]], //123 157 | ['_struct', [[['m_purchaseItemId', 81, -1]]]], //124 158 | ['_struct', [[['m_difficultyLevel', 81, -1]]]], //125 159 | ['_choice', [[0, 3], { 0: ['None', 88], 1: ['Checked', 13], 2: ['ValueChanged', 6], 3: ['SelectionChanged', 81], 4: ['TextChanged', 29], 5: ['MouseButton', 6]}]], //126 160 | ['_struct', [[['m_controlId', 81, -3], ['m_eventType', 81, -2], ['m_eventData', 126, -1]]]], //127 161 | ['_struct', [[['m_soundHash', 6, -2], ['m_length', 6, -1]]]], //128 162 | ['_array', [[0, 7], 6]], //129 163 | ['_struct', [[['m_soundHash', 129, -2], ['m_length', 129, -1]]]], //130 164 | ['_struct', [[['m_syncInfo', 130, -1]]]], //131 165 | ['_struct', [[['m_queryId', 77, -3], ['m_lengthMs', 6, -2], ['m_finishGameLoop', 6, -1]]]], //132 166 | ['_struct', [[['m_queryId', 77, -2], ['m_lengthMs', 6, -1]]]], //133 167 | ['_struct', [[['m_animWaitQueryId', 77, -1]]]], //134 168 | ['_struct', [[['m_sound', 6, -1]]]], //135 169 | ['_struct', [[['m_transmissionId', 81, -2], ['m_thread', 6, -1]]]], //136 170 | ['_struct', [[['m_transmissionId', 81, -1]]]], //137 171 | ['_optional', [78]], //138 172 | ['_optional', [77]], //139 173 | ['_optional', [108]], //140 174 | ['_struct', [[['m_target', 138, -6], ['m_distance', 139, -5], ['m_pitch', 139, -4], ['m_yaw', 139, -3], ['m_reason', 140, -2], ['m_follow', 13, -1]]]], //141 175 | ['_struct', [[['m_skipType', 122, -1]]]], //142 176 | ['_int', [[0, 11]]], //143 177 | ['_struct', [[['x', 143, -2], ['y', 143, -1]]]], //144 178 | ['_struct', [[['m_button', 6, -5], ['m_down', 13, -4], ['m_posUI', 144, -3], ['m_posWorld', 90, -2], ['m_flags', 108, -1]]]], //145 179 | ['_struct', [[['m_posUI', 144, -3], ['m_posWorld', 90, -2], ['m_flags', 108, -1]]]], //146 180 | ['_struct', [[['m_achievementLink', 77, -1]]]], //147 181 | ['_struct', [[['m_hotkey', 6, -2], ['m_down', 13, -1]]]], //148 182 | ['_struct', [[['m_abilLink', 77, -3], ['m_abilCmdIndex', 2, -2], ['m_state', 108, -1]]]], //149 183 | ['_struct', [[['m_soundtrack', 6, -1]]]], //150 184 | ['_struct', [[['m_planetId', 81, -1]]]], //151 185 | ['_struct', [[['m_key', 108, -2], ['m_flags', 108, -1]]]], //152 186 | ['_struct', [[['m_resources', 105, -1]]]], //153 187 | ['_struct', [[['m_fulfillRequestId', 81, -1]]]], //154 188 | ['_struct', [[['m_cancelRequestId', 81, -1]]]], //155 189 | ['_struct', [[['m_researchItemId', 81, -1]]]], //156 190 | ['_struct', [[['m_mercenaryId', 81, -1]]]], //157 191 | ['_struct', [[['m_battleReportId', 81, -2], ['m_difficultyLevel', 81, -1]]]], //158 192 | ['_struct', [[['m_battleReportId', 81, -1]]]], //159 193 | ['_int', [[0, 19]]], //160 194 | ['_struct', [[['m_decrementMs', 160, -1]]]], //161 195 | ['_struct', [[['m_portraitId', 81, -1]]]], //162 196 | ['_struct', [[['m_functionName', 19, -1]]]], //163 197 | ['_struct', [[['m_result', 81, -1]]]], //164 198 | ['_struct', [[['m_gameMenuItemIndex', 81, -1]]]], //165 199 | ['_struct', [[['m_purchaseCategoryId', 81, -1]]]], //166 200 | ['_struct', [[['m_button', 77, -1]]]], //167 201 | ['_struct', [[['m_cutsceneId', 81, -2], ['m_bookmarkName', 19, -1]]]], //168 202 | ['_struct', [[['m_cutsceneId', 81, -1]]]], //169 203 | ['_struct', [[['m_cutsceneId', 81, -3], ['m_conversationLine', 19, -2], ['m_altConversationLine', 19, -1]]]], //170 204 | ['_struct', [[['m_cutsceneId', 81, -2], ['m_conversationLine', 19, -1]]]], //171 205 | ['_struct', [[['m_observe', 23, -6], ['m_name', 9, -5], ['m_toonHandle', 119, -4], ['m_clanTag', 40, -3], ['m_clanLogo', 41, -2], ['m_hijack', 13, -1]]]], //172 206 | ['_struct', [[['m_state', 23, -1]]]], //173 207 | ['_struct', [[['m_managed', 13, -3], ['m_target', 90, -2], ['m_unitGroup', 42, -1]]]], //174 208 | ['_struct', [[['m_managed', 13, -3], ['m_target', 91, -2], ['m_unitGroup', 42, -1]]]], //175 209 | ['_struct', [[['m_catalog', 10, -4], ['m_entry', 77, -3], ['m_field', 9, -2], ['m_value', 9, -1]]]], //176 210 | ['_struct', [[['m_index', 6, -1]]]], //177 211 | ['_struct', [[['m_recipient', 12, -2], ['m_string', 29, -1]]]], //178 212 | ['_struct', [[['m_recipient', 12, -2], ['m_point', 82, -1]]]], //179 213 | ['_struct', [[['m_progress', 81, -1]]]], //180 214 | ['_struct', [[['m_status', 23, -1]]]], //181 215 | ['_struct', [[['m_scoreValueMineralsCurrent', 81, 0], ['m_scoreValueVespeneCurrent', 81, 1], ['m_scoreValueMineralsCollectionRate', 81, 2], ['m_scoreValueVespeneCollectionRate', 81, 3], ['m_scoreValueWorkersActiveCount', 81, 4], ['m_scoreValueMineralsUsedInProgressArmy', 81, 5], ['m_scoreValueMineralsUsedInProgressEconomy', 81, 6], ['m_scoreValueMineralsUsedInProgressTechnology', 81, 7], ['m_scoreValueVespeneUsedInProgressArmy', 81, 8], ['m_scoreValueVespeneUsedInProgressEconomy', 81, 9], ['m_scoreValueVespeneUsedInProgressTechnology', 81, 10], ['m_scoreValueMineralsUsedCurrentArmy', 81, 11], ['m_scoreValueMineralsUsedCurrentEconomy', 81, 12], ['m_scoreValueMineralsUsedCurrentTechnology', 81, 13], ['m_scoreValueVespeneUsedCurrentArmy', 81, 14], ['m_scoreValueVespeneUsedCurrentEconomy', 81, 15], ['m_scoreValueVespeneUsedCurrentTechnology', 81, 16], ['m_scoreValueMineralsLostArmy', 81, 17], ['m_scoreValueMineralsLostEconomy', 81, 18], ['m_scoreValueMineralsLostTechnology', 81, 19], ['m_scoreValueVespeneLostArmy', 81, 20], ['m_scoreValueVespeneLostEconomy', 81, 21], ['m_scoreValueVespeneLostTechnology', 81, 22], ['m_scoreValueMineralsKilledArmy', 81, 23], ['m_scoreValueMineralsKilledEconomy', 81, 24], ['m_scoreValueMineralsKilledTechnology', 81, 25], ['m_scoreValueVespeneKilledArmy', 81, 26], ['m_scoreValueVespeneKilledEconomy', 81, 27], ['m_scoreValueVespeneKilledTechnology', 81, 28], ['m_scoreValueFoodUsed', 81, 29], ['m_scoreValueFoodMade', 81, 30], ['m_scoreValueMineralsUsedActiveForces', 81, 31], ['m_scoreValueVespeneUsedActiveForces', 81, 32], ['m_scoreValueMineralsFriendlyFireArmy', 81, 33], ['m_scoreValueMineralsFriendlyFireEconomy', 81, 34], ['m_scoreValueMineralsFriendlyFireTechnology', 81, 35], ['m_scoreValueVespeneFriendlyFireArmy', 81, 36], ['m_scoreValueVespeneFriendlyFireEconomy', 81, 37], ['m_scoreValueVespeneFriendlyFireTechnology', 81, 38]]]], //182 216 | ['_struct', [[['m_playerId', 1, 0], ['m_stats', 182, 1]]]], //183 217 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1], ['m_unitTypeName', 28, 2], ['m_controlPlayerId', 1, 3], ['m_upkeepPlayerId', 1, 4], ['m_x', 10, 5], ['m_y', 10, 6]]]], //184 218 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1], ['m_killerPlayerId', 58, 2], ['m_x', 10, 3], ['m_y', 10, 4], ['m_killerUnitTagIndex', 42, 5], ['m_killerUnitTagRecycle', 42, 6]]]], //185 219 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1], ['m_controlPlayerId', 1, 2], ['m_upkeepPlayerId', 1, 3]]]], //186 220 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1], ['m_unitTypeName', 28, 2]]]], //187 221 | ['_struct', [[['m_playerId', 1, 0], ['m_upgradeTypeName', 28, 1], ['m_count', 81, 2]]]], //188 222 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1]]]], //189 223 | ['_array', [[0, 10], 81]], //190 224 | ['_struct', [[['m_firstUnitIndex', 6, 0], ['m_items', 190, 1]]]], //191 225 | ['_struct', [[['m_playerId', 1, 0], ['m_type', 6, 1], ['m_userId', 42, 2], ['m_slotId', 42, 3]]]] //192 226 | ]; 227 | 228 | // Map from protocol NNet.Game.*Event eventid to [typeid, name] 229 | const game_event_types = { 230 | 5: [76, 'NNet.Game.SUserFinishedLoadingSyncEvent'], 231 | 7: [75, 'NNet.Game.SUserOptionsEvent'], 232 | 9: [68, 'NNet.Game.SBankFileEvent'], 233 | 10: [70, 'NNet.Game.SBankSectionEvent'], 234 | 11: [71, 'NNet.Game.SBankKeyEvent'], 235 | 12: [72, 'NNet.Game.SBankValueEvent'], 236 | 13: [74, 'NNet.Game.SBankSignatureEvent'], 237 | 14: [79, 'NNet.Game.SCameraSaveEvent'], 238 | 21: [80, 'NNet.Game.SSaveGameEvent'], 239 | 22: [76, 'NNet.Game.SSaveGameDoneEvent'], 240 | 23: [76, 'NNet.Game.SLoadGameDoneEvent'], 241 | 26: [84, 'NNet.Game.SGameCheatEvent'], 242 | 27: [93, 'NNet.Game.SCmdEvent'], 243 | 28: [101, 'NNet.Game.SSelectionDeltaEvent'], 244 | 29: [102, 'NNet.Game.SControlGroupUpdateEvent'], 245 | 30: [104, 'NNet.Game.SSelectionSyncCheckEvent'], 246 | 31: [106, 'NNet.Game.SResourceTradeEvent'], 247 | 32: [107, 'NNet.Game.STriggerChatMessageEvent'], 248 | 33: [110, 'NNet.Game.SAICommunicateEvent'], 249 | 34: [111, 'NNet.Game.SSetAbsoluteGameSpeedEvent'], 250 | 35: [112, 'NNet.Game.SAddAbsoluteGameSpeedEvent'], 251 | 36: [113, 'NNet.Game.STriggerPingEvent'], 252 | 37: [114, 'NNet.Game.SBroadcastCheatEvent'], 253 | 38: [115, 'NNet.Game.SAllianceEvent'], 254 | 39: [116, 'NNet.Game.SUnitClickEvent'], 255 | 40: [117, 'NNet.Game.SUnitHighlightEvent'], 256 | 41: [118, 'NNet.Game.STriggerReplySelectedEvent'], 257 | 43: [123, 'NNet.Game.SHijackReplayGameEvent'], 258 | 44: [76, 'NNet.Game.STriggerSkippedEvent'], 259 | 45: [128, 'NNet.Game.STriggerSoundLengthQueryEvent'], 260 | 46: [135, 'NNet.Game.STriggerSoundOffsetEvent'], 261 | 47: [136, 'NNet.Game.STriggerTransmissionOffsetEvent'], 262 | 48: [137, 'NNet.Game.STriggerTransmissionCompleteEvent'], 263 | 49: [141, 'NNet.Game.SCameraUpdateEvent'], 264 | 50: [76, 'NNet.Game.STriggerAbortMissionEvent'], 265 | 51: [124, 'NNet.Game.STriggerPurchaseMadeEvent'], 266 | 52: [76, 'NNet.Game.STriggerPurchaseExitEvent'], 267 | 53: [125, 'NNet.Game.STriggerPlanetMissionLaunchedEvent'], 268 | 54: [76, 'NNet.Game.STriggerPlanetPanelCanceledEvent'], 269 | 55: [127, 'NNet.Game.STriggerDialogControlEvent'], 270 | 56: [131, 'NNet.Game.STriggerSoundLengthSyncEvent'], 271 | 57: [142, 'NNet.Game.STriggerConversationSkippedEvent'], 272 | 58: [145, 'NNet.Game.STriggerMouseClickedEvent'], 273 | 59: [146, 'NNet.Game.STriggerMouseMovedEvent'], 274 | 60: [147, 'NNet.Game.SAchievementAwardedEvent'], 275 | 61: [148, 'NNet.Game.STriggerHotkeyPressedEvent'], 276 | 62: [149, 'NNet.Game.STriggerTargetModeUpdateEvent'], 277 | 63: [76, 'NNet.Game.STriggerPlanetPanelReplayEvent'], 278 | 64: [150, 'NNet.Game.STriggerSoundtrackDoneEvent'], 279 | 65: [151, 'NNet.Game.STriggerPlanetMissionSelectedEvent'], 280 | 66: [152, 'NNet.Game.STriggerKeyPressedEvent'], 281 | 67: [163, 'NNet.Game.STriggerMovieFunctionEvent'], 282 | 68: [76, 'NNet.Game.STriggerPlanetPanelBirthCompleteEvent'], 283 | 69: [76, 'NNet.Game.STriggerPlanetPanelDeathCompleteEvent'], 284 | 70: [153, 'NNet.Game.SResourceRequestEvent'], 285 | 71: [154, 'NNet.Game.SResourceRequestFulfillEvent'], 286 | 72: [155, 'NNet.Game.SResourceRequestCancelEvent'], 287 | 73: [76, 'NNet.Game.STriggerResearchPanelExitEvent'], 288 | 74: [76, 'NNet.Game.STriggerResearchPanelPurchaseEvent'], 289 | 75: [156, 'NNet.Game.STriggerResearchPanelSelectionChangedEvent'], 290 | 77: [76, 'NNet.Game.STriggerMercenaryPanelExitEvent'], 291 | 78: [76, 'NNet.Game.STriggerMercenaryPanelPurchaseEvent'], 292 | 79: [157, 'NNet.Game.STriggerMercenaryPanelSelectionChangedEvent'], 293 | 80: [76, 'NNet.Game.STriggerVictoryPanelExitEvent'], 294 | 81: [76, 'NNet.Game.STriggerBattleReportPanelExitEvent'], 295 | 82: [158, 'NNet.Game.STriggerBattleReportPanelPlayMissionEvent'], 296 | 83: [159, 'NNet.Game.STriggerBattleReportPanelPlaySceneEvent'], 297 | 84: [159, 'NNet.Game.STriggerBattleReportPanelSelectionChangedEvent'], 298 | 85: [125, 'NNet.Game.STriggerVictoryPanelPlayMissionAgainEvent'], 299 | 86: [76, 'NNet.Game.STriggerMovieStartedEvent'], 300 | 87: [76, 'NNet.Game.STriggerMovieFinishedEvent'], 301 | 88: [161, 'NNet.Game.SDecrementGameTimeRemainingEvent'], 302 | 89: [162, 'NNet.Game.STriggerPortraitLoadedEvent'], 303 | 90: [164, 'NNet.Game.STriggerCustomDialogDismissedEvent'], 304 | 91: [165, 'NNet.Game.STriggerGameMenuItemSelectedEvent'], 305 | 93: [124, 'NNet.Game.STriggerPurchasePanelSelectedPurchaseItemChangedEvent'], 306 | 94: [166, 'NNet.Game.STriggerPurchasePanelSelectedPurchaseCategoryChangedEvent'], 307 | 95: [167, 'NNet.Game.STriggerButtonPressedEvent'], 308 | 96: [76, 'NNet.Game.STriggerGameCreditsFinishedEvent'], 309 | 97: [168, 'NNet.Game.STriggerCutsceneBookmarkFiredEvent'], 310 | 98: [169, 'NNet.Game.STriggerCutsceneEndSceneFiredEvent'], 311 | 99: [170, 'NNet.Game.STriggerCutsceneConversationLineEvent'], 312 | 100: [171, 'NNet.Game.STriggerCutsceneConversationLineMissingEvent'], 313 | 101: [76, 'NNet.Game.SGameUserLeaveEvent'], 314 | 102: [172, 'NNet.Game.SGameUserJoinEvent'], 315 | 103: [173, 'NNet.Game.SCommandManagerStateEvent'], 316 | 104: [174, 'NNet.Game.SCmdUpdateTargetPointEvent'], 317 | 105: [175, 'NNet.Game.SCmdUpdateTargetUnitEvent'], 318 | 106: [132, 'NNet.Game.STriggerAnimLengthQueryByNameEvent'], 319 | 107: [133, 'NNet.Game.STriggerAnimLengthQueryByPropsEvent'], 320 | 108: [134, 'NNet.Game.STriggerAnimOffsetEvent'], 321 | 109: [176, 'NNet.Game.SCatalogModifyEvent'], 322 | 110: [177, 'NNet.Game.SHeroTalentSelectedEvent'] 323 | }; 324 | 325 | // The typeid of the NNet.Game.EEventId enum. 326 | const game_eventid_typeid = 0; 327 | 328 | // Map from protocol NNet.Game.*Message eventid to [typeid, name] 329 | const message_event_types = { 330 | 0: [178, 'NNet.Game.SChatMessage'], 331 | 1: [179, 'NNet.Game.SPingMessage'], 332 | 2: [180, 'NNet.Game.SLoadingProgressMessage'], 333 | 3: [76, 'NNet.Game.SServerPingMessage'], 334 | 4: [181, 'NNet.Game.SReconnectNotifyMessage'] 335 | }; 336 | 337 | // The typeid of the NNet.Game.EMessageId enum. 338 | const message_eventid_typeid = 1; 339 | 340 | // Map from protocol NNet.Replay.Tracker.*Event eventid to [typeid, name] 341 | const tracker_event_types = { 342 | 0: [183, 'NNet.Replay.Tracker.SPlayerStatsEvent'], 343 | 1: [184, 'NNet.Replay.Tracker.SUnitBornEvent'], 344 | 2: [185, 'NNet.Replay.Tracker.SUnitDiedEvent'], 345 | 3: [186, 'NNet.Replay.Tracker.SUnitOwnerChangeEvent'], 346 | 4: [187, 'NNet.Replay.Tracker.SUnitTypeChangeEvent'], 347 | 5: [188, 'NNet.Replay.Tracker.SUpgradeEvent'], 348 | 6: [184, 'NNet.Replay.Tracker.SUnitInitEvent'], 349 | 7: [189, 'NNet.Replay.Tracker.SUnitDoneEvent'], 350 | 8: [191, 'NNet.Replay.Tracker.SUnitPositionsEvent'], 351 | 9: [192, 'NNet.Replay.Tracker.SPlayerSetupEvent'] 352 | }; 353 | 354 | // The typeid of the NNet.Replay.Tracker.EEventId enum. 355 | const tracker_eventid_typeid = 2; 356 | 357 | // The typeid of NNet.SVarUint32 (the type used to encode gameloop deltas). 358 | const svaruint32_typeid = 7; 359 | 360 | // The typeid of NNet.Replay.SGameUserId (the type used to encode player ids). 361 | const replay_userid_typeid = 8; 362 | 363 | // The typeid of NNet.Replay.SHeader (the type used to store replay game version and length). 364 | const replay_header_typeid = 17; 365 | 366 | // The typeid of NNet.Game.SDetails (the type used to store overall replay details). 367 | const game_details_typeid = 39; 368 | 369 | // The typeid of NNet.Replay.SInitData (the type used to store the inital lobby). 370 | const replay_initdata_typeid = 67; 371 | 372 | // not sure if correct port 373 | function _varuint32Value(value) { 374 | // Returns the numeric value from a SVarUint32 instance. 375 | return value[Object.keys(value)[0]]; 376 | } 377 | 378 | function* _decode_event_stream(decoder, eventidTypeid, eventTypes, decodeUserId) { 379 | // Decodes events prefixed with a gameloop and possibly userid 380 | var gameloop = 0; 381 | while (!decoder.done()) { 382 | var startBits = decoder.usedBits(); 383 | 384 | // decode the gameloop delta before each event 385 | var delta = _varuint32Value(decoder.instance(svaruint32_typeid)); 386 | gameloop += delta; 387 | 388 | // decode the userid before each event 389 | var userid = (decodeUserId === true) ? decoder.instance(replay_userid_typeid) : undefined; 390 | 391 | // decode the event id 392 | var eventid = decoder.instance(eventidTypeid); 393 | var eventType = eventTypes[eventid] || [null, null]; 394 | var typeid = eventType[0]; 395 | var typename = eventType[1]; 396 | if (typeid === null) throw new decoders.CorruptedError('eventid(' + eventid + ') at ' + decoder.toString()); 397 | 398 | // decode the event struct instance 399 | var event = decoder.instance(typeid); 400 | event._event = typename; 401 | event._eventid = eventid; 402 | 403 | // insert gameloop and userid 404 | event._gameloop = gameloop; 405 | if (decodeUserId) event._userid = userid; 406 | 407 | // the next event is byte aligned 408 | decoder.byteAlign(); 409 | 410 | // insert bits used in stream 411 | event._bits = decoder.usedBits() - startBits; 412 | 413 | yield event; 414 | } 415 | } 416 | 417 | exports.decodeReplayGameEvents = function* (contents) { 418 | // Decodes and yields each game event from the contents byte string. 419 | const decoder = new BitPackedDecoder(contents, typeinfos); 420 | for (let event of _decode_event_stream(decoder, game_eventid_typeid, game_event_types, true)) 421 | yield event; 422 | }; 423 | 424 | exports.decodeReplayMessageEvents = function* (contents) { 425 | // Decodes and yields each message event from the contents byte string. 426 | const decoder = new BitPackedDecoder(contents, typeinfos); 427 | for (let event of _decode_event_stream(decoder, message_eventid_typeid, message_event_types, true)) 428 | yield event; 429 | }; 430 | 431 | exports.decodeReplayTrackerEvents = function* (contents) { 432 | // Decodes and yields each tracker event from the contents byte string. 433 | const decoder = new VersionDecoder(contents, typeinfos); 434 | for (let event of _decode_event_stream(decoder, tracker_eventid_typeid, tracker_event_types, false)) 435 | yield event; 436 | }; 437 | 438 | exports.decodeReplayHeader = function(contents) { 439 | // Decodes and return the replay header from the contents byte string. 440 | const decoder = new VersionDecoder(contents, typeinfos); 441 | return decoder.instance(replay_header_typeid); 442 | }; 443 | 444 | exports.decodeReplayDetails = function(contents) { 445 | // Decodes and returns the game details from the contents byte string. 446 | const decoder = new VersionDecoder(contents, typeinfos); 447 | return decoder.instance(game_details_typeid); 448 | }; 449 | 450 | exports.decodeReplayInitdata = function(contents) { 451 | // Decodes and return the replay init data from the contents byte string. 452 | const decoder = new BitPackedDecoder(contents, typeinfos); 453 | return decoder.instance(replay_initdata_typeid); 454 | }; 455 | 456 | exports.decodeReplayAttributesEvents = function (contents) { 457 | // Decodes and yields each attribute from the contents byte string. 458 | const buffer = new decoders.BitPackedBuffer(contents, 'little'); 459 | const attributes = {}; 460 | 461 | if (!buffer.done()) { 462 | attributes.source = buffer.readBits(8); 463 | attributes.mapNameSpace = buffer.readBits(32); 464 | var count = buffer.readBits(32); 465 | attributes.scopes = {}; 466 | 467 | while (!buffer.done()) { 468 | var value = {}; 469 | value.namespace = buffer.readBits(32); 470 | var attrid = value.attrid = buffer.readBits(32); 471 | var scope = buffer.readBits(8); 472 | value.value = buffer.readAlignedBytes(4).reverse(); 473 | while (value.value[0] === 0) value.value = value.value.slice(1); 474 | while (value.value[value.value.length - 1] === 0) value.value = value.value.slice(0, -1); 475 | if (!attributes.scopes[scope]) 476 | attributes.scopes[scope] = {}; 477 | if (!attributes.scopes[scope][attrid]) 478 | attributes.scopes[scope][attrid] = []; 479 | attributes.scopes[scope][attrid].push(value); 480 | } 481 | } 482 | 483 | return attributes; 484 | }; 485 | 486 | exports.unitTag = function(unitTagIndex, unitTagRecycle) { 487 | return (unitTagIndex << 18) + unitTagRecycle; 488 | }; 489 | 490 | exports.unitTagIndex = function(unitTag) { 491 | return (unitTag >> 18) & 0x00003FFF; 492 | }; 493 | 494 | exports.unitTagRecycle = function(unitTag) { 495 | return unitTag & 0x0003FFFF; 496 | }; 497 | -------------------------------------------------------------------------------- /lib/protocol30829.js: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright (c) 2015 Blizzard Entertainment 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | */ 22 | "use strict"; 23 | 24 | exports.version = 30829; 25 | 26 | const decoders = require('./decoders'); 27 | const BitPackedDecoder = decoders.BitPackedDecoder; 28 | const VersionDecoder = decoders.VersionDecoder; 29 | 30 | 31 | // Decoding instructions for each protocol type. 32 | const typeinfos = [ 33 | ['_int', [[0, 7]]], //0 34 | ['_int', [[0, 4]]], //1 35 | ['_int', [[0, 5]]], //2 36 | ['_int', [[0, 6]]], //3 37 | ['_int', [[0, 14]]], //4 38 | ['_int', [[0, 22]]], //5 39 | ['_int', [[0, 32]]], //6 40 | ['_choice', [[0, 2], { 0: ['m_uint6', 3], 1: ['m_uint14', 4], 2: ['m_uint22', 5], 3: ['m_uint32', 6]}]], //7 41 | ['_struct', [[['m_userId', 2, -1]]]], //8 42 | ['_blob', [[0, 8]]], //9 43 | ['_int', [[0, 8]]], //10 44 | ['_struct', [[['m_flags', 10, 0], ['m_major', 10, 1], ['m_minor', 10, 2], ['m_revision', 10, 3], ['m_build', 6, 4], ['m_baseBuild', 6, 5]]]], //11 45 | ['_int', [[0, 3]]], //12 46 | ['_bool', []], //13 47 | ['_array', [[16, 0], 10]], //14 48 | ['_optional', [14]], //15 49 | ['_struct', [[['m_data', 15, 0]]]], //16 50 | ['_struct', [[['m_signature', 9, 0], ['m_version', 11, 1], ['m_type', 12, 2], ['m_elapsedGameLoops', 6, 3], ['m_useScaledTime', 13, 4], ['m_ngdpRootKey', 16, 5], ['m_dataBuildNum', 6, 6]]]], //17 51 | ['_fourcc', []], //18 52 | ['_blob', [[0, 7]]], //19 53 | ['_int', [[0, 64]]], //20 54 | ['_struct', [[['m_region', 10, 0], ['m_programId', 18, 1], ['m_realm', 6, 2], ['m_name', 19, 3], ['m_id', 20, 4]]]], //21 55 | ['_struct', [[['m_a', 10, 0], ['m_r', 10, 1], ['m_g', 10, 2], ['m_b', 10, 3]]]], //22 56 | ['_int', [[0, 2]]], //23 57 | ['_optional', [10]], //24 58 | ['_struct', [[['m_name', 9, 0], ['m_toon', 21, 1], ['m_race', 9, 2], ['m_color', 22, 3], ['m_control', 10, 4], ['m_teamId', 1, 5], ['m_handicap', 0, 6], ['m_observe', 23, 7], ['m_result', 23, 8], ['m_workingSetSlotId', 24, 9], ['m_hero', 9, 10]]]], //25 59 | ['_array', [[0, 5], 25]], //26 60 | ['_optional', [26]], //27 61 | ['_blob', [[0, 10]]], //28 62 | ['_blob', [[0, 11]]], //29 63 | ['_struct', [[['m_file', 29, 0]]]], //30 64 | ['_optional', [13]], //31 65 | ['_int', [[-9223372036854775808, 64]]], //32 66 | ['_blob', [[0, 12]]], //33 67 | ['_blob', [[40, 0]]], //34 68 | ['_array', [[0, 6], 34]], //35 69 | ['_optional', [35]], //36 70 | ['_array', [[0, 6], 29]], //37 71 | ['_optional', [37]], //38 72 | ['_struct', [[['m_playerList', 27, 0], ['m_title', 28, 1], ['m_difficulty', 9, 2], ['m_thumbnail', 30, 3], ['m_isBlizzardMap', 13, 4], ['m_restartAsTransitionMap', 31, 16], ['m_timeUTC', 32, 5], ['m_timeLocalOffset', 32, 6], ['m_description', 33, 7], ['m_imageFilePath', 29, 8], ['m_campaignIndex', 10, 15], ['m_mapFileName', 29, 9], ['m_cacheHandles', 36, 10], ['m_miniSave', 13, 11], ['m_gameSpeed', 12, 12], ['m_defaultDifficulty', 3, 13], ['m_modPaths', 38, 14]]]], //39 73 | ['_optional', [9]], //40 74 | ['_optional', [34]], //41 75 | ['_optional', [6]], //42 76 | ['_struct', [[['m_race', 24, -1]]]], //43 77 | ['_struct', [[['m_team', 24, -1]]]], //44 78 | ['_blob', [[0, 9]]], //45 79 | ['_struct', [[['m_name', 9, -18], ['m_clanTag', 40, -17], ['m_clanLogo', 41, -16], ['m_highestLeague', 24, -15], ['m_combinedRaceLevels', 42, -14], ['m_randomSeed', 6, -13], ['m_racePreference', 43, -12], ['m_teamPreference', 44, -11], ['m_testMap', 13, -10], ['m_testAuto', 13, -9], ['m_examine', 13, -8], ['m_customInterface', 13, -7], ['m_testType', 6, -6], ['m_observe', 23, -5], ['m_hero', 45, -4], ['m_skin', 45, -3], ['m_mount', 45, -2], ['m_toonHandle', 19, -1]]]], //46 80 | ['_array', [[0, 5], 46]], //47 81 | ['_struct', [[['m_lockTeams', 13, -14], ['m_teamsTogether', 13, -13], ['m_advancedSharedControl', 13, -12], ['m_randomRaces', 13, -11], ['m_battleNet', 13, -10], ['m_amm', 13, -9], ['m_competitive', 13, -8], ['m_practice', 13, -7], ['m_cooperative', 13, -6], ['m_noVictoryOrDefeat', 13, -5], ['m_fog', 23, -4], ['m_observers', 23, -3], ['m_userDifficulty', 23, -2], ['m_clientDebugFlags', 20, -1]]]], //48 82 | ['_int', [[1, 4]]], //49 83 | ['_int', [[1, 8]]], //50 84 | ['_bitarray', [[0, 6]]], //51 85 | ['_bitarray', [[0, 8]]], //52 86 | ['_bitarray', [[0, 2]]], //53 87 | ['_bitarray', [[0, 7]]], //54 88 | ['_struct', [[['m_allowedColors', 51, -6], ['m_allowedRaces', 52, -5], ['m_allowedDifficulty', 51, -4], ['m_allowedControls', 52, -3], ['m_allowedObserveTypes', 53, -2], ['m_allowedAIBuilds', 54, -1]]]], //55 89 | ['_array', [[0, 5], 55]], //56 90 | ['_struct', [[['m_randomValue', 6, -26], ['m_gameCacheName', 28, -25], ['m_gameOptions', 48, -24], ['m_gameSpeed', 12, -23], ['m_gameType', 12, -22], ['m_maxUsers', 2, -21], ['m_maxObservers', 2, -20], ['m_maxPlayers', 2, -19], ['m_maxTeams', 49, -18], ['m_maxColors', 3, -17], ['m_maxRaces', 50, -16], ['m_maxControls', 10, -15], ['m_mapSizeX', 10, -14], ['m_mapSizeY', 10, -13], ['m_mapFileSyncChecksum', 6, -12], ['m_mapFileName', 29, -11], ['m_mapAuthorName', 9, -10], ['m_modFileSyncChecksum', 6, -9], ['m_slotDescriptions', 56, -8], ['m_defaultDifficulty', 3, -7], ['m_defaultAIBuild', 0, -6], ['m_cacheHandles', 35, -5], ['m_hasExtensionMod', 13, -4], ['m_isBlizzardMap', 13, -3], ['m_isPremadeFFA', 13, -2], ['m_isCoopMode', 13, -1]]]], //57 91 | ['_optional', [1]], //58 92 | ['_optional', [2]], //59 93 | ['_struct', [[['m_color', 59, -1]]]], //60 94 | ['_array', [[0, 17], 6]], //61 95 | ['_array', [[0, 9], 6]], //62 96 | ['_struct', [[['m_control', 10, -16], ['m_userId', 58, -15], ['m_teamId', 1, -14], ['m_colorPref', 60, -13], ['m_racePref', 43, -12], ['m_difficulty', 3, -11], ['m_aiBuild', 0, -10], ['m_handicap', 0, -9], ['m_observe', 23, -8], ['m_hero', 45, -7], ['m_skin', 45, -6], ['m_mount', 45, -5], ['m_workingSetSlotId', 24, -4], ['m_rewards', 61, -3], ['m_toonHandle', 19, -2], ['m_licenses', 62, -1]]]], //63 97 | ['_array', [[0, 5], 63]], //64 98 | ['_struct', [[['m_phase', 12, -10], ['m_maxUsers', 2, -9], ['m_maxObservers', 2, -8], ['m_slots', 64, -7], ['m_randomSeed', 6, -6], ['m_hostUserId', 58, -5], ['m_isSinglePlayer', 13, -4], ['m_gameDuration', 6, -3], ['m_defaultDifficulty', 3, -2], ['m_defaultAIBuild', 0, -1]]]], //65 99 | ['_struct', [[['m_userInitialData', 47, -3], ['m_gameDescription', 57, -2], ['m_lobbyState', 65, -1]]]], //66 100 | ['_struct', [[['m_syncLobbyState', 66, -1]]]], //67 101 | ['_struct', [[['m_name', 19, -1]]]], //68 102 | ['_blob', [[0, 6]]], //69 103 | ['_struct', [[['m_name', 69, -1]]]], //70 104 | ['_struct', [[['m_name', 69, -3], ['m_type', 6, -2], ['m_data', 19, -1]]]], //71 105 | ['_struct', [[['m_type', 6, -3], ['m_name', 69, -2], ['m_data', 33, -1]]]], //72 106 | ['_array', [[0, 5], 10]], //73 107 | ['_struct', [[['m_signature', 73, -2], ['m_toonHandle', 19, -1]]]], //74 108 | ['_struct', [[['m_gameFullyDownloaded', 13, -14], ['m_developmentCheatsEnabled', 13, -13], ['m_multiplayerCheatsEnabled', 13, -12], ['m_syncChecksummingEnabled', 13, -11], ['m_isMapToMapTransition', 13, -10], ['m_startingRally', 13, -9], ['m_debugPauseEnabled', 13, -8], ['m_useGalaxyAsserts', 13, -7], ['m_platformMac', 13, -6], ['m_cameraFollow', 13, -5], ['m_baseBuildNum', 6, -4], ['m_buildNum', 6, -3], ['m_versionFlags', 6, -2], ['m_hotkeyProfile', 45, -1]]]], //75 109 | ['_struct', [[]]], //76 110 | ['_int', [[0, 16]]], //77 111 | ['_struct', [[['x', 77, -2], ['y', 77, -1]]]], //78 112 | ['_struct', [[['m_which', 12, -2], ['m_target', 78, -1]]]], //79 113 | ['_struct', [[['m_fileName', 29, -5], ['m_automatic', 13, -4], ['m_overwrite', 13, -3], ['m_name', 9, -2], ['m_description', 28, -1]]]], //80 114 | ['_int', [[-2147483648, 32]]], //81 115 | ['_struct', [[['x', 81, -2], ['y', 81, -1]]]], //82 116 | ['_struct', [[['m_point', 82, -4], ['m_time', 81, -3], ['m_verb', 28, -2], ['m_arguments', 28, -1]]]], //83 117 | ['_struct', [[['m_data', 83, -1]]]], //84 118 | ['_int', [[0, 21]]], //85 119 | ['_struct', [[['m_abilLink', 77, -3], ['m_abilCmdIndex', 2, -2], ['m_abilCmdData', 24, -1]]]], //86 120 | ['_optional', [86]], //87 121 | ['_null', []], //88 122 | ['_int', [[0, 20]]], //89 123 | ['_struct', [[['x', 89, -3], ['y', 89, -2], ['z', 81, -1]]]], //90 124 | ['_struct', [[['m_targetUnitFlags', 77, -7], ['m_timer', 10, -6], ['m_tag', 6, -5], ['m_snapshotUnitLink', 77, -4], ['m_snapshotControlPlayerId', 58, -3], ['m_snapshotUpkeepPlayerId', 58, -2], ['m_snapshotPoint', 90, -1]]]], //91 125 | ['_choice', [[0, 2], { 0: ['None', 88], 1: ['TargetPoint', 90], 2: ['TargetUnit', 91], 3: ['Data', 6]}]], //92 126 | ['_struct', [[['m_cmdFlags', 85, -5], ['m_abil', 87, -4], ['m_data', 92, -3], ['m_otherUnit', 42, -2], ['m_unitGroup', 42, -1]]]], //93 127 | ['_int', [[0, 9]]], //94 128 | ['_bitarray', [[0, 9]]], //95 129 | ['_array', [[0, 9], 94]], //96 130 | ['_choice', [[0, 2], { 0: ['None', 88], 1: ['Mask', 95], 2: ['OneIndices', 96], 3: ['ZeroIndices', 96]}]], //97 131 | ['_struct', [[['m_unitLink', 77, -4], ['m_subgroupPriority', 10, -3], ['m_intraSubgroupPriority', 10, -2], ['m_count', 94, -1]]]], //98 132 | ['_array', [[0, 9], 98]], //99 133 | ['_struct', [[['m_subgroupIndex', 94, -4], ['m_removeMask', 97, -3], ['m_addSubgroups', 99, -2], ['m_addUnitTags', 62, -1]]]], //100 134 | ['_struct', [[['m_controlGroupId', 1, -2], ['m_delta', 100, -1]]]], //101 135 | ['_struct', [[['m_controlGroupIndex', 1, -3], ['m_controlGroupUpdate', 23, -2], ['m_mask', 97, -1]]]], //102 136 | ['_struct', [[['m_count', 94, -6], ['m_subgroupCount', 94, -5], ['m_activeSubgroupIndex', 94, -4], ['m_unitTagsChecksum', 6, -3], ['m_subgroupIndicesChecksum', 6, -2], ['m_subgroupsChecksum', 6, -1]]]], //103 137 | ['_struct', [[['m_controlGroupId', 1, -2], ['m_selectionSyncData', 103, -1]]]], //104 138 | ['_array', [[0, 3], 81]], //105 139 | ['_struct', [[['m_recipientId', 1, -2], ['m_resources', 105, -1]]]], //106 140 | ['_struct', [[['m_chatMessage', 28, -1]]]], //107 141 | ['_int', [[-128, 8]]], //108 142 | ['_struct', [[['x', 81, -3], ['y', 81, -2], ['z', 81, -1]]]], //109 143 | ['_struct', [[['m_beacon', 108, -9], ['m_ally', 108, -8], ['m_flags', 108, -7], ['m_build', 108, -6], ['m_targetUnitTag', 6, -5], ['m_targetUnitSnapshotUnitLink', 77, -4], ['m_targetUnitSnapshotUpkeepPlayerId', 108, -3], ['m_targetUnitSnapshotControlPlayerId', 108, -2], ['m_targetPoint', 109, -1]]]], //110 144 | ['_struct', [[['m_speed', 12, -1]]]], //111 145 | ['_struct', [[['m_delta', 108, -1]]]], //112 146 | ['_struct', [[['m_point', 82, -4], ['m_unit', 6, -3], ['m_pingedMinimap', 13, -2], ['m_option', 81, -1]]]], //113 147 | ['_struct', [[['m_verb', 28, -2], ['m_arguments', 28, -1]]]], //114 148 | ['_struct', [[['m_alliance', 6, -2], ['m_control', 6, -1]]]], //115 149 | ['_struct', [[['m_unitTag', 6, -1]]]], //116 150 | ['_struct', [[['m_unitTag', 6, -2], ['m_flags', 10, -1]]]], //117 151 | ['_struct', [[['m_conversationId', 81, -2], ['m_replyId', 81, -1]]]], //118 152 | ['_optional', [19]], //119 153 | ['_struct', [[['m_gameUserId', 1, -6], ['m_observe', 23, -5], ['m_name', 9, -4], ['m_toonHandle', 119, -3], ['m_clanTag', 40, -2], ['m_clanLogo', 41, -1]]]], //120 154 | ['_array', [[0, 5], 120]], //121 155 | ['_int', [[0, 1]]], //122 156 | ['_struct', [[['m_userInfos', 121, -2], ['m_method', 122, -1]]]], //123 157 | ['_struct', [[['m_purchaseItemId', 81, -1]]]], //124 158 | ['_struct', [[['m_difficultyLevel', 81, -1]]]], //125 159 | ['_choice', [[0, 3], { 0: ['None', 88], 1: ['Checked', 13], 2: ['ValueChanged', 6], 3: ['SelectionChanged', 81], 4: ['TextChanged', 29], 5: ['MouseButton', 6]}]], //126 160 | ['_struct', [[['m_controlId', 81, -3], ['m_eventType', 81, -2], ['m_eventData', 126, -1]]]], //127 161 | ['_struct', [[['m_soundHash', 6, -2], ['m_length', 6, -1]]]], //128 162 | ['_array', [[0, 7], 6]], //129 163 | ['_struct', [[['m_soundHash', 129, -2], ['m_length', 129, -1]]]], //130 164 | ['_struct', [[['m_syncInfo', 130, -1]]]], //131 165 | ['_struct', [[['m_queryId', 77, -3], ['m_lengthMs', 6, -2], ['m_finishGameLoop', 6, -1]]]], //132 166 | ['_struct', [[['m_queryId', 77, -2], ['m_lengthMs', 6, -1]]]], //133 167 | ['_struct', [[['m_animWaitQueryId', 77, -1]]]], //134 168 | ['_struct', [[['m_sound', 6, -1]]]], //135 169 | ['_struct', [[['m_transmissionId', 81, -2], ['m_thread', 6, -1]]]], //136 170 | ['_struct', [[['m_transmissionId', 81, -1]]]], //137 171 | ['_optional', [78]], //138 172 | ['_optional', [77]], //139 173 | ['_optional', [108]], //140 174 | ['_struct', [[['m_target', 138, -6], ['m_distance', 139, -5], ['m_pitch', 139, -4], ['m_yaw', 139, -3], ['m_reason', 140, -2], ['m_follow', 13, -1]]]], //141 175 | ['_struct', [[['m_skipType', 122, -1]]]], //142 176 | ['_int', [[0, 11]]], //143 177 | ['_struct', [[['x', 143, -2], ['y', 143, -1]]]], //144 178 | ['_struct', [[['m_button', 6, -5], ['m_down', 13, -4], ['m_posUI', 144, -3], ['m_posWorld', 90, -2], ['m_flags', 108, -1]]]], //145 179 | ['_struct', [[['m_posUI', 144, -3], ['m_posWorld', 90, -2], ['m_flags', 108, -1]]]], //146 180 | ['_struct', [[['m_achievementLink', 77, -1]]]], //147 181 | ['_struct', [[['m_hotkey', 6, -2], ['m_down', 13, -1]]]], //148 182 | ['_struct', [[['m_abilLink', 77, -3], ['m_abilCmdIndex', 2, -2], ['m_state', 108, -1]]]], //149 183 | ['_struct', [[['m_soundtrack', 6, -1]]]], //150 184 | ['_struct', [[['m_planetId', 81, -1]]]], //151 185 | ['_struct', [[['m_key', 108, -2], ['m_flags', 108, -1]]]], //152 186 | ['_struct', [[['m_resources', 105, -1]]]], //153 187 | ['_struct', [[['m_fulfillRequestId', 81, -1]]]], //154 188 | ['_struct', [[['m_cancelRequestId', 81, -1]]]], //155 189 | ['_struct', [[['m_researchItemId', 81, -1]]]], //156 190 | ['_struct', [[['m_mercenaryId', 81, -1]]]], //157 191 | ['_struct', [[['m_battleReportId', 81, -2], ['m_difficultyLevel', 81, -1]]]], //158 192 | ['_struct', [[['m_battleReportId', 81, -1]]]], //159 193 | ['_int', [[0, 19]]], //160 194 | ['_struct', [[['m_decrementMs', 160, -1]]]], //161 195 | ['_struct', [[['m_portraitId', 81, -1]]]], //162 196 | ['_struct', [[['m_functionName', 19, -1]]]], //163 197 | ['_struct', [[['m_result', 81, -1]]]], //164 198 | ['_struct', [[['m_gameMenuItemIndex', 81, -1]]]], //165 199 | ['_struct', [[['m_purchaseCategoryId', 81, -1]]]], //166 200 | ['_struct', [[['m_button', 77, -1]]]], //167 201 | ['_struct', [[['m_cutsceneId', 81, -2], ['m_bookmarkName', 19, -1]]]], //168 202 | ['_struct', [[['m_cutsceneId', 81, -1]]]], //169 203 | ['_struct', [[['m_cutsceneId', 81, -3], ['m_conversationLine', 19, -2], ['m_altConversationLine', 19, -1]]]], //170 204 | ['_struct', [[['m_cutsceneId', 81, -2], ['m_conversationLine', 19, -1]]]], //171 205 | ['_struct', [[['m_observe', 23, -6], ['m_name', 9, -5], ['m_toonHandle', 119, -4], ['m_clanTag', 40, -3], ['m_clanLogo', 41, -2], ['m_hijack', 13, -1]]]], //172 206 | ['_struct', [[['m_state', 23, -1]]]], //173 207 | ['_struct', [[['m_managed', 13, -3], ['m_target', 90, -2], ['m_unitGroup', 42, -1]]]], //174 208 | ['_struct', [[['m_managed', 13, -3], ['m_target', 91, -2], ['m_unitGroup', 42, -1]]]], //175 209 | ['_struct', [[['m_catalog', 10, -4], ['m_entry', 77, -3], ['m_field', 9, -2], ['m_value', 9, -1]]]], //176 210 | ['_struct', [[['m_index', 6, -1]]]], //177 211 | ['_struct', [[['m_recipient', 12, -2], ['m_string', 29, -1]]]], //178 212 | ['_struct', [[['m_recipient', 12, -2], ['m_point', 82, -1]]]], //179 213 | ['_struct', [[['m_progress', 81, -1]]]], //180 214 | ['_struct', [[['m_status', 23, -1]]]], //181 215 | ['_struct', [[['m_scoreValueMineralsCurrent', 81, 0], ['m_scoreValueVespeneCurrent', 81, 1], ['m_scoreValueMineralsCollectionRate', 81, 2], ['m_scoreValueVespeneCollectionRate', 81, 3], ['m_scoreValueWorkersActiveCount', 81, 4], ['m_scoreValueMineralsUsedInProgressArmy', 81, 5], ['m_scoreValueMineralsUsedInProgressEconomy', 81, 6], ['m_scoreValueMineralsUsedInProgressTechnology', 81, 7], ['m_scoreValueVespeneUsedInProgressArmy', 81, 8], ['m_scoreValueVespeneUsedInProgressEconomy', 81, 9], ['m_scoreValueVespeneUsedInProgressTechnology', 81, 10], ['m_scoreValueMineralsUsedCurrentArmy', 81, 11], ['m_scoreValueMineralsUsedCurrentEconomy', 81, 12], ['m_scoreValueMineralsUsedCurrentTechnology', 81, 13], ['m_scoreValueVespeneUsedCurrentArmy', 81, 14], ['m_scoreValueVespeneUsedCurrentEconomy', 81, 15], ['m_scoreValueVespeneUsedCurrentTechnology', 81, 16], ['m_scoreValueMineralsLostArmy', 81, 17], ['m_scoreValueMineralsLostEconomy', 81, 18], ['m_scoreValueMineralsLostTechnology', 81, 19], ['m_scoreValueVespeneLostArmy', 81, 20], ['m_scoreValueVespeneLostEconomy', 81, 21], ['m_scoreValueVespeneLostTechnology', 81, 22], ['m_scoreValueMineralsKilledArmy', 81, 23], ['m_scoreValueMineralsKilledEconomy', 81, 24], ['m_scoreValueMineralsKilledTechnology', 81, 25], ['m_scoreValueVespeneKilledArmy', 81, 26], ['m_scoreValueVespeneKilledEconomy', 81, 27], ['m_scoreValueVespeneKilledTechnology', 81, 28], ['m_scoreValueFoodUsed', 81, 29], ['m_scoreValueFoodMade', 81, 30], ['m_scoreValueMineralsUsedActiveForces', 81, 31], ['m_scoreValueVespeneUsedActiveForces', 81, 32], ['m_scoreValueMineralsFriendlyFireArmy', 81, 33], ['m_scoreValueMineralsFriendlyFireEconomy', 81, 34], ['m_scoreValueMineralsFriendlyFireTechnology', 81, 35], ['m_scoreValueVespeneFriendlyFireArmy', 81, 36], ['m_scoreValueVespeneFriendlyFireEconomy', 81, 37], ['m_scoreValueVespeneFriendlyFireTechnology', 81, 38]]]], //182 216 | ['_struct', [[['m_playerId', 1, 0], ['m_stats', 182, 1]]]], //183 217 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1], ['m_unitTypeName', 28, 2], ['m_controlPlayerId', 1, 3], ['m_upkeepPlayerId', 1, 4], ['m_x', 10, 5], ['m_y', 10, 6]]]], //184 218 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1], ['m_killerPlayerId', 58, 2], ['m_x', 10, 3], ['m_y', 10, 4], ['m_killerUnitTagIndex', 42, 5], ['m_killerUnitTagRecycle', 42, 6]]]], //185 219 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1], ['m_controlPlayerId', 1, 2], ['m_upkeepPlayerId', 1, 3]]]], //186 220 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1], ['m_unitTypeName', 28, 2]]]], //187 221 | ['_struct', [[['m_playerId', 1, 0], ['m_upgradeTypeName', 28, 1], ['m_count', 81, 2]]]], //188 222 | ['_struct', [[['m_unitTagIndex', 6, 0], ['m_unitTagRecycle', 6, 1]]]], //189 223 | ['_array', [[0, 10], 81]], //190 224 | ['_struct', [[['m_firstUnitIndex', 6, 0], ['m_items', 190, 1]]]], //191 225 | ['_struct', [[['m_playerId', 1, 0], ['m_type', 6, 1], ['m_userId', 42, 2], ['m_slotId', 42, 3]]]] //192 226 | ]; 227 | 228 | // Map from protocol NNet.Game.*Event eventid to [typeid, name] 229 | const game_event_types = { 230 | 5: [76, 'NNet.Game.SUserFinishedLoadingSyncEvent'], 231 | 7: [75, 'NNet.Game.SUserOptionsEvent'], 232 | 9: [68, 'NNet.Game.SBankFileEvent'], 233 | 10: [70, 'NNet.Game.SBankSectionEvent'], 234 | 11: [71, 'NNet.Game.SBankKeyEvent'], 235 | 12: [72, 'NNet.Game.SBankValueEvent'], 236 | 13: [74, 'NNet.Game.SBankSignatureEvent'], 237 | 14: [79, 'NNet.Game.SCameraSaveEvent'], 238 | 21: [80, 'NNet.Game.SSaveGameEvent'], 239 | 22: [76, 'NNet.Game.SSaveGameDoneEvent'], 240 | 23: [76, 'NNet.Game.SLoadGameDoneEvent'], 241 | 26: [84, 'NNet.Game.SGameCheatEvent'], 242 | 27: [93, 'NNet.Game.SCmdEvent'], 243 | 28: [101, 'NNet.Game.SSelectionDeltaEvent'], 244 | 29: [102, 'NNet.Game.SControlGroupUpdateEvent'], 245 | 30: [104, 'NNet.Game.SSelectionSyncCheckEvent'], 246 | 31: [106, 'NNet.Game.SResourceTradeEvent'], 247 | 32: [107, 'NNet.Game.STriggerChatMessageEvent'], 248 | 33: [110, 'NNet.Game.SAICommunicateEvent'], 249 | 34: [111, 'NNet.Game.SSetAbsoluteGameSpeedEvent'], 250 | 35: [112, 'NNet.Game.SAddAbsoluteGameSpeedEvent'], 251 | 36: [113, 'NNet.Game.STriggerPingEvent'], 252 | 37: [114, 'NNet.Game.SBroadcastCheatEvent'], 253 | 38: [115, 'NNet.Game.SAllianceEvent'], 254 | 39: [116, 'NNet.Game.SUnitClickEvent'], 255 | 40: [117, 'NNet.Game.SUnitHighlightEvent'], 256 | 41: [118, 'NNet.Game.STriggerReplySelectedEvent'], 257 | 43: [123, 'NNet.Game.SHijackReplayGameEvent'], 258 | 44: [76, 'NNet.Game.STriggerSkippedEvent'], 259 | 45: [128, 'NNet.Game.STriggerSoundLengthQueryEvent'], 260 | 46: [135, 'NNet.Game.STriggerSoundOffsetEvent'], 261 | 47: [136, 'NNet.Game.STriggerTransmissionOffsetEvent'], 262 | 48: [137, 'NNet.Game.STriggerTransmissionCompleteEvent'], 263 | 49: [141, 'NNet.Game.SCameraUpdateEvent'], 264 | 50: [76, 'NNet.Game.STriggerAbortMissionEvent'], 265 | 51: [124, 'NNet.Game.STriggerPurchaseMadeEvent'], 266 | 52: [76, 'NNet.Game.STriggerPurchaseExitEvent'], 267 | 53: [125, 'NNet.Game.STriggerPlanetMissionLaunchedEvent'], 268 | 54: [76, 'NNet.Game.STriggerPlanetPanelCanceledEvent'], 269 | 55: [127, 'NNet.Game.STriggerDialogControlEvent'], 270 | 56: [131, 'NNet.Game.STriggerSoundLengthSyncEvent'], 271 | 57: [142, 'NNet.Game.STriggerConversationSkippedEvent'], 272 | 58: [145, 'NNet.Game.STriggerMouseClickedEvent'], 273 | 59: [146, 'NNet.Game.STriggerMouseMovedEvent'], 274 | 60: [147, 'NNet.Game.SAchievementAwardedEvent'], 275 | 61: [148, 'NNet.Game.STriggerHotkeyPressedEvent'], 276 | 62: [149, 'NNet.Game.STriggerTargetModeUpdateEvent'], 277 | 63: [76, 'NNet.Game.STriggerPlanetPanelReplayEvent'], 278 | 64: [150, 'NNet.Game.STriggerSoundtrackDoneEvent'], 279 | 65: [151, 'NNet.Game.STriggerPlanetMissionSelectedEvent'], 280 | 66: [152, 'NNet.Game.STriggerKeyPressedEvent'], 281 | 67: [163, 'NNet.Game.STriggerMovieFunctionEvent'], 282 | 68: [76, 'NNet.Game.STriggerPlanetPanelBirthCompleteEvent'], 283 | 69: [76, 'NNet.Game.STriggerPlanetPanelDeathCompleteEvent'], 284 | 70: [153, 'NNet.Game.SResourceRequestEvent'], 285 | 71: [154, 'NNet.Game.SResourceRequestFulfillEvent'], 286 | 72: [155, 'NNet.Game.SResourceRequestCancelEvent'], 287 | 73: [76, 'NNet.Game.STriggerResearchPanelExitEvent'], 288 | 74: [76, 'NNet.Game.STriggerResearchPanelPurchaseEvent'], 289 | 75: [156, 'NNet.Game.STriggerResearchPanelSelectionChangedEvent'], 290 | 77: [76, 'NNet.Game.STriggerMercenaryPanelExitEvent'], 291 | 78: [76, 'NNet.Game.STriggerMercenaryPanelPurchaseEvent'], 292 | 79: [157, 'NNet.Game.STriggerMercenaryPanelSelectionChangedEvent'], 293 | 80: [76, 'NNet.Game.STriggerVictoryPanelExitEvent'], 294 | 81: [76, 'NNet.Game.STriggerBattleReportPanelExitEvent'], 295 | 82: [158, 'NNet.Game.STriggerBattleReportPanelPlayMissionEvent'], 296 | 83: [159, 'NNet.Game.STriggerBattleReportPanelPlaySceneEvent'], 297 | 84: [159, 'NNet.Game.STriggerBattleReportPanelSelectionChangedEvent'], 298 | 85: [125, 'NNet.Game.STriggerVictoryPanelPlayMissionAgainEvent'], 299 | 86: [76, 'NNet.Game.STriggerMovieStartedEvent'], 300 | 87: [76, 'NNet.Game.STriggerMovieFinishedEvent'], 301 | 88: [161, 'NNet.Game.SDecrementGameTimeRemainingEvent'], 302 | 89: [162, 'NNet.Game.STriggerPortraitLoadedEvent'], 303 | 90: [164, 'NNet.Game.STriggerCustomDialogDismissedEvent'], 304 | 91: [165, 'NNet.Game.STriggerGameMenuItemSelectedEvent'], 305 | 93: [124, 'NNet.Game.STriggerPurchasePanelSelectedPurchaseItemChangedEvent'], 306 | 94: [166, 'NNet.Game.STriggerPurchasePanelSelectedPurchaseCategoryChangedEvent'], 307 | 95: [167, 'NNet.Game.STriggerButtonPressedEvent'], 308 | 96: [76, 'NNet.Game.STriggerGameCreditsFinishedEvent'], 309 | 97: [168, 'NNet.Game.STriggerCutsceneBookmarkFiredEvent'], 310 | 98: [169, 'NNet.Game.STriggerCutsceneEndSceneFiredEvent'], 311 | 99: [170, 'NNet.Game.STriggerCutsceneConversationLineEvent'], 312 | 100: [171, 'NNet.Game.STriggerCutsceneConversationLineMissingEvent'], 313 | 101: [76, 'NNet.Game.SGameUserLeaveEvent'], 314 | 102: [172, 'NNet.Game.SGameUserJoinEvent'], 315 | 103: [173, 'NNet.Game.SCommandManagerStateEvent'], 316 | 104: [174, 'NNet.Game.SCmdUpdateTargetPointEvent'], 317 | 105: [175, 'NNet.Game.SCmdUpdateTargetUnitEvent'], 318 | 106: [132, 'NNet.Game.STriggerAnimLengthQueryByNameEvent'], 319 | 107: [133, 'NNet.Game.STriggerAnimLengthQueryByPropsEvent'], 320 | 108: [134, 'NNet.Game.STriggerAnimOffsetEvent'], 321 | 109: [176, 'NNet.Game.SCatalogModifyEvent'], 322 | 110: [177, 'NNet.Game.SHeroTalentSelectedEvent'] 323 | }; 324 | 325 | // The typeid of the NNet.Game.EEventId enum. 326 | const game_eventid_typeid = 0; 327 | 328 | // Map from protocol NNet.Game.*Message eventid to [typeid, name] 329 | const message_event_types = { 330 | 0: [178, 'NNet.Game.SChatMessage'], 331 | 1: [179, 'NNet.Game.SPingMessage'], 332 | 2: [180, 'NNet.Game.SLoadingProgressMessage'], 333 | 3: [76, 'NNet.Game.SServerPingMessage'], 334 | 4: [181, 'NNet.Game.SReconnectNotifyMessage'] 335 | }; 336 | 337 | // The typeid of the NNet.Game.EMessageId enum. 338 | const message_eventid_typeid = 1; 339 | 340 | // Map from protocol NNet.Replay.Tracker.*Event eventid to [typeid, name] 341 | const tracker_event_types = { 342 | 0: [183, 'NNet.Replay.Tracker.SPlayerStatsEvent'], 343 | 1: [184, 'NNet.Replay.Tracker.SUnitBornEvent'], 344 | 2: [185, 'NNet.Replay.Tracker.SUnitDiedEvent'], 345 | 3: [186, 'NNet.Replay.Tracker.SUnitOwnerChangeEvent'], 346 | 4: [187, 'NNet.Replay.Tracker.SUnitTypeChangeEvent'], 347 | 5: [188, 'NNet.Replay.Tracker.SUpgradeEvent'], 348 | 6: [184, 'NNet.Replay.Tracker.SUnitInitEvent'], 349 | 7: [189, 'NNet.Replay.Tracker.SUnitDoneEvent'], 350 | 8: [191, 'NNet.Replay.Tracker.SUnitPositionsEvent'], 351 | 9: [192, 'NNet.Replay.Tracker.SPlayerSetupEvent'] 352 | }; 353 | 354 | // The typeid of the NNet.Replay.Tracker.EEventId enum. 355 | const tracker_eventid_typeid = 2; 356 | 357 | // The typeid of NNet.SVarUint32 (the type used to encode gameloop deltas). 358 | const svaruint32_typeid = 7; 359 | 360 | // The typeid of NNet.Replay.SGameUserId (the type used to encode player ids). 361 | const replay_userid_typeid = 8; 362 | 363 | // The typeid of NNet.Replay.SHeader (the type used to store replay game version and length). 364 | const replay_header_typeid = 17; 365 | 366 | // The typeid of NNet.Game.SDetails (the type used to store overall replay details). 367 | const game_details_typeid = 39; 368 | 369 | // The typeid of NNet.Replay.SInitData (the type used to store the inital lobby). 370 | const replay_initdata_typeid = 67; 371 | 372 | // not sure if correct port 373 | function _varuint32Value(value) { 374 | // Returns the numeric value from a SVarUint32 instance. 375 | return value[Object.keys(value)[0]]; 376 | } 377 | 378 | function* _decode_event_stream(decoder, eventidTypeid, eventTypes, decodeUserId) { 379 | // Decodes events prefixed with a gameloop and possibly userid 380 | var gameloop = 0; 381 | while (!decoder.done()) { 382 | var startBits = decoder.usedBits(); 383 | 384 | // decode the gameloop delta before each event 385 | var delta = _varuint32Value(decoder.instance(svaruint32_typeid)); 386 | gameloop += delta; 387 | 388 | // decode the userid before each event 389 | var userid = (decodeUserId === true) ? decoder.instance(replay_userid_typeid) : undefined; 390 | 391 | // decode the event id 392 | var eventid = decoder.instance(eventidTypeid); 393 | var eventType = eventTypes[eventid] || [null, null]; 394 | var typeid = eventType[0]; 395 | var typename = eventType[1]; 396 | if (typeid === null) throw new decoders.CorruptedError('eventid(' + eventid + ') at ' + decoder.toString()); 397 | 398 | // decode the event struct instance 399 | var event = decoder.instance(typeid); 400 | event._event = typename; 401 | event._eventid = eventid; 402 | 403 | // insert gameloop and userid 404 | event._gameloop = gameloop; 405 | if (decodeUserId) event._userid = userid; 406 | 407 | // the next event is byte aligned 408 | decoder.byteAlign(); 409 | 410 | // insert bits used in stream 411 | event._bits = decoder.usedBits() - startBits; 412 | 413 | yield event; 414 | } 415 | } 416 | 417 | exports.decodeReplayGameEvents = function* (contents) { 418 | // Decodes and yields each game event from the contents byte string. 419 | const decoder = new BitPackedDecoder(contents, typeinfos); 420 | for (let event of _decode_event_stream(decoder, game_eventid_typeid, game_event_types, true)) 421 | yield event; 422 | }; 423 | 424 | exports.decodeReplayMessageEvents = function* (contents) { 425 | // Decodes and yields each message event from the contents byte string. 426 | const decoder = new BitPackedDecoder(contents, typeinfos); 427 | for (let event of _decode_event_stream(decoder, message_eventid_typeid, message_event_types, true)) 428 | yield event; 429 | }; 430 | 431 | exports.decodeReplayTrackerEvents = function* (contents) { 432 | // Decodes and yields each tracker event from the contents byte string. 433 | const decoder = new VersionDecoder(contents, typeinfos); 434 | for (let event of _decode_event_stream(decoder, tracker_eventid_typeid, tracker_event_types, false)) 435 | yield event; 436 | }; 437 | 438 | exports.decodeReplayHeader = function(contents) { 439 | // Decodes and return the replay header from the contents byte string. 440 | const decoder = new VersionDecoder(contents, typeinfos); 441 | return decoder.instance(replay_header_typeid); 442 | }; 443 | 444 | exports.decodeReplayDetails = function(contents) { 445 | // Decodes and returns the game details from the contents byte string. 446 | const decoder = new VersionDecoder(contents, typeinfos); 447 | return decoder.instance(game_details_typeid); 448 | }; 449 | 450 | exports.decodeReplayInitdata = function(contents) { 451 | // Decodes and return the replay init data from the contents byte string. 452 | const decoder = new BitPackedDecoder(contents, typeinfos); 453 | return decoder.instance(replay_initdata_typeid); 454 | }; 455 | 456 | exports.decodeReplayAttributesEvents = function (contents) { 457 | // Decodes and yields each attribute from the contents byte string. 458 | const buffer = new decoders.BitPackedBuffer(contents, 'little'); 459 | const attributes = {}; 460 | 461 | if (!buffer.done()) { 462 | attributes.source = buffer.readBits(8); 463 | attributes.mapNameSpace = buffer.readBits(32); 464 | var count = buffer.readBits(32); 465 | attributes.scopes = {}; 466 | 467 | while (!buffer.done()) { 468 | var value = {}; 469 | value.namespace = buffer.readBits(32); 470 | var attrid = value.attrid = buffer.readBits(32); 471 | var scope = buffer.readBits(8); 472 | value.value = buffer.readAlignedBytes(4).reverse(); 473 | while (value.value[0] === 0) value.value = value.value.slice(1); 474 | while (value.value[value.value.length - 1] === 0) value.value = value.value.slice(0, -1); 475 | if (!attributes.scopes[scope]) 476 | attributes.scopes[scope] = {}; 477 | if (!attributes.scopes[scope][attrid]) 478 | attributes.scopes[scope][attrid] = []; 479 | attributes.scopes[scope][attrid].push(value); 480 | } 481 | } 482 | 483 | return attributes; 484 | }; 485 | 486 | exports.unitTag = function(unitTagIndex, unitTagRecycle) { 487 | return (unitTagIndex << 18) + unitTagRecycle; 488 | }; 489 | 490 | exports.unitTagIndex = function(unitTag) { 491 | return (unitTag >> 18) & 0x00003FFF; 492 | }; 493 | 494 | exports.unitTagRecycle = function(unitTag) { 495 | return unitTag & 0x0003FFFF; 496 | }; 497 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "heroprotocoljs", 3 | "version": "0.2.1", 4 | "description": "Javascript port of the heroprotocol Python library to decode Heroes of the Storm replay protocols.", 5 | "main": "index.js", 6 | "dependencies": { 7 | "mpyqjs": "^1.0.1", 8 | "yargs": "^3.32.0" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Farof/heroprotocoljs.git" 13 | }, 14 | "keywords": [ 15 | "hots", 16 | "blizzard", 17 | "stormreplay", 18 | "mpq" 19 | ], 20 | "author": "Farof", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/Farof/heroprotocoljs/issues" 24 | }, 25 | "homepage": "https://github.com/Farof/heroprotocoljs", 26 | "engines": { 27 | "node": ">=4.0.0" 28 | }, 29 | "devDependencies": { 30 | "grunt": "latest", 31 | "grunt-concurrent": "latest", 32 | "grunt-contrib-jshint": "latest", 33 | "grunt-contrib-watch": "latest", 34 | "grunt-simple-mocha": "latest" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /reference/header.md: -------------------------------------------------------------------------------- 1 | # 1. m_dataBuildNum (number) 2 | 3 | Build number. 4 | 5 | Don't know the difference with "m_version.m_baseBuild". 6 | 7 | # 2. m_elapsedGameLoops (number) 8 | 9 | # 3. m_ngdpRootKey (rootkey) 10 | 11 | # 4. m_signature (string) 12 | 13 | Always "Heroes of the Storm replay\u001b11"? 14 | 15 | # 5. m_type (number) 16 | 17 | # 6. m_useScaledTime (boolean) 18 | 19 | # 7. m_version (object) 20 | 21 | Version of the game the replay was created with. 22 | 23 | ## 7.1. m_baseBuild (number) 24 | 25 | Build number. 26 | 27 | Don't know the difference with "m_dataBuildNum". 28 | 29 | **Used to determine which protocol file to load.** 30 | 31 | ## 7.2. m_build (number) 32 | 33 | ## 7.3. m_flags (number) 34 | 35 | ## 7.4. m_major (number) 36 | 37 | ## 7.5. m_minor (number) 38 | 39 | ## 7.6. m_revision (number) -------------------------------------------------------------------------------- /reference/replay.details.md: -------------------------------------------------------------------------------- 1 | # 1. m_cacheHandles (array) 2 | 3 | The strings don't contain any readable information and are identical to those exported by the original python library. 4 | 5 | # 2. m_campaignIndex (number) 6 | 7 | # 3. m_defaultDifficulty (number) 8 | 9 | # 4. m_description (string) 10 | 11 | # 5. m_difficulty (string) 12 | 13 | # 6. m_gameSpeed (number) 14 | 15 | # 7. m_imageFilePath (string) 16 | 17 | # 8. m_isBlizzardMap (boolean) 18 | 19 | Specifies if the map played is an official Blizzard map or a custom map? 20 | 21 | Similar to `replayDecoder.initdata.m_syncLobbyState.m_gameDescription.m_isBlizzardMap`? 22 | 23 | # 9. m_mapFileName (string) 24 | 25 | # 10. m_miniSave (boolean) 26 | 27 | # 11. m_modPaths (null?) 28 | 29 | # 12. m_playerList (array) 30 | 31 | Array of the players in the match. 32 | 33 | The following entries describe the information of individual players. 34 | 35 | ## 12.1. m_color (object) 36 | 37 | Either rgba(36, 92, 255, 255) for "blue" or rgba(255, 0, 0, 255) for "red". 38 | 39 | ### 12.1.1. m_a (number) 40 | 41 | Always 255. 42 | 43 | ### 12.1.2. m_b (number) 44 | 45 | 255 for "blue" or 0 for "red". 46 | 47 | ### 12.1.3. m_g (number) 48 | 49 | 92 for "blue" or 0 for "red". 50 | 51 | ### 12.1.4. m_r (number) 52 | 53 | 30 for "blue" or 255 for "red". 54 | 55 | ## 12.2. m_control (number) 56 | 57 | ## 12.3. m_handicap (number) 58 | 59 | ## 12.4. m_hero (string) 60 | 61 | Player's hero name. 62 | 63 | Known heroes: 64 | 65 | - Abathur 66 | - Crusader (= Johanna) 67 | - DemonHunter (= Valla) 68 | - Jaina 69 | - Johanna (= Crusader) 70 | - Kael'thas (= Kaelthas) 71 | - Kaelthas (= Kael'thas) 72 | - Kerrigan 73 | - Leoric 74 | - Muradin 75 | - Sylvanas 76 | - Valla (= DemonHunter) 77 | 78 | `replay.details` uses real names while `replay.initdata` uses the class name for Diablo 3 heroes and removes the separator for heroes having one in their name. 79 | 80 | ## 12.5. m_name (string) 81 | 82 | Player's name. 83 | 84 | ## 12.6. m_observe (number) 85 | 86 | ## 12.7. m_race (string) 87 | 88 | ## 12.8. m_result (number) 89 | 90 | Indicates if the player lost or won the game? 91 | 92 | - 1 = won 93 | - 2 = lost 94 | 95 | ## 12.9. m_teamId (number) 96 | 97 | Either: 98 | 99 | - 0 for blue team, 1 for red team 100 | - 0 for left team, 1 for right team 101 | - 0 for "my" team, 1 for "their" team 102 | 103 | ## 12.10. m_toon (object) 104 | 105 | Combine properties to create your unique identifier `m_toonHandle`. This identifiier is in the path to your profile and replays on your file system. On Windows, by default: `C:\Users\{UserName}\Documents\Heroes of the Storm\Accounts\1022983\{m_toonHandle}\` 106 | 107 | Format: `m_toonHandle = {m_region}-{m_programId}-{m_realm}-{m_id}` 108 | 109 | `m_toonHandle` is referenced in `replay.initdata.md` 1.2.11.19. 110 | 111 | ### 12.10.1. m_id (number) 112 | 113 | Unique identifier for each account. 114 | 115 | ### 12.10.2. m_programId (string) 116 | 117 | ### 12.10.3. m_realm (number) 118 | 119 | Live or PTR? 120 | 121 | 1 = live? 122 | 123 | ### 12.10.4. m_region (number) 124 | 125 | Region number depending on if the game was played in European, American, Asian? 126 | 127 | Need to match a value to a region. 128 | 129 | 1 = North America 130 | 2 = Europe? 131 | 132 | ## 12.11. m_workingSetSlotId (number) 133 | 134 | Index of the player in the array and possibly arrays referencing players such as `replay.initdata.md` 1.2.11. `m_slots`. 135 | 136 | # 13. m_restartAsTransitionMap (false) 137 | 138 | # 14. m_thumbnail (object) 139 | 140 | ## 14.1. m_file (string) 141 | 142 | Always "ReplaysPreviewImage.tga"? 143 | 144 | Probably an existing image in the game files. 145 | 146 | # 15. m_timeLocalOffset (number) 147 | 148 | Number representation of a Date at the local timezone of the player. Should be constant for every game in the same timezone? 149 | 150 | The date is near epoch for me, don't know if it's normal and should maybe be offset or if it's an error. 151 | 152 | var timeLocalOffset = new Date(m_timeLocalOffsit); 153 | 154 | # 16. m_timeUTC (number) 155 | 156 | Number representation of a Date at the local timezone of the player. Time the game started? 157 | 158 | The date is near epoch for me, don't know if it's normal and should maybe be offset or if it's an error. 159 | 160 | var timeUTC = new Date(m_timeUTC); 161 | 162 | # 17. m_title (string) 163 | 164 | Name of the map being played. 165 | -------------------------------------------------------------------------------- /reference/replay.game.events.md: -------------------------------------------------------------------------------- 1 | replay.game.events is an array containing events whose types are specified in the `lib/protocol#####.js` files under the `game_event_types` variable. 2 | 3 | --- 4 | 5 | Convert `m_unitTagIndex` and `m_unitTagRecycle` into a unit tag `m_tag` with `protocol.unitTag(m_unitTagIndex, m_unitTagRecycle)`. 6 | 7 | `m_tag` is useful to determine what unit was targeted by a skill, for example in this `NNet.Game.SCmdEvent`: 8 | 9 | ``` 10 | { 11 | "_eventid": 27, 12 | "m_unitGroup": null, 13 | "_event": "NNet.Game.SCmdEvent", 14 | "m_abil": { 15 | "m_abilLink": 147, 16 | "m_abilCmdIndex": 0, 17 | "m_abilCmdData": null 18 | }, 19 | "_gameloop": 2365, 20 | "_bits": 264, 21 | "m_data": { 22 | "TargetUnit": { 23 | "m_snapshotControlPlayerId": 6, 24 | "m_snapshotPoint": { 25 | "y": 235853, 26 | "x": 579289, 27 | "z": 32441 28 | }, 29 | "m_snapshotUpkeepPlayerId": 6, 30 | "m_timer": 0, 31 | "m_targetUnitFlags": 111, 32 | "m_snapshotUnitLink": 281, 33 | "m_tag": 45350913 34 | } 35 | }, 36 | "_userid": { 37 | "m_userId": 5 38 | }, 39 | "m_cmdFlags": 2097408, 40 | "m_sequence": 451, 41 | "m_otherUnit": null 42 | } 43 | ``` 44 | 45 | We see that the unit with tag `45350913` was the target of the ability id `147` casted by player `5`. 46 | 47 | Convert `m_tag` into a unit tag index `m_unitTagIndex` and unit tag recycle `m_unitTagRecycle` with `protocol.unitTagIndex(m_tag)` and `protocol.unitTagRecycle(m_tag)`. 48 | 49 | --- 50 | 51 | Events contain generic properties and type specific properties. 52 | 53 | # Generic 54 | 55 | ## 1. _bits (number) 56 | 57 | ## 2. _event (string) 58 | 59 | Event type. 60 | 61 | ## 3. _eventid (number) 62 | 63 | Not a unique identifier. 64 | 65 | ## 4. _gameloop (number) 66 | 67 | ## 5. _userid (object) 68 | 69 | ### 5.1. m_userId (number) 70 | 71 | Index of the player associated with the event in arrays referencing players. 72 | 73 | # NNet.Game.SUserFinishedLoadingSyncEvent 74 | 75 | No specific properties. Use `_userid.m_userId`. 76 | 77 | # NNet.Game.SUserOptionsEvent 78 | 79 | Some configuration options of each player. 80 | 81 | ## 6. m_baseBuildNum (number) 82 | 83 | Base build of the game client that was used to write the replay. Version of the protocol to use to decode the replay. 84 | 85 | ## 7. m_buildNum (number) 86 | 87 | ## 8. m_cameraFollow (boolean) 88 | 89 | Specifies if the camera is set to follow the player automatically. 90 | 91 | ## 9. m_debugPauseEnabled (boolean) 92 | 93 | ## 10. m_developmentCheatsEnabled (boolean) 94 | 95 | ## 11. m_gameFullyDownloaded (boolean) 96 | 97 | ## 12. m_hotkeyProfile (string) 98 | 99 | Name of the hokey profile used. 100 | 101 | ## 13.m_isMapToMapTransition (boolean) 102 | 103 | ## 14. m_multiplayerCheatsEnabled (boolean) 104 | 105 | ## 15. m_platformMac (boolean) 106 | 107 | Specifies if the player is playing on Mac OS X. 108 | 109 | ## 16. m_syncChecksummingEnabled (boolean) 110 | 111 | ## 17. m_testCheatsEnabled (boolean) 112 | 113 | ## 18. m_useGalaxyAsserts (boolean) 114 | 115 | ## 19. m_versionFlags (number) 116 | 117 | # NNet.Game.SBankFileEvent 118 | 119 | ## 6. m_name (string) 120 | 121 | Known values: 122 | 123 | - PlayerSettings - only value? 124 | 125 | # NNet.Game.SBankSectionEvent 126 | 127 | ## 6. m_name (string) 128 | 129 | Known values: 130 | 131 | - DeathTipDisplayCounts 132 | - MapPlayCounts 133 | - NewUserTrainingMode 134 | - Settings 135 | - TotalPlayCount 136 | 137 | # NNet.Game.SBankKeyEvent 138 | 139 | ## 6. m_data (string) 140 | 141 | String representation of a number. 142 | 143 | ## 7. m_name (string) 144 | 145 | Known values: 146 | 147 | - BattlefieldOfEternity 148 | - BlackheartsBay 149 | - ControlPoints 150 | - Crypts 151 | - CursedHollow 152 | - DeathActionCam 153 | - DragonShire 154 | - EnemyFountainDeath 155 | - FollowMinions 156 | - Gangups 157 | - HauntedMines 158 | - HauntedWoods 159 | - MinimapCommandsEnabled 160 | - MinionsvsTowns 161 | - Moonwell 162 | - Mount 163 | - NEWUSER_ONOFF 164 | - NEWUSER_ReferenceCardTimes 165 | - NEWUSER_TIP_0 166 | - NEWUSER_TIP_1 167 | - NEWUSER_TIP_10 168 | - NEWUSER_TIP_11 169 | - NEWUSER_TIP_12 170 | - NEWUSER_TIP_13 171 | - NEWUSER_TIP_14 172 | - NEWUSER_TIP_15 173 | - NEWUSER_TIP_16 174 | - NEWUSER_TIP_17 175 | - NEWUSER_TIP_18 176 | - NEWUSER_TIP_19 177 | - NEWUSER_TIP_2 178 | - NEWUSER_TIP_3 179 | - NEWUSER_TIP_4 180 | - NEWUSER_TIP_5 181 | - NEWUSER_TIP_6 182 | - NEWUSER_TIP_7 183 | - NEWUSER_TIP_8 184 | - NEWUSER_TIP_9 185 | - NEWUSER_TalentTeaching 186 | - Retreat 187 | - Shrines 188 | - TotalPlayCount 189 | - TowersOfDoom 190 | - TrainTalents 191 | - UseYourAbilities 192 | 193 | ## 8. m_type (number) 194 | 195 | Either `1` or `2`. 196 | 197 | # NNet.Game.SBankValueEvent 198 | 199 | A replay can have none. 200 | 201 | # NNet.Game.SBankSignatureEvent 202 | 203 | ## 6. m_signature (array) 204 | 205 | Always empty? 206 | 207 | ## 7. m_toonHandle (string) 208 | 209 | Unique identifier for a player. See `replay.details` reference, section `12.10.`. 210 | 211 | # NNet.Game.SCameraSaveEvent 212 | 213 | A replay can have none. 214 | 215 | # NNet.Game.SSaveGameEvent 216 | 217 | A replay can have none. 218 | 219 | # NNet.Game.SSaveGameDoneEvent 220 | 221 | A replay can have none. 222 | 223 | # NNet.Game.SLoadGameDoneEvent 224 | 225 | A replay can have none. 226 | 227 | # NNet.Game.SCommandManagerResetEvent 228 | 229 | # 6. m_sequence (number) 230 | 231 | Always 1? 232 | 233 | # NNet.Game.SGameCheatEvent 234 | 235 | A replay can have none. 236 | 237 | Probably used for development purpose. 238 | 239 | # NNet.Game.SCmdEvent 240 | 241 | Describes a player ability usage. 242 | 243 | ## 6. m_abil (object) 244 | 245 | ### 6.1. m_abilCmdData (null | number) 246 | 247 | ### 6.2. m_abilCmdIndex (number) 248 | 249 | ### 6.3. m_abilLink (number) 250 | 251 | Ability used by player identified with `_userid.m_userId`. 252 | 253 | Known values: 254 | 255 | - 36 256 | - 45 257 | - 58 258 | - 120 259 | - 168 260 | - 169 261 | - 173 262 | - 174 263 | - 175 264 | - 176 265 | - 177 266 | - 181 267 | - 251 268 | - 273 269 | - 280 270 | - 291 271 | - 292 272 | - 294 273 | - 312 274 | - 353 275 | - 354 276 | - 371 277 | - 377 278 | - 378 279 | - 379 280 | - 380 281 | - 381 282 | - 396 283 | - 524 284 | - 543 285 | - 545 286 | - 546 287 | - 548 288 | - 552 289 | - 554 290 | - 556 291 | - 557 292 | - 562 293 | - 563 294 | - 564 295 | - 565 296 | - 569 297 | - 571 298 | - 572 299 | - 573 300 | - 575 301 | - 578 302 | - 582 303 | - 584 304 | - 662 305 | - 665 306 | - 666 307 | - 667 308 | - 668 309 | 310 | Need to match ablity ids with names. 311 | 312 | ## 7. m_cmdFlags (number) 313 | 314 | ## 8. m_data (object) 315 | 316 | Additional data relative to the ability used. The object has one of these property containing the data: 317 | 318 | ### None (null) 319 | 320 | The ability is non-targeted. 321 | 322 | Always `null`. 323 | 324 | ### TargetPoint (object) 325 | 326 | The ability is a skillshot, targeted at a point on the map. 327 | 328 | #### 8.1. x (number) 329 | 330 | #### 8.2. y (number) 331 | 332 | #### 8.3. z (number) 333 | 334 | ### TargetUnit (object) 335 | 336 | The ability targets a unit. 337 | 338 | #### 8.1. m_snapshotControlPlayerId (number) 339 | 340 | Id of the targeted player. 341 | 342 | #### 8.2. m_snapshotPoint (object) 343 | 344 | The position of the targeted unit. 345 | 346 | ##### 8.2.1. x (number) 347 | 348 | ##### 8.2.2. y (number) 349 | 350 | ##### 8.2.3. z (number) 351 | 352 | #### 8.3. m_snapshotUnitLink (number) 353 | 354 | #### 8.4. m_snapshotUpkeepPlayerId (5) 355 | 356 | Id of the targeted player. 357 | 358 | #### 8.5. m_tag (number) 359 | 360 | See note concerning unit tags at the top of this document. 361 | 362 | #### 8.6. m_targetUnitFlags (number) 363 | 364 | #### 8.7. m_timer (number) 365 | 366 | ## 9. m_otherUnit (null | number) 367 | 368 | ## 10. m_sequence (number) 369 | 370 | ## 11. m_unitGroup (null | number) 371 | 372 | # NNet.Game.SSelectionDeltaEvent 373 | 374 | ## 6. m_controlGroupId (number) 375 | 376 | ## 7. m_delta (object) 377 | 378 | ### 7.1. m_addSubgroups (array(object)) 379 | 380 | The following entries describe the objects contained in the `m_addSubgroups` array. 381 | 382 | #### 7.1.1. m_count (number) 383 | 384 | #### 7.1.2. m_intraSubgroupPriority (number) 385 | 386 | #### 7.1.3. m_subgroupPriority (number) 387 | 388 | #### 7.1.4. m_unitLink (number) 389 | 390 | ### 7.2. m_addUnitTags(array(number)) 391 | 392 | ### 7.3. m_removeMask (object) 393 | 394 | The object has one of these property containing the data: 395 | 396 | #### None (null) 397 | 398 | #### Mask (array) 399 | 400 | Byte array. 401 | 402 | ### 7.4. m_subgroupIndex (number) 403 | 404 | # NNet.Game.SControlGroupUpdateEvent 405 | 406 | A replay can have none. 407 | 408 | # NNet.Game.SSelectionSyncCheckEvent 409 | 410 | A replay can have none. 411 | 412 | # NNet.Game.SResourceTradeEvent 413 | 414 | A replay can have none. 415 | 416 | # NNet.Game.STriggerChatMessageEvent 417 | 418 | ## 6. m_chatMessage (string) 419 | 420 | Chat message sent by player `_userid.m_userId`. 421 | 422 | # NNet.Game.SAICommunicateEvent 423 | 424 | A replay can have none. 425 | 426 | # NNet.Game.SSetAbsoluteGameSpeedEvent 427 | 428 | A replay can have none. 429 | 430 | # NNet.Game.SAddAbsoluteGameSpeedEvent 431 | 432 | A replay can have none. 433 | 434 | # NNet.Game.STriggerPingEvent 435 | 436 | ## 6. m_option (number) 437 | 438 | ## 7. m_pingedMinimap (boolean) 439 | 440 | Specifies if the ping was done thought the minimap. 441 | 442 | ## 8. m_point (object) 443 | 444 | Ping coordinate. 445 | 446 | ### 8.1. x (number) 447 | 448 | ### 8.2. y (number) 449 | 450 | ## 9. m_unit (number) 451 | 452 | If the ping targets a unit, like a player or objective? Unit tag? 453 | 454 | # NNet.Game.SBroadcastCheatEvent 455 | 456 | A replay can have none. 457 | 458 | # NNet.Game.SAllianceEvent 459 | 460 | A replay can have none. 461 | 462 | # NNet.Game.SUnitClickEvent 463 | 464 | ## 6. m_unitTag (number) 465 | 466 | See note concerning unit tags at the top of this document. 467 | 468 | # NNet.Game.SUnitHighlightEvent 469 | 470 | A replay can have none. 471 | 472 | # NNet.Game.STriggerReplySelectedEvent 473 | 474 | A replay can have none. 475 | 476 | # NNet.Game.SHijackReplayGameEvent 477 | 478 | A replay can have none. 479 | 480 | # NNet.Game.STriggerSkippedEvent 481 | 482 | A replay can have none. 483 | 484 | # NNet.Game.STriggerSoundLengthQueryEvent 485 | 486 | A replay can have none. 487 | 488 | # NNet.Game.STriggerSoundOffsetEvent 489 | 490 | ## 6. m_sound (number) 491 | 492 | # NNet.Game.STriggerTransmissionOffsetEvent 493 | 494 | ## 6. m_thread (number) 495 | 496 | ## 7. m_transmissionId (number) 497 | 498 | # NNet.Game.STriggerTransmissionCompleteEvent 499 | 500 | ## 6. m_transmissionId (number) 501 | 502 | # NNet.Game.SCameraUpdateEvent 503 | 504 | ## 6. m_distance (null | number) 505 | 506 | ## 7. m_follow (boolean) 507 | 508 | ## 8. m_pitch (boolean) 509 | 510 | ## 9. m_reason (boolean) 511 | 512 | ## 10. m_target (object) 513 | 514 | ### 10.1. x (number) 515 | 516 | ### 10.2. y (number) 517 | 518 | ## 11. m_yaw (null | number) 519 | 520 | # NNet.Game.STriggerAbortMissionEvent 521 | 522 | A replay can have none. 523 | 524 | # NNet.Game.STriggerPurchaseMadeEvent 525 | 526 | A replay can have none. 527 | 528 | # NNet.Game.STriggerPurchaseExitEvent 529 | 530 | A replay can have none. 531 | 532 | # NNet.Game.STriggerPlanetMissionLaunchedEvent 533 | 534 | A replay can have none. 535 | 536 | # NNet.Game.STriggerPlanetPanelCanceledEvent 537 | 538 | A replay can have none. 539 | 540 | # NNet.Game.STriggerDialogControlEvent 541 | 542 | ## 6. m_controlId (number) 543 | 544 | ## 7. m_eventData (object) 545 | 546 | The object has one of these property containing the data: 547 | 548 | ### None (null) 549 | 550 | ### Checked (boolean) 551 | 552 | ### ValueChanged (number) 553 | 554 | ### SelectionChanged (number) 555 | 556 | ### TextChanged (string) 557 | 558 | ### MouseButton (number) 559 | 560 | ## 8. m_eventType (number) 561 | 562 | # NNet.Game.STriggerSoundLengthSyncEvent 563 | 564 | ## 6. m_syncInfo (object) 565 | 566 | ### 6.1. m_length (array(number)) 567 | 568 | ### 6.2. m_soundHash (array(number)) 569 | 570 | # NNet.Game.STriggerConversationSkippedEvent 571 | 572 | A replay can have none. 573 | 574 | # NNet.Game.STriggerMouseClickedEvent 575 | 576 | A replay can have none. 577 | 578 | # NNet.Game.STriggerMouseMovedEvent 579 | 580 | A replay can have none. 581 | 582 | # NNet.Game.SAchievementAwardedEvent 583 | 584 | A replay can have none. 585 | 586 | # NNet.Game.STriggerHotkeyPressedEvent 587 | 588 | A replay can have none. 589 | 590 | # NNet.Game.STriggerTargetModeUpdateEvent 591 | 592 | A replay can have none. 593 | 594 | # NNet.Game.STriggerPlanetPanelReplayEvent 595 | 596 | A replay can have none. 597 | 598 | # NNet.Game.STriggerSoundtrackDoneEvent 599 | 600 | ## 6. m_soundtrack (number) 601 | 602 | # NNet.Game.STriggerPlanetMissionSelectedEvent 603 | 604 | A replay can have none. 605 | 606 | # NNet.Game.STriggerKeyPressedEvent 607 | 608 | A replay can have none. 609 | 610 | # NNet.Game.STriggerMovieFunctionEvent 611 | 612 | A replay can have none. 613 | 614 | # NNet.Game.STriggerPlanetPanelBirthCompleteEvent 615 | 616 | A replay can have none. 617 | 618 | # NNet.Game.STriggerPlanetPanelDeathCompleteEvent 619 | 620 | A replay can have none. 621 | 622 | # NNet.Game.SResourceRequestEvent 623 | 624 | A replay can have none. 625 | 626 | # NNet.Game.SResourceRequestFulfillEvent 627 | 628 | A replay can have none. 629 | 630 | # NNet.Game.SResourceRequestCancelEvent 631 | 632 | A replay can have none. 633 | 634 | # NNet.Game.STriggerResearchPanelExitEvent 635 | 636 | A replay can have none. 637 | 638 | # NNet.Game.STriggerResearchPanelPurchaseEvent 639 | 640 | A replay can have none. 641 | 642 | # NNet.Game.STriggerResearchPanelSelectionChangedEvent 643 | 644 | A replay can have none. 645 | 646 | # NNet.Game.STriggerCommandErrorEvent 647 | 648 | A replay can have none. 649 | 650 | # NNet.Game.STriggerMercenaryPanelExitEvent 651 | 652 | A replay can have none. 653 | 654 | # NNet.Game.STriggerMercenaryPanelPurchaseEvent 655 | 656 | A replay can have none. 657 | 658 | # NNet.Game.STriggerMercenaryPanelSelectionChangedEvent 659 | 660 | A replay can have none. 661 | 662 | # NNet.Game.STriggerVictoryPanelExitEvent 663 | 664 | A replay can have none. 665 | 666 | # NNet.Game.STriggerBattleReportPanelExitEvent 667 | 668 | A replay can have none. 669 | 670 | # NNet.Game.STriggerBattleReportPanelPlayMissionEvent 671 | 672 | A replay can have none. 673 | 674 | # NNet.Game.STriggerBattleReportPanelPlaySceneEvent 675 | 676 | A replay can have none. 677 | 678 | # NNet.Game.STriggerBattleReportPanelSelectionChangedEvent 679 | 680 | A replay can have none. 681 | 682 | # NNet.Game.STriggerVictoryPanelPlayMissionAgainEvent 683 | 684 | A replay can have none. 685 | 686 | # NNet.Game.STriggerMovieStartedEvent 687 | 688 | A replay can have none. 689 | 690 | # NNet.Game.STriggerMovieFinishedEvent 691 | 692 | A replay can have none. 693 | 694 | # NNet.Game.SDecrementGameTimeRemainingEvent 695 | 696 | A replay can have none. 697 | 698 | # NNet.Game.STriggerPortraitLoadedEvent 699 | 700 | A replay can have none. 701 | 702 | # NNet.Game.STriggerCustomDialogDismissedEvent 703 | 704 | A replay can have none. 705 | 706 | # NNet.Game.STriggerGameMenuItemSelectedEvent 707 | 708 | A replay can have none. 709 | 710 | # NNet.Game.STriggerMouseWheelEvent 711 | 712 | A replay can have none. 713 | 714 | # NNet.Game.STriggerPurchasePanelSelectedPurchaseItemChangedEvent 715 | 716 | A replay can have none. 717 | 718 | # NNet.Game.STriggerPurchasePanelSelectedPurchaseCategoryChangedEvent 719 | 720 | A replay can have none. 721 | 722 | # NNet.Game.STriggerButtonPressedEvent 723 | 724 | A replay can have none. 725 | 726 | # NNet.Game.STriggerGameCreditsFinishedEvent 727 | 728 | A replay can have none. 729 | 730 | # NNet.Game.STriggerCutsceneBookmarkFiredEvent 731 | 732 | A replay can have none. 733 | 734 | # NNet.Game.STriggerCutsceneEndSceneFiredEvent 735 | 736 | 6. m_cutsceneId (number) 737 | 738 | # NNet.Game.STriggerCutsceneConversationLineEvent 739 | 740 | A replay can have none. 741 | 742 | # NNet.Game.STriggerCutsceneConversationLineMissingEvent 743 | 744 | A replay can have none. 745 | 746 | # NNet.Game.SGameUserLeaveEvent 747 | 748 | When a player leaves the game. 749 | 750 | ## 6. m_leaveReason (number) 751 | 752 | - 0: end of game 753 | - 11: disconnected? 754 | 755 | # NNet.Game.SGameUserJoinEvent 756 | 757 | Emitted only for reconnection event, not at the start of the game. 758 | 759 | ## 6. m_clanLogo (null | string) 760 | 761 | ## 7. m_clanTag (string) 762 | 763 | ## 8. m_hijack (boolean) 764 | 765 | ## 9. m_hijackCloneGameUserId (null | number) 766 | 767 | ## 10. m_name (string) 768 | 769 | Name of the player that joined. 770 | 771 | ## 11. m_observe (number) 772 | 773 | ## 12. m_toonHandle (string) 774 | 775 | Unique identifier for a player. See `replay.details` reference, section `12.10.`. 776 | 777 | # NNet.Game.SCommandManagerStateEvent 778 | 779 | ## 6. m_sequence (number) 780 | 781 | ## 7. m_state (number) 782 | 783 | # NNet.Game.SCmdUpdateTargetPointEvent 784 | 785 | ## 6. m_target (object) 786 | 787 | ### 6.1. x (number) 788 | 789 | ### 6.2. y (number) 790 | 791 | ### 6.3. z (number) 792 | 793 | # NNet.Game.SCmdUpdateTargetUnitEvent 794 | 795 | ## 6. m_target (object) 796 | 797 | ### 6.1 m_snapshotControlPlayerId (number) 798 | 799 | ### 6.2. m_snapshotPoint (object) 800 | 801 | #### 6.2.1. x (number) 802 | 803 | #### 6.2.2. y (number) 804 | 805 | #### 6.2.3. z (number) 806 | 807 | ### 6.3. m_snapshotUnitLink (number) 808 | 809 | ### 6.4. m_snapshotUpkeepPlayerId (number) 810 | 811 | ### 6.5. m_tag (number) 812 | 813 | See note concerning unit tags at the top of this document. 814 | 815 | ### 6.6. m_targetUnitFlags (number) 816 | 817 | ### 6.7. m_timer (number) 818 | 819 | # NNet.Game.STriggerAnimLengthQueryByNameEvent 820 | 821 | A replay can have none. 822 | 823 | # NNet.Game.STriggerAnimLengthQueryByPropsEvent 824 | 825 | A replay can have none. 826 | 827 | # NNet.Game.STriggerAnimOffsetEvent 828 | 829 | A replay can have none. 830 | 831 | # NNet.Game.SCatalogModifyEvent 832 | 833 | A replay can have none. 834 | 835 | # NNet.Game.SHeroTalentTreeSelectedEvent 836 | 837 | ## 6. m_index (number) 838 | 839 | Presumably the index of the talent for the player. 840 | 841 | Need to match this index to a talent for each hero. 842 | 843 | # NNet.Game.STriggerProfilerLoggingFinishedEvent 844 | 845 | A replay can have none. 846 | 847 | # NNet.Game.SHeroTalentTreeSelectionPanelToggledEvent 848 | 849 | A replay can have none. 850 | -------------------------------------------------------------------------------- /reference/replay.initdata.md: -------------------------------------------------------------------------------- 1 | # 1. m_syncLobbyState (object) 2 | 3 | ## 1.1. m_gameDescription (object) 4 | 5 | ### 1.1.1. m_cacheHandles (array(string, string, ..., string)) 6 | 7 | The strings don't contain any readable information and are identical to those exported by the original python library. 8 | 9 | ### 1.1.2. m_defaultAIBuild (number) 10 | 11 | ### 1.1.3. m_defaultDifficulty (number) 12 | 13 | ### 1.1.4. m_gameCacheName (string) 14 | 15 | ### 1.1.5. m_gameOptions (object) 16 | 17 | #### 1.1.5.1. m_advancedSharedControl (boolean) 18 | 19 | #### 1.1.5.2. m_amm (boolean) 20 | 21 | #### 1.1.5.3. m_battleNet (boolean) 22 | 23 | #### 1.1.5.4. m_clientDebugFlags (number) 24 | 25 | #### 1.1.5.5. m_competitive (boolean) 26 | 27 | Does not indicate if playing in Hero League or Team League. true for a QuickMatch game. Maybe false vs A.I.? 28 | 29 | #### 1.1.5.6. m_cooperative (boolean) 30 | 31 | If the player is in a group? 32 | 33 | #### 1.1.5.7. m_fog (number) 34 | 35 | #### 1.1.5.8. m_heroDuplicatesAllowed (boolean) 36 | 37 | #### 1.1.5.9. m_lockTeams (boolean) 38 | 39 | #### 1.1.5.10. m_noVictoryOrDefeat (boolean) 40 | 41 | #### 1.1.5.11. m_observers (number) 42 | 43 | #### 1.1.5.12. m_practice (boolean) 44 | 45 | Specifies if in try mode? 46 | 47 | #### 1.1.5.13. m_randomRaces (boolean) 48 | 49 | #### 1.1.5.14. m_teamsTogether (boolean) 50 | 51 | #### 1.1.5.15. m_userDifficulty (number) 52 | 53 | ### 1.1.6. m_gameSpeed (number) 54 | 55 | ### 1.1.7. m_gameType (number) 56 | 57 | ### 1.1.8. m_hasExtensionMod (boolean) 58 | 59 | ### 1.1.9. m_isBlizzardMap (boolean) 60 | 61 | Specifies if the map played is an official Blizzard map or a custom map? 62 | 63 | Similar to `replayDecoder.details.m_isBlizzardMap`? 64 | 65 | ### 1.1.10. m_isCoopMode (boolean) 66 | 67 | ### 1.1.11. m_isPremadeFFA (boolean) 68 | 69 | ### 1.1.12. m_mapAuthorName (string) 70 | 71 | ### 1.1.13. m_mapFileName (string) 72 | 73 | ### 1.1.14. m_mapFileSyncChecksum (number) 74 | 75 | ### 1.1.15. m_mapSizeX (number) 76 | 77 | Map width? Don't know what the unit represents. 78 | 79 | ### 1.1.16. m_mapSizeY (number) 80 | 81 | Map height? Don't know what the unit represents. 82 | 83 | ### 1.1.17. m_maxColors (number) 84 | 85 | ### 1.1.18. m_maxControls (number) 86 | 87 | ### 1.1.19. m_maxObservers (number) 88 | 89 | Always 6? As far as I know, the Starcraft 2 engine allows a maximum of 16 slots per game: 10 for the players, 2 for the observers, 2 for each base and 2 for something else I don't remember. 90 | 91 | ### 1.1.20. m_maxPlayers (number) 92 | 93 | Always 10? Represents the maximum number of players per match? 94 | 95 | ### 1.1.21. m_maxRaces (number) 96 | 97 | ### 1.1.22. m_maxTeams (number) 98 | 99 | Always 10? Import from the Starcraft 2 engine? 100 | 101 | ### 1.1.23. m_maxUsers (number) 102 | 103 | ### 1.1.24. m_modFileSyncChecksum (number) 104 | 105 | ### 1.1.25. m_randomValue (number) 106 | 107 | ### 1.1.26. m_slotDescriptions (array(object)) 108 | 109 | The following entries describe the objects contained in the `m_slotDescriptions` array. 110 | 111 | #### 1.1.26.1. m_allowedAIBuilds (array(number, number)) 112 | 113 | #### 1.1.26.2. m_allowedColors (array(number, number)) 114 | 115 | #### 1.1.26.3. m_allowedControls (array(number, number)) 116 | 117 | #### 1.1.26.4. m_allowedDifficulty (array(number, number)) 118 | 119 | #### 1.1.26.5. m_allowedObserveTypes (array(number, number)) 120 | 121 | #### 1.1.26.6. m_allowedRaces (array, array)) 122 | 123 | ## 1.2. m_lobbyState (object) 124 | 125 | ### 1.2.1. m_defaultAIBuild (number) 126 | 127 | ### 1.2.2. m_defaultDifficulty (number) 128 | 129 | ### 1.2.3. m_gameDuration (number) 130 | 131 | ### 1.2.4. m_hostUserId (null?) 132 | 133 | ### 1.2.5. m_isSinglePlayer (boolean) 134 | 135 | ### 1.2.6. m_maxObservers (number) 136 | 137 | See 1.1.19. 138 | 139 | ### 1.2.7. m_maxUsers (number) 140 | 141 | See 1.1.20. 142 | 143 | ### 1.2.8. m_phase (number) 144 | 145 | ### 1.2.9. m_pickedMapTag (number) 146 | 147 | ### 1.2.10. m_randomSeed (number) 148 | 149 | ### 1.2.11. m_slots (array(object)) 150 | 151 | The following entries describe the objects contained in the `m_slots` array. 152 | 153 | They represent the 16 slots available in a game. The first 10 represent players, 2 represent observers, and as far as I know 2 of the remaining represent each base, red and blue. 154 | 155 | #### 1.2.11.1. m_aiBuild (number) 156 | 157 | #### 1.2.11.2. m_artifacts (array(string, string, string)) 158 | 159 | #### 1.2.11.3. m_colorPref (object) 160 | 161 | ##### 1.2.11.3.1. m_color (number) 162 | 163 | #### 1.2.11.4. m_commander (string) 164 | 165 | #### 1.2.11.5. m_commanderLevel (number) 166 | 167 | #### 1.2.11.6. m_control (number) 168 | 169 | #### 1.2.11.7. m_difficulty (number) 170 | 171 | #### 1.2.11.8. m_handicap (number) 172 | 173 | #### 1.2.11.8. m_hasSilencePenalty (boolean) 174 | 175 | Specifies if the player is currently suffering from the silence penalty. 176 | 177 | #### 1.2.11.9. m_hero (string) 178 | 179 | Player's hero name. 180 | 181 | See `replay.details.md` 12.4. for known heroes. 182 | 183 | #### 1.2.11.10. m_licenses (array) 184 | 185 | #### 1.2.11.11. m_logoIndex (number) 186 | 187 | #### 1.2.11.12 m_mount (string) 188 | 189 | Name of the mount used by the player. 190 | 191 | Known mounts: 192 | 193 | - ArmoredHorsePurple 194 | - BillieGoat 195 | - Cloud9Nexagon 196 | - Direwolf 197 | - DirewolfBrown 198 | - HearthstoneCardRed 199 | - HorseBlack 200 | - HorseCommon 201 | - MalthaelsHerohorse 202 | 203 | #### 1.2.11.13. m_observe (number) 204 | 205 | #### 1.2.11.14. m_racePref (object) 206 | 207 | ##### 1.2.11.14.1. m_race (null?) 208 | 209 | #### 1.2.11.15. m_rewards (array(number, number, ..., number)) 210 | 211 | Full of numbers for actual players but empty for other slots. 212 | 213 | #### 1.2.11.16. m_skin (string) 214 | 215 | Name of the hero skin used by the player. 216 | 217 | Known skins: 218 | 219 | - AbathurBone 220 | - CrusaderRed 221 | - JainaBlue 222 | - KaelthasUltimateWhite 223 | - MuradinMagniDarkIron 224 | 225 | #### 1.2.11.17. m_tandemLeaderUserId (null?) 226 | 227 | #### 1.2.11.18. m_teamId (number) 228 | 229 | Either: 230 | 231 | - 0 for blue team, 1 for red team 232 | - 0 for left team, 1 for right team 233 | - 0 for "my" team, 1 for "their" team 234 | 235 | #### 1.2.11.19. m_toonHandle (string) 236 | 237 | Unique identifier for each player. 238 | 239 | Format: `{region}-Hero-{realm}-{(id)}` 240 | 241 | See `replay.details.md` 12.10. for details. 242 | 243 | #### 1.2.11.20. m_userId (number) 244 | 245 | #### 1.2.11.21. m_workingSetSlotId (number) 246 | 247 | Index in `m_slots` and probably other arrays referencing players such as `replay.details.md` 12. `m_playerList`. 248 | 249 | ## 1.3. m_userInitialData (array(object)) 250 | 251 | The following entries describe the objects in the `m_userInitialData` array. 252 | 253 | They represent the 16 slots available in a game. The first 10 represent players, 2 represent observers, and as far as I know 2 of the remaining represent each base, red and blue. 254 | 255 | ### 1.3.1. m_clanLogo (null?) 256 | 257 | ### 1.3.2. m_clanTag (string) 258 | 259 | ### 1.3.3. m_combinedRaceLevels (number) 260 | 261 | ### 1.3.4. m_customInterface (boolean) 262 | 263 | ### 1.3.5. m_examine (boolean) 264 | 265 | ### 1.3.6. m_hero (string) 266 | 267 | While this represents a player, it's hero is not set and this contains an empty string. Hero can be identified using `m_name`. 268 | 269 | ### 1.3.7. m_highestLeague (number) 270 | 271 | ### 1.3.8. m_mount (string) 272 | 273 | ### 1.3.9. m_name (string) 274 | 275 | Player name. 276 | 277 | ### 1.3.10. m_observe (number) 278 | 279 | ### 1.3.11. m_racePreference (object) 280 | 281 | #### 1.3.11.1. m_race (null?) 282 | 283 | ### 1.3.12. m_randomSeed (number) 284 | 285 | ### 1.3.13. m_skin (string) 286 | 287 | While this represents a player, it's skin is not set and this contains an empty string. Hero can be identified using `m_name`. 288 | 289 | ### 1.3.14. m_teamPreference (object) 290 | 291 | #### 1.3.14.1. m_team (null?) 292 | 293 | ### 1.3.15. m_testAuto (boolean) 294 | 295 | ### 1.3.16. m_testMap (boolean) 296 | 297 | ### 1.3.17. m_testType (number) 298 | 299 | ### 1.3.18. m_toonHandle (string) 300 | 301 | While this represents a player, it's toonHandle is not set and this contains an empty string. Hero can be identified using `m_name`. 302 | -------------------------------------------------------------------------------- /reference/replay.message.events.md: -------------------------------------------------------------------------------- 1 | replay.message.events is an array containing events whose types are specified in the `lib/protocol#####.js` files under the `message_event_types` variable. 2 | 3 | Events contain generic properties and type specific properties. 4 | 5 | # Generic 6 | 7 | ## 1. _bits (number) 8 | 9 | ## 2. _event (string) 10 | 11 | Event type. 12 | 13 | ## 3. _eventid (number) 14 | 15 | Not a unique identifier. 16 | 17 | ## 4. _gameloop (number) 18 | 19 | ## 5. _userid (object) 20 | 21 | ### 5.1. m_userId (number) 22 | 23 | Index of the player associated with the event in arrays referencing players. 24 | 25 | # NNet.Game.SChatMessage 26 | 27 | Event describing a chat message. 28 | 29 | ## 6. m_recipient (number) 30 | 31 | Maybe a chat type, like the game chat, whisper, others. 32 | 33 | - 1 = game chat? 34 | 35 | ## 7. m_string (string) 36 | 37 | The chat message sent by the player. 38 | 39 | # NNet.Game.SPingMessage 40 | 41 | ## 6. m_point (object) 42 | 43 | Event describing a ping. 44 | 45 | Is used for player pings. Are the map pings, like objective spawn, using this or other events? 46 | 47 | ### 6.1. x (number) 48 | 49 | Ping x coordinate. Parsed value can be a big negative, maybe an integer overflow? 50 | 51 | Should find how to translate it to actual usable coordinate. 52 | 53 | ### 6.2. y (number) 54 | 55 | Ping y coordinate. Parsed value can be a big negative, maybe an integer overflow? 56 | 57 | Should find how to translate it to actual usable coordinate. 58 | 59 | ## 7. m_recipient (number) 60 | 61 | Maybe a chat type, like the game chat, whisper, others. 62 | 63 | - 1 = game chat? 64 | 65 | # NNet.Game.SLoadingProgressMessage 66 | 67 | Probably describes each player progress in the loading screen. 68 | 69 | ## 6. m_progress (number) 70 | 71 | Parsed value can be a big negative, maybe an integer overflow? 72 | 73 | Should find how to translate it to an actual progress value. 74 | 75 | # NNet.Game.SServerPingMessage 76 | 77 | Not present in the file for analysis. The protocol source describes it as an empty structure. 78 | 79 | # NNet.Game.SReconnectNotifyMessage 80 | 81 | ## 6. m_status (number) 82 | -------------------------------------------------------------------------------- /reference/replay.tracker.events.md: -------------------------------------------------------------------------------- 1 | replay.tracker.events is an array containing events whose types are specified in the `lib/protocol#####.js` files under the `tracker_event_types` variable. 2 | 3 | --- 4 | 5 | Convert `m_unitTagIndex` and `m_unitTagRecycle` into a unit tag `m_tag` with `protocol.unitTag(m_unitTagIndex, m_unitTagRecycle)`. 6 | 7 | `m_tag` is useful to determine what unit was targeted by a skill, for example in this `NNet.Game.SCmdEvent` present in the `replay.game.events` file: 8 | 9 | ``` 10 | { 11 | "_eventid": 27, 12 | "m_unitGroup": null, 13 | "_event": "NNet.Game.SCmdEvent", 14 | "m_abil": { 15 | "m_abilLink": 147, 16 | "m_abilCmdIndex": 0, 17 | "m_abilCmdData": null 18 | }, 19 | "_gameloop": 2365, 20 | "_bits": 264, 21 | "m_data": { 22 | "TargetUnit": { 23 | "m_snapshotControlPlayerId": 6, 24 | "m_snapshotPoint": { 25 | "y": 235853, 26 | "x": 579289, 27 | "z": 32441 28 | }, 29 | "m_snapshotUpkeepPlayerId": 6, 30 | "m_timer": 0, 31 | "m_targetUnitFlags": 111, 32 | "m_snapshotUnitLink": 281, 33 | "m_tag": 45350913 34 | } 35 | }, 36 | "_userid": { 37 | "m_userId": 5 38 | }, 39 | "m_cmdFlags": 2097408, 40 | "m_sequence": 451, 41 | "m_otherUnit": null 42 | } 43 | ``` 44 | 45 | We see that the unit with tag `45350913` was the target of the ability id `147` casted by player `5`. 46 | 47 | Convert `m_tag` into a unit tag index `m_unitTagIndex` and unit tag recycle `m_unitTagRecycle` with `protocol.unitTagIndex(m_tag)` and `protocol.unitTagRecycle(m_tag)`. 48 | 49 | There is a known issue where revived units are not tracked, and placeholder units track death but not birth. 50 | 51 | The first 10 events are `NNet.Replay.Tracker.SPlayerSetupEvent` events, one for each player. Should check if there are also 10 if playing with or versus A.I. 52 | 53 | --- 54 | 55 | Events contain generic properties and type specific properties. 56 | 57 | # Generic 58 | 59 | ## 1. _bits (number) 60 | 61 | ## 2. _event (string) 62 | 63 | Event type. 64 | 65 | ## 3. _eventid (number) 66 | 67 | Not a unique identifier. 68 | 69 | ## 4. _gameloop (number) 70 | 71 | # NNet.Replay.Tracker.SPlayerStatsEvent 72 | 73 | In `NNet.Replay.Tracker.SPlayerStatsEvent`, `m_scoreValueFoodUsed` and `m_scoreValueFoodMade` are in fixed point (divide by 4096 for integer values). All other values are in integers. 74 | 75 | ## 5. m_playerId (number) 76 | 77 | Index of the player in arrays referencing players elsewhere? 78 | 79 | ## 6. m_stats (object) 80 | 81 | All these stats field seem to be from the Starcraft 2 engine. Don't know if any is actually useful for Heroes of the Storm. 82 | 83 | ## 6.1. m_scoreValueFoodMade (number) 84 | 85 | ## 6.2. m_scoreValueFoodUsed (number) 86 | 87 | ## 6.3. m_scoreValueMineralsCollectionRate (number) 88 | 89 | ## 6.4. m_scoreValueMineralsCurrent (number) 90 | 91 | ## 6.5. m_scoreValueMineralsFriendlyFireArmy (number) 92 | 93 | ## 6.6. m_scoreValueMineralsFriendlyFireEconomy (number) 94 | 95 | ## 6.7. m_scoreValueMineralsFriendlyFireTechnology (number) 96 | 97 | ## 6.8. m_scoreValueMineralsKilledArmy (number) 98 | 99 | ## 6.9. m_scoreValueMineralsKilledEconomy (number) 100 | 101 | ## 6.10. m_scoreValueMineralsKilledTechnology (number) 102 | 103 | ## 6.11. m_scoreValueMineralsLostArmy (number) 104 | 105 | ## 6.12. m_scoreValueMineralsLostEconomy (number) 106 | 107 | ## 6.13. m_scoreValueMineralsLostTechnology (number) 108 | 109 | ## 6.14. m_scoreValueMineralsUsedActiveForces (number) 110 | 111 | ## 6.15. m_scoreValueMineralsUsedCurrentArmy (number) 112 | 113 | ## 6.16. m_scoreValueMineralsUsedCurrentEconomy (number) 114 | 115 | ## 6.17. m_scoreValueMineralsUsedCurrentTechnology (number) 116 | 117 | ## 6.18. m_scoreValueMineralsUsedInProgressArmy (number) 118 | 119 | ## 6.19. m_scoreValueMineralsUsedInProgressEconomy (number) 120 | 121 | ## 6.20. m_scoreValueMineralsUsedInProgressTechnology (number) 122 | 123 | ## 6.21. m_scoreValueVespeneCollectionRate (number) 124 | 125 | ## 6.22. m_scoreValueVespeneCurrent (number) 126 | 127 | ## 6.23. m_scoreValueVespeneFriendlyFireArmy (number) 128 | 129 | ## 6.24. m_scoreValueVespeneFriendlyFireEconomy (number) 130 | 131 | ## 6.25. m_scoreValueVespeneFriendlyFireTechnology (number) 132 | 133 | ## 6.26. m_scoreValueVespeneKilledArmy (number) 134 | 135 | ## 6.27. m_scoreValueVespeneKilledEconomy (number) 136 | 137 | ## 6.28. m_scoreValueVespeneKilledTechnology (number) 138 | 139 | ## 6.29. m_scoreValueVespeneLostArmy (number) 140 | 141 | ## 6.30. m_scoreValueVespeneLostEconomy (number) 142 | 143 | ## 6.31. m_scoreValueVespeneLostTechnology (number) 144 | 145 | ## 6.32. m_scoreValueVespeneUsedActiveForces (number) 146 | 147 | ## 6.32. m_scoreValueVespeneUsedCurrentArmy (number) 148 | 149 | ## 6.33. m_scoreValueVespeneUsedCurrentEconomy (number) 150 | 151 | ## 6.34. m_scoreValueVespeneUsedCurrentTechnology (number) 152 | 153 | ## 6.35. m_scoreValueVespeneUsedInProgressArmy (number) 154 | 155 | ## 6.36. m_scoreValueVespeneUsedInProgressEconomy (number) 156 | 157 | ## 6.37. m_scoreValueVespeneUsedInProgressTechnology (number) 158 | 159 | ## 6.38. m_scoreValueWorkersActiveCount (number) 160 | 161 | # NNet.Replay.Tracker.SUnitBornEvent 162 | 163 | `NNet.Replay.Tracker.SUnitBornEvent` events appear for units that are created fully constructed. 164 | 165 | You may receive a `NNet.Replay.Tracker.SUnitDiedEvent` after either a UnitInit or UnitBorn event for the corresponding unit tag. 166 | 167 | ## 5. m_controlPlayerId (number) 168 | 169 | Player generating the event. Index ranging from 0 to 15? 170 | 171 | Known values: 172 | - 0 - (when `m_unitTypeName` equals `DiabloShrine`, should check if it's because diablo was in the game's slot 0) 173 | - 11 - one of the teams 174 | - 12 - the other team 175 | 176 | Always identical to `m_upkeepPlayerId`? 177 | 178 | ## 6. m_unitTagIndex (number) 179 | 180 | See note concerning unit tags at the top of this document. 181 | 182 | ## 7. m_unitTagRecycle (number) 183 | 184 | See note concerning unit tags at the top of this document. 185 | 186 | ## 8. m_unitTypeName (number) 187 | 188 | Name for different units. Several with the same name can be born. 189 | 190 | Known values: 191 | 192 | - CampOwnershipFlag 193 | - CatapultMinion - The catapult 194 | - DiabloShrine - Diablo's trait respawn point? Not unique. 195 | - FootmanMinion - The normal minion 196 | - GhostShipBeacon - Ghost ship you can 'controll' by paying coins 197 | - ItemSoulPickup - Item you can pickup in the `Tomb of the Spider` Map, awards 1 gem. 198 | - ItemSoulPickupFive - Item you can pickup in the `Tomb of the Spider` map, awards 5 gems. 199 | - ItemSoulPickupTwenty - Item you can pickup in the `Tomb of the Spider` map, awards 20 gems. 200 | - JungleGraveGolemDefender - Boss unit summoned in `Cursed Mines` map 201 | - JunglePlantHorror 202 | - KingsCore - The core of each team 203 | - LuxoriaTemple - Temple you can controll in the `Sky Temple` map 204 | - MercLanerMeleeOgre - Siege mercenaries 205 | - MercLanerSiegeGiant - Boss mercenarie that appears on most of the maps 206 | - MercLanerRangedOgre - Another siege mercenarie 207 | - RangedMinion - Ranged minion 208 | - RegenGlobe - A pickable unit that grants a small health and mana amount 209 | - RegenGlobeNeutral - A pickable unit that grants a small health and mana amount, usually drops when fighting bosses, it's color is purple. 210 | - SoulEater - Big spider summoned when a team completes the required amount of item souls. 211 | - SoulEaterMinion - Medium spiders summoned by the big spider in the `Tomb of the Spider` map 212 | - StormGameStartPathingBlocker - Gate blockers at the beginning of the game? 213 | - StormGameStartPathingBlockerDiagonal - Gate blockers at the beginning of the game? 214 | - TownCannonTowerDead 215 | - TownCannonTowerL2 216 | - TownCannonTowerL2Standalone 217 | - TownCannonTowerL3 218 | - TownCannonTowerL3Standalone 219 | - TownGateL215BLUR 220 | - TownGateL215BRUL 221 | - TownGateL2BLUR 222 | - TownGateL315BLUR 223 | - TownGateL315BRUL 224 | - TownGateL3BRUL 225 | - TownMoonwellL2 226 | - TownMoonwellL3 227 | - TownTownHallL2 228 | - TownTownHallL3 229 | - TownWallRadial14L3 230 | - TownWallRadial15L3 231 | - TownWallRadial16L2 232 | - TownWallRadial17L2 233 | - TownWallRadial17L3 234 | - TownWallRadial18L2 235 | - TownWallRadial18L3 236 | - TownWallRadial19L1 237 | - TownWallRadial19L2 238 | - TownWallRadial19L3 239 | - TownWallRadial20L3 240 | - TownWallRadial21L3 241 | - TownWallRadial2L3 242 | - TownWallRadial3L3 243 | - TownWallRadial4L2 244 | - TownWallRadial4L3 245 | - TownWallRadial5L2 246 | - TownWallRadial5L3 247 | - TownWallRadial6L2 248 | - TownWallRadial6L3 249 | - TownWallRadial7L2 250 | - TownWallRadial7L3 251 | - TownWallRadial8L3 252 | - TownWallRadial9L3 253 | - TownGateL2VerticalLeftVisionBlocked 254 | - TownGateL2VerticalRightVisionBlocked 255 | - TownGateL3BLURBRVisionBlocked 256 | - TownGateL3BLURTLVisionBlocked 257 | - TownGateL3BRULBLVisionBlocked 258 | - TownGateL3BRULTRVisionBlocked 259 | - TownGateL3VerticalLeftVisionBlocked 260 | - TownGateL3VerticalRightVisionBlocked 261 | - WeaponRackSpecialHeaven 262 | - WizardMinion - Wizard minion, usually drops a health globe when it dies 263 | 264 | 265 | Should map values to known in-game elements. Check each value count to narrow down what they could be. 266 | 267 | Does the L2 and L3 suffix reffer to each team? Each color? Orientation? 268 | 269 | Create tool to extract values programmatically instead of by hand. 270 | 271 | ## 9. m_upkeepPlayerId (number) 272 | 273 | Always identical to `m_controlPlayerId`? See for details 274 | 275 | ## 10. m_x (number) 276 | 277 | Unit spawn `x` coordinate. 278 | 279 | ## 11. m_y (number) 280 | 281 | Unit spawn `y` coordinate. 282 | 283 | # NNet.Replay.Tracker.SUnitDiedEvent 284 | 285 | You may receive a `NNet.Replay.Tracker.SUnitDiedEvent` after either a UnitInit or UnitBorn event for the corresponding unit tag. 286 | 287 | ## 5. m_killerPlayerId (number | null) 288 | 289 | References who gave the fatal blow to the unit that died. 290 | 291 | ## 6. m_killerUnitTagIndex (number | null) 292 | 293 | `m_unitTagIndex` for the unit that killed a particular unit, useful when the killer is not a human player but a NPC. 294 | 295 | See note concerning unit tags at the top of this document. 296 | 297 | ## 7. m_killerUnitTagRecycle (number | null) 298 | 299 | `m_unitTagRecycle` for the unit that killed a particular unit, useful when the killer is not a human player but a NPC. 300 | 301 | See note concerning unit tags at the top of this document. 302 | 303 | ## 8. m_unitTagIndex (number) 304 | 305 | See note concerning unit tags at the top of this document. 306 | 307 | ## 9. m_unitTagRecycle (number) 308 | 309 | See note concerning unit tags at the top of this document. 310 | 311 | ## 10. m_x (number) 312 | 313 | Unit death `x` coordinate. 314 | 315 | ## 11. m_y (number) 316 | 317 | Unit death `y` coordinate. 318 | 319 | # NNet.Replay.Tracker.SUnitOwnerChangeEvent 320 | 321 | This event occurs when a unit changes ownership, for example when the Dragon Statue is controlled by a team: 322 | 323 | ``` 324 | { 325 | "m_unitTagIndex": 104, 326 | "m_unitTagRecycle": 110, 327 | "m_controlPlayerId": 11, 328 | "_eventid": 3, 329 | "_event": "NNet.Replay.Tracker.SUnitOwnerChangeEvent", 330 | "_gameloop": 16102, 331 | "_bits": 176, 332 | "m_upkeepPlayerId": 11 333 | } 334 | ``` 335 | 336 | Here `m_upkeepPlayerId`/`m_controlPlayerId` is 11, meaning the control of the statue was granted to the blue team. 337 | 338 | ## 5. m_controlPlayerId (number) 339 | 340 | References a player. 341 | 342 | Always identical to `m_upkeepPlayerId`? 343 | 344 | ## 6. m_unitTagIndex (number) 345 | 346 | See note concerning unit tags at the top of this document. 347 | 348 | ## 7. m_unitTagRecycle (number) 349 | 350 | See note concerning unit tags at the top of this document. 351 | 352 | ## 8. m_upkeepPlayerId (number) 353 | 354 | References a player. 355 | 356 | Always identical to `m_controlPlayerId`? 357 | 358 | # NNet.Replay.Tracker.SUnitTypeChangeEvent 359 | 360 | ## 5. m_unitTagIndex (number) 361 | 362 | ## 6. m_unitTagRecycle (number) 363 | 364 | ## 7. m_unitTypeName (string) 365 | 366 | Known values: 367 | 368 | - TownCannonTowerDead 369 | 370 | # NNet.Replay.Tracker.SUpgradeEvent 371 | 372 | Limited number at the beginning of the game. 373 | 374 | Also used to determine when a unit is upgraded, for example when a player is transformed to a Dragon in the Dragon Shire map. 375 | 376 | ``` 377 | { 378 | "m_playerId": 4, 379 | "_eventid": 5, 380 | "m_count": 16, 381 | "_event": "NNet.Replay.Tracker.SUpgradeEvent", 382 | "_bits": 296, 383 | "_gameloop": 16165, 384 | "m_upgradeTypeName": "VehicleDragonUpgrade" 385 | } 386 | ``` 387 | 388 | Here, the player with `m_playerId` of `4`, which belongs to the blue team, took the dragon and was upgraded to `VehicleDragonUpgrade` unit. 389 | 390 | ## 5. m_count (number) 391 | 392 | ## 6. m_playerId (number) 393 | 394 | References one of the 16 game slot, need to find which. 395 | 396 | Probably not always referencing a player because a possible value is `15`? 397 | 398 | ## 7. m_upgradeTypeName (string) 399 | 400 | Known values: 401 | 402 | - CreepColor 403 | - GatesAreOpen 404 | - IsPlayer11 405 | - IsPlayer12 406 | - MinionsAreSpawning 407 | - VehicleDragonUpgrade 408 | 409 | # NNet.Replay.Tracker.SUnitInitEvent 410 | 411 | Not encountered in data dump. Structure extracted from source code. 412 | 413 | `NNet.Replay.Tracker.SUnitInitEvent` events appear for units under construction. When complete you'll see a `NNet.Replay.Tracker.SUnitDoneEvent` with the same unit tag. 414 | 415 | You may receive a `NNet.Replay.Tracker.SUnitDiedEvent` after either a UnitInit or UnitBorn event for the corresponding unit tag. 416 | 417 | ## 5. m_controlPlayerId (number) 418 | 419 | ## 6. m_unitTagIndex (number) 420 | 421 | ## 7. m_unitTagRecycle (number) 422 | 423 | ## 8. m_unitTypeName (string) 424 | 425 | ## 9. m_upkeepPlayerId (number) 426 | 427 | ## 10. m_x (number) 428 | 429 | ## 11. m_y (number) 430 | 431 | # NNet.Replay.Tracker.SUnitDoneEvent 432 | 433 | Not encountered in data dump. Structure extracted from source code. 434 | 435 | ## 5. m_unitTagIndex (number) 436 | 437 | ## 6. m_unitTagRecycle (number) 438 | 439 | # NNet.Replay.Tracker.SUnitPositionsEvent 440 | 441 | - Interpret the NNet.Replay.Tracker.SUnitPositionsEvent events like this: 442 | 443 | var unitIndex = event.m_firstUnitIndex; 444 | for (var i = 0, ln = event.m_items.length; i < ln; i += 3) { 445 | unitIndex += event.m_items[i + 0]; 446 | var x = event.m_items[i + 1] * 4; 447 | var y = event.m_items[i + 2] * 4; 448 | // unit identified by unitIndex at the current event._gameloop time is at approximate position (x, y) 449 | } 450 | 451 | - Only units that have inflicted or taken damage are mentioned in unit position events, and they occur periodically with a limit of 256 units mentioned per event. 452 | 453 | ## 5. m_firstUnitIndex (number) 454 | 455 | ## 6. m_items (array(number, number, ..., number)) 456 | 457 | # NNet.Replay.Tracker.SPlayerSetupEvent 458 | 459 | One and only one for each player at the very beginning of the event list. 460 | 461 | ## 5. m_playerId (number) 462 | 463 | Reference to the player, ranges from 1 to 10. 464 | 465 | ## 6. m_slotId (number) 466 | 467 | Index for the player, ranges from 0 to 9. 468 | 469 | Identical to `m_userId`. 470 | 471 | ## 7. m_type (number) 472 | 473 | Depends on if the player is a real person or a bot? 474 | 475 | - 1 = real player? 476 | 477 | ## 8. m_userId (number) 478 | 479 | Index for the player, ranges from 0 to 9. 480 | 481 | Identical to `m_slotId`. 482 | --------------------------------------------------------------------------------