//////////////////////////////////////////////////////////////////////////////////////// // // Nestopia - NES/Famicom emulator written in C++ // // Copyright (C) 2003-2008 Martin Freij // // This file is part of Nestopia. // // Nestopia is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // Nestopia 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 General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Nestopia; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // //////////////////////////////////////////////////////////////////////////////////////// #include #include "NstApplicationInstance.hpp" #include "NstWindowUser.hpp" #include "NstSystemDll.hpp" #include "NstObjectPod.hpp" #include "NstIoFile.hpp" #include "NstIoLog.hpp" #include "NstIoArchive.hpp" #include #include #include #include "../unrar/unrar.h" #include "../7zip/IArchive.h" #if NST_MSVC #pragma comment(lib,"zlibstat") #endif DEFINE_GUID(CLSID_CFormat7z,0x23170F69,0x40C1,0x278A,0x10,0x00,0x00,0x01,0x10,0x07,0x00,0x00); #define ZLIB_WINAPI #define ZCALLBACK WINAPIV #include "../zlib/unzip.h" namespace Nestopia { namespace Io { class Archive::Codec { public: enum Exception { ERR_DLL, ERR_CREATE, ERR_OPEN }; virtual ~Codec() {} virtual bool Build(Items&) = 0; virtual uint Extract(uint,void*,uint) = 0; }; class Archive::UnZip : public Codec { struct Stream { const uchar* const buffer; uint pos; const uint size; explicit Stream(const void* b=NULL,uint s=0) : buffer(static_cast(b)), pos(0), size(s) {} }; Stream stream; void* const handle; bool Build(Items& files) { uint numFiles; { unz_global_info_s info; if ( ::unzGetGlobalInfo( handle, &info ) != UNZ_OK || info.number_entry == 0 || ::unzGoToFirstFile( handle ) != UNZ_OK ) return false; numFiles = info.number_entry; } files.reserve( numFiles ); char path[MAX_PATH+1]; unz_file_info info; uint i=0; do { if (::unzGetCurrentFileInfo( handle, &info, path, MAX_PATH, NULL, 0, NULL, 0 ) == UNZ_OK && info.uncompressed_size && *path) { wchar_t unipath[MAX_PATH+1]; ::MultiByteToWideChar( CP_OEMCP, 0, path, -1, unipath, MAX_PATH ); files.push_back( Item(this,unipath,info.uncompressed_size,i) ); } } while (++i < numFiles && ::unzGoToNextFile( handle ) == UNZ_OK); return !files.empty(); } uint Extract(uint index,void* buffer,uint size) { if (::unzGoToFirstFile( handle ) == UNZ_OK) { for (uint i=0; i < index; ++i) { if (::unzGoToNextFile( handle ) != UNZ_OK) return false; } if (::unzOpenCurrentFile( handle ) == UNZ_OK) { const int extracted = ::unzReadCurrentFile( handle, buffer, size ); ::unzCloseCurrentFile( handle ); if (extracted == int(size)) return size; } } return false; } static void* ZCALLBACK OnOpen(void* opaque,cstring,int mode) { if (mode & ZLIB_FILEFUNC_MODE_WRITE) return NULL; else return opaque; } static ulong ZCALLBACK OnWrite(voidpf,voidpf,const void*,ulong) { return 0; } static int ZCALLBACK OnClose(voidpf,voidpf stream) { return !stream; } static int ZCALLBACK OnTest(voidpf,voidpf stream) { return !stream; } static ulong ZCALLBACK OnStreamRead(voidpf,voidpf object,void* data,ulong length) { if (object && data) { Stream& stream = *static_cast( object ); if (length > stream.size - stream.pos) length = stream.size - stream.pos; std::memcpy( data, stream.buffer + stream.pos, length ); stream.pos += length; return length; } else { return 0; } } static long ZCALLBACK OnStreamTell(voidpf,voidpf object) { if (const Stream* const stream = static_cast( object )) return stream->pos; else return -1; } static long ZCALLBACK OnStreamSeek(voidpf,voidpf object,ulong distance,int origin) { if (Stream* const stream = static_cast( object )) { switch (origin) { case ZLIB_FILEFUNC_SEEK_SET: stream->pos = distance; break; case ZLIB_FILEFUNC_SEEK_CUR: stream->pos += distance; break; case ZLIB_FILEFUNC_SEEK_END: stream->pos = stream->size + distance; break; default: return -1L; } if (stream->pos <= stream->size) return 0L; stream->pos = stream->size; } return -1L; } static ulong ZCALLBACK OnFileRead(voidpf,voidpf file,void* data,ulong size) { if (file && data) { try { return static_cast(file)->ReadSome( data, size ); } catch (...) { } } return 0; } static long ZCALLBACK OnFileTell(voidpf,voidpf file) { if (file) { try { return long (static_cast(file)->Position()); } catch (...) { } } return -1L; } static long ZCALLBACK OnFileSeek(voidpf,voidpf file,ulong distance,int origin) { NST_COMPILE_ASSERT ( ZLIB_FILEFUNC_SEEK_SET == File::BEGIN && ZLIB_FILEFUNC_SEEK_CUR == File::CURRENT && ZLIB_FILEFUNC_SEEK_END == File::END ); if (file && origin < 3) { try { static_cast(file)->Seek( static_cast(origin), distance ); return 0L; } catch (...) { } } return -1L; } static void* Create(const File& file) { zlib_filefunc_def_s def; def.zopen_file = &OnOpen; def.zread_file = &OnFileRead; def.zwrite_file = &OnWrite; def.ztell_file = &OnFileTell; def.zseek_file = &OnFileSeek; def.zclose_file = &OnClose; def.zerror_file = &OnTest; def.opaque = const_cast(static_cast(&file)); void* handle = ::unzOpen2( NULL, &def ); if (handle == NULL) throw ERR_OPEN; return handle; } static void* Create(Stream& stream) { zlib_filefunc_def_s def; def.zopen_file = &OnOpen; def.zread_file = &OnStreamRead; def.zwrite_file = &OnWrite; def.ztell_file = &OnStreamTell; def.zseek_file = &OnStreamSeek; def.zclose_file = &OnClose; def.zerror_file = &OnTest; def.opaque = &stream; void* handle = ::unzOpen2( NULL, &def ); if (handle == NULL) throw ERR_OPEN; return handle; } public: UnZip(const File& file) : handle(Create(file)) {} UnZip(const void* buffer,uint size) : stream(buffer,size), handle(Create(stream)) {} ~UnZip() { ::unzClose( handle ); } }; class Archive::UnRar : public Codec { public: explicit UnRar(const Path&); private: class Dll : System::Dll { typedef HANDLE (PASCAL *OpenArchiveExFunc)(RAROpenArchiveDataEx*); typedef int (PASCAL *CloseArchiveFunc)(HANDLE); typedef int (PASCAL *ReadHeaderFunc)(HANDLE,RARHeaderData*); typedef int (PASCAL *ReadHeaderExFunc)(HANDLE,RARHeaderDataEx*); typedef int (PASCAL *ProcessFileFunc)(HANDLE,int,char*,char*); typedef int (PASCAL *ProcessFileWFunc)(HANDLE,int,wchar_t*,wchar_t*); typedef void (PASCAL *SetProcessDataProcFunc)(HANDLE,PROCESSDATAPROC); public: OpenArchiveExFunc OpenArchiveEx; CloseArchiveFunc CloseArchive; ReadHeaderFunc ReadHeader; ReadHeaderExFunc ReadHeaderEx; ProcessFileFunc ProcessFile; ProcessFileWFunc ProcessFileW; SetProcessDataProcFunc SetProcessDataProc; bool Load() { if (*this) { return true; } else if (System::Dll::Load( L"unrar.dll" )) { if ( NULL != (OpenArchiveEx = Fetch< OpenArchiveExFunc >( "RAROpenArchiveEx" )) && NULL != (CloseArchive = Fetch< CloseArchiveFunc >( "RARCloseArchive" )) && NULL != (ReadHeader = Fetch< ReadHeaderFunc >( "RARReadHeader" )) && NULL != (ReadHeaderEx = Fetch< ReadHeaderExFunc >( "RARReadHeaderEx" )) && NULL != (ProcessFile = Fetch< ProcessFileFunc >( "RARProcessFile" )) && NULL != (ProcessFileW = Fetch< ProcessFileWFunc >( "RARProcessFileW" )) && NULL != (SetProcessDataProc = Fetch< SetProcessDataProcFunc >( "RARSetProcessDataProc" )) ) return true; Unload(); } return false; } }; static Dll dll; void* Open(uint); uint Extract(uint,void*,uint); bool Build(Items&); Path path; }; Archive::UnRar::Dll Archive::UnRar::dll; Archive::UnRar::UnRar(const Path& string) : path(string) { if (!dll.Load()) throw ERR_DLL; } void* Archive::UnRar::Open(uint mode) { Object::Pod data; data.ArcName = NULL; data.ArcNameW = path.Ptr(); data.OpenMode = mode; data.CmtBuf = NULL; return dll.OpenArchiveEx( &data ); } bool Archive::UnRar::Build(Items& files) { if (void* const handle = Open( RAR_OM_LIST )) { try { Object::Pod header; for (uint i=0; dll.ReadHeaderEx( handle, &header ) == 0; ++i) { if (header.UnpSize && *header.FileName) files.push_back( Item(this,header.FileNameW,header.UnpSize,i) ); if (dll.ProcessFile( handle, RAR_SKIP, NULL, NULL )) break; } } catch (...) { files.clear(); } dll.CloseArchive( handle ); return !files.empty(); } return false; } uint Archive::UnRar::Extract(uint index,void* buffer,uint size) { static struct { uchar* buffer; uint pos; uint size; } stream; struct Callback { static int PASCAL OnRead(uchar* input,int size) { if (input && size > 0 && size <= stream.size) { std::memcpy( stream.buffer + stream.pos, input, size ); stream.pos += size; return 1; } else { return 0; } }; }; uint extracted = 0; if (void* const handle = Open( RAR_OM_EXTRACT )) { stream.buffer = static_cast(buffer); stream.pos = 0; stream.size = size; dll.SetProcessDataProc( handle, Callback::OnRead ); try { Object::Pod header; header.CmtBuf = NULL; for (uint i=0; ; ++i) { if (dll.ReadHeader( handle, &header )) break; if (i < index) { if (dll.ProcessFile( handle, RAR_SKIP, NULL, NULL )) break; } else { Path path( Application::Instance::GetTmpPath() ); if (dll.ProcessFileW( handle, RAR_EXTRACT, NULL, path.Ptr() ) == 0) { if (stream.pos == stream.size) extracted = size; if (!File::Delete( path.Ptr() )) Log() << "Archive: warning, couldn't delete temporary RAR file: " << path << '!'; } break; } } } catch (...) { } dll.CloseArchive( handle ); } return extracted; } class Archive::Un7zip : public Codec { public: explicit Un7zip(const File&); Un7zip(const void*,uint); private: ~Un7zip(); IInArchive& archive; IInStream* const stream; static IInArchive* Create(); class InStream : public IInStream, private IStreamGetSize { ulong refCount; HRESULT STDMETHODCALLTYPE QueryInterface(REFGUID,void**) { return E_NOINTERFACE; } HRESULT STDMETHODCALLTYPE GetSize(UInt64* save) { if (save) { *save = size; return S_OK; } else { return E_INVALIDARG; } } ulong STDMETHODCALLTYPE AddRef() { return ++refCount; } ulong STDMETHODCALLTYPE Release() { return --refCount; } protected: const uint size; public: explicit InStream(uint s) : refCount(0), size(s) {} }; class InFileStream : public InStream { const File& file; HRESULT STDMETHODCALLTYPE Read(void* data,UInt32 length,UInt32* save) { if (data != NULL || length == 0) { try { length = file.ReadSome( data, length ); } catch (File::Exception) { return E_FAIL; } if (save) *save = length; return S_OK; } else { return E_INVALIDARG; } } HRESULT STDMETHODCALLTYPE Seek(Int64 offset,UInt32 origin,UInt64* pos) { NST_COMPILE_ASSERT( File::BEGIN == 0 && File::CURRENT == 1 && File::END == 2 ); if (origin < 3) { try { origin = file.Seek( static_cast(origin), int(offset) ); } catch (File::Exception) { return E_FAIL; } if (pos) *pos = origin; return S_OK; } else { return E_INVALIDARG; } } public: explicit InFileStream(const File& f) : InStream(f.Size()), file(f) {} }; class InMemStream : public InStream { const uchar* const buffer; uint pos; HRESULT STDMETHODCALLTYPE Read(void* data,UInt32 length,UInt32* save) { if (data != NULL || length == 0) { if (length > size - pos) length = size - pos; std::memcpy( data, buffer + pos, length ); pos += length; if (save) *save = length; return S_OK; } else { return E_INVALIDARG; } } HRESULT STDMETHODCALLTYPE Seek(Int64 offset,UInt32 origin,UInt64* save) { if (origin < 3) { pos = (origin == 0 ? 0 : origin == 1 ? pos : size) + int(offset); if (pos > size) pos = size; if (save) *save = pos; return S_OK; } else { return E_INVALIDARG; } } public: InMemStream(const void* b,uint s) : InStream(s), buffer(static_cast(b)), pos(0) {} }; class OutStream : public IArchiveExtractCallback { class SeqStream : public ISequentialOutStream { uchar* const output; uint pos; const uint size; ulong refCount; HRESULT STDMETHODCALLTYPE QueryInterface(REFGUID,void**) { return E_NOINTERFACE; } HRESULT STDMETHODCALLTYPE Write(const void* data,UInt32 length,UInt32* save) { if (data != NULL || size == 0) { NST_VERIFY( length <= size - pos ); if (length > size - pos) length = size - pos; std::memcpy( output + pos, data, length ); pos += length; if (save) *save = length; return S_OK; } else { return E_INVALIDARG; } } ulong STDMETHODCALLTYPE AddRef() { return ++refCount; } ulong STDMETHODCALLTYPE Release() { return --refCount; } public: SeqStream(void* d,uint s) : output(static_cast(d)), pos(0), size(s), refCount(0) {} uint Size() const { return pos; } }; SeqStream seqStream; const uint index; ulong refCount; HRESULT STDMETHODCALLTYPE QueryInterface(REFGUID,void**) { return E_NOINTERFACE; } HRESULT STDMETHODCALLTYPE PrepareOperation(Int32) { return S_OK; } HRESULT STDMETHODCALLTYPE SetTotal(UInt64) { return S_OK; } HRESULT STDMETHODCALLTYPE SetCompleted(const UInt64*) { return S_OK; } HRESULT STDMETHODCALLTYPE SetOperationResult(Int32) { return S_OK; } HRESULT STDMETHODCALLTYPE GetStream(UInt32 id,ISequentialOutStream** ptr,Int32 mode) { switch (mode) { case NArchive::NExtract::NAskMode::kExtract: case NArchive::NExtract::NAskMode::kTest: if (id != index || ptr == NULL) return S_FALSE; else *ptr = &seqStream; case NArchive::NExtract::NAskMode::kSkip: return S_OK; default: return E_INVALIDARG; } } ulong STDMETHODCALLTYPE AddRef() { return ++refCount; } ulong STDMETHODCALLTYPE Release() { return --refCount; } public: OutStream(uint index,void* data,uint size) : seqStream(data,size), index(index), refCount(0) {} uint Size() const { return seqStream.Size(); } }; void Open(); void Close(); bool Build(Items&); uint Extract(uint,void*,uint); }; IInArchive* Archive::Un7zip::Create() { static System::Dll dll; if (!dll && !dll.Load( L"7zxa.dll" )) throw ERR_DLL; typedef UINT32 (WINAPI *CreateObjectFunc)(const GUID*,const GUID*,void**); CreateObjectFunc const CreateObject = dll.Fetch("CreateObject"); void* object; if (CreateObject == NULL || FAILED(CreateObject( &CLSID_CFormat7z, &IID_IInArchive, &object ))) throw ERR_CREATE; return static_cast(object); } Archive::Un7zip::Un7zip(const void* data,uint size) : archive(*Create()), stream(new (std::nothrow) InMemStream(data,size)) { Open(); } Archive::Un7zip::Un7zip(const File& file) : archive(*Create()), stream(new (std::nothrow) InFileStream(file)) { Open(); } Archive::Un7zip::~Un7zip() { Close(); } void Archive::Un7zip::Open() { if (stream) { try { if (SUCCEEDED(archive.Open( stream, NULL, NULL ))) return; } catch (...) { } delete stream; } archive.Release(); throw ERR_OPEN; } void Archive::Un7zip::Close() { archive.Close(); delete stream; archive.Release(); } bool Archive::Un7zip::Build(Items& files) { UInt32 numFiles; if (SUCCEEDED(archive.GetNumberOfItems( &numFiles )) && numFiles) { files.reserve( numFiles ); Path path; PROPVARIANT prop; for (uint i=0; i < numFiles; ++i) { prop.vt = VT_EMPTY; if (FAILED(archive.GetProperty( i, kpidSize, &prop )) || prop.vt != VT_UI8 || !prop.uhVal.LowPart || prop.uhVal.HighPart) continue; const uint size = prop.uhVal.LowPart; prop.vt = VT_EMPTY; if (FAILED(archive.GetProperty( i, kpidPath, &prop )) || prop.vt != VT_BSTR || prop.bstrVal == NULL) continue; path = prop.bstrVal; ::VariantClear( reinterpret_cast(&prop) ); if (path.Length()) files.push_back( Item(this,path.Ptr(),size,i) ); } } return false; } uint Archive::Un7zip::Extract(uint index,void* data,uint size) { NST_ASSERT( data ); OutStream outStream( index, data, size ); const UInt32 indices[1] = {index}; if (SUCCEEDED(archive.Extract( indices, sizeof(array(indices)), 0, &outStream )) && outStream.Size() == size) return size; else return 0; } Archive::Archive() : codec(NULL) { } Archive::Archive(const File& file) : codec(NULL) { Open( &file, NULL, 0 ); } Archive::Archive(const void* raw,uint size) : codec(NULL) { Open( NULL, raw, size ); } Archive::~Archive() { Close(); } bool Archive::Open(const File& file) { Close(); return Open( &file, NULL, 0 ); } bool Archive::Open(const void* raw,uint size) { Close(); return Open( NULL, raw, size ); } bool Archive::Open(const File* const file,const void* const raw,uint size) { try { if (raw) { if (size >= 4) { switch (FourCC<>::T(static_cast(raw))) { case FILE_ID_ZIP: codec = new UnZip( raw, size ); break; case FILE_ID_7Z: codec = new Un7zip( raw, size ); break; } } } else switch (file->Peek32()) { case FILE_ID_ZIP: codec = new UnZip( *file ); break; case FILE_ID_7Z: codec = new Un7zip( *file ); break; case FILE_ID_RAR: codec = new UnRar( file->GetName() ); break; } return codec ? codec->Build( files ) : false; } catch (...) { } Close(); return false; } void Archive::Close() { if (codec) { delete codec; codec = NULL; } files.clear(); } inline Archive::Item::Item ( Codec* const c, wcstring const n, const uint s, const uint i ) : codec ( c ), name ( n ), size ( s ), index ( i ) {} uint Archive::Item::Uncompress(void* const data) const { NST_ASSERT( data && codec && size ); return codec->Extract( index, data, size ); } uint Archive::Find(const GenericString name) const { for (uint i=0, n=files.size(); i < n; ++i) { if (files[i].GetName() == name) return i+1; } return NO_FILES; } uint Archive::UserSelect() const { if (files.empty()) { return NO_FILES; } else if (files.size() == 1) { return FIRST_FILE; } else { std::vector names( files.size() ); for (uint i=0, n=names.size(); i < n; ++i) names[i] = files[i].GetName().Ptr(); return Window::User::Choose( IDS_CHOOSE_FILE, IDS_TEXT_ABORT, &names.front(), names.size() ); } } uint Archive::UserSelect(const GenericString* const filter,const uint count) const { if (filter && count) { std::vector names; std::vector indices; for (Items::const_iterator it(files.begin()), end(files.end()); it != end; ++it) { const GenericString extension( it->GetName().Extension() ); for (uint i=0; i < count; ++i) { if (filter[i] == extension) { indices.push_back( it - files.begin() ); names.push_back( it->GetName().Ptr() ); break; } } } if (names.empty()) { return NO_FILES; } else if (names.size() == 1) { return indices.front() + 1; } else { return Window::User::Choose ( IDS_CHOOSE_FILE, IDS_TEXT_ABORT, &names.front(), names.size(), &indices.front() ); } } else { return UserSelect(); } } } }