Читаем/пишем бинарники на JavaScript (JScript) для Win Wscript

Да, проблема в том, что JavaScript пытается преобразовать полученные байты в юникод (unicode), и портит большинство байт, которые больше 0x7F. Поэтому при чтении/записи бинарников получается не то, что мы хотим.

Пошарив какое-то время в инете, я нашёл табличку преобразований символов больше либо равным 0x80, но она в итоге оказалась не полной, и мои бинарники читались и писались с ошибками.

В итоге я решил составить свои таблички для преобразования байт. Но вручную искать и записывать байты из – в мне показалось утомительным, поэтому я написал пару небольших скриптов.

Идея такая – создать бинарный файл, состоящий из последовательности байт от 0 до 255, то есть размером 256 байт, а потом распрасить его JavaScript’ом и проанализировать полученные символы.

Пишем краткий сценрий на PHP, ибо PHP всегда под рукой, что в Виньде ,что в Линуксе, что на серверах.

<?php
/* create file of bytes from 0 to 255 */
	$s = '';
	for($i = 0; $i < 256; $i++)
		$s .= chr($i);
 
	file_put_contents('256.dat', $s);

В итоге, мы имеем файл 256.dat, который проанализируем JavaScript’ и составим табличку преобразований на лету.

var name = '256.dat', i; // путь к нашему файлу и переменная д/цикла
 
/* ******************************************
* Используем API AkelPad's Scripts плагина  *
******************************************* */
var bytes = AkelPad.ReadFile(name, 0);	// binary
log('bytes.length=' + bytes.length); // должно быть 256
 
var abytes = bytes.split('');
log('abytes.length=' + abytes.length); // должно быть 256
 
var ascii = '', fromAscii = '', c;
for(i = 0; i < abytes.length; i++) {
        // table asciiCodeAt
        if(i > 127)
	  ascii += 'case 0x' + bin2hex(abytes[i]) +
            ': return 0x' + i.toString(16) + '; break;\n'
	// REVERSE TABLE fromAscii
	c = bin2hex(abytes[i]);
	if(c.length == 3)
		c = '0' + c;
	else
	if(c.length == 2)
		c = '00' + c;
	// count only high values
	if(i > 127)
	  fromAscii += 'case 0x' + i.toString(16) + ': s += \'\\u' +
            c.toUpperCase() + '\'; break;\n'
}
 
log('// ASCII:');
log(ascii)
log('// fromAscii:');
log(fromAscii);
 
function bin2hex(s) {
  //  discuss at: http://phpjs.org/functions/bin2hex/
  // original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // bugfixed by: Onno Marsman
  // bugfixed by: Linuxworld
  // improved by: ntoniazzi (http://phpjs.org/functions/bin2hex:361#comment_177616)
  //   example 1: bin2hex('Kev');
  //   returns 1: '4b6576'
  //   example 2: bin2hex(String.fromCharCode(0x00));
  //   returns 2: '00'
 
  var i, l, o = '',
    n;
 
  s += '';
 
  for (i = 0, l = s.length; i < l; i++) {
    n = s.charCodeAt(i)
      .toString(16);
    o += n.length < 2 ? '0' + n : n;
  }
 
  return o;
}
 
// Логируем текст с помощью AkelPad'овского Log плагина
function log(text)
{
  AkelPad.Call("Log::Output", 4, text + '\n');
}

Потом копируем полученный вывод в отдельный файл, добавляем необохдимую разметку и получаем в итоге полные таблицы преобразований to ASCII и from Ascii Code для JavaScript:

/*
	Needed for reading from binary files
 	See actionUtils.getImageSize method in emmet-app.js
*/
String.prototype.asciiCodeAt = function(i) {
    // charCodeAt returns some bytes translated to unicode characters. 
    // this function means to counteract that.
    switch(this.charCodeAt(i)) {
        case 0x402: return 0x80; break;
				case 0x403: return 0x81; break;
				case 0x201a: return 0x82; break;
				case 0x453: return 0x83; break;
				case 0x201e: return 0x84; break;
				case 0x2026: return 0x85; break;
				case 0x2020: return 0x86; break;
				case 0x2021: return 0x87; break;
				case 0x20ac: return 0x88; break;
				case 0x2030: return 0x89; break;
				case 0x409: return 0x8a; break;
				case 0x2039: return 0x8b; break;
				case 0x40a: return 0x8c; break;
				case 0x40c: return 0x8d; break;
				case 0x40b: return 0x8e; break;
				case 0x40f: return 0x8f; break;
				case 0x452: return 0x90; break;
				case 0x2018: return 0x91; break;
				case 0x2019: return 0x92; break;
				case 0x201c: return 0x93; break;
				case 0x201d: return 0x94; break;
				case 0x2022: return 0x95; break;
				case 0x2013: return 0x96; break;
				case 0x2014: return 0x97; break;
				case 0x98: return 0x98; break;
				case 0x2122: return 0x99; break;
				case 0x459: return 0x9a; break;
				case 0x203a: return 0x9b; break;
				case 0x45a: return 0x9c; break;
				case 0x45c: return 0x9d; break;
				case 0x45b: return 0x9e; break;
				case 0x45f: return 0x9f; break;
				case 0xa0: return 0xa0; break;
				case 0x40e: return 0xa1; break;
				case 0x45e: return 0xa2; break;
				case 0x408: return 0xa3; break;
				case 0xa4: return 0xa4; break;
				case 0x490: return 0xa5; break;
				case 0xa6: return 0xa6; break;
				case 0xa7: return 0xa7; break;
				case 0x401: return 0xa8; break;
				case 0xa9: return 0xa9; break;
				case 0x404: return 0xaa; break;
				case 0xab: return 0xab; break;
				case 0xac: return 0xac; break;
				case 0xad: return 0xad; break;
				case 0xae: return 0xae; break;
				case 0x407: return 0xaf; break;
				case 0xb0: return 0xb0; break;
				case 0xb1: return 0xb1; break;
				case 0x406: return 0xb2; break;
				case 0x456: return 0xb3; break;
				case 0x491: return 0xb4; break;
				case 0xb5: return 0xb5; break;
				case 0xb6: return 0xb6; break;
				case 0xb7: return 0xb7; break;
				case 0x451: return 0xb8; break;
				case 0x2116: return 0xb9; break;
				case 0x454: return 0xba; break;
				case 0xbb: return 0xbb; break;
				case 0x458: return 0xbc; break;
				case 0x405: return 0xbd; break;
				case 0x455: return 0xbe; break;
				case 0x457: return 0xbf; break;
				case 0x410: return 0xc0; break;
				case 0x411: return 0xc1; break;
				case 0x412: return 0xc2; break;
				case 0x413: return 0xc3; break;
				case 0x414: return 0xc4; break;
				case 0x415: return 0xc5; break;
				case 0x416: return 0xc6; break;
				case 0x417: return 0xc7; break;
				case 0x418: return 0xc8; break;
				case 0x419: return 0xc9; break;
				case 0x41a: return 0xca; break;
				case 0x41b: return 0xcb; break;
				case 0x41c: return 0xcc; break;
				case 0x41d: return 0xcd; break;
				case 0x41e: return 0xce; break;
				case 0x41f: return 0xcf; break;
				case 0x420: return 0xd0; break;
				case 0x421: return 0xd1; break;
				case 0x422: return 0xd2; break;
				case 0x423: return 0xd3; break;
				case 0x424: return 0xd4; break;
				case 0x425: return 0xd5; break;
				case 0x426: return 0xd6; break;
				case 0x427: return 0xd7; break;
				case 0x428: return 0xd8; break;
				case 0x429: return 0xd9; break;
				case 0x42a: return 0xda; break;
				case 0x42b: return 0xdb; break;
				case 0x42c: return 0xdc; break;
				case 0x42d: return 0xdd; break;
				case 0x42e: return 0xde; break;
				case 0x42f: return 0xdf; break;
				case 0x430: return 0xe0; break;
				case 0x431: return 0xe1; break;
				case 0x432: return 0xe2; break;
				case 0x433: return 0xe3; break;
				case 0x434: return 0xe4; break;
				case 0x435: return 0xe5; break;
				case 0x436: return 0xe6; break;
				case 0x437: return 0xe7; break;
				case 0x438: return 0xe8; break;
				case 0x439: return 0xe9; break;
				case 0x43a: return 0xea; break;
				case 0x43b: return 0xeb; break;
				case 0x43c: return 0xec; break;
				case 0x43d: return 0xed; break;
				case 0x43e: return 0xee; break;
				case 0x43f: return 0xef; break;
				case 0x440: return 0xf0; break;
				case 0x441: return 0xf1; break;
				case 0x442: return 0xf2; break;
				case 0x443: return 0xf3; break;
				case 0x444: return 0xf4; break;
				case 0x445: return 0xf5; break;
				case 0x446: return 0xf6; break;
				case 0x447: return 0xf7; break;
				case 0x448: return 0xf8; break;
				case 0x449: return 0xf9; break;
				case 0x44a: return 0xfa; break;
				case 0x44b: return 0xfb; break;
				case 0x44c: return 0xfc; break;
				case 0x44d: return 0xfd; break;
				case 0x44e: return 0xfe; break;
				case 0x44f: return 0xff; break;
 
        default: return this.charCodeAt(i);
    }
}
 
/*
	Needed for writing to binary files.
 	See base64.decode method in emmet-app.js
*/
String.fromAsciiCode = function() {
    // reverse for asciiCodeAt() method
    var s = '', i, c = arguments.length;
 
    for(i = 0; i < c; i++)
    	switch(arguments[i]) {
				case 0x80: s += '\u0402'; break;
				case 0x81: s += '\u0403'; break;
				case 0x82: s += '\u201A'; break;
				case 0x83: s += '\u0453'; break;
				case 0x84: s += '\u201E'; break;
				case 0x85: s += '\u2026'; break;
				case 0x86: s += '\u2020'; break;
				case 0x87: s += '\u2021'; break;
				case 0x88: s += '\u20AC'; break;
				case 0x89: s += '\u2030'; break;
				case 0x8a: s += '\u0409'; break;
				case 0x8b: s += '\u2039'; break;
				case 0x8c: s += '\u040A'; break;
				case 0x8d: s += '\u040C'; break;
				case 0x8e: s += '\u040B'; break;
				case 0x8f: s += '\u040F'; break;
				case 0x90: s += '\u0452'; break;
				case 0x91: s += '\u2018'; break;
				case 0x92: s += '\u2019'; break;
				case 0x93: s += '\u201C'; break;
				case 0x94: s += '\u201D'; break;
				case 0x95: s += '\u2022'; break;
				case 0x96: s += '\u2013'; break;
				case 0x97: s += '\u2014'; break;
				//case 0x98: s += '\u0098'; break;
				case 0x99: s += '\u2122'; break;
				case 0x9a: s += '\u0459'; break;
				case 0x9b: s += '\u203A'; break;
				case 0x9c: s += '\u045A'; break;
				case 0x9d: s += '\u045C'; break;
				case 0x9e: s += '\u045B'; break;
				case 0x9f: s += '\u045F'; break;
				//case 0xa0: s += '\u00A0'; break;
				case 0xa1: s += '\u040E'; break;
				case 0xa2: s += '\u045E'; break;
				case 0xa3: s += '\u0408'; break;
				//case 0xa4: s += '\u00A4'; break;
				case 0xa5: s += '\u0490'; break;
				//case 0xa6: s += '\u00A6'; break;
				//case 0xa7: s += '\u00A7'; break;
				case 0xa8: s += '\u0401'; break;
				//case 0xa9: s += '\u00A9'; break;
				case 0xaa: s += '\u0404'; break;
				//case 0xab: s += '\u00AB'; break;
				//case 0xac: s += '\u00AC'; break;
				//case 0xad: s += '\u00AD'; break;
				//case 0xae: s += '\u00AE'; break;
				case 0xaf: s += '\u0407'; break;
				//case 0xb0: s += '\u00B0'; break;
				//case 0xb1: s += '\u00B1'; break;
				case 0xb2: s += '\u0406'; break;
				case 0xb3: s += '\u0456'; break;
				case 0xb4: s += '\u0491'; break;
				//case 0xb5: s += '\u00B5'; break;
				//case 0xb6: s += '\u00B6'; break;
				//case 0xb7: s += '\u00B7'; break;
				case 0xb8: s += '\u0451'; break;
				case 0xb9: s += '\u2116'; break;
				case 0xba: s += '\u0454'; break;
				//case 0xbb: s += '\u00BB'; break;
				case 0xbc: s += '\u0458'; break;
				case 0xbd: s += '\u0405'; break;
				case 0xbe: s += '\u0455'; break;
				case 0xbf: s += '\u0457'; break;
				case 0xc0: s += '\u0410'; break;
				case 0xc1: s += '\u0411'; break;
				case 0xc2: s += '\u0412'; break;
				case 0xc3: s += '\u0413'; break;
				case 0xc4: s += '\u0414'; break;
				case 0xc5: s += '\u0415'; break;
				case 0xc6: s += '\u0416'; break;
				case 0xc7: s += '\u0417'; break;
				case 0xc8: s += '\u0418'; break;
				case 0xc9: s += '\u0419'; break;
				case 0xca: s += '\u041A'; break;
				case 0xcb: s += '\u041B'; break;
				case 0xcc: s += '\u041C'; break;
				case 0xcd: s += '\u041D'; break;
				case 0xce: s += '\u041E'; break;
				case 0xcf: s += '\u041F'; break;
				case 0xd0: s += '\u0420'; break;
				case 0xd1: s += '\u0421'; break;
				case 0xd2: s += '\u0422'; break;
				case 0xd3: s += '\u0423'; break;
				case 0xd4: s += '\u0424'; break;
				case 0xd5: s += '\u0425'; break;
				case 0xd6: s += '\u0426'; break;
				case 0xd7: s += '\u0427'; break;
				case 0xd8: s += '\u0428'; break;
				case 0xd9: s += '\u0429'; break;
				case 0xda: s += '\u042A'; break;
				case 0xdb: s += '\u042B'; break;
				case 0xdc: s += '\u042C'; break;
				case 0xdd: s += '\u042D'; break;
				case 0xde: s += '\u042E'; break;
				case 0xdf: s += '\u042F'; break;
				case 0xe0: s += '\u0430'; break;
				case 0xe1: s += '\u0431'; break;
				case 0xe2: s += '\u0432'; break;
				case 0xe3: s += '\u0433'; break;
				case 0xe4: s += '\u0434'; break;
				case 0xe5: s += '\u0435'; break;
				case 0xe6: s += '\u0436'; break;
				case 0xe7: s += '\u0437'; break;
				case 0xe8: s += '\u0438'; break;
				case 0xe9: s += '\u0439'; break;
				case 0xea: s += '\u043A'; break;
				case 0xeb: s += '\u043B'; break;
				case 0xec: s += '\u043C'; break;
				case 0xed: s += '\u043D'; break;
				case 0xee: s += '\u043E'; break;
				case 0xef: s += '\u043F'; break;
				case 0xf0: s += '\u0440'; break;
				case 0xf1: s += '\u0441'; break;
				case 0xf2: s += '\u0442'; break;
				case 0xf3: s += '\u0443'; break;
				case 0xf4: s += '\u0444'; break;
				case 0xf5: s += '\u0445'; break;
				case 0xf6: s += '\u0446'; break;
				case 0xf7: s += '\u0447'; break;
				case 0xf8: s += '\u0448'; break;
				case 0xf9: s += '\u0449'; break;
				case 0xfa: s += '\u044A'; break;
				case 0xfb: s += '\u044B'; break;
				case 0xfc: s += '\u044C'; break;
				case 0xfd: s += '\u044D'; break;
				case 0xfe: s += '\u044E'; break;
				case 0xff: s += '\u044F'; break;
 
        default: s += String.fromCharCode(arguments[i]);
    }
    return s;
}

Первую табличку мы используем при чтении из бинарного файла средствами JavaScript, вторую – при записи в бинарник.

Например, используем эти таблички для base64 кодирования/декодирования бинарных данных (методы из emmet-app.js)

/**
 * @author Sergey Chikuyonok (serge.che@gmail.com)
 * @link http://chikuyonok.ru
 */
emmet.define('base64', function(require, _) {
	var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
 
	return {
		/**
		 * Encodes data using base64 algorithm
		 * @author Tyler Akins (http://rumkin.com)
		 * @param {String} input
		 * @returns {String}
		 * @memberOf emmet.base64
		 */
		encode : function(input) {
			var output = [];
			var chr1, chr2, chr3, enc1, enc2, enc3, enc4, cdp1, cdp2, cdp3;
			var i = 0, il = input.length, b64 = chars;
 
			while (i < il) {
 
				// call .asciiCodeAt() instead of .charCodeAt()
				// see AkelEmmet.js in Scripts folder for details
				cdp1 = input.asciiCodeAt(i++);
				cdp2 = input.asciiCodeAt(i++);
				cdp3 = input.asciiCodeAt(i++);
 
				chr1 = cdp1 & 0xff;
				chr2 = cdp2 & 0xff;
				chr3 = cdp3 & 0xff;
 
				enc1 = chr1 >> 2;
				enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
				enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
				enc4 = chr3 & 63;
 
				if (isNaN(cdp2)) {
					enc3 = enc4 = 64;
				} else if (isNaN(cdp3)) {
					enc4 = 64;
				}
 
				output.push(b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4));
			}
 
			return output.join('');
		},
 
		/**
		 * Decodes string using MIME base64 algorithm
		 * 
		 * @author Tyler Akins (http://rumkin.com)
		 * @param {String} data
		 * @return {String}
		 */
		decode : function(data) {
			var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, tmpArr = [];
			var b64 = chars, il = data.length;
 
			if (!data) {
				return data;
			}
 
			data += '';
 
			do { // unpack four hexets into three octets using index points in b64
				h1 = b64.indexOf(data.charAt(i++));
				h2 = b64.indexOf(data.charAt(i++));
				h3 = b64.indexOf(data.charAt(i++));
				h4 = b64.indexOf(data.charAt(i++));
 
				bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
 
				o1 = bits >> 16 & 0xff;
				o2 = bits >> 8 & 0xff;
				o3 = bits & 0xff;
 
				if (h3 == 64) {
					tmpArr[ac++] = String.fromAsciiCode(o1); // String.fromCharCode(o1);
				} else if (h4 == 64) {
					tmpArr[ac++] = String.fromAsciiCode(o1, o2); //String.fromCharCode(o1, o2);
				} else {
					tmpArr[ac++] = String.fromAsciiCode(o1, o2, o3); //String.fromCharCode(o1, o2, o3);
				}
			} while (i < il);
 
			return tmpArr.join('');
		}
	};
});

Крепкого Вам чая в бокале, сладкого лимона и главное конечно, я знаю, что банален, но желаю хорошего настроения. От чего оно зависит?