diff --git a/Minecraft.Client/Minecraft.cpp b/Minecraft.Client/Minecraft.cpp index bc437f18..b33d4a26 100644 --- a/Minecraft.Client/Minecraft.cpp +++ b/Minecraft.Client/Minecraft.cpp @@ -1554,9 +1554,6 @@ void Minecraft::run_middle() localplayers[i]->ullButtonsPressed|=1LL<ullButtonsPressed|=1LL<thirdPersonView = !options->thirdPersonView; } -#ifdef _WINDOWS64 - if(player->ullButtonsPressed&(1LL<GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&pBackBuffer); - if (SUCCEEDED(hr)) - { - D3D11_TEXTURE2D_DESC desc; - pBackBuffer->GetDesc(&desc); - desc.Usage = D3D11_USAGE_STAGING; - desc.BindFlags = 0; - desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; - desc.MiscFlags = 0; - - ID3D11Texture2D* pStaging = nullptr; - hr = g_pd3dDevice->CreateTexture2D(&desc, nullptr, &pStaging); - if (SUCCEEDED(hr)) - { - g_pImmediateContext->CopyResource(pStaging, pBackBuffer); - - // Build path next to the executable - wchar_t exePath[MAX_PATH]; - GetModuleFileNameW(NULL, exePath, MAX_PATH); - wchar_t* lastSlash = wcsrchr(exePath, L'\\'); - if (lastSlash) *(lastSlash + 1) = L'\0'; - wstring screenshotDirPath = wstring(exePath) + L"screenshots"; - CreateDirectoryW(screenshotDirPath.c_str(), NULL); - - SYSTEMTIME st; - GetLocalTime(&st); - wchar_t filename[128]; - swprintf_s(filename, L"%04d-%02d-%02d_%02d.%02d.%02d.png", - st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); - wstring screenshotPath = screenshotDirPath + L"\\" + filename; - - D3D11_MAPPED_SUBRESOURCE mapped; - hr = g_pImmediateContext->Map(pStaging, 0, D3D11_MAP_READ, 0, &mapped); - if (SUCCEEDED(hr)) - { - // Copy rows and force alpha to fully opaque - unsigned char* rgba = new unsigned char[desc.Width * desc.Height * 4]; - for (UINT row = 0; row < desc.Height; row++) - { - unsigned char* src = (unsigned char*)mapped.pData + row * mapped.RowPitch; - unsigned char* dst = rgba + row * desc.Width * 4; - memcpy(dst, src, desc.Width * 4); - for (UINT x = 0; x < desc.Width; x++) - dst[x * 4 + 3] = 0xFF; - } - g_pImmediateContext->Unmap(pStaging, 0); - - // Save PNG via stb_image_write - string narrowPath(screenshotPath.begin(), screenshotPath.end()); - int writeResult = stbi_write_png(narrowPath.c_str(), desc.Width, desc.Height, 4, rgba, desc.Width * 4); - delete[] rgba; - - // Send local-only chat message on success - if (writeResult) - { - wstring msg = L"Saved screenshot to " + wstring(filename); - gui->addMessage(msg, iPad); - } - } - pStaging->Release(); - } - pBackBuffer->Release(); - } - } -#endif - if((player->ullButtonsPressed&(1LL<isInputAllowed(MINECRAFT_ACTION_GAME_INFO)) { ui.NavigateToScene(iPad,eUIScene_InGameInfoMenu); diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp index 869d23d0..5abb1cae 100644 --- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp +++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp @@ -49,6 +49,7 @@ #include "Network\WinsockNetLayer.h" #include "Windows64_Xuid.h" #include "Common/UI/UI.h" +#include "stb_image_write.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. @@ -474,6 +475,77 @@ static bool g_bTearingSupported = false; static bool g_bPendingExclusiveFullscreen = false; static bool g_bPendingExclusiveFullscreenValue = false; +// Captures the D3D11 back buffer and saves it as a PNG screenshot. +// Returns true on success and sets outFilename to the saved filename. +static bool TakeScreenshot(wstring& outFilename) +{ + if (!g_pSwapChain || !g_pd3dDevice || !g_pImmediateContext) + return false; + + ID3D11Texture2D* pBackBuffer = nullptr; + HRESULT hr = g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&pBackBuffer); + if (FAILED(hr)) + return false; + + D3D11_TEXTURE2D_DESC desc; + pBackBuffer->GetDesc(&desc); + desc.Usage = D3D11_USAGE_STAGING; + desc.BindFlags = 0; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + desc.MiscFlags = 0; + + bool success = false; + ID3D11Texture2D* pStaging = nullptr; + hr = g_pd3dDevice->CreateTexture2D(&desc, nullptr, &pStaging); + if (SUCCEEDED(hr)) + { + g_pImmediateContext->CopyResource(pStaging, pBackBuffer); + + wchar_t exePath[MAX_PATH]; + GetModuleFileNameW(NULL, exePath, MAX_PATH); + wchar_t* lastSlash = wcsrchr(exePath, L'\\'); + if (lastSlash) *(lastSlash + 1) = L'\0'; + wstring screenshotDirPath = wstring(exePath) + L"screenshots"; + CreateDirectoryW(screenshotDirPath.c_str(), NULL); + + SYSTEMTIME st; + GetLocalTime(&st); + wchar_t filename[128]; + swprintf_s(filename, L"%04d-%02d-%02d_%02d.%02d.%02d.png", + st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); + wstring screenshotPath = screenshotDirPath + L"\\" + filename; + + D3D11_MAPPED_SUBRESOURCE mapped; + hr = g_pImmediateContext->Map(pStaging, 0, D3D11_MAP_READ, 0, &mapped); + if (SUCCEEDED(hr)) + { + unsigned char* rgba = new unsigned char[desc.Width * desc.Height * 4]; + for (UINT row = 0; row < desc.Height; row++) + { + unsigned char* src = (unsigned char*)mapped.pData + row * mapped.RowPitch; + unsigned char* dst = rgba + row * desc.Width * 4; + memcpy(dst, src, desc.Width * 4); + for (UINT x = 0; x < desc.Width; x++) + dst[x * 4 + 3] = 0xFF; + } + g_pImmediateContext->Unmap(pStaging, 0); + + string narrowPath(screenshotPath.begin(), screenshotPath.end()); + int writeResult = stbi_write_png(narrowPath.c_str(), desc.Width, desc.Height, 4, rgba, desc.Width * 4); + delete[] rgba; + + if (writeResult) + { + outFilename = filename; + success = true; + } + } + pStaging->Release(); + } + pBackBuffer->Release(); + return success; +} + // COM proxy for IDXGISwapChain — delegates all calls to the real swap chain, // but overrides Present() to set SyncInterval=1 when VSync is enabled. // Avoids vtable patching, which conflicts with the D3D11 debug layer. @@ -1857,6 +1929,20 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, } } + // F2 takes a screenshot (works in any context) + if (g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_SCREENSHOT)) + { + wstring filename; + if (TakeScreenshot(filename)) + { + if (pMinecraft->gui && pMinecraft->player) + { + wstring msg = L"Saved screenshot to " + filename; + pMinecraft->gui->addMessage(msg, ProfileManager.GetPrimaryPad()); + } + } + } + // F1 toggles the HUD if (g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_TOGGLE_HUD)) { diff --git a/README.md b/README.md index 910f0948..a8904705 100644 --- a/README.md +++ b/README.md @@ -148,9 +148,10 @@ Dedicated server releases support Hardcore Mode! - Dedicated Server (`LCREServerWindows64.zip`): download from the Nightly-Dedicated-Server release on GitHub - Docker: pull `ghcr.io/itsrevela/minecraft-lce-dedicated-server:nightly` for server container -In-game screenshot functionality with F2! +Screenshot functionality with F2! - pressing F2 will save a screenshot to a `screenshots` folder in your root game directory -- a local-only chat message is shown to the player notifying the screenshot filename that was saved +- works in any context: main menu, pause menu, settings, inventory, crafting, and during gameplay +- a local-only chat message is shown to the player when in-game Proper implementation of Hardcore Mode in LCRE! - difficulty slider included in create world menu