/*******************************************************************************
  TPI - flexible but useless plug-in framework.
  Copyright (C) 2002-2009 Silky

  This library is free software; you can redistribute it and/or modify it under
  the terms of the GNU Lesser General Public License as published by the Free
  Software Foundation; either version 2.1 of the License, or (at your option)
  any later version.

  This library is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
  for more details.

  You should have received a copy of the GNU Lesser General Public License along
  with this library; if not, write to the Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

  $Id: 7zArc.cpp 367 2010-10-01 12:51:58Z sirakaba $
*******************************************************************************/

//******************************************************************************
//    Includes
//******************************************************************************

#define wxUSE_DYNLIB_CLASS 1
#include "../../common/header/plugin.h"
#include "../../common/header/plugin-extra.h"
#include "../../common/library/library.h"
#include <wx/dynlib.h>
#include "7zArc.h"

#ifdef __LINUX__
#define LIB_NAME wxT("7z.so")
#else
#define LIB_NAME g_hLib.CanonicalizeName(wxT("7z"))
#endif

//******************************************************************************
//    Global varients
//******************************************************************************

wxDynamicLibrary g_hLib;
void * g_fpProc;
TPI_PROC g_prProc;
wxULongLong_t g_nEngineId;

#ifdef __LINUX__
extern int global_use_utf16_conversion;
#endif

//******************************************************************************
//    Inside Functions
//******************************************************************************

int GetFileInformation2(void * _hArchive, TPI_FILEINFO * _fiInfo, wxULongLong_t nIndex)
{
	IInArchive * hArc = (IInArchive *) _hArchive;
	NWindows::NCOM::CPropVariant prop;
	hArc->GetProperty(nIndex, kpidAttrib, & prop);
	_fiInfo->dwAttribute    = prop.vt == VT_EMPTY ? 0 : prop.uintVal;
	hArc->GetProperty(nIndex, kpidEncrypted, & prop);
	if (prop.vt == VT_BOOL && VARIANT_BOOLToBool(prop.boolVal))
	{
		_fiInfo->dwAttribute |= TPI_ATTRIBUTE_ENCRYPTED;
	}
	hArc->GetProperty(nIndex, kpidIsDir, & prop);
	if (prop.vt == VT_BOOL && VARIANT_BOOLToBool(prop.boolVal))
	{
		_fiInfo->dwAttribute |= TPI_ATTRIBUTE_DIRECTORY;
	}
	hArc->GetProperty(nIndex, kpidCRC, & prop);
	_fiInfo->dwCRC32        = prop.ulVal;
	hArc->GetProperty(nIndex, kpidPackSize, & prop);
	_fiInfo->nPackedSize    = prop.vt == VT_EMPTY ? 0 : prop.vt == VT_UI8 ? prop.uhVal.QuadPart : prop.ulVal;
	hArc->GetProperty(nIndex, kpidSize, & prop);
	_fiInfo->nUnpackedSize  = prop.vt == VT_EMPTY ? 0 : prop.vt == VT_UI8 ? prop.uhVal.QuadPart : prop.ulVal;
	unsigned int t;
	hArc->GetProperty(nIndex, kpidMTime, & prop);
	if (prop.vt == VT_FILETIME)
	{
		NWindows::NTime::FileTimeToUnixTime(prop.filetime, t);
		_fiInfo->tmModified.Set((time_t) t);
	}
	hArc->GetProperty(nIndex, kpidCTime, & prop);
	if (prop.vt == VT_FILETIME)
	{
		NWindows::NTime::FileTimeToUnixTime(prop.filetime, t);
		_fiInfo->tmCreate.Set((time_t) t);
	}
	hArc->GetProperty(nIndex, kpidATime, & prop);
	if (prop.vt == VT_FILETIME)
	{
		NWindows::NTime::FileTimeToUnixTime(prop.filetime, t);
		_fiInfo->tmAccess.Set((time_t) t);
	}
	hArc->GetProperty(nIndex, kpidPath, & prop);
	if (prop.vt == VT_BSTR)
	{
		_fiInfo->szStoredName = WC2String(prop.bstrVal);
		_fiInfo->fnFileName     = wxFileName(_fiInfo->szStoredName, wxPATH_DOS);
	}
	else
	{
		hArc->GetProperty(nIndex, kpidExtension, & prop);
		_fiInfo->szStoredName = wxT("data") + (prop.vt == VT_BSTR ? wxT(".") + WC2String(prop.bstrVal) : (wxString) wxEmptyString);
		_fiInfo->fnFileName     = wxFileName(_fiInfo->szStoredName, wxPATH_DOS);
	}
	hArc->GetProperty(nIndex, kpidMethod, & prop);
	if (prop.vt == VT_BSTR)
	{
		_fiInfo->szMethod = WC2String(prop.bstrVal);
	}
	hArc->GetProperty(nIndex, kpidComment, & prop);
	if (prop.vt == VT_BSTR)
	{
		_fiInfo->szComment  = WC2String(prop.bstrVal);
	}
	_fiInfo->nFileId        = nIndex;
	return TPI_ERROR_SUCCESS;
}

int __stdcall GetFormatInformation2(TPI_FORMATINFO * _fiInfo, wxULongLong_t & nIndex)
{
	// Initialization.
	_fiInfo->fArchive           = true;
	_fiInfo->fComment           = false;
	_fiInfo->fEncryptKeyFile    = false;
	_fiInfo->fEncryptPassword   = false;
	_fiInfo->fEncryptHeader     = false;
	_fiInfo->fCompressHeader    = false;
	_fiInfo->fMMOptimize        = false;
	_fiInfo->fMultiVolume       = false;
	_fiInfo->fSFX               = false;
	_fiInfo->fSolid             = false;
	_fiInfo->nCompressLevelMin  = 0;
	_fiInfo->nCompressLevelMax  = 0;
	_fiInfo->nRecoveryRecordMin = 0;
	_fiInfo->nRecoveryRecordMax = 0;
	_fiInfo->szTypeName.Empty();
	_fiInfo->szSuffix.Empty();
	_fiInfo->szEngineName       = LIB_NAME;
	_fiInfo->szTPIName          = wxT("7zArc");
	_fiInfo->eSupportedCommand  = TPI_COMMAND_EXTRACT | TPI_COMMAND_TEST;
	_fiInfo->nTypeId            = nIndex;
	_fiInfo->pCustomInfo        = NULL;
	wxULongLong_t nBase = nIndex++;
	if (nBase >= sizeof(guidList) / sizeof(GUID))
	{
		return TPI_ERROR_S_ENDOFDATA;
	}
	else if (guidList[nBase] == CLSID_CFormatZip)
	{
		_fiInfo->szTypeName          = wxT("7-zip");
		_fiInfo->szSuffix            = wxT("zip");
		_fiInfo->eSupportedCommand  |= TPI_COMMAND_CREATE;// | TPI_COMMAND_ADD;
		_fiInfo->fSFX                = true;
		_fiInfo->fMultiVolume        = true;
		_fiInfo->fEncryptPassword    = true;
		_fiInfo->nCompressLevelMin   = 0;
		_fiInfo->nCompressLevelMax   = 9;
	}
	else if (guidList[nBase] == CLSID_CFormatBZip2)
	{
		_fiInfo->szTypeName          = wxT("BZip2");
		_fiInfo->szSuffix            = wxT("bz2");
		_fiInfo->eSupportedCommand  |= TPI_COMMAND_CREATE;
		_fiInfo->fArchive            = false;
		_fiInfo->nCompressLevelMin   = 1;
		_fiInfo->nCompressLevelMax   = 9;
	}
//	else if (guidList[nBase] == CLSID_CFormatRar)  { _fiInfo->szTypeName = wxT("Rar");     _fiInfo->szSuffix = wxT("rar"); }
#ifdef __LINUX__
	else if (guidList[nBase] == CLSID_CFormatArj)  { _fiInfo->szTypeName = wxT("Arj");     _fiInfo->szSuffix = wxT("arj"); _fiInfo->fEncryptPassword = true; _fiInfo->fSFX = true; }
#endif
	else if (guidList[nBase] == CLSID_CFormatZ)    { _fiInfo->szTypeName = wxT("Z");       _fiInfo->szSuffix = wxT("Z"); _fiInfo->fArchive = false; }
#ifdef __LINUX__
	else if (guidList[nBase] == CLSID_CFormatLzh)  { _fiInfo->szTypeName = wxT("Lzh");     _fiInfo->szSuffix = wxT("lzh;lha;lzs"); _fiInfo->fSFX = true; }
#endif
	else if (guidList[nBase] == CLSID_CFormat7z)
	{
		_fiInfo->szTypeName          = wxT("7z");
		_fiInfo->szSuffix            = wxT("7z");
		_fiInfo->eSupportedCommand  |= TPI_COMMAND_CREATE;// | TPI_COMMAND_ADD;
		_fiInfo->fSFX                = true;
		_fiInfo->fMultiVolume        = true;
		_fiInfo->fSolid              = true;
		_fiInfo->fEncryptPassword    = true;
		_fiInfo->fEncryptHeader      = true;
		_fiInfo->fCompressHeader     = true;
		_fiInfo->nCompressLevelMin   = 0;
		_fiInfo->nCompressLevelMax   = 9;
	}
#ifdef __LINUX__
	else if (guidList[nBase] == CLSID_CFormatCab)  { _fiInfo->szTypeName = wxT("Cab");     _fiInfo->szSuffix = wxT("cab"); _fiInfo->fMultiVolume = true; _fiInfo->fSFX = true; }
#endif
	else if (guidList[nBase] == CLSID_CFormatNsis) { _fiInfo->szTypeName = wxT("NSIS");    _fiInfo->szSuffix = wxT("exe"); _fiInfo->fSFX = true; }
	else if (guidList[nBase] == CLSID_CFormatLzma) { _fiInfo->szTypeName = wxT("LZMA");    _fiInfo->szSuffix = wxT("lzma"); _fiInfo->fArchive = false; }
	else if (guidList[nBase] == CLSID_CFormatLzma86){_fiInfo->szTypeName = wxT("LZMA86");  _fiInfo->szSuffix = wxT("lzma"); _fiInfo->fArchive = false; }
	else if (guidList[nBase] == CLSID_CFormatXz)
	{
		_fiInfo->szTypeName          = wxT("XZ");
		_fiInfo->szSuffix            = wxT("xz");
		_fiInfo->eSupportedCommand  |= TPI_COMMAND_CREATE;
		_fiInfo->fArchive            = false;
		_fiInfo->nCompressLevelMin   = 0;
		_fiInfo->nCompressLevelMax   = 9;
	}
	else if (guidList[nBase] == CLSID_CFormatApm)  { _fiInfo->szTypeName = wxT("Apm");     _fiInfo->szSuffix = wxT("apm"); }
	else if (guidList[nBase] == CLSID_CFormatMslz) { _fiInfo->szTypeName = wxT("MSLZ");    _fiInfo->szSuffix = wxT("xx_"); _fiInfo->fArchive = false; }
	else if (guidList[nBase] == CLSID_CFormatFlv)  { _fiInfo->szTypeName = wxT("Flv");     _fiInfo->szSuffix = wxT("flv"); }
//	else if (guidList[nBase] == CLSID_CFormatSwf)  { _fiInfo->szTypeName = wxT("Swf");     _fiInfo->szSuffix = wxT("swf"); }
	else if (guidList[nBase] == CLSID_CFormatSwfc) { _fiInfo->szTypeName = wxT("Swfc");    _fiInfo->szSuffix = wxT("swf"); }
	else if (guidList[nBase] == CLSID_CFormatNtfs) { _fiInfo->szTypeName = wxT("NTFS");    _fiInfo->szSuffix = wxT("ntfs");}
	else if (guidList[nBase] == CLSID_CFormatFat)  { _fiInfo->szTypeName = wxT("FAT");     _fiInfo->szSuffix = wxT("fat"); }
	else if (guidList[nBase] == CLSID_CFormatMbr)  { _fiInfo->szTypeName = wxT("Mbr");     _fiInfo->szSuffix = wxT("mbr"); }
	else if (guidList[nBase] == CLSID_CFormatVhd)  { _fiInfo->szTypeName = wxT("Vhd");     _fiInfo->szSuffix = wxT("vhd"); }
//	else if (guidList[nBase] == CLSID_CFormatPe)   { _fiInfo->szTypeName = wxT("PE");      _fiInfo->szSuffix = wxT("exe"); _fiInfo->fArchive = false; }
//	else if (guidList[nBase] == CLSID_CFormatElf)  { _fiInfo->szTypeName = wxT("ELF");     _fiInfo->fArchive = false; }
	else if (guidList[nBase] == CLSID_CFormatMachO){ _fiInfo->szTypeName = wxT("Mach-O"); }
	else if (guidList[nBase] == CLSID_CFormatUdf)  { _fiInfo->szTypeName = wxT("Udf");     _fiInfo->szSuffix = wxT("udf"); }
	else if (guidList[nBase] == CLSID_CFormatXar)  { _fiInfo->szTypeName = wxT("Xar");     _fiInfo->szSuffix = wxT("xar"); }
	else if (guidList[nBase] == CLSID_CFormatMub)  { _fiInfo->szTypeName = wxT("Mub");     _fiInfo->szSuffix = wxT("mub"); }
	else if (guidList[nBase] == CLSID_CFormatHfs)  { _fiInfo->szTypeName = wxT("HFS");     _fiInfo->szSuffix = wxT("hfs"); }
	else if (guidList[nBase] == CLSID_CFormatDmg)  { _fiInfo->szTypeName = wxT("DMG");     _fiInfo->szSuffix = wxT("dmg"); }
	else if (guidList[nBase] == CLSID_CFormatCom)  { _fiInfo->szTypeName = wxT("Compound");_fiInfo->szSuffix = wxT("msi"); _fiInfo->fSFX = true; }
	else if (guidList[nBase] == CLSID_CFormatWim)
	{
		_fiInfo->szTypeName          = wxT("WIM");
		_fiInfo->szSuffix            = wxT("wim");
		_fiInfo->eSupportedCommand  |= TPI_COMMAND_CREATE;// | TPI_COMMAND_ADD;
	}
	else if (guidList[nBase] == CLSID_CFormatIso)  { _fiInfo->szTypeName = wxT("ISO");     _fiInfo->szSuffix = wxT("iso"); }
	else if (guidList[nBase] == CLSID_CFormatBkf)  { _fiInfo->szTypeName = wxT("Bkf");     _fiInfo->szSuffix = wxT("bkf"); }
	else if (guidList[nBase] == CLSID_CFormatChm)  { _fiInfo->szTypeName = wxT("Chm");     _fiInfo->szSuffix = wxT("chm"); }
	else if (guidList[nBase] == CLSID_CFormatSplit){ _fiInfo->szTypeName = wxT("Split"); }
#ifdef __LINUX__
	else if (guidList[nBase] == CLSID_CFormatRpm)  { _fiInfo->szTypeName = wxT("RPM");     _fiInfo->szSuffix = wxT("rpm"); _fiInfo->fArchive = false; }
	else if (guidList[nBase] == CLSID_CFormatDeb)  { _fiInfo->szTypeName = wxT("Deb");     _fiInfo->szSuffix = wxT("deb"); }
	else if (guidList[nBase] == CLSID_CFormatCpio) { _fiInfo->szTypeName = wxT("Cpio");    _fiInfo->szSuffix = wxT("cpio");}
	else if (guidList[nBase] == CLSID_CFormatTar)
	{
		_fiInfo->szTypeName          = wxT("TAR");
		_fiInfo->szSuffix            = wxT("tar");
		_fiInfo->fSFX                = true;
		_fiInfo->eSupportedCommand  |= TPI_COMMAND_CREATE;// | TPI_COMMAND_ADD;
	}
#endif
	else if (guidList[nBase] == CLSID_CFormatGZip)
	{
		_fiInfo->szTypeName          = wxT("GZip");
		_fiInfo->szSuffix            = wxT("gz");
		_fiInfo->eSupportedCommand  |= TPI_COMMAND_CREATE;
		_fiInfo->fArchive            = false;
		_fiInfo->nCompressLevelMin   = 1;
		_fiInfo->nCompressLevelMax   = 9;
	}
	else
	{
		return GetFormatInformation2(_fiInfo, nIndex);
	}
	return TPI_ERROR_SUCCESS;
}

//******************************************************************************
//    Callback Wrapper
//******************************************************************************

// CArchiveOpenCallback
class CArchiveOpenCallback: public IArchiveOpenCallback, public IArchiveOpenVolumeCallback, public IArchiveOpenSetSubArchiveName, public ICryptoGetTextPassword, public CMyUnknownImp
{
public:
	MY_UNKNOWN_IMP1(ICryptoGetTextPassword)
	INTERFACE_IArchiveOpenCallback(;)
	INTERFACE_IArchiveOpenVolumeCallback(;)
	STDMETHOD(SetSubArchiveName)(const wchar_t * szName);
	STDMETHOD(CryptoGetTextPassword)(BSTR *password);
	int nErrorCode;
	wxString szSubArchiveName;
	NWindows::NFile::NFind::CFileInfoW fiInfo;

private:
	TPI_PROCESSINFO piInfo;
};

STDMETHODIMP CArchiveOpenCallback::SetTotal(const wxULongLong_t *, const wxULongLong_t *)
{
	return S_OK;
}

STDMETHODIMP CArchiveOpenCallback::SetCompleted(const wxULongLong_t *, const wxULongLong_t *)
{
	return S_OK;
}

STDMETHODIMP CArchiveOpenCallback::GetProperty(PROPID propID, PROPVARIANT * value)
{
	NWindows::NCOM::CPropVariant prop;
	if (! this->szSubArchiveName.IsEmpty())
	{
		switch (propID)
		{
			case kpidName: prop = (wxChar *) this->szSubArchiveName.wchar_str(); break;
		}
	}
	else
	{
		switch (propID)
		{
			case kpidName:  prop = this->fiInfo.Name; break;
			case kpidIsDir: prop = this->fiInfo.IsDir(); break;
			case kpidSize:  prop = this->fiInfo.Size; break;
			case kpidAttrib:prop = (unsigned int) this->fiInfo.Attrib; break;
			case kpidCTime: prop = this->fiInfo.CTime; break;
			case kpidATime: prop = this->fiInfo.ATime; break;
			case kpidMTime: prop = this->fiInfo.MTime; break;
		}
	}
	prop.Detach(value);
	return S_OK;
}

STDMETHODIMP CArchiveOpenCallback::GetStream(const wchar_t * szName, IInStream ** inStream)
{
	if (! this->szSubArchiveName.IsEmpty())
	{
		return S_FALSE;
	}
	* inStream = NULL;
	wxString szFileName = this->piInfo.fiInfo.fnFileName.GetPathWithSep() + szName;
	if (! this->fiInfo.Find(szFileName.c_str()) || fiInfo.IsDir())
	{
		this->nErrorCode = TPI_ERROR_IO_ARC_OPEN;
		return S_FALSE;
	}

	CInFileStream * inFile = new CInFileStream;
	CMyComPtr<IInStream> inStreamTemp = inFile;
	if (! inFile->Open(szFileName.c_str()))
	{
		this->nErrorCode = TPI_ERROR_IO_ARC_OPEN;
		return S_FALSE;
	}
	* inStream = inStreamTemp.Detach();
	return S_OK;
}

STDMETHODIMP CArchiveOpenCallback::SetSubArchiveName(const wchar_t * szName)
{
	this->szSubArchiveName = WC2String(szName);
	return S_OK;
}

STDMETHODIMP CArchiveOpenCallback::CryptoGetTextPassword(BSTR * szPassword)
{
	this->piInfo.eMessage           = TPI_MESSAGE_ASK;
	this->piInfo.eStatus            = TPI_PARAM_PASSWORD;
	return (g_prProc != NULL && g_prProc(TPI_NOTIFY_COMMON, & this->piInfo) == TPI_CALLBACK_CONTINUE) ? ::StringToBstr(this->piInfo.szParam.c_str(), szPassword) : E_ABORT;
}

// CArchiveExtractCallback
class CArchiveExtractCallback: public IArchiveExtractCallback, public ICryptoGetTextPassword, public CMyUnknownImp
{
public:
	MY_UNKNOWN_IMP1(ICryptoGetTextPassword)
	INTERFACE_IArchiveExtractCallback(;)
	STDMETHOD(CryptoGetTextPassword)(BSTR * szPassword);
	CArchiveExtractCallback(CMyComPtr<IInArchive>, TPI_SWITCHES *);

	int nErrorCode;
	TPI_SWITCHES * swInfo;

private:
	int nMode;
	bool fTriedPassword;
	TPI_PROCESSINFO piInfo;
	wxULongLong_t nCurrentPos;
	COutFileStream * _outFileStreamSpec;
	CMyComPtr<ISequentialOutStream> _outFileStream;
	CMyComPtr<IInArchive> hArc;
};

CArchiveExtractCallback::CArchiveExtractCallback(CMyComPtr<IInArchive> hArc, TPI_SWITCHES * swInfo)
{
	this->hArc = hArc;
	this->swInfo = swInfo;
	this->fTriedPassword = false;
}

STDMETHODIMP CArchiveExtractCallback::SetTotal(wxULongLong_t)
{
	return S_OK;
}

STDMETHODIMP CArchiveExtractCallback::SetCompleted(const wxULongLong_t * nProcessed)
{
	this->piInfo.eStatus		    = TPI_STATUS_INPROCESS;
	this->piInfo.nProcessedSize     = * nProcessed - this->nCurrentPos;
	if (g_prProc == NULL || this->piInfo.fiInfo.szStoredName.IsEmpty() || ! this->piInfo.fnDestination.IsOk() || g_prProc(TPI_NOTIFY_COMMON, & this->piInfo) == TPI_CALLBACK_CONTINUE)
	{
		return S_OK;
	}
	else
	{
		this->nErrorCode = TPI_ERROR_D_SKIPPED;
		return E_ABORT;
	}
}

STDMETHODIMP CArchiveExtractCallback::GetStream(unsigned int nIndex, ISequentialOutStream ** outStream, int nExtractMode)
{
	* outStream = 0;
	_outFileStream.Release();

	// 前のファイルの処理の終了を通知。
	this->nCurrentPos += this->piInfo.fiInfo.nUnpackedSize;
	if (this->nCurrentPos != 0)
	{
		this->piInfo.eMessage   = TPI_MESSAGE_STATUS;
		this->piInfo.eStatus    = TPI_STATUS_ENDPROCESS;
		if (g_prProc != NULL && g_prProc(TPI_NOTIFY_COMMON, & this->piInfo) != TPI_CALLBACK_CONTINUE)
		{
			this->nErrorCode = TPI_ERROR_D_SKIPPED;
			return E_ABORT;
		}
	}

	// ファイルを処理するか確認。
	this->piInfo.eMessage   = TPI_MESSAGE_ASK;
	this->piInfo.eStatus    = TPI_PARAM_DEST;
	GetFileInformation2(this->hArc, & this->piInfo.fiInfo, nIndex);
	this->piInfo.fnDestination          = wxFileName(swInfo->fnDestinationDirectory.GetFullPath() + wxFileName::GetPathSeparator() + (swInfo->fStoreDirectoryPathes ? this->piInfo.fiInfo.fnFileName.GetPath() : (wxString) wxEmptyString), this->piInfo.fiInfo.fnFileName.GetFullName());
	if (g_prProc != NULL && g_prProc(TPI_NOTIFY_COMMON, & this->piInfo) != TPI_CALLBACK_CONTINUE)
	{
		this->nErrorCode = TPI_ERROR_D_SKIPPED;
		return E_ABORT;
	}
	if (! piInfo.fnDestination.IsOk())
	{
		return S_OK;
	}

	// ファイルの基本情報を取得。
	this->piInfo.eMessage   = TPI_MESSAGE_STATUS;
	this->piInfo.eStatus    = TPI_STATUS_BEGINPROCESS;
	if (g_prProc != NULL && g_prProc(TPI_NOTIFY_COMMON, & this->piInfo) != TPI_CALLBACK_CONTINUE)
	{
		this->nErrorCode = TPI_ERROR_D_SKIPPED;
		return E_ABORT;
	}

	if (nExtractMode != NArchive::NExtract::NAskMode::kExtract)
	{
		return S_OK;
	}

	// ディレクトリかどうかを判定し、必要なディレクトリを作成。
	if (! ::wxFileName::Mkdir(this->piInfo.fiInfo.dwAttribute & TPI_ATTRIBUTE_DIRECTORY ? this->piInfo.fnDestination.GetFullPath() : this->piInfo.fnDestination.GetPath(), 0777, wxPATH_MKDIR_FULL))
	{
		this->nErrorCode = TPI_ERROR_IO_DIR_OPEN;
		return E_ABORT;
	}
	if (this->piInfo.fiInfo.dwAttribute & TPI_ATTRIBUTE_DIRECTORY)
	{
		return S_OK;
	}

	// ファイルを作成。
	_outFileStreamSpec = new COutFileStream;
	CMyComPtr<ISequentialOutStream> outStreamLoc(_outFileStreamSpec);
	if (! _outFileStreamSpec->Open(this->piInfo.fnDestination.GetFullPath(), CREATE_NEW))
	{
		this->nErrorCode = TPI_ERROR_IO_FILE_OPEN;
		return E_ABORT;
	}

	_outFileStream = outStreamLoc;
	* outStream = outStreamLoc.Detach();
	return S_OK;
}

STDMETHODIMP CArchiveExtractCallback::PrepareOperation(int nExtractMode)
{
	this->nMode = nExtractMode;
	return S_OK;
}

STDMETHODIMP CArchiveExtractCallback::SetOperationResult(int nResult)
{
	switch (nResult)
	{
	case NArchive::NExtract::NOperationResult::kOK:                this->nErrorCode = TPI_ERROR_SUCCESS;         break;
	case NArchive::NExtract::NOperationResult::kUnSupportedMethod: this->nErrorCode = TPI_ERROR_ARC_UNSUPPORTED; break;
	case NArchive::NExtract::NOperationResult::kDataError:         this->nErrorCode = TPI_ERROR_ARC_BROKEN_MISC; break;
	case NArchive::NExtract::NOperationResult::kCRCError:          this->nErrorCode = TPI_ERROR_ARC_BROKEN_SUM;  break;
	default:                                                       this->nErrorCode = TPI_ERROR_UNDEFINED;       break;
	}

	// 更新時刻を記録。
	if (_outFileStream != NULL)
	{
		FILETIME ft;
		NWindows::NTime::UnixTimeToFileTime(this->piInfo.fiInfo.tmModified.GetTicks(), ft);
		_outFileStreamSpec->SetMTime(& ft);
		_outFileStreamSpec->Close();
	}
	_outFileStream.Release();

	// 属性を記録。
	if (this->nMode == NArchive::NExtract::NAskMode::kExtract)
	{
//		NWindows::NFile::NDirectory::MySetFileAttributes(this->piInfo.fiInfo.fnFileName.GetFullPath(), this->piInfo.fiInfo.dwAttribute);
	}

	return S_OK;
}

STDMETHODIMP CArchiveExtractCallback::CryptoGetTextPassword(BSTR * szPassword)
{
	if (this->fTriedPassword || this->swInfo->szPassword.IsEmpty())
	{
		this->piInfo.eMessage           = TPI_MESSAGE_ASK;
		this->piInfo.eStatus            = TPI_PARAM_PASSWORD;
		// 次のファイル名が取得できないので、とりあえず空にしておく。
		this->piInfo.fiInfo.fnFileName.Clear();
		return (g_prProc != NULL && g_prProc(TPI_NOTIFY_COMMON, & this->piInfo) == TPI_CALLBACK_CONTINUE) ? ::StringToBstr(this->piInfo.szParam.c_str(), szPassword) : E_ABORT;
	}
	else
	{
		// 既定のパスワードがある場合はまずそれを試す。
		this->fTriedPassword = true;
		return ::StringToBstr(this->swInfo->szPassword, szPassword);
	}
}

// CArchiveUpdateCallback
class CArchiveUpdateCallback: public IArchiveUpdateCallback2, public ICryptoGetTextPassword2, public CMyUnknownImp
{
public:
	MY_UNKNOWN_IMP2(IArchiveUpdateCallback2, ICryptoGetTextPassword2)
	INTERFACE_IArchiveUpdateCallback2(;)
	STDMETHOD(CryptoGetTextPassword2)(int * nPasswordIsDefined, BSTR * szPassword);
	CArchiveUpdateCallback(TPI_SWITCHES *, CObjectVector<NWindows::NFile::NFind::CFileInfoW> *);

	const CObjectVector<NWindows::NFile::NFind::CFileInfoW> * fiItems;
	int nErrorCode;

private:
	wxULongLong_t nCurrentPos;
	TPI_PROCESSINFO piInfo;
	TPI_SWITCHES * swInfo;
};

CArchiveUpdateCallback::CArchiveUpdateCallback(TPI_SWITCHES * swInfo, CObjectVector<NWindows::NFile::NFind::CFileInfoW> * fiItems)
{
	this->swInfo = swInfo;
	this->fiItems = fiItems;
}

STDMETHODIMP CArchiveUpdateCallback::SetTotal(wxULongLong_t)
{
	return S_OK;
}

STDMETHODIMP CArchiveUpdateCallback::SetCompleted(const wxULongLong_t * nProcessed)
{
	this->piInfo.eStatus            = TPI_STATUS_INPROCESS;
	this->piInfo.nProcessedSize     = * nProcessed - this->nCurrentPos;
	if (g_prProc == NULL || this->piInfo.fiInfo.szStoredName.IsEmpty() || g_prProc(TPI_NOTIFY_COMMON, & this->piInfo) == TPI_CALLBACK_CONTINUE)
	{
		return S_OK;
	}
	else
	{
		this->nErrorCode = TPI_ERROR_D_SKIPPED;
		return E_ABORT;
	}
}

STDMETHODIMP CArchiveUpdateCallback::GetUpdateItemInfo(unsigned int /* index */, int *newData, int *newProperties, unsigned int *indexInArchive)
{
	if (newData != NULL)
	{
		* newData = 1;
	}
	if (newProperties != NULL)
	{
		* newProperties = 1;
	}
	if (indexInArchive != NULL)
	{
		* indexInArchive = (unsigned) -1;
	}
	return S_OK;
}

STDMETHODIMP CArchiveUpdateCallback::GetProperty(unsigned int nIndex, PROPID propID, PROPVARIANT * value)
{
	NWindows::NCOM::CPropVariant prop;
	if (propID == kpidIsAnti)
	{
		prop = false;
		prop.Detach(value);
		return S_OK;
	}

	const NWindows::NFile::NFind::CFileInfoW fiItem = (* this->fiItems)[nIndex];
	switch (propID)
	{
		case kpidPath:  prop = fiItem.Name;  break;
		case kpidIsDir: prop = (unsigned int) fiItem.Attrib & TPI_ATTRIBUTE_DIRECTORY ? true : false; break;
		case kpidSize:  prop = fiItem.Size;  break;
		case kpidAttrib:prop = (unsigned int) fiItem.Attrib;break;
		case kpidCTime: prop = fiItem.CTime; break;
		case kpidATime: prop = fiItem.ATime; break;
		case kpidMTime: prop = fiItem.MTime; break;
	}
	prop.Detach(value);
	return S_OK;
}

STDMETHODIMP CArchiveUpdateCallback::GetStream(unsigned int nIndex, ISequentialInStream ** inStream)
{
	// 前のファイルの処理の終了を通知。
	this->nCurrentPos += this->piInfo.fiInfo.nUnpackedSize;
	this->piInfo.eMessage               = TPI_MESSAGE_STATUS;
	if (this->nCurrentPos != 0)
	{
		this->piInfo.eStatus            = TPI_STATUS_ENDPROCESS;
		if (g_prProc != NULL && g_prProc(TPI_NOTIFY_COMMON, & this->piInfo) != TPI_CALLBACK_CONTINUE)
		{
			this->nErrorCode = TPI_ERROR_D_SKIPPED;
			return E_ABORT;
		}
	}

	// ファイルの基本情報を取得。
	NWindows::NCOM::CPropVariant prop;
	this->GetProperty(nIndex, kpidAttrib, & prop);
	this->piInfo.fiInfo.dwAttribute     = prop.uintVal;
	this->GetProperty(nIndex, kpidSize, & prop);
	this->piInfo.fiInfo.nUnpackedSize   = prop.vt == VT_UI8 ? prop.uhVal.QuadPart : prop.ulVal;
	unsigned int t;
	this->GetProperty(nIndex, kpidMTime, & prop);
	NWindows::NTime::FileTimeToUnixTime(prop.filetime, t);
	this->piInfo.fiInfo.tmModified.Set((time_t) t);
	this->GetProperty(nIndex, kpidCTime, & prop);
	NWindows::NTime::FileTimeToUnixTime(prop.filetime, t);
	this->piInfo.fiInfo.tmCreate.Set((time_t) t);
	this->GetProperty(nIndex, kpidATime, & prop);
	NWindows::NTime::FileTimeToUnixTime(prop.filetime, t);
	this->piInfo.fiInfo.tmAccess.Set((time_t) t);
	this->GetProperty(nIndex, kpidPath, & prop);
	this->piInfo.fiInfo.szStoredName = WC2String(prop.bstrVal);
	this->piInfo.fiInfo.nFileId         = nIndex;
	this->piInfo.fiInfo.fnFileName      = wxFileName(this->piInfo.fiInfo.szStoredName);
	this->piInfo.fiInfo.fnFileName.Normalize(wxPATH_NORM_DOTS | wxPATH_NORM_ABSOLUTE | wxPATH_NORM_LONG, this->swInfo->fnDestinationDirectory.GetFullPath());
	this->piInfo.eStatus                = TPI_STATUS_BEGINPROCESS;
	this->piInfo.fnDestination          = wxFileName(this->piInfo.fiInfo.szStoredName);

	// コールバック関数に送信。
	if (g_prProc != NULL && g_prProc(TPI_NOTIFY_COMMON, & this->piInfo) != TPI_CALLBACK_CONTINUE)
	{
		this->nErrorCode = TPI_ERROR_D_SKIPPED;
		return E_ABORT;
	}

	if (this->piInfo.fiInfo.dwAttribute & TPI_ATTRIBUTE_DIRECTORY)
	{
		return S_OK;
	}

	CInFileStream * inStreamSpec = new CInFileStream;
	CMyComPtr<ISequentialInStream> inStreamLoc(inStreamSpec);
	if (! inStreamSpec->Open(this->piInfo.fiInfo.fnFileName.GetFullPath().c_str()))
	{
		this->nErrorCode = TPI_ERROR_IO_FILE_OPEN;
		return S_FALSE;
	}

	* inStream = inStreamLoc.Detach();
	return S_OK;
}

STDMETHODIMP CArchiveUpdateCallback::SetOperationResult(int nResult)
{
	switch (nResult)
	{
	case NArchive::NUpdate::NOperationResult::kOK:                 this->nErrorCode = TPI_ERROR_SUCCESS;         break;
	case NArchive::NUpdate::NOperationResult::kError:
	default:                                                       this->nErrorCode = TPI_ERROR_UNDEFINED;       break;
	}
	return S_OK;
}

STDMETHODIMP CArchiveUpdateCallback::GetVolumeSize(unsigned int, wxULongLong_t * nSplitSize)
{
	* nSplitSize = this->swInfo->nSplitSize;
	return S_OK;
}

STDMETHODIMP CArchiveUpdateCallback::GetVolumeStream(unsigned int nIndex, ISequentialOutStream ** volumeStream)
{
	COutFileStream * streamSpec = new COutFileStream;
	CMyComPtr<ISequentialOutStream> streamLoc(streamSpec);
	if (! streamSpec->Create(this->piInfo.fiInfo.fnFileName.GetFullPath() + wxString::Format(wxT(".%03d"), nIndex + 1), false))
	{
		return TPI_ERROR_IO_ARC_OPEN;
	}
	* volumeStream = streamLoc.Detach();
	return S_OK;
}

STDMETHODIMP CArchiveUpdateCallback::CryptoGetTextPassword2(int * nPasswordIsDefined, BSTR * szPassword)
{
	* nPasswordIsDefined = ! this->swInfo->szPassword.IsEmpty();
	return ::StringToBstr(this->swInfo->szPassword.c_str(), szPassword);
}

//******************************************************************************
//    Functions
//******************************************************************************

#ifdef __cplusplus
extern "C"
{
#endif

int __stdcall GetPluginInformation
(
	unsigned int _uInfoId,
	wxULongLong_t,
	void * _pPtr
)
{
	if (_pPtr == NULL)
	{
		return TPI_ERROR_D_PARAMETER;
	}
	switch (_uInfoId)
	{
	case TPI_INFO_VERSION_MAJOR:
	case TPI_INFO_VERSION_MINOR:
		* (int *) _pPtr = 0;
		break;
	case TPI_INFO_VERSION_API:
		* (int *) _pPtr = 2;
		break;
	default:
		return TPI_ERROR_D_UNSUPPORTED;
	}
	return TPI_ERROR_SUCCESS;
}

int __stdcall GetFormatInformation(TPI_FORMATINFO * _fiInfo, bool _bFirst)
{
	static wxULongLong_t s_nTypeId;
	if (_bFirst)
	{
		s_nTypeId = 0;
	}
	return GetFormatInformation2(_fiInfo, s_nTypeId);
}

int __stdcall LoadPlugin
(
	const wxString & _szArcName,
	wxULongLong_t _nTypeId
)
{
	::RemoveCwdFromSearchPath();
#ifdef __LINUX__
	global_use_utf16_conversion = 1;
#endif
	g_hLib.Load(LIB_NAME);
	if (! g_hLib.IsLoaded())
	{
		g_hLib.Unload();
		return TPI_ERROR_U_LOAD_LIBRARY;
	}

	if (! g_hLib.HasSymbol(wxT("CreateObject")))
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}
	g_fpProc = g_hLib.GetSymbol(wxT("CreateObject"));
	if (! g_fpProc)
	{
		return TPI_ERROR_U_USE_LIBRARY;
	}

	if (! ::wxFileExists(_szArcName))
	{
		g_nEngineId = _nTypeId;
	}

	return TPI_ERROR_SUCCESS;
}

int __stdcall FreePlugin
(
	void * // _pReserved
)
{
	g_hLib.Unload();
	return TPI_ERROR_SUCCESS;
}

int __stdcall OpenArchive
(
	const wxString & _szArcName,
	void * * _hArchive,
	wxULongLong_t * _nFileCount
)
{
	// ***.tar.xxxは不便なので弾く。
	wxString s = _szArcName.BeforeLast(wxT('.'));
	if (s.Find(wxT('.')) != wxNOT_FOUND && s.AfterLast(wxT('.')) == wxT("tar"))
	{
		return TPI_ERROR_IO_ARC_OPEN;
	}

	// 書庫形式を認識。
	IInArchive * hArc;
	for (wxULongLong_t i = 0; i < sizeof(guidList) / sizeof(GUID); )
	{
		if (((unsigned int (__stdcall *)(const GUID *, const GUID *, void * *)) g_fpProc)(& guidList[i], & IID_IInArchive, (void **) & hArc) != S_OK)
		{
			i++;
			continue;
		}

		// 書庫を開く。
		CInFileStream * fileSpec = new CInFileStream;
		CMyComPtr<IInStream> file = fileSpec;
		if (! fileSpec->Open(_szArcName.c_str()))
		{
			return TPI_ERROR_IO_ARC_OPEN;
		}

		// 形式の情報を取得。
		g_nEngineId = i;
		TPI_FORMATINFO fiInfo;
		GetFormatInformation2(& fiInfo, i);

		// 書庫として開く。
		CArchiveOpenCallback * openCallbackSpec = new CArchiveOpenCallback;
		CMyComPtr<IArchiveOpenCallback> openCallback(openCallbackSpec);
		wxULongLong_t nMax = 65536;
/*
		// SFXを完全に検出するには全体をロードする必要がある。
		if (fiInfo.fSFX)
		{
			fileSpec->GetSize(& nMax);
		}
*/
		if (hArc->Open(file, & nMax, openCallback) == S_OK)
		{
			if (_nFileCount != NULL)
			{
				unsigned int n;
				hArc->GetNumberOfItems(& n);
				* _nFileCount = n;
			}
			* _hArchive = hArc;
			return TPI_ERROR_SUCCESS;
		}
	}
	return TPI_ERROR_IO_ARC_OPEN;
}

int __stdcall CloseArchive
(
	void * _hArchive
)
{
    IInArchive * hArc = (IInArchive *) _hArchive;
	hArc->Close();
	hArc->Release();
	return TPI_ERROR_SUCCESS;
}

int __stdcall GetFileInformation
(
	void * _hArchive,
	TPI_FILEINFO * _fiInfo,
	bool _bFirst
)
{
	static unsigned int s_uFileId, s_uFileCount;
	if (_bFirst)
	{
		s_uFileId = 0;
		((IInArchive *) _hArchive)->GetNumberOfItems(& s_uFileCount);
	}

	if (s_uFileId >= s_uFileCount)
	{
		return TPI_ERROR_S_ENDOFDATA;
	}

	return GetFileInformation2(_hArchive, _fiInfo, s_uFileId++);
}

int __stdcall GetArchiveInformation
(
	void * _hArchive,
	TPI_ARCHIVEINFO * _aiInfo
)
{
    IInArchive * hArc = (IInArchive *) _hArchive;
	NWindows::NCOM::CPropVariant prop;
	hArc->GetArchiveProperty(kpidSolid, & prop);
	_aiInfo->fSolid         = VARIANT_BOOLToBool(prop.boolVal);
	hArc->GetArchiveProperty(kpidComment, & prop);
	if (prop.vt == VT_BSTR)
	{
		_aiInfo->szComment  = WC2String(prop.bstrVal);
	}
	wxULongLong_t n = g_nEngineId;
	GetFormatInformation2(& _aiInfo->fiInfo, n);
	return TPI_ERROR_SUCCESS;
}

int __stdcall Command
(
	wxULongLong_t _eCommand,
	TPI_SWITCHES * _swInfo,
	const wxString & _szArcName,
	const wxArrayString & _szFiles
)
{
	// コマンドを実行。
	int nErrorCode;
	switch (_eCommand)
	{
	case TPI_COMMAND_EXTRACT:
	case TPI_COMMAND_TEST:
	{
		// 開きなおす。
		IInArchive * hArc;
		nErrorCode = OpenArchive(_szArcName, (void **) & hArc);
		if (nErrorCode != TPI_ERROR_SUCCESS)
		{
			return nErrorCode;
		}

		// ファイル名からインデックスを取得。
		TPI_FILEINFO fiInfo;
		CRecordVector<unsigned int> nIndexes;
		if (GetFileInformation(hArc, & fiInfo, true) == TPI_ERROR_SUCCESS)
		{
			do
			{
				if (_szFiles.Index(fiInfo.szStoredName) != wxNOT_FOUND)
				{
					nIndexes.Add(fiInfo.nFileId);
				}
			}
			while (GetFileInformation(hArc, & fiInfo) == TPI_ERROR_SUCCESS);
		}

		CArchiveExtractCallback * extractCallbackSpec = new CArchiveExtractCallback(hArc,_swInfo);
		CMyComPtr<IArchiveExtractCallback> extractCallback(extractCallbackSpec);
		hArc->Extract(& nIndexes.Front(), nIndexes.Size(), _eCommand == TPI_COMMAND_TEST, extractCallback);
		nErrorCode = extractCallbackSpec->nErrorCode;
		CloseArchive(hArc);
		break;
	}
	case TPI_COMMAND_CREATE:
//	case TPI_COMMAND_ADD:
	{
		// 入力リストを作成。
		CObjectVector<NWindows::NFile::NFind::CFileInfoW> fiItems;
		for (unsigned int i = 0; i < _szFiles.GetCount(); i++)
		{
			NWindows::NFile::NFind::CFileInfoW fi;
			wxFileName fn(_szFiles[i]);
			fn.Normalize(wxPATH_NORM_DOTS | wxPATH_NORM_ABSOLUTE | wxPATH_NORM_LONG, _swInfo->fnDestinationDirectory.GetFullPath());
			if (! fi.Find(fn.GetFullPath().c_str()))
			{
				return TPI_ERROR_IO_FILE_ACCESS;
			}
			fiItems.Add(fi);
		}

		// 書庫を作成。
		COutFileStream * outFileStreamSpec = new COutFileStream;
		CMyComPtr<IOutStream> outFileStream = outFileStreamSpec;
		if (! outFileStreamSpec->Create(_szArcName.c_str(), false))
		{
			return TPI_ERROR_IO_ARC_OPEN;
		}

		// エンジンを読み込み。
		IOutArchive * hArc;
		if (((unsigned int (__stdcall *)(const GUID *, const GUID *, void **)) g_fpProc)(& guidList[g_nEngineId], & IID_IOutArchive, (void **) & hArc) != S_OK)
		{
			return TPI_ERROR_ARC_UNSUPPORTED;
		}

		// 形式情報を取得。
		TPI_FORMATINFO fiInfo;
		wxULongLong_t nIndex = g_nEngineId;
		GetFormatInformation2(& fiInfo, nIndex);

		// パラメータを設定。
		ISetProperties * setProp;
		if (hArc->QueryInterface(IID_ISetProperties, (void **) & setProp) == S_OK)
		{
			CRecordVector<const wchar_t *> szProps;
			NWindows::NCOM::CPropVariant propValues[15];
			wxString szProp;
			// 圧縮レベル。
			if (fiInfo.nCompressLevelMin != fiInfo.nCompressLevelMax)
			{
				szProp = wxString::Format(wxT("x%d"), _swInfo->nCompressLevel);
				szProps.Add(szProp.c_str());
			}
			// Solid圧縮。
			if (fiInfo.fSolid)
			{
				propValues[szProps.Size()] = _swInfo->fSolid;
				szProps.Add(wxT("s"));
			}
			// ヘッダ圧縮。
			if (fiInfo.fCompressHeader)
			{
				propValues[szProps.Size()] = _swInfo->fCompressHeader;
				szProps.Add(wxT("hc"));
			}
			// ヘッダ暗号化。
			if (fiInfo.fEncryptHeader)
			{
				propValues[szProps.Size()] = _swInfo->fEncryptHeader;
				szProps.Add(wxT("he"));
			}

//			szProp = wxString::Format(wxT("m"));
//			propValues[szProps.Size()] = L"Deflate";
//			szProps.Add(szProp.c_str());

//			szProp = wxString::Format(wxT("fb%d"), );
//			szProps.Add(szProp.c_str());

			setProp->SetProperties(& szProps.Front(), propValues, szProps.Size());
		}

		// 更新処理を実行。
		CArchiveUpdateCallback * updateCallbackSpec = new CArchiveUpdateCallback(_swInfo, & fiItems);
		CMyComPtr<IArchiveUpdateCallback2> updateCallback(updateCallbackSpec);
		hArc->UpdateItems(outFileStream, fiItems.Size(), updateCallback);
		nErrorCode = updateCallbackSpec->nErrorCode;
		break;
	}
	default:
		nErrorCode = TPI_ERROR_D_UNSUPPORTED;
	}
	return nErrorCode;
}

int __stdcall SetCallbackProc
(
	TPI_PROC _prArcProc
)
{
	// ポインタを保存。
	if (_prArcProc == NULL)
	{
		return TPI_ERROR_D_PARAMETER;
	}
	g_prProc = * _prArcProc;

	return TPI_ERROR_SUCCESS;
}

#ifdef __cplusplus
}
#endif
