Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Minecraft.Client/Common/App_enums.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions Minecraft.Client/Minecraft.Client.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -20727,6 +20727,7 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CU</Comman
</ClInclude>
<ClInclude Include="Rect2i.h" />
<ClInclude Include="ResourceLocation.h" />
<ClInclude Include="ScreenshotManager.h" />
<ClInclude Include="ServerCommandDispatcher.h" />
<ClInclude Include="ServerConnection.h" />
<ClInclude Include="ServerInterface.h" />
Expand Down Expand Up @@ -37951,6 +37952,7 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CU</Comman
<ClCompile Include="RedDustParticle.cpp" />
<ClCompile Include="RenameWorldScreen.cpp" />
<ClCompile Include="Screen.cpp" />
<ClCompile Include="ScreenshotManager.cpp" />
<ClCompile Include="ScreenSizeCalculator.cpp" />
<ClCompile Include="ScrolledSelectionList.cpp" />
<ClCompile Include="SelectWorldScreen.cpp" />
Expand Down
6 changes: 6 additions & 0 deletions Minecraft.Client/Minecraft.Client.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -3793,6 +3793,9 @@
<ClInclude Include="..\include\lce_filesystem\lce_filesystem.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ScreenshotManager.h">
<Filter>Windows64\Source Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp">
Expand Down Expand Up @@ -5949,6 +5952,9 @@
<ClCompile Include="..\include\lce_filesystem\lce_filesystem.cpp">
<Filter>include\lce_filesystem</Filter>
</ClCompile>
<ClCompile Include="ScreenshotManager.cpp">
<Filter>Windows64\Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Library Include="Xbox\4JLibs\libs\4J_Render_d.lib">
Expand Down
24 changes: 13 additions & 11 deletions Minecraft.Client/Minecraft.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1459,6 +1459,7 @@ void Minecraft::run_middle()
if(InputManager.ButtonPressed(i, ACTION_MENU_GTC_PAUSE)) localplayers[i]->ullButtonsPressed|=1LL<<ACTION_MENU_GTC_PAUSE;
#endif
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
if(localplayers[i]->abilities.flying)
Expand Down Expand Up @@ -1509,20 +1510,21 @@ void Minecraft::run_middle()
}
}

if(g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_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);
if (g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_DROP)) {
localplayers[i]->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<<MINECRAFT_ACTION_CRAFTING;
}
}
}

for (int slot = 0; slot < 9; slot++)
{
Expand Down
217 changes: 217 additions & 0 deletions Minecraft.Client/ScreenshotManager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
#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;

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<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) {
// 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<BYTE>(bb);
row[x * 4 + 1] = static_cast<BYTE>(bg);
row[x * 4 + 2] = static_cast<BYTE>(br);
row[x * 4 + 3] = 255;
}
}

// 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;
}
16 changes: 16 additions & 0 deletions Minecraft.Client/ScreenshotManager.h
Original file line number Diff line number Diff line change
@@ -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;
};
11 changes: 11 additions & 0 deletions Minecraft.Client/Windows64/Windows64_Minecraft.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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))
{
Expand Down
Loading