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)) {