Skip to content

Commit

Permalink
Added Thumbnail provider for PAA files (outside of PBO)
Browse files Browse the repository at this point in the history
  • Loading branch information
dedmen committed Jul 22, 2024
1 parent d76818e commit bbb9034
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 20 deletions.
12 changes: 8 additions & 4 deletions src/ClassFactory.ixx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module;
#include <windows.h>
#include <shlobj.h>
#include <thumbcache.h>

#include "PboFolder.hpp"
#include "PboDataObject.hpp"
Expand All @@ -14,6 +15,7 @@ export module ClassFactory;

import ComRef;
import Tracy;
import PaaThumbnailProvider;

extern HINSTANCE g_hInst;

Expand Down Expand Up @@ -85,14 +87,16 @@ public:
return S_OK;
}

if (IsEqualIID(riid, IID_IThumbnailProvider))
{
ComRef<PaaThumbnailProvider>::CreateForReturn<IThumbnailProvider>(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<ShellExt>::Create();
if (!pShellExt)
Expand Down
228 changes: 216 additions & 12 deletions src/PaaThumbnailHandler.ixx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module;
#define NOMINMAX
#include <thumbcache.h>
#include "DebugLogger.hpp"
#include "PboFileDirectory.hpp"
Expand All @@ -16,21 +17,171 @@ import Util;
import PboLib;
import MipMapTool;

class IStreamBufWrapper : public std::streambuf {
std::vector<char> 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<char>::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<int64_t>(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<IPboFolder> pboFile;
HWND hwnd;
struct PboFileContent {
PboSubFile imageFile;
std::shared_ptr<IPboFolder> pboFile;
};

std::variant<PboFileContent, ComRef<IStream>, std::filesystem::path> contentSource;
public:
PaaThumbnailProvider(PboSubFile imageFile, std::shared_ptr<IPboFolder> mainFolder, HWND hwnd) : imageFile(imageFile), pboFile(std::move(mainFolder)), hwnd(hwnd) {
PaaThumbnailProvider(PboSubFile imageFile, std::shared_ptr<IPboFolder> mainFolder, HWND hwnd) : contentSource(PboFileContent{ std::move(imageFile), std::move(mainFolder) }) {

}
PaaThumbnailProvider() : contentSource() {

}

Expand All @@ -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<IStream> 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<IStream>& 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<PboFileContent>(contentSource));
if (contentSource.index() == 1)
return LoadTextureFromStream(maxSize, std::get<ComRef<IStream>>(contentSource));
if (contentSource.index() == 2)
return LoadTextureFromFile(maxSize, std::get<std::filesystem::path>(contentSource));
else
Util::TryDebugBreak();

// Garbage dummy, invalid
return LoadTextureFromStream(maxSize, std::get<ComRef<IStream>>(contentSource));

// Internal compiler error :)
//return std::visit([this, maxSize](auto&& arg)
// {
// using T = std::decay_t<decltype(arg)>;
// if constexpr (std::is_same_v<T, PboFileContent>)
// return LoadTextureFromPbo(maxSize, arg);
// else if constexpr (std::is_same_v<T, ComRef<IStream>>)
// 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<uint32_t> x(reinterpret_cast<uint32_t*>(tex.data()), tex.size() / sizeof(uint32_t));
Expand Down
16 changes: 12 additions & 4 deletions src/dllmain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -103,7 +103,6 @@ STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppReturn)
Util::TryDebugBreak(); // should be done via ClassFactory
//*ppReturn = static_cast<IShellFolder2*>(new PboFolder());
}


if (!IsEqualIID(riid, IID_IClassFactory))
__debugbreak();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions src/util/ComRef.ixx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/util/DebugLogger.ixx
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,7 @@ static GUIDLookup<LookupInfoStorageT> 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
Expand Down
5 changes: 5 additions & 0 deletions src/util/Tracy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit bbb9034

Please sign in to comment.