Skip to content

Instantly share code, notes, and snippets.

@tec27
Created January 28, 2012 04:33
Show Gist options
  • Save tec27/1692641 to your computer and use it in GitHub Desktop.
Save tec27/1692641 to your computer and use it in GitHub Desktop.
New Blizzerial Parsing (Chains!)
// 3rd attempt at blizzerial parsing. Hopefully this time it will be easy to follow,
// easy to test, and fast. Pick 2. :D
var Cursorize = require('./cursorize'),
BigInteger = require('bigdecimal').BigInteger;
var Blizzerial = exports = module.exports = function() {
if(!(this instanceof Blizzerial)) return new Blizzerial();
this.chain = [];
};
Blizzerial.prototype.execute = function(buffer) {
var curs = new Cursorize(buffer);
var result = {};
var entry;
while((entry = this.chain.shift())) {
result[entry.name] = entry.execute(curs);
}
return result;
}
var TYPES = {
String: 2,
Array: 4,
Hash: 5,
Int8: 6,
Int32: 7,
IntV: 9
};
function readType(curs) {
return curs.read()[0];
}
function signAndShift(n) {
return Math.pow(-1, n & 0x1) * (n >>> 1);
}
// we need this because IntV's are used as lengths all over the place, without a preceding type byte
function readIntV(curs) {
var val, result = BigInteger.valueOf(0), cnt = 0;
do {
val = curs.read();
result = result.add(BigInteger.valueOf(val & 0x7F).shiftLeft(7 * cnt++));
} while(val >= 0x80);
if(cnt <= 4) return signAndShift(result.intValue());
else return result.shiftRight(1).multiply(BigInteger.valueOf(Math.pow(-1, result.intValue() & 0x1)));
}
Blizzerial.prototype.BzHash = function(name, blizzerial) {
this.chain.push(new BzHash(name, blizzerial.chain));
return this;
};
function BzHash(name, chains) {
this.type = 'BzHash';
this.name = name;
this.chain = chains || [];
}
BzHash.prototype.execute = function(curs) {
if(readType(curs) != TYPES.Hash)
throw new Error('Tried to read ' + this.name + ' as a BzHash, but found incorrect type.');
var len = readIntV(curs);
var results = {};
var entry;
for(var i = 0; i < len; i++) {
var entryNum = readIntV(curs);
entry = this.chain[entryNum];
results[entry.name] = entry.execute(curs);
}
return results;
};
Blizzerial.prototype.BzArray = function(name, blizzerial) {
this.chain.push(new BzArray(name, blizzerial.chain));
return this;
};
function BzArray(name, chains) {
this.type = 'BzArray';
this.name = name;
this.chain = chains || [];
}
BzArray.prototype.execute = function(curs) {
if(readType(curs) != TYPES.Array)
throw new Error('Tried to read ' + this.name + ' as a BzArray, but found incorrect type.');
curs.skip(2);
var len = readIntV(curs);
var results = [], entry;
for(var i = 0; i < len; i++) {
results[i] = {};
for(var j = 0; j < this.chain.length; j++) {
entry = this.chain[j];
results[i][entry.name] = entry.execute(curs);
}
}
return results;
};
Blizzerial.prototype.BzInt8 = function(name) {
this.chain.push(new BzInt8(name));
return this;
};
function BzInt8(name) {
this.type = 'BzInt8';
this.name = name;
}
BzInt8.prototype.execute = function(curs) {
if(readType(curs) != TYPES.Int8)
throw new Error('Tried to read ' + this.name + ' as a BzInt8, but found incorrect type.');
return signAndShift(curs.read()[0]);
}
Blizzerial.prototype.BzInt32 = function(name) {
this.chain.push(new BzInt32(name));
return this;
};
function BzInt32(name) {
this.type = 'BzInt32';
this.name = name;
}
BzInt32.prototype.execute = function(curs) {
if(readType(curs) != TYPES.Int32)
throw new Error('Tried to read ' + this.name + ' as a BzInt32, but found incorrect type.');
var bytes = curs.read(4);
return signAndShift(bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24));
};
Blizzerial.prototype.BzIntV = function(name) {
this.chain.push(new BzIntV(name));
return this;
};
function BzIntV(name) {
this.type = 'BzIntV';
this.name = name;
}
BzIntV.prototype.execute = function(curs) {
if(readType(curs) != TYPES.IntV)
throw new Error('Tried to read ' + this.name + ' as a BzIntV, but found incorrect type.');
return readIntV(curs);
}
Blizzerial.prototype.BzString = function(name, decode) {
this.chain.push(new BzString(name, decode));
return this;
};
function BzString(name, decode) {
this.type = 'BzString';
this.name = name;
this.decode = decode;
}
BzString.prototype.execute = function(curs) {
if(readType(curs) != TYPES.String)
throw new Error('Tried to read ' + this.name + ' as a BzString, but found incorrect type.');
var strLen = readIntV(curs);
if(strLen === 0) return null;
var buf = curs.readBuf(strLen);
if(!this.decode) return buf;
return buf.toString('utf8');
}
var Bz = require('./blizzerial'),
EventEmitter = require('events').EventEmitter;
var headerParser = Bz().BzHash('header',
Bz().BzString('unknown0', true)
.BzHash('version',
Bz().BzIntV('unknown0')
.BzIntV('major')
.BzIntV('minor')
.BzIntV('patch')
.BzIntV('build')
.BzIntV('unknown1')
)
.BzIntV('unknown1')
.BzIntV('gameLength')
);
module.exports.parse = function(data) {
console.log('Header Parser:');
console.log(require('util').inspect(headerParser, false, null, true)); // awesome thing about this parser is it outputs a nice tree automatically :)
var emitter = new EventEmitter();
process.nextTick(function() {
var result;
try {
result = headerParser.execute(data);
}
catch(err) {
return emitter.emit('error', err);
}
emitter.emit('success', result.header);
});
return emitter;
};
Header Parser:
{ chain:
[ { type: 'BzHash',
name: 'header',
chain:
[ { type: 'BzString',
name: 'unknown0',
decode: true },
{ type: 'BzHash',
name: 'version',
chain:
[ { type: 'BzIntV', name: 'unknown0' },
{ type: 'BzIntV', name: 'major' },
{ type: 'BzIntV', name: 'minor' },
{ type: 'BzIntV', name: 'patch' },
{ type: 'BzIntV', name: 'build' },
{ type: 'BzIntV', name: 'unknown1' } ] },
{ type: 'BzIntV', name: 'unknown1' },
{ type: 'BzIntV', name: 'gameLength' } ] } ] }
After parsing an SC2Replay header, we get an object back. object.header is:
{ unknown0: 'StarCraft II replay\u001b11',
version:
{ unknown0: 1,
major: 1,
minor: 4,
patch: 1,
build: 19776,
unknown1: 19679 },
unknown1: 2,
gameLength: 14419 }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment