MinecraftConsoles/Minecraft.Client/ScreenshotManager.cpp
retuci0 e87fb36bc4 added screenshot functionality
alpha is broken tho
2026-03-16 17:41:28 +01:00

208 lines
6.9 KiB
C++

#include "stdafx.h"
#include "ScreenshotManager.h"
#include <d3d11.h>
#include <dxgi.h>
#include <wincodec.h>
#include <shlobj.h>
#include <chrono>
#include <iomanip>
#include <sstream>
#include <vector>
// link WIC
#pragma comment(lib, "windowscodecs.lib")
// defined in Windows64_Minecraft.cpp
extern IDXGISwapChain* g_pSwapChain;
extern ID3D11Device* g_pd3dDevice;
extern ID3D11DeviceContext* g_pImmediateContext;
extern int g_rScreenWidth;
extern int g_rScreenHeight;
// make sure the output foldler exists
bool ScreenshotManager::ensureScreenshotsFolder(std::wstring& outPath) {
wchar_t exePath[MAX_PATH] = {};
if (!GetModuleFileNameW(nullptr, exePath, _countof(exePath))) return false;
wchar_t* lastSlash = wcsrchr(exePath, L'\\');
if (lastSlash) *(lastSlash + 1) = L'\0';
std::wstring folder = exePath;
folder += L"screenshots";
// create if doesn't exist
DWORD attrib = GetFileAttributesW(folder.c_str());
if (attrib == INVALID_FILE_ATTRIBUTES || !(attrib & FILE_ATTRIBUTE_DIRECTORY)) {
if (!CreateDirectoryW(folder.c_str(), nullptr)) {
return false;
}
}
outPath = folder + L"\\";
return true;
}
// create a formatted name with the current timestamp
std::wstring ScreenshotManager::makeTimestampedFilename() {
auto now = std::chrono::system_clock::now();
auto t = std::chrono::system_clock::to_time_t(now);
std::wstringstream ss;
ss << L"screenshot_";
std::tm tm{};
localtime_s(&tm, &t);
ss << std::put_time(&tm, L"%Y-%m-%d_%H-%M-%S") << ".png";
return ss.str();
}
// takes a screenshot of the current frame
bool ScreenshotManager::takeScreenshot(const std::wstring& path) {
if (!g_pSwapChain || !g_pd3dDevice || !g_pImmediateContext) return false;
HRESULT hr = S_OK;
ID3D11Texture2D* pBack = nullptr;
hr = g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**) &pBack);
if (FAILED(hr) || !pBack) return false;
D3D11_TEXTURE2D_DESC desc;
pBack->GetDesc(&desc);
D3D11_TEXTURE2D_DESC stagingDesc = desc;
stagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
stagingDesc.Usage = D3D11_USAGE_STAGING;
stagingDesc.BindFlags = 0;
stagingDesc.MiscFlags = 0;
stagingDesc.SampleDesc.Count = 1;
stagingDesc.SampleDesc.Quality = 0;
ID3D11Texture2D* pStaging = nullptr;
hr = g_pd3dDevice->CreateTexture2D(&stagingDesc, nullptr, &pStaging);
if (FAILED(hr) || !pStaging) {
pBack->Release();
return false;
}
g_pImmediateContext->CopyResource(pStaging, pBack);
// map and read
D3D11_MAPPED_SUBRESOURCE mapped;
hr = g_pImmediateContext->Map(pStaging, 0, D3D11_MAP_READ, 0, &mapped);
if (FAILED(hr)) {
pStaging->Release();
pBack->Release();
return false;
}
// prepare WIC
IWICImagingFactory* pFactory = nullptr;
hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (FAILED(hr) && hr != RPC_E_CHANGED_MODE) {
g_pImmediateContext->Unmap(pStaging, 0);
pStaging->Release();
pBack->Release();
return false;
}
hr = CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFactory));
if (FAILED(hr)) {
g_pImmediateContext->Unmap(pStaging, 0);
pStaging->Release();
pBack->Release();
return false;
}
std::wstring outFolder;
if (!ensureScreenshotsFolder(outFolder)) {
pFactory->Release();
g_pImmediateContext->Unmap(pStaging, 0);
pStaging->Release();
pBack->Release();
return false;
}
std::wstring fileName = path;
if (fileName.empty()) {
fileName = outFolder + makeTimestampedFilename();
} else {
if (fileName.find(L":\\") == std::wstring::npos) {
fileName = outFolder + fileName;
}
}
GUID wicFormat = GUID_WICPixelFormat32bppBGRA;
// we swizzle R / B because WIC expects BGRA for some fucking reason
const UINT width = desc.Width;
const UINT height = desc.Height;
const UINT srcRowPitch = mapped.RowPitch;
const UINT dstStride = width * 4;
std::vector<BYTE> imageData(dstStride * height);
BYTE* dst = imageData.data();
BYTE* srcBase = reinterpret_cast<BYTE*>(mapped.pData);
for (UINT y = 0; y < height; ++y) {
BYTE* src = srcBase + y * srcRowPitch;
BYTE* row = dst + y * dstStride;
for (UINT x = 0; x < width; ++x) {
BYTE r = src[x * 4 + 0];
BYTE g = src[x * 4 + 1];
BYTE b = src[x * 4 + 2];
BYTE a = src[x * 4 + 3];
row[x * 4 + 0] = b;
row[x * 4 + 1] = g;
row[x * 4 + 2] = r;
row[x * 4 + 3] = a;
}
}
// create WIC bitmap from memory
IWICBitmap* pBitmap = nullptr;
hr = pFactory->CreateBitmapFromMemory(width, height, wicFormat, dstStride, static_cast<UINT>(imageData.size()), imageData.data(), &pBitmap);
if (FAILED(hr)) { pFactory->Release(); g_pImmediateContext->Unmap(pStaging, 0); pStaging->Release(); pBack->Release(); return false; }
// create encoder
IWICStream* pStream = nullptr;
IWICBitmapEncoder* pEncoder = nullptr;
hr = pFactory->CreateStream(&pStream);
if (FAILED(hr)) { pBitmap->Release(); pFactory->Release(); g_pImmediateContext->Unmap(pStaging, 0); pStaging->Release(); pBack->Release(); return false; }
hr = pStream->InitializeFromFilename(fileName.c_str(), GENERIC_WRITE);
if (FAILED(hr)) { pStream->Release(); pBitmap->Release(); pFactory->Release(); g_pImmediateContext->Unmap(pStaging, 0); pStaging->Release(); pBack->Release(); return false; }
hr = pFactory->CreateEncoder(GUID_ContainerFormatPng, nullptr, &pEncoder);
if (FAILED(hr)) { pStream->Release(); pBitmap->Release(); pFactory->Release(); g_pImmediateContext->Unmap(pStaging, 0); pStaging->Release(); pBack->Release(); return false; }
hr = pEncoder->Initialize(pStream, WICBitmapEncoderNoCache);
if (FAILED(hr)) { pEncoder->Release(); pStream->Release(); pBitmap->Release(); pFactory->Release(); g_pImmediateContext->Unmap(pStaging, 0); pStaging->Release(); pBack->Release(); return false; }
IWICBitmapFrameEncode* pFrame = nullptr;
IPropertyBag2* pProps = nullptr;
hr = pEncoder->CreateNewFrame(&pFrame, &pProps);
if (FAILED(hr)) { pEncoder->Release(); pStream->Release(); pBitmap->Release(); pFactory->Release(); g_pImmediateContext->Unmap(pStaging, 0); pStaging->Release(); pBack->Release(); return false; }
hr = pFrame->Initialize(pProps);
if (FAILED(hr)) { pFrame->Release(); if (pProps) pProps->Release(); pEncoder->Release(); pStream->Release(); pBitmap->Release(); pFactory->Release(); g_pImmediateContext->Unmap(pStaging, 0); pStaging->Release(); pBack->Release(); return false; }
hr = pFrame->SetSize(width, height);
if (FAILED(hr)) { pFrame->Release(); if (pProps) pProps->Release(); pEncoder->Release(); pStream->Release(); pBitmap->Release(); pFactory->Release(); g_pImmediateContext->Unmap(pStaging, 0); pStaging->Release(); pBack->Release(); return false; }
hr = pFrame->SetPixelFormat(&wicFormat); // prefer BGRA
hr = pFrame->WriteSource(pBitmap, nullptr);
hr = pFrame->Commit();
hr = pEncoder->Commit();
// cleanup
if (pProps) pProps->Release();
pFrame->Release();
pEncoder->Release();
pStream->Release();
pBitmap->Release();
pFactory->Release();
g_pImmediateContext->Unmap(pStaging, 0);
pStaging->Release();
pBack->Release();
CoUninitialize();
return true;
}