added screenshot functionality

alpha is broken tho
This commit is contained in:
retuci0 2026-03-16 17:41:28 +01:00
parent 43d520f692
commit e87fb36bc4
7 changed files with 257 additions and 11 deletions

View file

@ -862,6 +862,7 @@ enum EControllerActions
MINECRAFT_ACTION_INVENTORY, MINECRAFT_ACTION_INVENTORY,
MINECRAFT_ACTION_PAUSEMENU, MINECRAFT_ACTION_PAUSEMENU,
MINECRAFT_ACTION_DROP, MINECRAFT_ACTION_DROP,
MINECRAFT_ACTION_DROP_ALL,
MINECRAFT_ACTION_SNEAK_TOGGLE, MINECRAFT_ACTION_SNEAK_TOGGLE,
MINECRAFT_ACTION_CRAFTING, MINECRAFT_ACTION_CRAFTING,
MINECRAFT_ACTION_RENDER_THIRD_PERSON, MINECRAFT_ACTION_RENDER_THIRD_PERSON,

View file

@ -20727,6 +20727,7 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CU</Comman
</ClInclude> </ClInclude>
<ClInclude Include="Rect2i.h" /> <ClInclude Include="Rect2i.h" />
<ClInclude Include="ResourceLocation.h" /> <ClInclude Include="ResourceLocation.h" />
<ClInclude Include="ScreenshotManager.h" />
<ClInclude Include="ServerCommandDispatcher.h" /> <ClInclude Include="ServerCommandDispatcher.h" />
<ClInclude Include="ServerConnection.h" /> <ClInclude Include="ServerConnection.h" />
<ClInclude Include="ServerInterface.h" /> <ClInclude Include="ServerInterface.h" />
@ -37951,6 +37952,7 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CU</Comman
<ClCompile Include="RedDustParticle.cpp" /> <ClCompile Include="RedDustParticle.cpp" />
<ClCompile Include="RenameWorldScreen.cpp" /> <ClCompile Include="RenameWorldScreen.cpp" />
<ClCompile Include="Screen.cpp" /> <ClCompile Include="Screen.cpp" />
<ClCompile Include="ScreenshotManager.cpp" />
<ClCompile Include="ScreenSizeCalculator.cpp" /> <ClCompile Include="ScreenSizeCalculator.cpp" />
<ClCompile Include="ScrolledSelectionList.cpp" /> <ClCompile Include="ScrolledSelectionList.cpp" />
<ClCompile Include="SelectWorldScreen.cpp" /> <ClCompile Include="SelectWorldScreen.cpp" />

View file

@ -3793,6 +3793,9 @@
<ClInclude Include="..\include\lce_filesystem\lce_filesystem.h"> <ClInclude Include="..\include\lce_filesystem\lce_filesystem.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="ScreenshotManager.h">
<Filter>Windows64\Source Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="stdafx.cpp"> <ClCompile Include="stdafx.cpp">
@ -5949,6 +5952,9 @@
<ClCompile Include="..\include\lce_filesystem\lce_filesystem.cpp"> <ClCompile Include="..\include\lce_filesystem\lce_filesystem.cpp">
<Filter>include\lce_filesystem</Filter> <Filter>include\lce_filesystem</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="ScreenshotManager.cpp">
<Filter>Windows64\Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Library Include="Xbox\4JLibs\libs\4J_Render_d.lib"> <Library Include="Xbox\4JLibs\libs\4J_Render_d.lib">

View file

@ -1459,6 +1459,7 @@ void Minecraft::run_middle()
if(InputManager.ButtonPressed(i, ACTION_MENU_GTC_PAUSE)) localplayers[i]->ullButtonsPressed|=1LL<<ACTION_MENU_GTC_PAUSE; if(InputManager.ButtonPressed(i, ACTION_MENU_GTC_PAUSE)) localplayers[i]->ullButtonsPressed|=1LL<<ACTION_MENU_GTC_PAUSE;
#endif #endif
if(InputManager.ButtonPressed(i, MINECRAFT_ACTION_DROP)) localplayers[i]->ullButtonsPressed|=1LL<<MINECRAFT_ACTION_DROP; if(InputManager.ButtonPressed(i, MINECRAFT_ACTION_DROP)) localplayers[i]->ullButtonsPressed|=1LL<<MINECRAFT_ACTION_DROP;
if (InputManager.ButtonPressed(i, MINECRAFT_ACTION_DROP_ALL)) localplayers[i]->ullButtonsPressed |= 1LL << MINECRAFT_ACTION_DROP_ALL;
// 4J-PB - If we're flying, the sneak needs to be held on to go down // 4J-PB - If we're flying, the sneak needs to be held on to go down
if(localplayers[i]->abilities.flying) if(localplayers[i]->abilities.flying)
@ -1509,17 +1510,18 @@ void Minecraft::run_middle()
} }
} }
if(g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_DROP)) if (g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_DROP)) {
localplayers[i]->ullButtonsPressed |= 1LL << MINECRAFT_ACTION_DROP; localplayers[i]->ullButtonsPressed |= 1LL << MINECRAFT_ACTION_DROP;
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
{ 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<<MINECRAFT_ACTION_CRAFTING; localplayers[i]->ullButtonsPressed|=1LL<<MINECRAFT_ACTION_CRAFTING;
} }
} }

View file

@ -0,0 +1,208 @@
#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;
}

View file

@ -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;
};

View file

@ -48,6 +48,7 @@
#include "Network\WinsockNetLayer.h" #include "Network\WinsockNetLayer.h"
#include "Windows64_Xuid.h" #include "Windows64_Xuid.h"
#include "Common/UI/UI.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. // 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. // 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); 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 // F3 toggles onscreen debug info
if (g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_DEBUG_INFO)) if (g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_DEBUG_INFO))
{ {