Programing Tips

プログラム関連の小ネタです。
間違いを発見した方、質問や文句がある方は こちらへどうぞ。



Menu


自己解凍ファイルを作ろう

更新日時 2003/10/12 初稿
はじめに
はじめに言っておきますが、この方法はあまり賢いやり方ではないと思われます。
EXEファイルのヘッダーを解析すれば、もっと賢い方法が見つかるのかもしれませんが、 私にはそのような知識はありません。で、これから紹介する方法になったのです。


自己解凍ファイルとは
自己解凍ファイルとはWinRARなどのアーカイブユーティリティーや暗号化ソフトなどで使われる、 プログラムなしで格納されているデータを取り出すことができるファイルです。 (自己解凍ファイルがプログラムなのですが)

で、それを作ってしまおう、というわけです。 ただし、ここでアーカイブユーティリティーや暗号化ソフトの作り方は説明はしません。てか、できません。 Googleででも探してください。


自己解凍ファイルの構造
これから作成する自己解凍ファイルの構造は次のようになってます。

プログラム データファイルのヘッダー データ

プログラムはデータの解凍や複合(暗号を解くこと)をするプログラム、 データファイルのヘッダーは圧縮、暗号化などのデータの情報を格納するファイルヘッダー、 データは圧縮されていたり、暗号化されていたりするデータです。
ファイルヘッダーはファイルの種類によっては存在しないかもしれません。 作成するプログラムのファイル構造によって変わってくるでしょう。
自己解凍ファイルを作るとき、一番問題になるのは、 どうやってデータファイルのヘッダーの位置(何バイト目にあるのか)を知るか、です。
ファイルヘッダーの場所がわかれば読み込んで解凍や複合を行うのは簡単です。


つくってみよう

ファイルを解凍したり、複合(暗号化を解くこと)したりするプログラムができたとします。
まず、自己解凍プログラムは自分自身のパス(場所 C:\Windowsとか)を取得しなければなりません。
自分自身のパスを取得するにはC++ではこんなかんじに書きます。


#include <windows.h>
// hInstanceにプログラムのインスタンスをいれてやる。
char GetExePath(HINSTANCE hInstance){
char fullpath[_MAX_PATH+1]; // フルパスを受け取る変数
// 実行ファイルのフルパスを取得
GetModuleFileName(hInstance,fullpath,_MAX_PATH);
// ディレクトリ情報を返す
return fullpath;
}


C++じゃない人もGetModuleFileName関数を使えば大丈夫だと思います。

そして、ifstreamでも使って自分自身を読み取って、データを取り出せばよいのですが、 どうやって、データファイルのヘッダーの位置を取得しましょうか?
ここからがポイントです。

まず、4バイトのファイルを作成し、適当な拡張子で保存します。
ただし、BMPとかはやめといたほうがよいです。
Windowsに登録されていない拡張子だとさらによろしいです。

次にこれを解凍プログラムにカスタムリソースとして取り込みます。 リソースのタイプはご自由に。

さらに、このリソースを解凍プログラムで読み込んで、そこからサイズを取得し、 ファイルのヘッダーへの位置を取得するプログラムを作ります。

C++なら下のような感じ。


// リソースを検索
// hInstanceは自己解凍プログラムのインスタンス
// リソースのタイプはMYRESOURCE、リソースのIDはMYRESOURCE1
// Windows.hをインクルードしてください。 HRSRC hRes=FindResource(
hInstance, MAKEINTRESOURCE(MYRESOURCE1), "MYRESOURCE"
);
if (hRes==NULL){
cout <<"Error";
return 0;
}

// リソースをロード
HGLOBAL hGlobal=LoadResource(hInstance, hRes);
if (hGlobal==NULL){
cout <<"Error";
return 0;
}

// リソースのサイズを取得
DWORD size=SizeofResource(hInstance,hRes);

// リソースをロック
char *pData=(char *)LockResource(hGlobal);
if(pData==NULL){
cout <<"Error";
return 0;
}

// 自己解凍プログラムのサイズをリソースから取得
unsigned long Exesize=*pData;

// リソースを解放
FreeResource(hGlobal);
// あとはifstream.ignore(Exesize)かなんかで解凍プログラム部分を読み飛ばし、
// ファイルヘッダーを読み込んで、適宜解凍なり、複合なりを行わせます。


ここいらのことはここ などを参考にするとよいと思われます。


最後にこのリソースに自己解凍プログラムのサイズを記入します。

NT系のWindowsを使える人は 次のようなプログラムで、このリソースにプログラムのサイズを書き込みます。


// 解凍プログラムのサイズは123456だとします
// リソースのタイプはMYRESOURCE、
// 埋め込んだりソースのIDはMYRESOURCE1だとします。
// windows.hをインクルードする必要があります。
// Windows 9x、Meでは使えません。

unsigned long size=123456; // サイズを代入
char path[_MAX_PATH];
HANDLE hRes=BeginUpdateResource(path,FALSE);

BOOL err=UpdateResource(
hRes,// 更新ファイルのハンドル
"MYRESOURCE",// 更新するリソースのタイプ
MAKEINTRESOURCE(MYRESOURCE1),// 更新するリソース名(ID)
MAKELANGID(LANG_JAPANESE,SUBLANG_DEFAULT),// ユーザーのデフォルトの言語
&size,(DWORD)4
);

if (err==FALSE){
MessageBox(
NULL,"実行ファイルの書き込みに失敗しました。",
"Error",MB_ICONEXCLAMATION
);
EndUpdateResource(hRes,TRUE); // 更新を書き込まない
}
else {
EndUpdateResource(hRes,FALSE); // 更新を書き込む
MessageBox(
NULL,"実行ファイルの更新成功",
"正常終了",MB_ICONINFORMATION
);
}


BeginUpdateResourceUpdateResourceEndUpdateResource はリンク先(英語)を参考にしてください。

NT系のOSじゃない人はバイナリエディタなどで4バイトのファイルにプログラムのサイズを書き込み、 それをResource Hackerなどで 埋め込んだりソースに置き換えてやりましょう。

これで自己解凍プログラムは自分のサイズだけ読み飛ばし、ヘッダーにたどり着くことができます。
あとはこのプログラムの後ろにofstreamのOpenでstd::ios::ateでも使ってヘッダー、データを書き込めばOKです。

まとめるとこんな感じです。
  1. プログラムに4バイトのリソースを埋め込む。
  2. そのリソースを読み込んでその値だけ、読み飛ばして自分自身を解凍するプログラムを作る。
  3. 何らかの手段で4バイトのリソースに自己解凍プログラムのサイズを書き込む。
  4. 自己解凍プログラムの後ろに実際のデータを書き込む。
おつかれさまでした。


微妙に便利なコメント

更新日時 2004/02/05 初稿
ちょっとだけ便利なコメント使い方

ちょっとだけ便利な、コメントの書き方です。
プログラムの一部分をまとめてスキップするときには普通、

/*
int a = 2;
int b = 3;
int c = a * b;
*/


を使います。このコメントを何回もつけたりはずしたりする必要があるとき、

/*
int a = 2;
int b = 3;
int c = a * b;
//*/
← ポイント

と一工夫してやると、

//*← ポイント
int a = 2;
int b = 3;
int c = a * b;
//*/

とすることで、キーを一回打つだけで、コメントの切り替えが出来ます。

ちょっとだけど、実は便利なコメントのつけ方でした。