バイナリファイルを16進ダンプ／逆変換するスクリプトをささっと作る

# 0. はじめに

とある事情でバイナリファイル（実行プログラム）をイジることになった。バイナリエディタを使えば一発なのだが，インストールが面倒なのとテキスト形式で変更前後の差分を残しておきたいということで

- バイナリを16進数ダンプするスクリプト
- 16進数ダンプをバイナリに戻すスクリプト

の二つを同時に作ることにした。

# 1. 基本方針

バイナリファイルの読み書きには `ADODB.Stream` オブジェクトを使う。

```javascript:バイナリファイルの一括読み込み
var	stream = WScript.CreateObject("ADODB.Stream");
stream.Type = 1;				/* 1:adTypeBinary */
stream.Open();
stream.LoadFromFile(filename);
var	bin = stream.Read(-1);		/* -1:adReadAll */
stream.Close();
```

```javascript:バイナリファイルの一括書き込み
var	stream = WScript.CreateObject("ADODB.Stream");
stream.Type = 1;				/* 1:adTypeBinary */
stream.Open();
stream.Write(bin);
stream.SaveToFile(filename, 2);	/* 2:adSaveCreateOverWrite */
stream.Close();
```

問題は，こうして読み込んだバイナリデータ，あるいは書き込むときに用意するバイナリデータのいずれも JavaScript の既存のデータ型としてアクセスすることはできないということだ。バイナリデータと16進数テキストの相互変換には `MSXML2.DOMDocument` オブジェクトを使う。

```javascript
var	document = WScript.CreateObject("MSXML2.DOMDocument");
var	element = document.createElement("hex");
element.dataType = "bin.hex";
```

こうして宣言した `element` オブジェクトに対して `nodeTypedValue` メンバにバイナリデータを書き込むと明示的な変換メソッドを呼び出さなくても `text` メンバを参照するだけで16進数文字列を取得することができる。また逆に `text` メンバに16進数文字列を書き込むだけで `nodeTypedValue` メンバを参照すればバイナリデータが得られる。

```plantuml
@startuml
<style>
root {
  margin: 0;
  padding: 0;
}
footer {
  fontColor: black;
  fontSize: 16;
}
objectDiagram {
  fontSize: 16;
}
</style>
object "element.nodeTypedValue" as O1
object "element.text" as O2

O1 : バイナリ配列
O2 : 16進数文字列

O1 <-right-> O2

footer 図１　バイナリと16進数テキストの相互変換
@enduml
```

ちなみに `element.Type = "bin.base64"` とすると，BASE64文字列のエンコード／デコードが可能になる。

# 2. 基本仕様

16進数ダンプのデータは逆変換の手間を考えてデータのみとする。すなわちアドレス無し，ASCII文字列も無しとする。なお，16進数ダンプは見易いように１バイトごとに空白，１６バイトごとに改行を挟むが，逆変換時はその限りではなく，フリーフォーマットとする。※単純にスペースやタブ，改行コードは無視する。

```:16進数ダンプのサンプル
4d 5a 90 00 03 00 00 00 04 00 00 00 ff ff 00 00
b8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 f0 00 00 00
0e 1f ba 0e 00 b4 09 cd 21 b8 01 4c cd 21 54 68

/* 以下略 */
```

# 3. 実装コード

## BIN2HEX.JS の実装コード

```javascript:BIN2HEX.JS
//------------------------------------------------------------------------------
// メイン関数
//------------------------------------------------------------------------------
function main(args) {
	//--------------------------------------------------------------------------
	// ヘルプメッセージ
	//--------------------------------------------------------------------------
	if(args.Count < 2) {
		WScript.StdErr.WriteLine("Usage: bin2hex(.js) [input(.hex)] [output(.bin)]");
		return false;
	}
	//--------------------------------------------------------------------------
	// 入力ファイルのチェック
	//--------------------------------------------------------------------------
	var	fs = WScript.CreateObject("Scripting.FileSystemObject");
	if(!fs.FileExists(args(0))) {
		WScript.StdErr.WriteLine("ファイル " + args(0)+ " は存在しません!!");
		return false;
	}
	//--------------------------------------------------------------------------
	// 入力ファイル（バイナリファイル）の一括読み込み
	//--------------------------------------------------------------------------
	var	stream = WScript.CreateObject("ADODB.Stream");
	stream.Type = 1;	/* 1:adTypeBinary */
	stream.Open();
	stream.LoadFromFile(args(0));
	var	bin = stream.Read(-1);	/* -1:adReadAll */
	stream.Close();
	//--------------------------------------------------------------------------
	//出力ファイル（テキストファイル）の書き込み
	//--------------------------------------------------------------------------
	var	document = WScript.CreateObject("MSXML2.DOMDocument");
	var	element = document.createElement("hex");
	element.dataType = "bin.hex";
	element.nodeTypedValue = bin;
	var	ts = fs.CreateTextFile(args(1), true);
	var	s = element.text;
	for(var i = 0; i < s.length; i += 32 ) {
		var	t = s.substr(i, 32);
		var	a = [];
		for(var j = 0; j < t.length; j += 2)
			a.push(t.substr(j, 2));
		ts.WriteLine(a.join(" "));
	}
	ts.Close();
	return true;
}
//------------------------------------------------------------------------------
// メイン関数の呼び出し
//------------------------------------------------------------------------------
var	args = WScript.Arguments.Unnamed;
var	ret = main(args);
try {
	WScript.Quit(ret);
} catch(e) {
	/* 何もしない */
}
```

## HEX2BIN.JS の実装コード

```javascript:HEX2BIN.JS
//------------------------------------------------------------------------------
// メイン関数
//------------------------------------------------------------------------------
function main(args) {
	//--------------------------------------------------------------------------
	// ヘルプメッセージ
	//--------------------------------------------------------------------------
	if(args.Count < 2) {
		WScript.StdErr.WriteLine("Usage: hex2bin(.js) [input(.hex)] [output(.bin)]");
		return false;
	}
	//--------------------------------------------------------------------------
	// 入力ファイル（テキストファイル）の存在チェック
	//--------------------------------------------------------------------------
	var	fs = WScript.CreateObject("Scripting.FileSystemObject");
	if(!fs.FileExists(args(0))) {
		WScript.StdErr.WriteLine("ファイル " + args(0) + " は存在しません!!");
		return false;
	}
	//--------------------------------------------------------------------------
	// 入力ファイル（テキストファイル）の一括読み込み
	//--------------------------------------------------------------------------
	var	ts = fs.OpenTextFile(args(0));
	var	buf = ts.ReadAll();
	ts.Close();
	//--------------------------------------------------------------------------
	// 余分な空白・改行コードを取り除く
	//--------------------------------------------------------------------------
	buf = buf.replace(/\s/g, "");
	//--------------------------------------------------------------------------
	// 出力ファイル（バイナリファイル）の一括書き込み
	//--------------------------------------------------------------------------
	var	stream = WScript.CreateObject("ADODB.Stream");
	var	doc = WScript.CreateObject("MSXML2.DOMDocument");
	var	element = doc.createElement("hex");
	element.dataType = "bin.hex";
	element.text = buf;
	stream.Type = 1;	/* 1:adTypeBinary */
	stream.Open();
	stream.Write(element.nodeTypedValue);
	stream.SaveToFile(args(1), 2);	/* 2:adSaveCreateOverWrite */
	stream.Close();
	return true;
}
//------------------------------------------------------------------------------
// メイン関数の呼び出し
//------------------------------------------------------------------------------
var	args = WScript.Arguments.Unnamed;
var	ret = main(args);
try {
	WScript.Quit(ret);
} catch(e) {
	/* 何もしない */
}
```

# 4. 参考文献

- [JScriptでBase64 - Qiita](https://qiita.com/tnakagawa/items/beaec98fd64b20894245)
- [JScriptでHash取得 - Qiita](https://qiita.com/tnakagawa/items/9bcc26b501f5b995d896)
- [Msxml2.DOMDocumentのdataType ってなによ？- Hatena Blog](https://masayoshi-9a7ee.hatenablog.com/entry/20160629/1467161830)