diff --git a/Minecraft.Client/Common/App_enums.h b/Minecraft.Client/Common/App_enums.h
index 15a179787b..696acd5cd9 100644
--- a/Minecraft.Client/Common/App_enums.h
+++ b/Minecraft.Client/Common/App_enums.h
@@ -862,6 +862,7 @@ enum EControllerActions
MINECRAFT_ACTION_INVENTORY,
MINECRAFT_ACTION_PAUSEMENU,
MINECRAFT_ACTION_DROP,
+ MINECRAFT_ACTION_DROP_ALL,
MINECRAFT_ACTION_SNEAK_TOGGLE,
MINECRAFT_ACTION_CRAFTING,
MINECRAFT_ACTION_RENDER_THIRD_PERSON,
diff --git a/Minecraft.Client/Minecraft.Client.vcxproj b/Minecraft.Client/Minecraft.Client.vcxproj
index d97cbc383c..31909c6f93 100644
--- a/Minecraft.Client/Minecraft.Client.vcxproj
+++ b/Minecraft.Client/Minecraft.Client.vcxproj
@@ -20727,6 +20727,7 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CU
+
@@ -37951,6 +37952,7 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CU
+
diff --git a/Minecraft.Client/Minecraft.Client.vcxproj.filters b/Minecraft.Client/Minecraft.Client.vcxproj.filters
index 23b754fa9a..d2f09ae4c4 100644
--- a/Minecraft.Client/Minecraft.Client.vcxproj.filters
+++ b/Minecraft.Client/Minecraft.Client.vcxproj.filters
@@ -3793,6 +3793,9 @@
Header Files
+
+ Windows64\Source Files
+
@@ -5949,6 +5952,9 @@
include\lce_filesystem
+
+ Windows64\Source Files
+
diff --git a/Minecraft.Client/Minecraft.cpp b/Minecraft.Client/Minecraft.cpp
index 11fd81a0d0..6114cdaa08 100644
--- a/Minecraft.Client/Minecraft.cpp
+++ b/Minecraft.Client/Minecraft.cpp
@@ -1459,6 +1459,7 @@ void Minecraft::run_middle()
if(InputManager.ButtonPressed(i, ACTION_MENU_GTC_PAUSE)) localplayers[i]->ullButtonsPressed|=1LL<ullButtonsPressed|=1LL<ullButtonsPressed |= 1LL << MINECRAFT_ACTION_DROP_ALL;
// 4J-PB - If we're flying, the sneak needs to be held on to go down
if(localplayers[i]->abilities.flying)
@@ -1509,20 +1510,21 @@ void Minecraft::run_middle()
}
}
- if(g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_DROP))
- localplayers[i]->ullButtonsPressed|=1LL<ullButtonsPressed |= 1LL << MINECRAFT_ACTION_DROP;
}
- else
- {
+
+ if (g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_CRAFTING) || g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_CRAFTING_ALT)) {
+ if((ui.IsSceneInStack(i, eUIScene_Crafting2x2Menu)
+ || ui.IsSceneInStack(i, eUIScene_Crafting3x3Menu)
+ || ui.IsSceneInStack(i, eUIScene_CreativeMenu)
+ || isClosableByEitherKey)
+ && !isEditing) {
+ ui.CloseUIScenes(i);
+ } else {
localplayers[i]->ullButtonsPressed|=1LL<
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// 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;
+
+ const UINT width = desc.Width;
+ const UINT height = desc.Height;
+ const UINT srcRowPitch = mapped.RowPitch;
+ const UINT dstStride = width * 4;
+
+ // write data and shi
+ std::vector imageData(dstStride * height);
+ BYTE* dst = imageData.data();
+ BYTE* srcBase = reinterpret_cast(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) {
+ // read original rgba
+ BYTE r = src[x * 4 + 0];
+ BYTE g = src[x * 4 + 1];
+ BYTE b = src[x * 4 + 2];
+ BYTE a = src[x * 4 + 3];
+
+ // black background to fix opacity issues (sorta)
+ // (using integer math with rounding: (value * a + 127) / 255)
+ UINT br = (r * a + 127) / 255;
+ UINT bg = (g * a + 127) / 255;
+ UINT bb = (b * a + 127) / 255;
+
+ // we swizzle R / B because WIC expects BGRA for some fucking reason
+ row[x * 4 + 0] = static_cast(bb);
+ row[x * 4 + 1] = static_cast(bg);
+ row[x * 4 + 2] = static_cast(br);
+ row[x * 4 + 3] = 255;
+ }
+ }
+
+ // create WIC bitmap from memory
+ IWICBitmap* pBitmap = nullptr;
+ hr = pFactory->CreateBitmapFromMemory(width, height, wicFormat, dstStride, static_cast(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;
+}
\ No newline at end of file
diff --git a/Minecraft.Client/ScreenshotManager.h b/Minecraft.Client/ScreenshotManager.h
new file mode 100644
index 0000000000..d74f85638b
--- /dev/null
+++ b/Minecraft.Client/ScreenshotManager.h
@@ -0,0 +1,16 @@
+#pragma once
+
+class ScreenshotManager {
+public:
+ static ScreenshotManager* getInstance() {
+ static ScreenshotManager instance;
+ return &instance;
+ }
+
+ bool ensureScreenshotsFolder(std::wstring& outPath);
+ std::wstring makeTimestampedFilename();
+ bool takeScreenshot(const std::wstring& path = L"");
+
+private:
+ ScreenshotManager() = default;
+};
\ No newline at end of file
diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp
index 81430ffcc7..82914dc535 100644
--- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp
+++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp
@@ -48,6 +48,7 @@
#include "Network\WinsockNetLayer.h"
#include "Windows64_Xuid.h"
#include "Common/UI/UI.h"
+#include "ScreenshotManager.h"
// Forward-declare the internal Renderer class and its global instance from 4J_Render_PC_d.lib.
// C4JRender (RenderManager) is a stateless wrapper — all D3D state lives in InternalRenderManager.
@@ -1773,6 +1774,16 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
app.SetGameSettings(primaryPad, eGameSetting_DisplayHand, displayHud ? 0 : 1);
}
+ // F2 for screenshot - retucio
+ if (g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_SCREENSHOT)) {
+ Minecraft* mc = Minecraft::GetInstance();
+ if (ScreenshotManager::getInstance()->takeScreenshot() && mc->player) {
+ mc->gui->addMessage(L"screenshot taken", -1, false);
+ } else if (mc->player) {
+ mc->gui->addMessage(L"failed to take screenshot", -1, false);
+ }
+ }
+
// F3 toggles onscreen debug info
if (g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_DEBUG_INFO))
{