From bbb90344a401495ac0045c871d1ff58dd568c8b3 Mon Sep 17 00:00:00 2001 From: Dedmen Miller Date: Mon, 22 Jul 2024 20:20:48 +0200 Subject: [PATCH] Added Thumbnail provider for PAA files (outside of PBO) --- src/ClassFactory.ixx | 12 +- src/PaaThumbnailHandler.ixx | 228 ++++++++++++++++++++++++++++++++++-- src/dllmain.cpp | 16 ++- src/util/ComRef.ixx | 9 ++ src/util/DebugLogger.ixx | 1 + src/util/Tracy.cpp | 5 + 6 files changed, 251 insertions(+), 20 deletions(-) diff --git a/src/ClassFactory.ixx b/src/ClassFactory.ixx index b8063fe..76d5c18 100644 --- a/src/ClassFactory.ixx +++ b/src/ClassFactory.ixx @@ -1,6 +1,7 @@ module; #include #include +#include #include "PboFolder.hpp" #include "PboDataObject.hpp" @@ -14,6 +15,7 @@ export module ClassFactory; import ComRef; import Tracy; +import PaaThumbnailProvider; extern HINSTANCE g_hInst; @@ -85,14 +87,16 @@ public: return S_OK; } + if (IsEqualIID(riid, IID_IThumbnailProvider)) + { + ComRef::CreateForReturn(ppvObject); + return S_OK; + } + if (!IsEqualIID(riid, IID_IUnknown) && !IsEqualIID(riid, IID_IContextMenu) && !IsEqualIID(riid, IID_IShellPropSheetExt)) __debugbreak(); - - - - // creates the namespace's main class auto pShellExt = ComRef::Create(); if (!pShellExt) diff --git a/src/PaaThumbnailHandler.ixx b/src/PaaThumbnailHandler.ixx index a5628b5..ef213ea 100644 --- a/src/PaaThumbnailHandler.ixx +++ b/src/PaaThumbnailHandler.ixx @@ -1,4 +1,5 @@ module; +#define NOMINMAX #include #include "DebugLogger.hpp" #include "PboFileDirectory.hpp" @@ -16,21 +17,171 @@ import Util; import PboLib; import MipMapTool; +class IStreamBufWrapper : public std::streambuf { + std::vector buffer; + IStream* source; + size_t totalSize = 0; + // Position in source file, of the character after the last character that's currently in our buffer + size_t bufferEndFilePos{ 0 }; +public: + IStreamBufWrapper(IStream* source) : buffer(4096), source(source){ + char* start = &buffer.front(); + setg(start, start, start); + + STATSTG stats {}; + source->Stat(&stats, STATFLAG_NONAME); + totalSize = stats.cbSize.QuadPart; + } + + int underflow() override { + if (gptr() < egptr()) // buffer not exhausted + return traits_type::to_int_type(*gptr()); + + LARGE_INTEGER seekOffs; + seekOffs.QuadPart = bufferEndFilePos; + source->Seek(seekOffs, STREAM_SEEK_SET, nullptr); + + ULONG bytesRead = 0; + source->Read(buffer.data(), buffer.size(), &bytesRead); + bufferEndFilePos += bytesRead; + + setg(&buffer.front(), &buffer.front(), &buffer.front() + bytesRead); + + return std::char_traits::to_int_type(*this->gptr()); + } + int64_t xsgetn(char* _Ptr, int64_t _Count) override { + // get _Count characters from stream + const int64_t _Start_count = _Count; + + while (_Count) { + size_t dataLeft = egptr() - gptr(); + if (dataLeft == 0) { + + LARGE_INTEGER seekOffs; + seekOffs.QuadPart = bufferEndFilePos; + source->Seek(seekOffs, STREAM_SEEK_SET, nullptr); + + ULONG bytesRead = 0; + source->Read(buffer.data(), buffer.size(), &bytesRead); + bufferEndFilePos += bytesRead; + setg(&buffer.front(), &buffer.front(), &buffer.front() + bytesRead); + + dataLeft = std::min((size_t)bytesRead, (size_t)_Count); + } + else + dataLeft = std::min(dataLeft, (size_t)_Count); + + std::copy(gptr(), gptr() + dataLeft, _Ptr); + _Ptr += dataLeft; + _Count -= dataLeft; + gbump(dataLeft); + } + + return (_Start_count - _Count); + } + pos_type seekoff(off_type offs, std::ios_base::seekdir dir, std::ios_base::openmode mode) override { + switch (dir) { + case std::ios_base::beg: { + //#TODO negative offs is error + + + size_t dataLeft = egptr() - gptr(); + auto bufferOffset = gptr() - &buffer.front(); //Where we currently are inside the buffer + auto bufferStartInFile = bufferEndFilePos - (dataLeft + bufferOffset); //at which offset in the PboEntry file our buffer starts + + //offset is still inside buffer + if (bufferStartInFile <= offs && bufferEndFilePos > offs) { + auto curFilePos = (bufferEndFilePos - dataLeft); + + int64_t offsetToCurPos = offs - static_cast(curFilePos); + gbump(offsetToCurPos); //Jump inside buffer till we find offs + return offs; + } + + //We are outside of buffer. Just reset and exit + bufferEndFilePos = offs; + setg(&buffer.front(), &buffer.front(), &buffer.front()); //no data available + return bufferEndFilePos; + } + break; + case std::ios_base::cur: { + size_t dataLeft = egptr() - gptr(); + auto curFilePos = (bufferEndFilePos - dataLeft); + + if (offs == 0) return curFilePos; + + if (dataLeft == 0) { + bufferEndFilePos += offs; + return bufferEndFilePos; + } + + + if (offs > 0 && dataLeft > offs) { // offset is still inside buffer + gbump(offs); + return curFilePos + offs; + } + if (offs > 0) { //offset is outside of buffer + bufferEndFilePos = curFilePos + offs; + setg(&buffer.front(), &buffer.front(), &buffer.front()); //no data available + return bufferEndFilePos; + } + + if (offs < 0) { + + auto bufferOffset = gptr() - &buffer.front(); //Where we currently are inside the buffer + if (bufferOffset >= -offs) {//offset is still in buffer + gbump(offs); + return bufferOffset + offs; + } + + bufferEndFilePos = curFilePos + offs; + setg(&buffer.front(), &buffer.front(), &buffer.front()); //no data available + return bufferEndFilePos; + } + } + break; + case std::ios_base::end: + //#TODO positive offs is error + bufferEndFilePos = totalSize + offs; + setg(&buffer.front(), &buffer.front(), &buffer.front()); //no data available + return bufferEndFilePos; + break; + } + return -1; //#TODO this is error + } + pos_type seekpos(pos_type offs, std::ios_base::openmode mode) override { + return seekoff(offs, std::ios_base::beg, mode); + } + std::streamsize showmanyc() override { + //How many characters are left in current buffer + size_t dataInBuffer = egptr() - gptr(); + ULARGE_INTEGER sourceFilePos; + source->Seek(LARGE_INTEGER{}, STREAM_SEEK_CUR, &sourceFilePos); + size_t dataInSource = totalSize - sourceFilePos.QuadPart; + return dataInBuffer + dataInSource; + } +}; export class PaaThumbnailProvider : GlobalRefCounted, public RefCountedCOM< PaaThumbnailProvider, IInitializeWithStream, + IInitializeWithFile, IThumbnailProvider > { - PboSubFile imageFile; - std::shared_ptr pboFile; - HWND hwnd; + struct PboFileContent { + PboSubFile imageFile; + std::shared_ptr pboFile; + }; + std::variant, std::filesystem::path> contentSource; public: - PaaThumbnailProvider(PboSubFile imageFile, std::shared_ptr mainFolder, HWND hwnd) : imageFile(imageFile), pboFile(std::move(mainFolder)), hwnd(hwnd) { + PaaThumbnailProvider(PboSubFile imageFile, std::shared_ptr mainFolder, HWND hwnd) : contentSource(PboFileContent{ std::move(imageFile), std::move(mainFolder) }) { + + } + PaaThumbnailProvider() : contentSource() { } @@ -50,21 +201,74 @@ public: return(E_NOINTERFACE); } } - HRESULT Initialize(IStream* pstream, DWORD grfMode) override { - Util::TryDebugBreak(); - return E_NOINTERFACE; + + HRESULT Initialize(LPCWSTR pszFilePath, DWORD grfMode) override { + contentSource = std::filesystem::path(pszFilePath); + return S_OK; } - HRESULT GetThumbnail(UINT cx, HBITMAP* phbmp, WTS_ALPHATYPE* pdwAlpha) override - { + HRESULT Initialize(IStream* pStream, DWORD grfMode) override { + ComRef myStream; + pStream->QueryInterface(IID_IStream, myStream.AsQueryInterfaceTarget()); + contentSource = std::move(myStream); + return S_OK; + } - std::ifstream pboInputStream(pboFile->GetRootFile()->diskPath, std::ios::in | std::ios::binary); + auto LoadTextureFromPbo(uint16_t maxSize, const PboFileContent& arg) { + std::ifstream pboInputStream(arg.pboFile->GetRootFile()->diskPath, std::ios::in | std::ios::binary); PboReader pboReader(pboInputStream); - PboEntry ent{ "", imageFile.filesize, imageFile.dataSize, imageFile.startOffset, PboEntryPackingMethod::none }; // PAA could be compressed, but I've never seen one + PboEntry ent{ "", arg.imageFile.filesize, arg.imageFile.dataSize, arg.imageFile.startOffset, PboEntryPackingMethod::none }; // PAA could be compressed, but I've never seen one PboEntryBuffer fileBuffer(pboReader, ent); std::istream sourceStream(&fileBuffer); - auto [tex, width, height] = MipMapTool::LoadRGBATexture(sourceStream, cx); + return MipMapTool::LoadRGBATexture(sourceStream, maxSize); + } + + auto LoadTextureFromStream(uint16_t maxSize, ComRef& arg) + { + IStreamBufWrapper wrapper(arg); + std::istream sourceStream(&wrapper); + + return MipMapTool::LoadRGBATexture(sourceStream, maxSize); + } + + auto LoadTextureFromFile(uint16_t maxSize, std::filesystem::path arg) + { + std::ifstream sourceStream(arg); + return MipMapTool::LoadRGBATexture(sourceStream, maxSize); + } + + + auto LoadTexture(uint16_t maxSize) + { + if (contentSource.index() == 0) + return LoadTextureFromPbo(maxSize, std::get(contentSource)); + if (contentSource.index() == 1) + return LoadTextureFromStream(maxSize, std::get>(contentSource)); + if (contentSource.index() == 2) + return LoadTextureFromFile(maxSize, std::get(contentSource)); + else + Util::TryDebugBreak(); + + // Garbage dummy, invalid + return LoadTextureFromStream(maxSize, std::get>(contentSource)); + + // Internal compiler error :) + //return std::visit([this, maxSize](auto&& arg) + // { + // using T = std::decay_t; + // if constexpr (std::is_same_v) + // return LoadTextureFromPbo(maxSize, arg); + // else if constexpr (std::is_same_v>) + // return LoadTextureFromStream(maxSize, arg); + // else + // static_assert(false, "non-exhaustive visitor!"); + // }, contentSource); + } + + HRESULT GetThumbnail(UINT cx, HBITMAP* phbmp, WTS_ALPHATYPE* pdwAlpha) override + { + auto [tex, width, height] = LoadTexture(cx); // Input data is RGBA, we need ARGB std::span x(reinterpret_cast(tex.data()), tex.size() / sizeof(uint32_t)); diff --git a/src/dllmain.cpp b/src/dllmain.cpp index 249169e..e06ed6e 100644 --- a/src/dllmain.cpp +++ b/src/dllmain.cpp @@ -51,7 +51,7 @@ BOOL APIENTRY DllMain( HMODULE hModule, break; case DLL_PROCESS_DETACH: #if _DEBUG - Util::WaitForDebuggerPrompt("DLL Detach", true); + //Util::WaitForDebuggerPrompt("DLL Detach", true); #endif #ifdef ENABLE_SENTRY Sentry::Close(); @@ -103,7 +103,6 @@ STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppReturn) Util::TryDebugBreak(); // should be done via ClassFactory //*ppReturn = static_cast(new PboFolder()); } - if (!IsEqualIID(riid, IID_IClassFactory)) __debugbreak(); @@ -233,7 +232,12 @@ WAVtoWSS.bat // Same thing again? just for CLSID? Dunno. {HKEY_CLASSES_ROOT, L"SystemFileAssociations\\.pbo", true}, - {HKEY_CLASSES_ROOT, L"SystemFileAssociations\\.pbo\\CLSID", L"", L"{0}", true} + {HKEY_CLASSES_ROOT, L"SystemFileAssociations\\.pbo\\CLSID", L"", L"{0}", true}, + + + // Thumbnail provider for PAA + // IID_IThumbnailProvider == {e357fccd-a995-4576-b01f-234630154e96} + { HKEY_CLASSES_ROOT, L".paa\\ShellEx\\{{e357fccd-a995-4576-b01f-234630154e96}}",L"", L"PboExplorer", true }, }; //#TODO Computer\HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pbo\OpenWithProgids @@ -356,7 +360,11 @@ STDAPI DllUnregisterServer(VOID) // Same thing again? just for CLSID? Dunno. {HKEY_CLASSES_ROOT, L"SystemFileAssociations\\.pbo", true}, - {HKEY_CLASSES_ROOT, L"SystemFileAssociations\\.pbo\\CLSID", L"", L"{0}", true} + {HKEY_CLASSES_ROOT, L"SystemFileAssociations\\.pbo\\CLSID", L"", L"{0}", true}, + + // Thumbnail provider for PAA + // IID_IThumbnailProvider == {e357fccd-a995-4576-b01f-234630154e96} + { HKEY_CLASSES_ROOT, L".paa\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}",L"", L"PboExplorer", true }, }; // register the extension as approved by NT diff --git a/src/util/ComRef.ixx b/src/util/ComRef.ixx index e059427..8b947e1 100644 --- a/src/util/ComRef.ixx +++ b/src/util/ComRef.ixx @@ -17,6 +17,7 @@ class ComRef public: ComRef() : _ref(nullptr) {} + ComRef(ComRef&& o) noexcept : _ref(o._ref) { o._ref = nullptr; } ComRef(T* ref) : _ref(ref) { @@ -112,6 +113,14 @@ public: return *this; } + ComRef& operator=(ComRef&& o) noexcept + { + _ref = o._ref; + o._ref = nullptr; + + return *this; + } + operator bool() { return _ref != nullptr; diff --git a/src/util/DebugLogger.ixx b/src/util/DebugLogger.ixx index 41f12df..534eaa3 100644 --- a/src/util/DebugLogger.ixx +++ b/src/util/DebugLogger.ixx @@ -634,6 +634,7 @@ static GUIDLookup guidLookupTable{ LookupFromText("IID_IDataObject", L"{3CEE8CC1-1ADB-327F-9B97-7A9C8089BFB3}"), // https://microsoft.public.platformsdk.shell.narkive.com/t6GVO0vR/sendmail-in-xp LookupFromText("IID_IViewResultRelatedItem", L"{50BC72DA-9633-47CB-80AC-727661FB9B9F}", DebugInterestLevel::NotInterested), LookupFromText("IID_IFolderViewCapabilities", L"{7B88EA95-1C91-42AA-BAE5-6D730CBEC794}", DebugInterestLevel::NotInterested), + LookupFromText("CIconAndThumbnailOplockWrapper", L"{2968087C-7490-430F-BB8B-2156610D825A}", DebugInterestLevel::NotInterested), // general ref https://gist.github.com/invokethreatguy/b2482f4204d2e71dcb5f9a081ccf7baf diff --git a/src/util/Tracy.cpp b/src/util/Tracy.cpp index de77ea4..a13766b 100644 --- a/src/util/Tracy.cpp +++ b/src/util/Tracy.cpp @@ -13,6 +13,11 @@ #define TRACY_ENABLE #define TRACY_CALLSTACK 8 #define TRACY_TIMER_FALLBACK + +#ifndef _DEBUG +#define TRACY_NO_SYSTEM_TRACING +#endif + #include "../lib/tracy/public/tracy/Tracy.hpp" #include "../lib/tracy/public/client/TracyAlloc.cpp"