diff --git a/Archive.def b/Archive.def index 145516d..ca3d4e3 100644 --- a/Archive.def +++ b/Archive.def @@ -5,6 +5,7 @@ EXPORTS GetNumberOfFormats PRIVATE GetHandlerProperty2 PRIVATE GetIsArc PRIVATE + GetFormatLevelMask PRIVATE SetCodecs PRIVATE diff --git a/ArchiveExports.cpp b/ArchiveExports.cpp index d62dea9..80ced5c 100644 --- a/ArchiveExports.cpp +++ b/ArchiveExports.cpp @@ -143,4 +143,13 @@ STDAPI GetIsArc( UInt32 formatIndex, Func_IsArc* isArc ) return E_INVALIDARG; *isArc = g_Arcs[formatIndex]->IsArc; return S_OK; +} + +STDAPI GetFormatLevelMask(UInt32 formatIndex, UInt32 *mask) +{ + *mask = -1; + if ( formatIndex >= g_NumArcs ) + return E_INVALIDARG; + *mask = g_Arcs[formatIndex]->LevelMask; + return S_OK; } \ No newline at end of file diff --git a/Handler.cpp b/Handler.cpp index da52fbd..1a7dbd7 100644 --- a/Handler.cpp +++ b/Handler.cpp @@ -18,15 +18,16 @@ class CHandler : public IInArchive, public IInArchiveGetStream, // reading - public IOutArchive, public ISetProperties, // writing + public IOutArchive, public ISetProperties, public IMultiVolumeOutArchive, // writing public CMyUnknownImp { - MY_UNKNOWN_IMP4( IInArchive, IInArchiveGetStream, IOutArchive, ISetProperties ) + MY_UNKNOWN_IMP5( IInArchive, IInArchiveGetStream, IOutArchive, ISetProperties, IMultiVolumeOutArchive ) INTERFACE_IInArchive( override ) INTERFACE_IOutArchive( override ) STDMETHOD( GetStream )( UInt32 index, ISequentialInStream** stream ) override; STDMETHOD( SetProperties )( const wchar_t* const* names, const PROPVARIANT* values, UInt32 numProps ) override; + STDMETHOD( GetMultiArchiveNameFmt )( PROPVARIANT* nameMod, PROPVARIANT* prefix, PROPVARIANT* postfix, BOOL* numberAfterExt, UInt32* digitCount ) override; private: libvpk::VPKSet vpk; @@ -79,6 +80,8 @@ STDMETHODIMP CHandler::Open( IInStream* inStream, const UInt64* maxCheckStartPos auto end = name.ReverseFind_Dot(); if ( end > 3 && name[end - 1] == L'r' && name[end - 2] == L'i' && name[end - 3] == L'd' ) name.DeleteFrom( end - 3 ); + else + name += '_'; for ( int i = 0; i < largestId + 1; i++ ) { @@ -296,7 +299,7 @@ STDMETHODIMP CHandler::Extract( const UInt32* indices, UInt32 numItems, Int32 te opRes = NArchive::NExtract::NOperationResult::kDataError; else { - RINOK( copyCoder->Code( inStream, outStream, NULL, NULL, progress ) ); + RINOK( copyCoder->Code( inStream, outStream, nullptr, nullptr, progress ) ); opRes = outStream->IsFinishedOK() ? NArchive::NExtract::NOperationResult::kOK : NArchive::NExtract::NOperationResult::kDataError; } outStream->ReleaseStream(); @@ -308,9 +311,12 @@ STDMETHODIMP CHandler::Extract( const UInt32* indices, UInt32 numItems, Int32 te STDMETHODIMP CHandler::GetStream( UInt32 index, ISequentialInStream** stream ) { - *stream = 0; + *stream = nullptr; const auto& i = vpk.files().container().at( index ).second; + if ( missingFiles && !paks[i.archiveIdx] ) + return HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ); + if ( !i.fileLength ) { *stream = new CEmptyInStream(); @@ -542,7 +548,7 @@ class CCacheOutStream : public IOutStream, public CMyUnknownImp return MyWrite( kCacheBlockSize - ( (size_t)_cachedPos & ( kCacheBlockSize - 1 ) ) ); } public: - CCacheOutStream() : _cache( NULL ) {} + CCacheOutStream() : _cache( nullptr ) {} ~CCacheOutStream(); bool Allocate(); HRESULT Init( ISequentialOutStream* seqStream, IOutStream* stream ); @@ -559,7 +565,7 @@ bool CCacheOutStream::Allocate() { if ( !_cache ) _cache = ( Byte* )::MidAlloc( kCacheSize ); - return _cache != NULL; + return _cache != nullptr; } HRESULT CCacheOutStream::Init( ISequentialOutStream* seqStream, IOutStream* stream ) @@ -619,7 +625,7 @@ CCacheOutStream::~CCacheOutStream() if ( _virtSize != _phySize ) _stream->SetSize( _virtSize ); if ( _virtPos != _phyPos ) - _stream->Seek( _virtPos, STREAM_SEEK_SET, NULL ); + _stream->Seek( _virtPos, STREAM_SEEK_SET, nullptr ); } ::MidFree( _cache ); } @@ -746,7 +752,12 @@ STDMETHODIMP CCacheOutStream::SetSize( UInt64 newSize ) static constexpr std::string_view bannedExts[] = { - ".bat", ".cmd", ".com", ".dll", ".exe", ".msi", ".rar", ".reg", ".zip", ".so" + ".bat", ".cmd", ".com", ".dll", ".exe", ".msi", ".rar", ".reg", ".zip", ".tar", ".gz", ".tgz", ".so", ".vpk" +}; + +static constexpr std::string_view standardDirs[] = +{ + "cfg", "expressions", "maps", "materials", "media", "missions", "models", "panorama", "particles", "resource", "scenes", "scripts", "sound" }; class VpkWriter @@ -758,21 +769,19 @@ class VpkWriter { internalPath.MakeLower_Ascii(); const std::string_view path{ internalPath.Ptr(), internalPath.Len() }; - const auto spl = path.rfind( '/' ); - for ( size_t i = 0; i < ARRAYSIZE( bannedExts ); i++ ) - { - if ( path.find( bannedExts[i] ) != std::string::npos ) + for ( size_t i = 0; i < ARRAYSIZE( bannedExts ); ++i ) + if ( path.ends_with( bannedExts[i] ) ) return E_FAIL; - } + const auto spl = path.rfind( '/' ); auto& dir = resolvePath( root, spl == std::string::npos ? std::string_view{} : path.substr( 0, spl ), {} ); const auto& name = path.substr( spl + 1 ); - dir.files.emplace( name, Dir::File{ size, stream } ); + dir.files.emplace( name, Dir::File{ size, 0, stream } ); return S_OK; } - HRESULT write_pak( ISequentialOutStream* outStream, IArchiveUpdateCallback* callback ) + HRESULT writePak( ISequentialOutStream* outStream, IArchiveUpdateCallback2* callback, UInt64 volSize ) { CMyComPtr progress = new CLocalProgress; progress->Init( callback, true ); @@ -787,16 +796,38 @@ class VpkWriter return E_OUTOFMEMORY; RINOK( stream->Init( outStream, stream_ ) ); + if ( root.files.empty() && root.folders.size() == 1 ) + { + auto& realRoot = root.folders.modify_container().at( 0 ); + for ( size_t i = 0; i < ARRAYSIZE( standardDirs ); ++i ) + { + if ( realRoot.first == standardDirs[i] ) + goto dont; + } + + chobo::flat_map rootFolders = std::move( realRoot.second.folders ); + chobo::flat_map rootFiles = std::move( realRoot.second.files ); + + root.folders.clear(); + + root.folders = std::move( rootFolders ); + root.files = std::move( rootFiles ); + + recurseRemoveRootName( root ); + } + dont: + UInt32 size = 0, treeSize = 0; FilesByExt sorted_files; - sort_files( sorted_files, root, size, treeSize ); + sortFiles( sorted_files, root, size, treeSize ); for ( auto& [ext, dirs] : sorted_files ) treeSize += static_cast( dirs.size() + 1 ); // add null after each all files in each directory + null after last dir ++treeSize; // null after last ext - RINOK( write_header( stream, size, treeSize ) ); + RINOK( writeHeader( stream, size, treeSize ) ); + UInt16 curPak = volSize > 0 ? 0 : 0x7FFF; UInt32 currentOffset = 0; for ( auto& [ext, dirs] : sorted_files ) { @@ -811,14 +842,20 @@ class VpkWriter progress->InSize = progress->OutSize = pos; RINOK( progress->SetCur() ); RINOK( write( stream, file ) ); - RINOK( write( stream, calc_crc( *data ) ) ); + RINOK( write( stream, calcCrc( *data ) ) ); RINOK( write( stream, 0 ) ); - RINOK( write( stream, 0x7FFF ) ); + RINOK( write( stream, curPak ) ); RINOK( write( stream, currentOffset ) ); RINOK( write( stream, data->size ) ); RINOK( write( stream, 0xFFFF ) ); currentOffset += data->size; + data->pak = curPak; + if ( volSize && currentOffset > volSize ) + { + currentOffset = 0; + ++curPak; + } } RINOK( write( stream, {} ) ); } @@ -828,18 +865,48 @@ class VpkWriter RINOK( stream->Seek( treeSize + sizeof( libvpk::meta::VPKHeader ), STREAM_SEEK_SET, nullptr ) ); - for ( auto& [ext, dirs] : sorted_files ) + if ( !volSize ) { - for ( auto& [dir, files] : dirs ) + for ( auto& [ext, dirs] : sorted_files ) { - for ( auto& [file, data] : files ) + for ( auto& [dir, files] : dirs ) { - UInt64 pos; - RINOK( stream->Seek( 0, STREAM_SEEK_CUR, &pos ) ); - progress->InSize = progress->OutSize = pos; - RINOK( progress->SetCur() ); - RINOK( copyCoder->Code( data->stream, stream, NULL, NULL, progress ) ); - data->stream->Release(); + for ( auto& [file, data] : files ) + { + UInt64 pos; + RINOK( stream->Seek( 0, STREAM_SEEK_CUR, &pos ) ); + progress->InSize = progress->OutSize = pos; + RINOK( progress->SetCur() ); + RINOK( copyCoder->Code( data->stream, stream, nullptr, nullptr, progress ) ); + data->stream.Release(); + } + } + } + } + else + { + CMyComPtr pakStream; + UInt16 lastPak = 0xFFFF; + UInt32 written = 0; + for ( auto& [ext, dirs] : sorted_files ) + { + for ( auto& [dir, files] : dirs ) + { + for ( auto& [file, data] : files ) + { + if ( lastPak != data->pak ) + { + lastPak = data->pak; + pakStream.Release(); + RINOK( callback->GetVolumeStream( static_cast( lastPak - 1 ), &pakStream ) ); + } + + progress->InSize = progress->OutSize = written; + written += data->size; + RINOK( progress->SetCur() ); + RINOK( copyCoder->Code( data->stream, pakStream, nullptr, nullptr, progress ) ); + data->stream.Release(); + } } } } @@ -861,7 +928,7 @@ class VpkWriter return stream->Write( string.c_str(), static_cast( string.size() + 1 ), nullptr ); } - static HRESULT write_header( IOutStream* stream, UInt32 size, UInt32 treeSize ) + static HRESULT writeHeader( IOutStream* stream, UInt32 size, UInt32 treeSize ) { libvpk::meta::VPKHeader header; header.signature = libvpk::meta::VPKHeader::ValidSignature; @@ -877,6 +944,7 @@ class VpkWriter struct File { UInt32 size = 0; + UInt16 pak = 0; CMyComPtr stream; }; @@ -902,7 +970,16 @@ class VpkWriter return resolvePath( res.first->second, sep == std::string::npos ? std::string_view{} : path.substr( sep + 1 ), res.first->second.name ); } - static void sort_files( FilesByExt& files, Dir& root, UInt32& size, UInt32& treeSize ) + static void recurseRemoveRootName( Dir& root ) + { + for ( auto& f : root.folders ) + { + f.second.name = f.second.name.substr( f.second.name.find( '/' ) + 1 ); + recurseRemoveRootName( f.second ); + } + } + + static void sortFiles( FilesByExt& files, Dir& root, UInt32& size, UInt32& treeSize ) { constexpr const auto vpkMetaSize = 3 * sizeof( Int32 ) + 3 * sizeof( Int16 ); @@ -922,10 +999,10 @@ class VpkWriter } for ( auto& folder : root.folders ) - sort_files( files, folder.second, size, treeSize ); + sortFiles( files, folder.second, size, treeSize ); } - static CRC32_t calc_crc( Dir::File& file ) + static CRC32_t calcCrc( Dir::File& file ) { CRC32_t crc; CRC32_Init( crc ); @@ -949,7 +1026,7 @@ class VpkWriter static const wchar_t kOsPathSepar = WCHAR_PATH_SEPARATOR; static const wchar_t kUnixPathSepar = L'/'; -void ReplaceSlashes_OsToUnix( UString& name ) +static void ReplaceSlashes_OsToUnix( UString& name ) { name.Replace( kOsPathSepar, kUnixPathSepar ); } @@ -964,6 +1041,12 @@ STDMETHODIMP CHandler::UpdateItems( ISequentialOutStream* outStream, UInt32 numI if ( !callback ) return E_FAIL; + CMyComPtr callback2; + RINOK( callback->QueryInterface( IID_IArchiveUpdateCallback2, (void**)&callback2 ) ); + UInt64 volSize = 0; + callback2->GetVolumeSize( 0, &volSize ); + + UInt64 totalSize = 0; VpkWriter writer; for ( UInt32 i = 0; i < numItems; i++ ) { @@ -987,6 +1070,10 @@ STDMETHODIMP CHandler::UpdateItems( ISequentialOutStream* outStream, UInt32 numI return E_INVALIDARG; UInt64 size = prop.uhVal.QuadPart; + totalSize += size; + if ( !volSize && totalSize > INT32_MAX ) // 2GB max non-chunked + return E_OUTOFMEMORY; + CMyComPtr fileInStream; RINOK( callback->GetStream( i, &fileInStream ) ); if ( !fileInStream ) @@ -1000,7 +1087,7 @@ STDMETHODIMP CHandler::UpdateItems( ISequentialOutStream* outStream, UInt32 numI RINOK( writer.addItem( UnicodeStringToMultiByte( name ), static_cast( size ), inStream ) ); } - RINOK( writer.write_pak( outStream, callback ) ); + RINOK( writer.writePak( outStream, callback2, volSize ) ); RINOK( callback->SetOperationResult( NArchive::NExtract::NOperationResult::kOK ) ); @@ -1013,6 +1100,22 @@ STDMETHODIMP CHandler::SetProperties( const wchar_t* const* names, const PROPVAR return S_OK; } +STDMETHODIMP CHandler::GetMultiArchiveNameFmt( PROPVARIANT* nameMod, PROPVARIANT* prefix, PROPVARIANT* postfix, BOOL* numberAfterExt, UInt32* digitCount ) +{ + NWindows::NCOM::CPropVariant prop; + prop = "_"; + prop.Detach( prefix ); + UString name = nameMod->bstrVal; + auto end = name.Len(); + if ( end > 4 && name[end - 1] == L'r' && name[end - 2] == L'i' && name[end - 3] == L'd' && name[end - 4] == L'_' ) + name.DeleteFrom( end - 4 ); + prop = name; + prop.Detach( nameMod ); + *numberAfterExt = FALSE; + *digitCount = 3; + return S_OK; +} + static constexpr const Byte k_Signature[] = { @@ -1037,5 +1140,5 @@ REGISTER_ARC_IO( k_Signature, 0, NArcInfoFlags::kMultiSignature | NArcInfoFlags::kUseGlobalOffset | NArcInfoFlags::kPureStartOpen, - IsArc_Vpk + IsArc_Vpk, 1 ) \ No newline at end of file diff --git a/IArchive.h b/IArchive.h index f384b0e..1677551 100644 --- a/IArchive.h +++ b/IArchive.h @@ -487,6 +487,11 @@ ARCHIVE_INTERFACE(IOutArchive, 0xA0) INTERFACE_IOutArchive(PURE) }; +ARCHIVE_INTERFACE(IMultiVolumeOutArchive, 0xFF) +{ + STDMETHOD(GetMultiArchiveNameFmt)(PROPVARIANT* nameMod, PROPVARIANT* prefix, PROPVARIANT* postfix, BOOL* numberAfterExt, UInt32* digitCount) PURE; +}; + /* ISetProperties::SetProperties() diff --git a/RegisterArc.h b/RegisterArc.h index af5626c..e7f87b4 100644 --- a/RegisterArc.h +++ b/RegisterArc.h @@ -11,6 +11,7 @@ struct CArcInfo Byte Id; Byte SignatureSize; UInt16 SignatureOffset; + UInt32 LevelMask; const Byte *Signature; const char *Name; @@ -39,39 +40,39 @@ void RegisterArc(const CArcInfo *arcInfo) throw(); #define IMP_CreateArcOut static IOutArchive *CreateArcOut() { return new CHandler(); } #endif -#define REGISTER_ARC_V(n, e, ae, id, sigSize, sig, offs, flags, crIn, crOut, isArc) \ - static const CArcInfo g_ArcInfo = { flags, id, sigSize, offs, sig, n, e, ae, crIn, crOut, isArc } ; \ +#define REGISTER_ARC_V(n, e, ae, id, sigSize, sig, offs, flags, crIn, crOut, isArc, mask) \ + static const CArcInfo g_ArcInfo = { flags, id, sigSize, offs, mask, sig, n, e, ae, crIn, crOut, isArc } ; \ -#define REGISTER_ARC_R(n, e, ae, id, sigSize, sig, offs, flags, crIn, crOut, isArc) \ - REGISTER_ARC_V(n, e, ae, id, sigSize, sig, offs, flags, crIn, crOut, isArc) \ +#define REGISTER_ARC_R(n, e, ae, id, sigSize, sig, offs, flags, crIn, crOut, isArc, mask) \ + REGISTER_ARC_V(n, e, ae, id, sigSize, sig, offs, flags, crIn, crOut, isArc, mask) \ struct CRegisterArc { CRegisterArc() { RegisterArc(&g_ArcInfo); }}; \ static CRegisterArc g_RegisterArc; -#define REGISTER_ARC_I_CLS(cls, n, e, ae, id, sig, offs, flags, isArc) \ +#define REGISTER_ARC_I_CLS(cls, n, e, ae, id, sig, offs, flags, isArc, mask) \ IMP_CreateArcIn_2(cls) \ - REGISTER_ARC_R(n, e, ae, id, ARRAYSIZE(sig), sig, offs, flags, CreateArc, NULL, isArc) + REGISTER_ARC_R(n, e, ae, id, ARRAYSIZE(sig), sig, offs, flags, CreateArc, NULL, isArc, mask) -#define REGISTER_ARC_I_CLS_NO_SIG(cls, n, e, ae, id, offs, flags, isArc) \ +#define REGISTER_ARC_I_CLS_NO_SIG(cls, n, e, ae, id, offs, flags, isArc, mask) \ IMP_CreateArcIn_2(cls) \ - REGISTER_ARC_R(n, e, ae, id, 0, NULL, offs, flags, CreateArc, NULL, isArc) + REGISTER_ARC_R(n, e, ae, id, 0, NULL, offs, flags, CreateArc, NULL, isArc, mask) -#define REGISTER_ARC_I(n, e, ae, id, sig, offs, flags, isArc) \ - REGISTER_ARC_I_CLS(CHandler(), n, e, ae, id, sig, offs, flags, isArc) +#define REGISTER_ARC_I(n, e, ae, id, sig, offs, flags, isArc, mask) \ + REGISTER_ARC_I_CLS(CHandler(), n, e, ae, id, sig, offs, flags, isArc, mask) -#define REGISTER_ARC_I_NO_SIG(n, e, ae, id, offs, flags, isArc) \ - REGISTER_ARC_I_CLS_NO_SIG(CHandler(), n, e, ae, id, offs, flags, isArc) +#define REGISTER_ARC_I_NO_SIG(n, e, ae, id, offs, flags, isArc, mask) \ + REGISTER_ARC_I_CLS_NO_SIG(CHandler(), n, e, ae, id, offs, flags, isArc, mask) -#define REGISTER_ARC_IO(n, e, ae, id, sig, offs, flags, isArc) \ +#define REGISTER_ARC_IO(n, e, ae, id, sig, offs, flags, isArc, mask) \ IMP_CreateArcIn \ IMP_CreateArcOut \ - REGISTER_ARC_R(n, e, ae, id, ARRAYSIZE(sig), sig, offs, flags, CreateArc, CreateArcOut, isArc) + REGISTER_ARC_R(n, e, ae, id, ARRAYSIZE(sig), sig, offs, flags, CreateArc, CreateArcOut, isArc, mask) -#define REGISTER_ARC_IO_DECREMENT_SIG(n, e, ae, id, sig, offs, flags, isArc) \ +#define REGISTER_ARC_IO_DECREMENT_SIG(n, e, ae, id, sig, offs, flags, isArc, mask) \ IMP_CreateArcIn \ IMP_CreateArcOut \ - REGISTER_ARC_V(n, e, ae, id, ARRAYSIZE(sig), sig, offs, flags, CreateArc, CreateArcOut, isArc) \ + REGISTER_ARC_V(n, e, ae, id, ARRAYSIZE(sig), sig, offs, flags, CreateArc, CreateArcOut, isArc, mask) \ struct CRegisterArcDecSig { CRegisterArcDecSig() { sig[0]--; RegisterArc(&g_ArcInfo); }}; \ static CRegisterArcDecSig g_RegisterArc; diff --git a/VpkHandler.vcxproj b/VpkHandler.vcxproj index 5cc836a..3c339a8 100644 --- a/VpkHandler.vcxproj +++ b/VpkHandler.vcxproj @@ -95,7 +95,7 @@ NotUsing pch.h MultiThreadedDebug - stdcpp17 + stdcpplatest Windows @@ -115,7 +115,7 @@ NotUsing pch.h MultiThreaded - stdcpp17 + stdcpplatest None @@ -136,7 +136,7 @@ NotUsing pch.h MultiThreadedDebug - stdcpp17 + stdcpplatest Windows @@ -156,7 +156,7 @@ NotUsing pch.h MultiThreaded - stdcpp17 + stdcpplatest None diff --git a/flat_map.hpp b/flat_map.hpp index 837cb38..e5f9afe 100644 --- a/flat_map.hpp +++ b/flat_map.hpp @@ -169,7 +169,7 @@ class flat_map m_container = x.m_container; return *this; } - flat_map& operator=(flat_map&& x) + flat_map& operator=(flat_map&& x) noexcept { m_cmp = std::move(x.m_cmp); m_container = std::move(x.m_container);