phpjs
Version:
359 lines (313 loc) • 11.6 kB
JavaScript
function unpack(format, data) {
// http://kevin.vanzonneveld.net
// + original by: Tim de Koning (http://www.kingsquare.nl)
// + parts by: Jonas Raoni Soares Silva
// + http://www.jsfromhell.com
// + bugfixed by: marcuswestin
// % note 1: Float decoding by: Jonas Raoni Soares Silva
// % note 2: Home: http://www.kingsquare.nl/blog/22-12-2009/13650536
// % note 3: Feedback: phpjs-unpack@kingsquare.nl
// % note 4: 'machine dependant byte order and size' aren't
// % note 5: applicable for JavaScript unpack works as on a 32bit,
// % note 6: little endian machine
// * example 1: unpack('f2test', 'abcddbca');
// * returns 1: { 'test1': 1.6777999408082E+22.
// * returns 2: 'test2': 2.6100787562286E+20 }
var formatPointer = 0, dataPointer = 0, result = {}, instruction = '',
quantifier = '', label = '', currentData = '', i = 0, j = 0,
word = '', precisionBits = 0, exponentBits = 0, dataByteLength = 0;
// Used by float decoding
var b = [], bias, signal, exponent, significand, divisor, curByte,
byteValue, startBit = 0, mask, currentResult;
var readBits = function(start, length, byteArray) {
var offsetLeft, offsetRight, curByte, lastByte, diff, sum;
function shl(a, b) {
for (++b; --b;) {
a = ((a %= 0x7fffffff + 1) & 0x40000000) === 0x40000000 ?
a * 2 :
(a - 0x40000000) * 2 + 0x7fffffff + 1;
}
return a;
}
if (start < 0 || length <= 0) {
return 0;
}
offsetRight = start % 8;
curByte = byteArray.length - (start >> 3) - 1;
lastByte = byteArray.length + (-(start + length) >> 3);
diff = curByte - lastByte;
sum = (
(byteArray[curByte] >> offsetRight) &
((1 << (diff ? 8 - offsetRight : length)) - 1)
) + (
diff && (offsetLeft = (start + length) % 8) ?
(byteArray[lastByte++] & ((1 << offsetLeft) - 1)) <<
(diff-- << 3) - offsetRight :
0
);
for (; diff;) {
sum += shl(byteArray[lastByte++], (diff-- << 3) - offsetRight);
}
return sum;
};
while (formatPointer < format.length) {
instruction = format.charAt(formatPointer);
// Start reading 'quantifier'
quantifier = '';
formatPointer++;
while ((formatPointer < format.length) &&
(format.charAt(formatPointer).match(/[\d\*]/) !== null)) {
quantifier += format.charAt(formatPointer);
formatPointer++;
}
if (quantifier === '') {
quantifier = '1';
}
// Start reading label
label = '';
while ((formatPointer < format.length) &&
(format.charAt(formatPointer) !== '/')) {
label += format.charAt(formatPointer);
formatPointer++;
}
if (format.charAt(formatPointer) === '/') {
formatPointer++;
}
// Process given instruction
switch (instruction) {
case 'a': // NUL-padded string
case 'A': // SPACE-padded string
if (quantifier === '*') {
quantifier = data.length - dataPointer;
} else {
quantifier = parseInt(quantifier, 10);
}
currentData = data.substr(dataPointer, quantifier);
dataPointer += quantifier;
if (instruction === 'a') {
currentResult = currentData.replace(/\0+$/, '');
} else {
currentResult = currentData.replace(/ +$/, '');
}
result[label] = currentResult;
break;
case 'h': // Hex string, low nibble first
case 'H': // Hex string, high nibble first
if (quantifier === '*') {
quantifier = data.length - dataPointer;
} else {
quantifier = parseInt(quantifier, 10);
}
currentData = data.substr(dataPointer, quantifier);
dataPointer += quantifier;
if (quantifier > currentData.length) {
throw new Error('Warning: unpack(): Type ' + instruction +
': not enough input, need ' + quantifier);
}
currentResult = '';
for (i = 0; i < currentData.length; i++) {
word = currentData.charCodeAt(i).toString(16);
if (instruction === 'h') {
word = word[1] + word[0];
}
currentResult += word;
}
result[label] = currentResult;
break;
case 'c': // signed char
case 'C': // unsigned c
if (quantifier === '*') {
quantifier = data.length - dataPointer;
} else {
quantifier = parseInt(quantifier, 10);
}
currentData = data.substr(dataPointer, quantifier);
dataPointer += quantifier;
for (i = 0; i < currentData.length; i++) {
currentResult = currentData.charCodeAt(i);
if ((instruction === 'c') && (currentResult >= 128)) {
currentResult -= 256;
}
result[label + (quantifier > 1 ?
(i + 1) :
'')] = currentResult;
}
break;
case 'S': // unsigned short (always 16 bit, machine byte order)
case 's': // signed short (always 16 bit, machine byte order)
case 'v': // unsigned short (always 16 bit, little endian byte order)
if (quantifier === '*') {
quantifier = (data.length - dataPointer) / 2;
} else {
quantifier = parseInt(quantifier, 10);
}
currentData = data.substr(dataPointer, quantifier * 2);
dataPointer += quantifier * 2;
for (i = 0; i < currentData.length; i += 2) {
// sum per word;
currentResult = ((currentData.charCodeAt(i + 1) & 0xFF) << 8) +
(currentData.charCodeAt(i) & 0xFF);
if ((instruction === 's') && (currentResult >= 32768)) {
currentResult -= 65536;
}
result[label + (quantifier > 1 ?
((i / 2) + 1) :
'')] = currentResult;
}
break;
case 'n': // unsigned short (always 16 bit, big endian byte order)
if (quantifier === '*') {
quantifier = (data.length - dataPointer) / 2;
} else {
quantifier = parseInt(quantifier, 10);
}
currentData = data.substr(dataPointer, quantifier * 2);
dataPointer += quantifier * 2;
for (i = 0; i < currentData.length; i += 2) {
// sum per word;
currentResult = ((currentData.charCodeAt(i) & 0xFF) << 8) +
(currentData.charCodeAt(i + 1) & 0xFF);
result[label + (quantifier > 1 ?
((i / 2) + 1) :
'')] = currentResult;
}
break;
case 'i': // signed integer (machine dependent size and byte order)
case 'I': // unsigned integer (machine dependent size & byte order)
case 'l': // signed long (always 32 bit, machine byte order)
case 'L': // unsigned long (always 32 bit, machine byte order)
case 'V': // unsigned long (always 32 bit, little endian byte order)
if (quantifier === '*') {
quantifier = (data.length - dataPointer) / 4;
} else {
quantifier = parseInt(quantifier, 10);
}
currentData = data.substr(dataPointer, quantifier * 4);
dataPointer += quantifier * 4;
for (i = 0; i < currentData.length; i += 4) {
currentResult =
((currentData.charCodeAt(i + 3) & 0xFF) << 24) +
((currentData.charCodeAt(i + 2) & 0xFF) << 16) +
((currentData.charCodeAt(i + 1) & 0xFF) << 8) +
((currentData.charCodeAt(i) & 0xFF));
result[label + (quantifier > 1 ?
((i / 4) + 1) :
'')] = currentResult;
}
break;
case 'N': // unsigned long (always 32 bit, little endian byte order)
if (quantifier === '*') {
quantifier = (data.length - dataPointer) / 4;
} else {
quantifier = parseInt(quantifier, 10);
}
currentData = data.substr(dataPointer, quantifier * 4);
dataPointer += quantifier * 4;
for (i = 0; i < currentData.length; i += 4) {
currentResult =
((currentData.charCodeAt(i) & 0xFF) << 24) +
((currentData.charCodeAt(i + 1) & 0xFF) << 16) +
((currentData.charCodeAt(i + 2) & 0xFF) << 8) +
((currentData.charCodeAt(i + 3) & 0xFF));
result[label + (quantifier > 1 ?
((i / 4) + 1) :
'')] = currentResult;
}
break;
case 'f':
case 'd':
exponentBits = 8;
dataByteLength = 4;
if (instruction === 'd') {
exponentBits = 11;
dataByteLength = 8;
}
if (quantifier === '*') {
quantifier = (data.length - dataPointer) / dataByteLength;
} else {
quantifier = parseInt(quantifier, 10);
}
currentData = data.substr(dataPointer,
quantifier * dataByteLength);
dataPointer += quantifier * dataByteLength;
for (i = 0; i < currentData.length; i += dataByteLength) {
data = currentData.substr(i, dataByteLength);
b = [];
for (j = data.length - 1; j >= 0; --j) {
b.push(data.charCodeAt(j));
}
precisionBits = (instruction === 'f') ? 23 : 52;
bias = Math.pow(2, exponentBits - 1) - 1;
signal = readBits(precisionBits + exponentBits, 1, b);
exponent = readBits(precisionBits, exponentBits, b);
significand = 0;
divisor = 2;
curByte = b.length + (-precisionBits >> 3) - 1;
startBit = 0;
do {
byteValue = b[++curByte];
startBit = precisionBits % 8 || 8;
mask = 1 << startBit;
for (; (mask >>= 1);) {
if (byteValue & mask) {
significand += 1 / divisor;
}
divisor *= 2;
}
} while ((precisionBits -= startBit));
if (exponent === (bias << 1) + 1) {
if (significand) {
currentResult = NaN;
} else {
if (signal) {
currentResult = -Infinity;
} else {
currentResult = +Infinity;
}
}
} else {
if ((1 + signal * -2) * (exponent || significand)) {
if (!exponent) {
currentResult = Math.pow(2, -bias + 1) *
significand;
} else {
currentResult = Math.pow(2,
exponent - bias) *
(1 + significand);
}
} else {
currentResult = 0;
}
}
result[label + (quantifier > 1 ?
((i / 4) + 1) :
'')] = currentResult;
}
break;
case 'x': // NUL byte
case 'X': // Back up one byte
case '@': // NUL byte
if (quantifier === '*') {
quantifier = data.length - dataPointer;
} else {
quantifier = parseInt(quantifier, 10);
}
if (quantifier > 0) {
if (instruction === 'X') {
dataPointer -= quantifier;
} else {
if (instruction === 'x') {
dataPointer += quantifier;
} else {
dataPointer = quantifier;
}
}
}
break;
default:
throw new Error('Warning: unpack() Type ' + instruction +
': unknown format code');
}
}
return result;
}