/* 更新履歴:
 *
 *	* Mon Jun 7 04:30:00 JST 2004 Naoyuki Sawa
 *	- 1bitマスク付き(独自拡張)に対応しました。
 *	  4bitマスクなし(独自拡張)に対応しました。
 *	  4bitマスク付き(独自拡張)に対応しました。
 *	* Sat Dec 24 21:19:40 JST 2016 Naoyuki Sawa
 *	- VQ圧縮テクスチャ形式(独自拡張)に対応しました。
 *	- これまでは、当ツールはホームページ上だけに置いていましたが、変更し易いように、/clip/tool/preview/に置くようにしました。
 *	* Sun Dec 25 23:57:41 JST 2016 Naoyuki Sawa
 *	- clipvqtc.cモジュールにて、符号の数の上限を256⇒65536に変更した事に伴い、当ツールも修正しました。
 *	* Fri May 26 22:00:03 JST 2017 Naoyuki Sawa
 *	- pexファイル添付リソースに対応しました。
 *	  これまでは、pexファイル添付リソースが添付されたpexファイルをロードした際、unpex()の処理で、圧縮されたpex本体の部分(=srf相当)だけを展開して、pexファイル添付リソースが有っても捨ててしまっていました。
 *	  pexファイル添付リソースは独自拡張の仕様ですから、これまでの処理でも間違いではないのですが、自作のプログラムでグラフィックデータをpexファイル添付リソースとして添付した場合に、当ツールではそれが見られないという不便が生じました。
 *	  暫定対策としては、プロジェクトフォルダ内で、pexに添付する前のfpk等を直接当ツールにドラッグ&ドロップすれば一応見られるのですが、pexに添付してしまった後に見られないという不便が有りました。
 *	- そこで今回正式に、当ツールがpexファイル添付リソースも認識するように修正しました。
 *	  今回の変更によって、pexファイル添付リソースが添付されたpexファイルを当ツールにドラッグ&ドロップして、pexファイル添付リソース内のグラフィックデータも見られるようになりました。
 *	  尚、今回の変更は、pexファイル添付リソースが添付されていない普通のpexに対しては、影響は有りません。
 */

#include "stdafx.h"
#include "preview.h"
#include "decode.h"

/* なぜかMFCスタティックライブラリでビルドすると、終了時にCTL3Dで落ちます。
 * 仕方ないので、MFCダイナミックライブラリでビルドしています。
 */

#define _VERSION_	"20170526"
#define VERSION_INFO	"PReView - P/ECEリソースビューア (" _VERSION_ ")\n" \
			"Copyright (C) 2002-2017 Naoyuki Sawa"

/****************************************************************************
 *	
 ****************************************************************************/

#define WIDTH_MAX	4096	/* 最大幅 */
#define HEIGHT_MAX	4096	/* 最大高さ */

#define BUFFER_SIZE	(WIDTH_MAX * HEIGHT_MAX)	/* 最低512KBは必要 */
static unsigned char  in_buffer[BUFFER_SIZE];
static unsigned char out_buffer[BUFFER_SIZE];
static int data_length;
static unsigned char icon_data[32 * 32 / 4];

static int
unpex()
{
//{{2017/05/26追加:pexファイル添付リソースに対応しました。
	static unsigned char res_buffer[BUFFER_SIZE];
//}}2017/05/26追加:pexファイル添付リソースに対応しました。
	pffsFileHEADER* fh;
	unsigned crc;

	/* 正しいPEXファイルかチェック。 */
	if(data_length < sizeof(pffsFileHEADER)) return 0; /* ヘッダより小さい */
	fh = (pffsFileHEADER*)in_buffer;
	if(fh->mark != 'X') return 0;
	if(fh->ofs_data < sizeof(pffsFileHEADER)) return 0; /* データがヘッダにかぶってる */
	if(fh->length < 8) return 0; /* データが小さすぎ(サイズとCRCは必須) */
	if(fh->length > BUFFER_SIZE) return 0; /* データが大きすぎ */
	if((int)(fh->ofs_data + fh->length) > data_length) return 0; /* データがファイル終端を超えてる */

	/* アイコンイメージがあればコピー。 */
	if(fh->ofs_icon != 0 &&
	   (int)(fh->ofs_icon + sizeof icon_data) <= data_length) { /* アイコンイメージがファイル終端を超えていないこと。 */
		memcpy(icon_data, in_buffer + fh->ofs_icon, sizeof icon_data);
	}

//{{2017/05/26追加:pexファイル添付リソースに対応しました。
	//pexファイル添付リソース(有れば)の、オフセットとサイズを求める。
	// - fhはin_buffer内を指しているが、この後のzlbdecode()によってin_bufferは上書きされ,また,data_lengthも上書きされるので、その前に求めておく必要が有る。
	int ofs_res = (fh->ofs_data + fh->length + 3) & ~3;	//pexファイル添付リソースの仕様等については、'/clip/clipres.c'のresource_address()，又は，'/clip/tool/dpexres/main.c'のmain()の'a'オプションの処理を参照して下さい。
	int res_size = data_length - ofs_res;
	//pexファイル添付リソースが有れば…
	if(res_size > 0) {
		//pexファイル添付リソースをres_bufferにコピーして保持しておく。
		// - この下の処理でin_bufferは上書きされるので、その前にコピーしておく必要が有る。
		//   尚、この下の処理でin_bufferをout_bufferにコピーしてはいるが、それは圧縮されたpex本体の部分(=srf相当)だけであり、pexファイル添付リソースの部分はコピーしていないので、ここでres_bufferに保持しておく必要が有る。
		if(res_size > BUFFER_SIZE) return 0; /* データが大きすぎ */
		memcpy(res_buffer, in_buffer + ofs_res, res_size);
	}
//}}2017/05/26追加:pexファイル添付リソースに対応しました。

	/* 圧縮されたデータ本体を展開。 */
	memcpy(out_buffer, in_buffer + fh->ofs_data, fh->length);
	data_length = zlbdecode(out_buffer + 8, fh->length - 8, in_buffer);
	if(data_length != *(int*)(out_buffer + 0)) return 0; /* サイズが違う */
	calcrc_init();
	crc = calcrc(in_buffer, data_length);
	if(crc != *(unsigned*)(out_buffer + 4)) return 0; /* CRCが違う */

//{{2017/05/26追加:pexファイル添付リソースに対応しました。
	//pexファイル添付リソースが有れば…
	if(res_size > 0) {
		//展開したデータの後に、pexファイル添付リソースを追加する。
		if((data_length + res_size) > BUFFER_SIZE) return 0; /* データが大きすぎ */
		memcpy(in_buffer + data_length, res_buffer, res_size);	//展開したデータはout_bufferではなくin_bufferに有るので、out_bufferではなくin_bufferの後に追加する事に注意せよ。
		data_length += res_size;
	}
//}}2017/05/26追加:pexファイル添付リソースに対応しました。

	return 1;
}

/****************************************************************************
 *	CPbmp
 ****************************************************************************/

IMPLEMENT_DYNAMIC(CPbmp, CObject)

/* bhとdataを分けた理由:
 * おでマルは独自ヘッダを使っていて、プログラム内で標準ヘッダに治してるみたいです。
 * いずれロード時にヘッダを生成した場合、ヘッダの直後にデータが来るとは限りません。
 * その時に備えて、分けておきました。
 */
CPbmp::CPbmp(PBMP_FILEHEADER* bh, void* data)
{
//{{2016/12/24追加:VQ圧縮テクスチャ形式(独自拡張)に対応しました。
  if(!bh->bpp && !bh->mask) {
    VQTC_DATAHEADER* dathdr = (VQTC_DATAHEADER*)(bh + 1);
    if(dathdr->sig == ('V'<<8|'Q')) {

	int c, i, j, k, x, y, bits, cols, rows, col, row, pos;
	BITMAPINFOHEADER* bi;
	RGBQUAD* rgb;
	HBITMAP hbm;
	unsigned char *codebook, *code, cell[16];

	memcpy(&m_bh, bh, sizeof(PBMP_FILEHEADER));
	memcpy(&m_dathdr, dathdr, sizeof(VQTC_DATAHEADER));

	m_bmi = (BITMAPINFO*)malloc(sizeof(BITMAPINFO) + sizeof(RGBQUAD) * 256);
	if(m_bmi == NULL) {
		AfxMessageBox("CPBmp::CPBmp(): malloc() failed.", MB_ICONSTOP | MB_OK);
		AfxAbort();
	}
	bi = &m_bmi->bmiHeader;
	rgb = m_bmi->bmiColors;

	memset(bi, 0, sizeof(BITMAPINFOHEADER));
	bi->biSize = sizeof(BITMAPINFOHEADER);
	bi->biWidth = bh->w;
	bi->biHeight = bh->h;
	bi->biPlanes = 1;
	bi->biBitCount = 8;

	memset(rgb, 0, sizeof(RGBQUAD) * 256);
	i = 0;
	while(i < (1 << 2/*bpp*/)) {
		rgb[i].rgbRed   =
		rgb[i].rgbGreen =
		rgb[i].rgbBlue  = 255 - (255 / ((1 << 2/*bpp*/) - 1) * i);
		i++;
	}
	while(i < 256) {
		rgb[i].rgbRed   =   0;
		rgb[i].rgbGreen = 255;
		rgb[i].rgbBlue  =   0;
		i++;
	}

	hbm = CreateDIBSection(NULL, m_bmi, DIB_RGB_COLORS, (void**)&m_bits, NULL, NULL);
	if(hbm == NULL) {
		AfxMessageBox("CPbmp::CPbmp(): CreateDIBSection() failed.\n", MB_ICONSTOP | MB_OK);
		AfxAbort();
	}
	m_bmp.Attach(hbm);

	//符号の数を取得する。
	k = dathdr->k_1 + 1;
	//コードブックのベースアドレスを取得する。
	codebook = (unsigned char*)(dathdr + 1);
	//符号配列のベースアドレスを取得する。
	code = codebook + (((k * 5) + 3) & ~3);
	//符号のビット数を取得する。
//{{2016/12/25変更:clipvqtc.cモジュールにて、符号の数の上限を256⇒65536に変更した事に伴い、当ツールも修正しました。
//	for(bits = 0; bits < 8; bits++) {	//┐
//		if((1 << bits) >= k) { break; }	//├「bits = VqTc_CalculateCodeBits(k);」に相当する処理。
//	}					//┘
//↓2016/12/25変更:clipvqtc.cモジュールにて、符号の数の上限を256⇒65536に変更した事に伴い、当ツールも修正しました。
	for(bits = 0; bits < 16; bits++) {	//┐
		if((1 << bits) >= k) { break; }	//├「bits = VqTc_CalculateCodeBits(k);」に相当する処理。
	}					//┘
//}}2016/12/25変更:clipvqtc.cモジュールにて、符号の数の上限を256⇒65536に変更した事に伴い、当ツールも修正しました。
	//符号配列の列数を取得する。
	cols = (bh->w + 3) >> 2;
	//符号配列の行数を取得する。
	rows = (bh->h + 3) >> 2;
	//各セルについて…
	pos = 0;
	for(row = 0; row < rows; row++) {
		for(col = 0; col < cols; col++) {
			//「i = bitarray_lsb1st_get(code, pos, bits);」に相当する処理。
			i = 0;
			if(bits) {
				const int* p = (const int*)code + (pos >> 5);
				int s1 = pos & 31;
				int s2 = 32 - s1;
				i = (unsigned)p[0] >> s1;
				if(bits > s2) { i |= (unsigned)p[1] << s2; }
				i &= ((unsigned)-1 >> (32 - bits));
			}
			//「VqTc_DecodeCell(&((unsigned char*)codebook)[i * 5], cell);」に相当する処理。
			if((unsigned)i < (unsigned)k) {	//'i≧k'ならばバグ,又は,データ破損
				unsigned char* src = &codebook[i * 5];
				unsigned char* dst = cell;
				int a = *src++;
				for(i = 0; i < 4; i++) {
					x = a & 3;
					x <<= 8;
					x |= *src++;
					for(j = 0; j < 4; j++) {
						c = x % 5;
						x = x / 5;
						if(--c < 0) { c = 4; }
						*dst++ = c;
					}
					a >>= 2;
				}
			}
			for(y = 0; y < 4; y++) {
				int yy = (row * 4) + y;
				for(x = 0; x < 4; x++) {
					int xx = (col * 4) + x;
					c = cell[(y * 4) + x];
					if((xx < bh->w) && (yy < bh->h)) {
						m_bits[((bh->w + 3) & ~3) * (bh->h - 1 - yy) + xx] = c;
					}
				}
			}
			pos += bits;
		}
	}

    } else {
      AfxAbort(); //ここで停止したらCMyDlg::open_file()のバグです。
    }
  } else {
//}}2016/12/24追加:VQ圧縮テクスチャ形式(独自拡張)に対応しました。

	/* かなり手抜きな実装です。 */

	int i, x, y;
	BITMAPINFOHEADER* bi;
	RGBQUAD* rgb;
	HBITMAP hbm;
	unsigned char *p, *m, b;

	memcpy(&m_bh, bh, sizeof(PBMP_FILEHEADER));

	m_bmi = (BITMAPINFO*)malloc(sizeof(BITMAPINFO) + sizeof(RGBQUAD) * 256);
	if(m_bmi == NULL) {
		AfxMessageBox("CPBmp::CPBmp(): malloc() failed.", MB_ICONSTOP | MB_OK);
		AfxAbort();
	}
	bi = &m_bmi->bmiHeader;
	rgb = m_bmi->bmiColors;

	memset(bi, 0, sizeof(BITMAPINFOHEADER));
	bi->biSize = sizeof(BITMAPINFOHEADER);
	bi->biWidth = bh->w;
	bi->biHeight = bh->h;
	bi->biPlanes = 1;
	bi->biBitCount = 8;

	memset(rgb, 0, sizeof(RGBQUAD) * 256);
	i = 0;
	while(i < (1 << bh->bpp)) {
		rgb[i].rgbRed   =
		rgb[i].rgbGreen =
		rgb[i].rgbBlue  = 255 - (255 / ((1 << bh->bpp) - 1) * i);
		i++;
	}
	while(i < 256) {
		rgb[i].rgbRed   =   0;
		rgb[i].rgbGreen = 255;
		rgb[i].rgbBlue  =   0;
		i++;
	}

	hbm = CreateDIBSection(NULL, m_bmi, DIB_RGB_COLORS, (void**)&m_bits, NULL, NULL);
	if(hbm == NULL) {
		AfxMessageBox("CPbmp::CPbmp(): CreateDIBSection() failed.\n", MB_ICONSTOP | MB_OK);
		AfxAbort();
	}
	m_bmp.Attach(hbm);

	p = (unsigned char*)data;
	if(bh->mask) {
		m = p + (bh->w * bh->h) / (8 / bh->bpp);
	} else {
		m = NULL;
	}

	for(y = 0; y < bh->h; y++) {
		for(x = 0; x < bh->w; x++) {
			switch(bh->bpp) {
			case 1: b = (*p >> (7 - (x & 7)) * 1) &  1; if((x & 7) == 7) p++; break;
			case 2: b = (*p >> (3 - (x & 3)) * 2) &  3; if((x & 3) == 3) p++; break;
			case 4: b = (*p >> (1 - (x & 1)) * 4) & 15; if((x & 1) == 1) p++; break;
			default: b = 255; break;
			}
			if(m != NULL) {
				if((*m >> (7 - (x & 7)) & 1) == 0) b = 255;
				if((x & 7) == 7) m++;
			}
			m_bits[bh->w * (bh->h - 1 - y) + x] = b;
		}
	}

//{{2016/12/24追加:VQ圧縮テクスチャ形式(独自拡張)に対応しました。
  }
//}}2016/12/24追加:VQ圧縮テクスチャ形式(独自拡張)に対応しました。
}

CPbmp::~CPbmp()
{
	free(m_bmi);
}

CString
CPbmp::str() const
{
	CString s;
//{{2016/12/24追加:VQ圧縮テクスチャ形式(独自拡張)に対応しました。
  if(!m_bh.bpp && !m_bh.mask) {
    if(m_dathdr.sig == ('V'<<8|'Q')) {
      s.Format("%3d×%3d k=%d", m_bh.w, m_bh.h, m_dathdr.k_1 + 1);
    } else {
      AfxAbort(); //ここで停止したらCPbmp::CPbmp()のバグです。
    }
  } else {
//}}2016/12/24追加:VQ圧縮テクスチャ形式(独自拡張)に対応しました。
	s.Format("%3d×%3d %dbitデータ", m_bh.w, m_bh.h, m_bh.bpp);
	if(m_bh.mask != 0) s += "mask付き";
//{{2016/12/24追加:VQ圧縮テクスチャ形式(独自拡張)に対応しました。
  }
//}}2016/12/24追加:VQ圧縮テクスチャ形式(独自拡張)に対応しました。
	return s;
}

int
CPbmp::save(FILE* fp) const
{
	int retval, n;
	BITMAPFILEHEADER bf;

	memset(&bf, 0, sizeof bf);
	memcpy(&bf.bfType, "BM", 2);
//{{2016/12/24変更:VQ圧縮テクスチャ形式(独自拡張)に対応しました。
//	bf.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256 + m_bh.w * m_bh.h;
//↓2016/12/24変更:VQ圧縮テクスチャ形式(独自拡張)に対応しました。
	bf.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256 + ((m_bh.w + 3) & ~3) * m_bh.h;	//これまではPIECE_BMPの仕様で元から幅が8Nでしたが、VQ圧縮テクスチャの場合は任意の幅で8Nでない可能性も有るため、Windowsビットマップの仕様に合わせるため明示的に幅を4Nにする必要が生じました。
//}}2016/12/24変更:VQ圧縮テクスチャ形式(独自拡張)に対応しました。
	bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256;
	n = sizeof bf;
	retval = fwrite(&bf, 1, n, fp);
	if(retval != n) return 0;

	n = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256;
	retval = fwrite(m_bmi, 1, n, fp);
	if(retval != n) return 0;

//{{2016/12/24変更:VQ圧縮テクスチャ形式(独自拡張)に対応しました。
//	n = m_bh.w * m_bh.h;
//↓2016/12/24変更:VQ圧縮テクスチャ形式(独自拡張)に対応しました。
	n = ((m_bh.w + 3) & ~3) * m_bh.h;	//これまではPIECE_BMPの仕様で元から幅が8Nでしたが、VQ圧縮テクスチャの場合は任意の幅で8Nでない可能性も有るため、Windowsビットマップの仕様に合わせるため明示的に幅を4Nにする必要が生じました。
//}}2016/12/24変更:VQ圧縮テクスチャ形式(独自拡張)に対応しました。
	retval = fwrite(m_bits, 1, n, fp);
	if(retval != n) return 0;

	return 1;
}

/****************************************************************************
 *	CMyApp
 ****************************************************************************/

BEGIN_MESSAGE_MAP(CMyApp, CWinApp)
	//{{AFX_MSG_MAP(CMyApp)
	//}}AFX_MSG
END_MESSAGE_MAP()
CMyApp theApp;

CMyApp::CMyApp()
{
}

BOOL
CMyApp::InitInstance()
{
	CMyDlg dlg;
	m_pMainWnd = &dlg;
	dlg.DoModal();
	return FALSE;
}

/****************************************************************************
 *	CMyDlg
 ****************************************************************************/

BEGIN_MESSAGE_MAP(CMyDlg, CDialog)
	//{{AFX_MSG_MAP(CMyDlg)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_OPEN, OnOpen)
	ON_LBN_SELCHANGE(IDC_ITEMLIST, OnSelchangeItemlist)
	ON_WM_DESTROY()
	ON_BN_CLICKED(IDC_SAVE, OnSave)
	ON_WM_SYSCOMMAND()
	ON_BN_CLICKED(IDC_SAVEALL, OnSaveall)
	ON_WM_DROPFILES()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

CMyDlg::CMyDlg(CWnd* pParent)
: CDialog(CMyDlg::IDD, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CMyDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CMyDlg)
	DDX_Control(pDX, IDC_FILENAME, m_filename);
	DDX_Control(pDX, IDC_ITEMLIST, m_itemlist);
	DDX_Control(pDX, IDC_OPEN, m_open);
	DDX_Control(pDX, IDC_SAVE, m_save);
	DDX_Control(pDX, IDC_SAVEALL, m_saveall);
	DDX_Control(pDX, IDCANCEL, m_cancel);
	//}}AFX_DATA_MAP
}

BOOL
CMyDlg::OnInitDialog()
{
	CMenu* sys_menu;

	CDialog::OnInitDialog();

	SetIcon(m_hIcon, TRUE);
	SetIcon(m_hIcon, FALSE);

	sys_menu = GetSystemMenu(FALSE);
	sys_menu->AppendMenu(MF_SEPARATOR);
	sys_menu->AppendMenu(MF_STRING, 100, "バージョン情報(&A)...");

	DragAcceptFiles();

	m_viewwnd = new CViewWnd(this);
	close_file();

	return TRUE;
}

void
CMyDlg::OnSysCommand(UINT nID, LPARAM lParam) 
{
	switch(nID) {
	case 100:
		{
			AfxMessageBox(VERSION_INFO, MB_ICONINFORMATION | MB_OK);
			return;
		}
	}

	/* 基底クラスへ流します。 */
	CDialog::OnSysCommand(nID, lParam);
}

void
CMyDlg::OnPaint()
{
	if(IsIconic()) {
		CPaintDC dc(this);
		SendMessage(WM_ICONERASEBKGND, (WPARAM)dc.GetSafeHdc());
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;
		dc.DrawIcon(x, y, m_hIcon);
	} else {
		CDialog::OnPaint();
	}
}

HCURSOR
CMyDlg::OnQueryDragIcon()
{
	return (HCURSOR)m_hIcon;
}

void
CMyDlg::OnDestroy()
{
	close_file();
	delete m_viewwnd;

	CDialog::OnDestroy();
}

void
CMyDlg::OnSelchangeItemlist()
{
	int sel;
	CPbmp* pbmp;

	sel = m_itemlist.GetCurSel();
	pbmp = (CPbmp*)m_itemlist.GetItemData(sel);
	m_viewwnd->show(pbmp);
	if(sel != LB_ERR) {
		m_save.EnableWindow(TRUE);
	} else {
		m_save.EnableWindow(FALSE);
	}
}

void
CMyDlg::OnOpen()
{
	int retval;
	CString path;

	CFileDialog dlg(
		TRUE,
		NULL,
		NULL,
		OFN_HIDEREADONLY,
		"圧縮ファイル (*.pex)|*.pex|非圧縮ファイル (*.srf)|*.srf|全てのファイル (*.*)|*.*||",
		this);
	retval = dlg.DoModal();
	if(retval != IDOK) return;
	path = dlg.GetPathName();

	open_file(path);
}

void
CMyDlg::OnDropFiles(HDROP hDropInfo) 
{
	int retval;
	char path[_MAX_PATH];

	/* 複数ドロップされた場合は、一つ目だけをロードします。 */
	retval = DragQueryFile(hDropInfo, 0, path, sizeof path);
	if(retval != 0) {
		open_file(path);
	}
	DragFinish(hDropInfo);
}

void
CMyDlg::OnSave()
{
	save_file();
}

void
CMyDlg::OnSaveall() 
{
	save_all();
}

int
CMyDlg::open_file(const char* path)
{
	int retval, odemaru, i;
	char fname[_MAX_FNAME];
	char ext[_MAX_EXT];
	FILE* fp;
	PBMP_FILEHEADER* bh;
	CPbmp* pbmp;

	/* ファイルを読み込みます。 */
	fp = fopen(path, "rb");
	if(fp == NULL) {
		AfxMessageBox("ファイルが開けません。", MB_ICONEXCLAMATION | MB_OK);
		return 0;
	}
	data_length = fread(in_buffer, 1, BUFFER_SIZE, fp);
	fclose(fp);
	if(data_length <= 0) {
		AfxMessageBox("ファイルが読めません。", MB_ICONEXCLAMATION | MB_OK);
		return 0;
	}

	close_file(); /* ファイルの読み込みに成功したら、現在のデータを破棄！ */

	/* pexファイルなら、展開します。 */
	_splitpath(path, NULL, NULL, fname, ext);
	if(strcmpi(ext, ".pex") == 0) {
		retval = unpex();
		if(!retval) {
			AfxMessageBox("ファイルが展開できません。", MB_ICONEXCLAMATION | MB_OK);
			return 0;
		}
	}

	/* アイコンイメージがあれば、追加します。 */
	for(i = 0; i < sizeof icon_data; i++) {
		if(icon_data[i] != 0) {
			PBMP_FILEHEADER bh; /* アイコンにはヘッダがないので、一時的に作成 */
			memset(&bh, 0, sizeof bh);
			memcpy(&bh.head, "PMBP", 4);
			bh.fsize = sizeof bh + sizeof icon_data;
			bh.bpp = 2;
			bh.mask = 0;
			bh.w = 32;
			bh.h = 32;
			bh.buf_size = sizeof icon_data;
			pbmp = new CPbmp(&bh, icon_data);
			m_pbmplist.AddTail(pbmp);
			retval = m_itemlist.AddString(pbmp->str() + "(アイコン)");
			m_itemlist.SetItemData(retval, (DWORD)pbmp);
			break;
		}
	}

	/* データ本体から、ビットマップデータを探して、追加します。 */
	odemaru = strcmpi(fname, "odemaru") == 0 && strcmpi(ext, ".grp") == 0;
	for(i = 0; i < (int)(data_length - sizeof(PBMP_FILEHEADER)); i++) {
		bh = (PBMP_FILEHEADER*)(in_buffer + i);

		if(strncmp((char*)&bh->head, "PMBP", 4) != 0) continue; /* シグネチャが違う */
		if(odemaru) {
			decode(out_buffer, (BYTE*)bh - 5);
			bh = (PBMP_FILEHEADER*)out_buffer;
		}
		if(bh->fsize < sizeof(PBMP_FILEHEADER)) continue; /* ヘッダより小さい */
		if(!odemaru) {
			if((int)(i + bh->fsize) > data_length) continue; /* ファイル終端を超えてる */
		}
		if(bh->w <= 0 || WIDTH_MAX < bh->w) continue; /* 幅大きすぎ */
		if(bh->h <= 0 || HEIGHT_MAX < bh->h) continue; /* 高さ大きすぎ */
		if(bh->buf_size <= 0) continue; /* データ小さすぎ */
		if(sizeof(PBMP_FILEHEADER) + bh->buf_size > bh->fsize) continue; /* ファイル終端を超えてる */

		pbmp = NULL;
		if((bh->bpp == 1 || bh->bpp == 2 || bh->bpp == 4) &&
		   (bh->mask == 0 || bh->mask == 1)) {
			pbmp = new CPbmp(bh, bh + 1);
//{{2016/12/24追加:VQ圧縮テクスチャ形式(独自拡張)に対応しました。
		} else if(!bh->bpp && !bh->mask) {
			VQTC_DATAHEADER* dathdr = (VQTC_DATAHEADER*)(bh + 1);
			if(dathdr->sig == ('V'<<8|'Q')) {
				pbmp = new CPbmp(bh, bh + 1);
			}
//}}2016/12/24追加:VQ圧縮テクスチャ形式(独自拡張)に対応しました。
		}
		if(pbmp != NULL) {
			m_pbmplist.AddTail(pbmp);
			retval = m_itemlist.AddString(pbmp->str());
			m_itemlist.SetItemData(retval, (DWORD)pbmp);
		}
	}

	/* コントロールの再設定。 */
	m_filename.SetWindowText(path);
	m_itemlist.EnableWindow(TRUE);
	m_itemlist.SetFocus();
	if(m_itemlist.GetCount() != 0) {
		m_itemlist.SetCurSel(0); /* コードで選択したときは */
		OnSelchangeItemlist();   /* SelChangeは来ないから。*/
		m_saveall.EnableWindow(TRUE);
	}

	return 1;
}

void
CMyDlg::close_file()
{
	m_viewwnd->hide();

	m_filename.SetWindowText(NULL);
	m_itemlist.ResetContent();
	m_itemlist.EnableWindow(FALSE);
	m_save.EnableWindow(FALSE);
	m_saveall.EnableWindow(FALSE);
	m_open.SetFocus();

	while(!m_pbmplist.IsEmpty()) delete m_pbmplist.RemoveHead();
	memset(icon_data, 0, sizeof icon_data); /* アイコンなしに戻す */
}

int
CMyDlg::save_file()
{
	int retval, sel;
	CPbmp* pbmp;
	CString path;
	FILE* fp;

	/* 現在の選択アイテムを取得します。 */
	sel = m_itemlist.GetCurSel();
	if(sel == LB_ERR) return 0;
	pbmp = (CPbmp*)m_itemlist.GetItemData(sel);

	/* 保存するファイルを問い合わせます。 */
	CFileDialog dlg(
		FALSE,
		".bmp", /* デフォルト拡張子 */
		NULL,
		OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
		"ビットマップ (*.bmp)|*.bmp|全てのファイル (*.*)|*.*||",
		this);
	retval = dlg.DoModal();
	if(retval != IDOK) return 0;

	path = dlg.GetPathName();
	fp = fopen(path, "wb");
	if(fp == NULL) {
		AfxMessageBox("ファイルが作成できません。", MB_ICONEXCLAMATION | MB_OK);
		return 0;
	}
	retval = pbmp->save(fp);
	fclose(fp);
	if(!retval) {
		AfxMessageBox("ファイルの作成に失敗しました。", MB_ICONEXCLAMATION | MB_OK);
		return 0;
	}

	return 1;
}

int
CMyDlg::save_all()
{
	int retval, n, i;
	POSITION pos;
	CPbmp* pbmp;
	CString path, fname2;
	char path2[_MAX_PATH];
	char drive[_MAX_DRIVE];
	char dir[_MAX_DIR];
	char fname[_MAX_FNAME];
	char ext[_MAX_EXT];
	FILE* fp;
	const char* fmt;

	/* 保存するファイルのプレフィクスを問い合わせます。 */
	CFileDialog dlg(
		FALSE,
		".bmp", /* デフォルト拡張子 */
		NULL,
		OFN_HIDEREADONLY,
		"ビットマップ (*.bmp)|*.bmp|全てのファイル (*.*)|*.*||",
		this);
	retval = dlg.DoModal();
	if(retval != IDOK) return 0;

	/* プレフィクスを分解します。 */
	path = dlg.GetPathName();
	_splitpath(path, drive, dir, fname, ext);

	/* ファイル名の長さが揃うように、サフィックスの書式を選びます。 */
	n = m_pbmplist.GetCount();
	if(n < 10) {
		fmt = "%s%01d";
	} else if(n < 100) {
		fmt = "%s%02d";
	} else if(n < 1000) {
		fmt = "%s%03d";
	} else {
		fmt = "%s%d";
	}

	pos = m_pbmplist.GetHeadPosition();
	i = 0;
	while(pos != NULL) {
		pbmp = m_pbmplist.GetNext(pos);
		fname2.Format(fmt, fname, i++);
		_makepath(path2, drive, dir, fname2, ext);

		fp = fopen(path2, "wb");
		if(fp == NULL) {
			AfxMessageBox(fname2 + " - ファイルが作成できません。", MB_ICONEXCLAMATION | MB_OK);
			return 0;
		}
		retval = pbmp->save(fp);
		fclose(fp);
		if(!retval) {
			AfxMessageBox(fname2 + " - ファイルの作成に失敗しました。", MB_ICONEXCLAMATION | MB_OK);
			return 0;
		}
	}

	return 1;
}

/****************************************************************************
 *	CViewWnd
 ****************************************************************************/

BEGIN_MESSAGE_MAP(CViewWnd, CWnd)
	//{{AFX_MSG_MAP(CViewWnd)
	ON_WM_ERASEBKGND()
	ON_WM_PAINT()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

CViewWnd::CViewWnd(CWnd* parent)
{
	int retval;
	const char* class_name;
	CRect parent_rect;

	/* ウインドウクラスを登録します。
	 * 二回目以降は同じクラス名が返されます。
	 */
	class_name = AfxRegisterWndClass(0);
	if(class_name == NULL) {
		TRACE("CMsgWnd::CMsgWnd: AfxRegisterWndClass failed\n");
		return; /* 進捗表示なし */
	}

	/* ポップアップウインドウを作成します。
	 * まだ表示しません。
	 */
	retval = CreateEx(
		WS_EX_TOOLWINDOW,			/* 拡張スタイル */
		class_name,				/* クラス名 */
		NULL,					/* ウインドウタイトル */
		WS_POPUP | WS_CAPTION | WS_BORDER,	/* ウインドウスタイル */
		0, 0, 100, 100,				/* 位置・サイズ(0,0,0,0だとNCサイズが間違うからダメ) */
		parent->m_hWnd,				/* 親ウインドウ */
		NULL);					/* メニュー */
	if(!retval) {
		TRACE("CViewWnd::CViewWnd(): CraeteEx() failed\n");
		AfxAbort();
	}

	m_pbmp = NULL;
}

CViewWnd::~CViewWnd()
{
	if(m_hWnd == NULL) return;

	DestroyWindow();
}

void
CViewWnd::show(CPbmp* pbmp)
{
	CRect window_rect, client_rect;
	CSize nc_size;

	if(m_hWnd == NULL) return;

	if(pbmp == NULL) {
		hide();
		return;
	}

	GetWindowRect(&window_rect);
	GetClientRect(&client_rect);
	nc_size.cx = window_rect.Width() - client_rect.Width();
	nc_size.cy = window_rect.Height() - client_rect.Height();
	if(m_pbmp == NULL) GetParent()->GetWindowRect(&window_rect);
	MoveWindow(window_rect.left, window_rect.top,
		nc_size.cx + pbmp->m_bh.w, nc_size.cy + pbmp->m_bh.h);

	m_pbmp = pbmp;
	ShowWindow(SW_SHOWNOACTIVATE);
	Invalidate();
}

void
CViewWnd::hide()
{
	if(m_hWnd == NULL) return;

	m_pbmp = NULL;
	ShowWindow(SW_HIDE);
}

BOOL
CViewWnd::OnEraseBkgnd(CDC* pDC) 
{
	return TRUE;
}

void
CViewWnd::OnPaint() 
{
	int retval;
	CPaintDC dc(this);
	CDC memDC;
	CBitmap* old_bmp;

	if(m_pbmp == NULL) return;

	retval = memDC.CreateCompatibleDC(&dc);
	if(!retval) {
		AfxMessageBox("CViewWnd::OnPaint(): CreateCompatibleDC() failed.", MB_ICONSTOP | MB_OK);
		AfxAbort();
	}

	old_bmp = memDC.SelectObject(&m_pbmp->m_bmp);
	if(old_bmp == NULL) {
		AfxMessageBox("CViewWnd::OnPaint(): SelectObject() failed.", MB_ICONSTOP | MB_OK);
		AfxAbort();
	}
	retval = dc.BitBlt(0, 0, m_pbmp->m_bh.w, m_pbmp->m_bh.h, &memDC, 0, 0, SRCCOPY);
	if(!retval) {
		AfxMessageBox("CViewWnd::OnPaint(): BitBlt() failed.", MB_ICONSTOP | MB_OK);
		AfxAbort();
	}
	memDC.SelectObject(old_bmp);
}
