Created
January 28, 2012 04:33
-
-
Save tec27/1692641 to your computer and use it in GitHub Desktop.
New Blizzerial Parsing (Chains!)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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'); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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