mirror of
https://github.com/smartcmd/MinecraftConsoles.git
synced 2026-08-20 07:07:14 +00:00
fix: Display save thumbnails on Windows64
This commit is contained in:
parent
2fba264c08
commit
756442fc0e
|
|
@ -110,6 +110,8 @@ UIScene_LoadMenu::UIScene_LoadMenu(int iPad, void *initData, UILayer *parentLaye
|
||||||
m_bThumbnailGetFailed = false;
|
m_bThumbnailGetFailed = false;
|
||||||
m_seed = 0;
|
m_seed = 0;
|
||||||
m_bIsCorrupt = false;
|
m_bIsCorrupt = false;
|
||||||
|
m_pbThumbnailData = nullptr;
|
||||||
|
m_uiThumbnailSize = 0;
|
||||||
|
|
||||||
m_bMultiplayerAllowed = ProfileManager.IsSignedInLive( m_iPad ) && ProfileManager.AllowedToPlayMultiplayer(m_iPad);
|
m_bMultiplayerAllowed = ProfileManager.IsSignedInLive( m_iPad ) && ProfileManager.AllowedToPlayMultiplayer(m_iPad);
|
||||||
// 4J-PB - read the settings for the online flag. We'll only save this setting if the user changed it.
|
// 4J-PB - read the settings for the online flag. We'll only save this setting if the user changed it.
|
||||||
|
|
@ -249,13 +251,73 @@ UIScene_LoadMenu::UIScene_LoadMenu(int iPad, void *initData, UILayer *parentLaye
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#ifdef _WINDOWS64
|
#ifdef _WINDOWS64
|
||||||
if (params->saveDetails != nullptr && params->saveDetails->UTF8SaveName[0] != '\0')
|
if (params->saveDetails != nullptr)
|
||||||
{
|
{
|
||||||
wchar_t wSaveName[128];
|
if (params->saveDetails->UTF8SaveName[0] != '\0')
|
||||||
ZeroMemory(wSaveName, sizeof(wSaveName));
|
{
|
||||||
mbstowcs(wSaveName, params->saveDetails->UTF8SaveName, 127);
|
wchar_t wSaveName[128];
|
||||||
m_levelName = wstring(wSaveName);
|
ZeroMemory(wSaveName, sizeof(wSaveName));
|
||||||
m_labelGameName.init(m_levelName);
|
mbstowcs(wSaveName, params->saveDetails->UTF8SaveName, 127);
|
||||||
|
m_levelName = wstring(wSaveName);
|
||||||
|
m_labelGameName.init(m_levelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// texture name = the save folder name in wide chars
|
||||||
|
wchar_t wFilename[MAX_SAVEFILENAME_LENGTH];
|
||||||
|
ZeroMemory(wFilename, sizeof(wFilename));
|
||||||
|
mbstowcs(wFilename, params->saveDetails->UTF8SaveFilename, MAX_SAVEFILENAME_LENGTH - 1);
|
||||||
|
m_thumbnailName = wFilename;
|
||||||
|
|
||||||
|
// use whatever thumbnail the save list already loaded, or read it
|
||||||
|
// from disk. don't set m_bSaveThumbnailReady because tick() calls
|
||||||
|
// NavigateBack() to dismiss the timer scene which we never showed
|
||||||
|
PBYTE thumbData = nullptr;
|
||||||
|
DWORD thumbSize = 0;
|
||||||
|
|
||||||
|
if (params->saveDetails->pbThumbnailData && params->saveDetails->dwThumbnailSize > 0)
|
||||||
|
{
|
||||||
|
thumbData = params->saveDetails->pbThumbnailData;
|
||||||
|
thumbSize = params->saveDetails->dwThumbnailSize;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// not in memory yet, try the file on disk
|
||||||
|
char thumbPath[MAX_PATH];
|
||||||
|
sprintf_s(thumbPath, MAX_PATH, "Windows64\\GameHDD\\%s\\thumbnail.png",
|
||||||
|
params->saveDetails->UTF8SaveFilename);
|
||||||
|
|
||||||
|
HANDLE hFile = CreateFileA(thumbPath, GENERIC_READ, FILE_SHARE_READ, nullptr,
|
||||||
|
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
|
||||||
|
if (hFile != INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
DWORD fileSize = GetFileSize(hFile, nullptr);
|
||||||
|
if (fileSize > 0 && fileSize != INVALID_FILE_SIZE && fileSize < 4 * 1024 * 1024)
|
||||||
|
{
|
||||||
|
PBYTE fileData = new BYTE[fileSize];
|
||||||
|
DWORD bytesRead = 0;
|
||||||
|
if (ReadFile(hFile, fileData, fileSize, &bytesRead, nullptr) && bytesRead == fileSize)
|
||||||
|
{
|
||||||
|
thumbData = fileData;
|
||||||
|
thumbSize = fileSize;
|
||||||
|
app.DebugPrintf("LoadMenu: Loaded thumbnail.png from disk\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
delete[] fileData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CloseHandle(hFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thumbData && thumbSize > 0)
|
||||||
|
{
|
||||||
|
registerSubstitutionTexture(wFilename, thumbData, thumbSize);
|
||||||
|
m_bitmapIcon.setTextureName(wFilename);
|
||||||
|
m_pbThumbnailData = thumbData;
|
||||||
|
m_uiThumbnailSize = thumbSize;
|
||||||
|
}
|
||||||
|
m_bRetrievingSaveThumbnail = false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -822,6 +822,63 @@ void UIScene_LoadOrJoinMenu::tick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef _WINDOWS64
|
||||||
|
// LoadSaveDataThumbnail in the storage lib doesn't work on Win64,
|
||||||
|
// so just read thumbnail.png straight from each save folder
|
||||||
|
if(!m_bExitScene && m_bSavesDisplayed && !m_bAllLoaded)
|
||||||
|
{
|
||||||
|
PSAVE_DETAILS pSaveDetails = StorageManager.ReturnSavesInfo();
|
||||||
|
int totalSaves = m_buttonListSaves.getItemCount() - m_iDefaultButtonsC;
|
||||||
|
|
||||||
|
while (m_iRequestingThumbnailId < totalSaves)
|
||||||
|
{
|
||||||
|
int saveId = m_saveDetails[m_iRequestingThumbnailId].saveId;
|
||||||
|
|
||||||
|
// Read thumbnail.png from the save folder
|
||||||
|
char thumbPath[MAX_PATH];
|
||||||
|
sprintf_s(thumbPath, MAX_PATH, "Windows64\\GameHDD\\%s\\thumbnail.png",
|
||||||
|
pSaveDetails->SaveInfoA[saveId].UTF8SaveFilename);
|
||||||
|
|
||||||
|
HANDLE hFile = CreateFileA(thumbPath, GENERIC_READ, FILE_SHARE_READ, nullptr,
|
||||||
|
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
|
||||||
|
if (hFile != INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
DWORD fileSize = GetFileSize(hFile, nullptr);
|
||||||
|
if (fileSize > 0 && fileSize != INVALID_FILE_SIZE && fileSize < 4 * 1024 * 1024)
|
||||||
|
{
|
||||||
|
PBYTE fileData = new BYTE[fileSize];
|
||||||
|
DWORD bytesRead = 0;
|
||||||
|
if (ReadFile(hFile, fileData, fileSize, &bytesRead, nullptr) && bytesRead == fileSize)
|
||||||
|
{
|
||||||
|
m_saveDetails[m_iRequestingThumbnailId].pbThumbnailData = fileData;
|
||||||
|
m_saveDetails[m_iRequestingThumbnailId].dwThumbnailSize = fileSize;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
delete[] fileData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CloseHandle(hFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// register the texture if we got one
|
||||||
|
wchar_t wFilename[MAX_SAVEFILENAME_LENGTH];
|
||||||
|
ZeroMemory(wFilename, sizeof(wFilename));
|
||||||
|
mbstowcs(wFilename, m_saveDetails[m_iRequestingThumbnailId].UTF8SaveFilename, MAX_SAVEFILENAME_LENGTH - 1);
|
||||||
|
|
||||||
|
if (m_saveDetails[m_iRequestingThumbnailId].pbThumbnailData)
|
||||||
|
{
|
||||||
|
registerSubstitutionTexture(wFilename,
|
||||||
|
m_saveDetails[m_iRequestingThumbnailId].pbThumbnailData,
|
||||||
|
m_saveDetails[m_iRequestingThumbnailId].dwThumbnailSize);
|
||||||
|
}
|
||||||
|
m_buttonListSaves.setTextureName(m_iRequestingThumbnailId + m_iDefaultButtonsC, wFilename);
|
||||||
|
|
||||||
|
++m_iRequestingThumbnailId;
|
||||||
|
}
|
||||||
|
m_bAllLoaded = true;
|
||||||
|
}
|
||||||
|
#else
|
||||||
if(!m_bExitScene && m_bSavesDisplayed && !m_bRetrievingSaveThumbnails && !m_bAllLoaded)
|
if(!m_bExitScene && m_bSavesDisplayed && !m_bRetrievingSaveThumbnails && !m_bAllLoaded)
|
||||||
{
|
{
|
||||||
if( m_iRequestingThumbnailId < (m_buttonListSaves.getItemCount() - m_iDefaultButtonsC ))
|
if( m_iRequestingThumbnailId < (m_buttonListSaves.getItemCount() - m_iDefaultButtonsC ))
|
||||||
|
|
@ -830,11 +887,7 @@ void UIScene_LoadOrJoinMenu::tick()
|
||||||
app.DebugPrintf("Requesting the first thumbnail\n");
|
app.DebugPrintf("Requesting the first thumbnail\n");
|
||||||
// set the save to load
|
// set the save to load
|
||||||
PSAVE_DETAILS pSaveDetails=StorageManager.ReturnSavesInfo();
|
PSAVE_DETAILS pSaveDetails=StorageManager.ReturnSavesInfo();
|
||||||
#ifdef _WINDOWS64
|
|
||||||
C4JStorage::ESaveGameState eLoadStatus=StorageManager.LoadSaveDataThumbnail(&pSaveDetails->SaveInfoA[m_saveDetails[m_iRequestingThumbnailId].saveId],&LoadSaveDataThumbnailReturned,this);
|
|
||||||
#else
|
|
||||||
C4JStorage::ESaveGameState eLoadStatus=StorageManager.LoadSaveDataThumbnail(&pSaveDetails->SaveInfoA[(int)m_iRequestingThumbnailId],&LoadSaveDataThumbnailReturned,this);
|
C4JStorage::ESaveGameState eLoadStatus=StorageManager.LoadSaveDataThumbnail(&pSaveDetails->SaveInfoA[(int)m_iRequestingThumbnailId],&LoadSaveDataThumbnailReturned,this);
|
||||||
#endif
|
|
||||||
|
|
||||||
if(eLoadStatus!=C4JStorage::ESaveGame_GetSaveThumbnail)
|
if(eLoadStatus!=C4JStorage::ESaveGame_GetSaveThumbnail)
|
||||||
{
|
{
|
||||||
|
|
@ -856,16 +909,6 @@ void UIScene_LoadOrJoinMenu::tick()
|
||||||
#ifdef _DURANGO
|
#ifdef _DURANGO
|
||||||
// Already utf16 on durango
|
// Already utf16 on durango
|
||||||
memcpy(u16Message, m_saveDetails[m_iRequestingThumbnailId].UTF16SaveFilename, MAX_SAVEFILENAME_LENGTH);
|
memcpy(u16Message, m_saveDetails[m_iRequestingThumbnailId].UTF16SaveFilename, MAX_SAVEFILENAME_LENGTH);
|
||||||
#elif defined(_WINDOWS64)
|
|
||||||
int result = ::MultiByteToWideChar(
|
|
||||||
CP_UTF8, // convert from UTF-8
|
|
||||||
MB_ERR_INVALID_CHARS, // error on invalid chars
|
|
||||||
m_saveDetails[m_iRequestingThumbnailId].UTF8SaveFilename, // source UTF-8 string
|
|
||||||
MAX_SAVEFILENAME_LENGTH, // total length of source UTF-8 string,
|
|
||||||
// in CHAR's (= bytes), including end-of-string \0
|
|
||||||
(wchar_t *)u16Message, // destination buffer
|
|
||||||
MAX_SAVEFILENAME_LENGTH // size of destination buffer, in WCHAR's
|
|
||||||
);
|
|
||||||
#else
|
#else
|
||||||
#ifdef __PS3
|
#ifdef __PS3
|
||||||
size_t srcmax,dstmax;
|
size_t srcmax,dstmax;
|
||||||
|
|
@ -897,11 +940,7 @@ void UIScene_LoadOrJoinMenu::tick()
|
||||||
app.DebugPrintf("Requesting another thumbnail\n");
|
app.DebugPrintf("Requesting another thumbnail\n");
|
||||||
// set the save to load
|
// set the save to load
|
||||||
PSAVE_DETAILS pSaveDetails=StorageManager.ReturnSavesInfo();
|
PSAVE_DETAILS pSaveDetails=StorageManager.ReturnSavesInfo();
|
||||||
#ifdef _WINDOWS64
|
|
||||||
C4JStorage::ESaveGameState eLoadStatus=StorageManager.LoadSaveDataThumbnail(&pSaveDetails->SaveInfoA[m_saveDetails[m_iRequestingThumbnailId].saveId],&LoadSaveDataThumbnailReturned,this);
|
|
||||||
#else
|
|
||||||
C4JStorage::ESaveGameState eLoadStatus=StorageManager.LoadSaveDataThumbnail(&pSaveDetails->SaveInfoA[(int)m_iRequestingThumbnailId],&LoadSaveDataThumbnailReturned,this);
|
C4JStorage::ESaveGameState eLoadStatus=StorageManager.LoadSaveDataThumbnail(&pSaveDetails->SaveInfoA[(int)m_iRequestingThumbnailId],&LoadSaveDataThumbnailReturned,this);
|
||||||
#endif
|
|
||||||
if(eLoadStatus!=C4JStorage::ESaveGame_GetSaveThumbnail)
|
if(eLoadStatus!=C4JStorage::ESaveGame_GetSaveThumbnail)
|
||||||
{
|
{
|
||||||
// something went wrong
|
// something went wrong
|
||||||
|
|
@ -921,6 +960,7 @@ void UIScene_LoadOrJoinMenu::tick()
|
||||||
m_bRetrievingSaveThumbnails = false;
|
m_bRetrievingSaveThumbnails = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(m_iState)
|
switch(m_iState)
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,225 @@ void CConsoleMinecraftApp::FatalLoadError()
|
||||||
void CConsoleMinecraftApp::CaptureSaveThumbnail()
|
void CConsoleMinecraftApp::CaptureSaveThumbnail()
|
||||||
{
|
{
|
||||||
RenderManager.CaptureThumbnail(&m_ThumbnailBuffer);
|
RenderManager.CaptureThumbnail(&m_ThumbnailBuffer);
|
||||||
|
|
||||||
|
// CaptureThumbnail in the render lib doesn't produce data on Win64,
|
||||||
|
// so grab the back buffer via D3D11 and encode a PNG manually
|
||||||
|
if (!m_ThumbnailBuffer.Allocated())
|
||||||
|
{
|
||||||
|
extern ID3D11Device* g_pd3dDevice;
|
||||||
|
extern ID3D11DeviceContext* g_pImmediateContext;
|
||||||
|
extern IDXGISwapChain* g_pSwapChain;
|
||||||
|
|
||||||
|
if (!g_pd3dDevice || !g_pImmediateContext || !g_pSwapChain)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ID3D11Texture2D* pBackBuffer = nullptr;
|
||||||
|
HRESULT hr = g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
|
||||||
|
if (FAILED(hr) || !pBackBuffer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
D3D11_TEXTURE2D_DESC bbDesc;
|
||||||
|
pBackBuffer->GetDesc(&bbDesc);
|
||||||
|
|
||||||
|
// 64x64 to match other platforms
|
||||||
|
const int THUMB_W = 64;
|
||||||
|
const int THUMB_H = 64;
|
||||||
|
|
||||||
|
// staging texture for CPU readback
|
||||||
|
D3D11_TEXTURE2D_DESC stagingDesc = {};
|
||||||
|
stagingDesc.Width = bbDesc.Width;
|
||||||
|
stagingDesc.Height = bbDesc.Height;
|
||||||
|
stagingDesc.MipLevels = 1;
|
||||||
|
stagingDesc.ArraySize = 1;
|
||||||
|
stagingDesc.Format = bbDesc.Format;
|
||||||
|
stagingDesc.SampleDesc.Count = 1;
|
||||||
|
stagingDesc.Usage = D3D11_USAGE_STAGING;
|
||||||
|
stagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||||
|
|
||||||
|
ID3D11Texture2D* pStaging = nullptr;
|
||||||
|
|
||||||
|
// resolve MSAA if needed
|
||||||
|
if (bbDesc.SampleDesc.Count > 1)
|
||||||
|
{
|
||||||
|
D3D11_TEXTURE2D_DESC resolveDesc = bbDesc;
|
||||||
|
resolveDesc.SampleDesc.Count = 1;
|
||||||
|
resolveDesc.SampleDesc.Quality = 0;
|
||||||
|
resolveDesc.Usage = D3D11_USAGE_DEFAULT;
|
||||||
|
resolveDesc.BindFlags = 0;
|
||||||
|
resolveDesc.CPUAccessFlags = 0;
|
||||||
|
|
||||||
|
ID3D11Texture2D* pResolved = nullptr;
|
||||||
|
hr = g_pd3dDevice->CreateTexture2D(&resolveDesc, nullptr, &pResolved);
|
||||||
|
if (SUCCEEDED(hr))
|
||||||
|
{
|
||||||
|
g_pImmediateContext->ResolveSubresource(pResolved, 0, pBackBuffer, 0, bbDesc.Format);
|
||||||
|
hr = g_pd3dDevice->CreateTexture2D(&stagingDesc, nullptr, &pStaging);
|
||||||
|
if (SUCCEEDED(hr))
|
||||||
|
g_pImmediateContext->CopyResource(pStaging, pResolved);
|
||||||
|
pResolved->Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hr = g_pd3dDevice->CreateTexture2D(&stagingDesc, nullptr, &pStaging);
|
||||||
|
if (SUCCEEDED(hr))
|
||||||
|
g_pImmediateContext->CopyResource(pStaging, pBackBuffer);
|
||||||
|
}
|
||||||
|
pBackBuffer->Release();
|
||||||
|
|
||||||
|
if (!pStaging)
|
||||||
|
return;
|
||||||
|
|
||||||
|
D3D11_MAPPED_SUBRESOURCE mapped;
|
||||||
|
hr = g_pImmediateContext->Map(pStaging, 0, D3D11_MAP_READ, 0, &mapped);
|
||||||
|
if (FAILED(hr))
|
||||||
|
{
|
||||||
|
pStaging->Release();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// nearest-neighbor downsample to 64x64
|
||||||
|
BYTE* thumbPixels = new BYTE[THUMB_W * THUMB_H * 4];
|
||||||
|
for (int y = 0; y < THUMB_H; y++)
|
||||||
|
{
|
||||||
|
int srcY = y * (int)bbDesc.Height / THUMB_H;
|
||||||
|
BYTE* srcRow = (BYTE*)mapped.pData + srcY * mapped.RowPitch;
|
||||||
|
for (int x = 0; x < THUMB_W; x++)
|
||||||
|
{
|
||||||
|
int srcX = x * (int)bbDesc.Width / THUMB_W;
|
||||||
|
BYTE* src = srcRow + srcX * 4; // assumes BGRA/RGBA 32bpp
|
||||||
|
BYTE* dst = thumbPixels + (y * THUMB_W + x) * 4;
|
||||||
|
dst[0] = src[2]; // R (from B in BGRA)
|
||||||
|
dst[1] = src[1]; // G
|
||||||
|
dst[2] = src[0]; // B (from R in BGRA)
|
||||||
|
dst[3] = 255; // A
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_pImmediateContext->Unmap(pStaging, 0);
|
||||||
|
pStaging->Release();
|
||||||
|
|
||||||
|
// encode uncompressed PNG (IHDR + stored IDAT + IEND)
|
||||||
|
int rawRowSize = 1 + THUMB_W * 3; // filter byte + RGB
|
||||||
|
int rawSize = rawRowSize * THUMB_H;
|
||||||
|
|
||||||
|
// single stored deflate block (rawSize fits in 16 bits)
|
||||||
|
int deflateSize = 5 + rawSize; // 5-byte block header + data
|
||||||
|
if (rawSize > 65535)
|
||||||
|
{
|
||||||
|
// shouldn't happen at 64x64 but bail just in case
|
||||||
|
delete[] thumbPixels;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int idatDataSize = 2 + deflateSize + 4; // zlib header(2) + deflate + adler32(4)
|
||||||
|
int pngSize = 8 + (12 + 13) + (12 + idatDataSize) + 12; // sig + IHDR + IDAT + IEND
|
||||||
|
|
||||||
|
BYTE* png = (BYTE*)malloc(pngSize);
|
||||||
|
if (!png)
|
||||||
|
{
|
||||||
|
delete[] thumbPixels;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int pos = 0;
|
||||||
|
|
||||||
|
// PNG signature
|
||||||
|
static const BYTE sig[8] = {0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A};
|
||||||
|
memcpy(png + pos, sig, 8); pos += 8;
|
||||||
|
|
||||||
|
// big-endian write helper
|
||||||
|
auto writeBE32 = [&](int offset, unsigned int val) {
|
||||||
|
png[offset+0] = (val >> 24) & 0xFF;
|
||||||
|
png[offset+1] = (val >> 16) & 0xFF;
|
||||||
|
png[offset+2] = (val >> 8) & 0xFF;
|
||||||
|
png[offset+3] = (val ) & 0xFF;
|
||||||
|
};
|
||||||
|
|
||||||
|
// CRC32 for PNG chunks
|
||||||
|
static unsigned int crcTable[256];
|
||||||
|
static bool crcInit = false;
|
||||||
|
if (!crcInit)
|
||||||
|
{
|
||||||
|
for (int n = 0; n < 256; n++)
|
||||||
|
{
|
||||||
|
unsigned int c = n;
|
||||||
|
for (int k = 0; k < 8; k++)
|
||||||
|
c = (c & 1) ? 0xEDB88320 ^ (c >> 1) : c >> 1;
|
||||||
|
crcTable[n] = c;
|
||||||
|
}
|
||||||
|
crcInit = true;
|
||||||
|
}
|
||||||
|
auto crc32 = [&](BYTE* data, int len) -> unsigned int {
|
||||||
|
unsigned int c = 0xFFFFFFFF;
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
|
c = crcTable[(c ^ data[i]) & 0xFF] ^ (c >> 8);
|
||||||
|
return c ^ 0xFFFFFFFF;
|
||||||
|
};
|
||||||
|
|
||||||
|
// IHDR chunk
|
||||||
|
writeBE32(pos, 13); pos += 4; // length
|
||||||
|
int ihdrStart = pos;
|
||||||
|
png[pos++]='I'; png[pos++]='H'; png[pos++]='D'; png[pos++]='R';
|
||||||
|
writeBE32(pos, THUMB_W); pos += 4;
|
||||||
|
writeBE32(pos, THUMB_H); pos += 4;
|
||||||
|
png[pos++] = 8; // bit depth
|
||||||
|
png[pos++] = 2; // color type RGB
|
||||||
|
png[pos++] = 0; // compression
|
||||||
|
png[pos++] = 0; // filter
|
||||||
|
png[pos++] = 0; // interlace
|
||||||
|
writeBE32(pos, crc32(png + ihdrStart, 17)); pos += 4;
|
||||||
|
|
||||||
|
// IDAT chunk
|
||||||
|
writeBE32(pos, idatDataSize); pos += 4;
|
||||||
|
int idatStart = pos;
|
||||||
|
png[pos++]='I'; png[pos++]='D'; png[pos++]='A'; png[pos++]='T';
|
||||||
|
|
||||||
|
// zlib header (no compression)
|
||||||
|
png[pos++] = 0x78; png[pos++] = 0x01;
|
||||||
|
|
||||||
|
// Deflate stored block (final)
|
||||||
|
png[pos++] = 0x01; // final block, stored
|
||||||
|
png[pos++] = (rawSize ) & 0xFF;
|
||||||
|
png[pos++] = (rawSize >> 8) & 0xFF;
|
||||||
|
png[pos++] = ~rawSize & 0xFF;
|
||||||
|
png[pos++] = (~rawSize >> 8) & 0xFF;
|
||||||
|
|
||||||
|
// Raw PNG data (filter=None for each row, RGB)
|
||||||
|
unsigned int adlerA = 1, adlerB = 0;
|
||||||
|
for (int y = 0; y < THUMB_H; y++)
|
||||||
|
{
|
||||||
|
png[pos++] = 0; // filter byte = None
|
||||||
|
adlerA = (adlerA + 0) % 65521;
|
||||||
|
adlerB = (adlerB + adlerA) % 65521;
|
||||||
|
for (int x = 0; x < THUMB_W; x++)
|
||||||
|
{
|
||||||
|
BYTE* px = thumbPixels + (y * THUMB_W + x) * 4;
|
||||||
|
for (int c = 0; c < 3; c++)
|
||||||
|
{
|
||||||
|
png[pos++] = px[c];
|
||||||
|
adlerA = (adlerA + px[c]) % 65521;
|
||||||
|
adlerB = (adlerB + adlerA) % 65521;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Adler32
|
||||||
|
unsigned int adler = (adlerB << 16) | adlerA;
|
||||||
|
writeBE32(pos, adler); pos += 4;
|
||||||
|
// IDAT CRC
|
||||||
|
writeBE32(pos, crc32(png + idatStart, 4 + idatDataSize)); pos += 4;
|
||||||
|
|
||||||
|
// IEND chunk
|
||||||
|
writeBE32(pos, 0); pos += 4;
|
||||||
|
int iendStart = pos;
|
||||||
|
png[pos++]='I'; png[pos++]='E'; png[pos++]='N'; png[pos++]='D';
|
||||||
|
writeBE32(pos, crc32(png + iendStart, 4)); pos += 4;
|
||||||
|
|
||||||
|
delete[] thumbPixels;
|
||||||
|
|
||||||
|
m_ThumbnailBuffer.m_type = ImageFileBuffer::e_typePNG;
|
||||||
|
m_ThumbnailBuffer.m_pBuffer = png;
|
||||||
|
m_ThumbnailBuffer.m_bufferSize = pos;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void CConsoleMinecraftApp::GetSaveThumbnail(PBYTE *pbData,DWORD *pdwSize)
|
void CConsoleMinecraftApp::GetSaveThumbnail(PBYTE *pbData,DWORD *pdwSize)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,11 @@ static BackgroundSaveResult s_bgResult;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef _WINDOWS64
|
||||||
|
static PBYTE s_pendingThumbData = nullptr;
|
||||||
|
static DWORD s_pendingThumbSize = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef _XBOX
|
#ifdef _XBOX
|
||||||
#define RESERVE_ALLOCATION MEM_RESERVE | MEM_LARGE_PAGES
|
#define RESERVE_ALLOCATION MEM_RESERVE | MEM_LARGE_PAGES
|
||||||
#define COMMIT_ALLOCATION MEM_COMMIT | MEM_LARGE_PAGES
|
#define COMMIT_ALLOCATION MEM_COMMIT | MEM_LARGE_PAGES
|
||||||
|
|
@ -869,6 +874,17 @@ void ConsoleSaveFileOriginal::Flush(bool autosave, bool updateThumbnail )
|
||||||
app.GetSaveThumbnail(&pbThumbnailData,&dwThumbnailDataSize,&pbDataSaveImage,&dwDataSizeSaveImage);
|
app.GetSaveThumbnail(&pbThumbnailData,&dwThumbnailDataSize,&pbDataSaveImage,&dwDataSizeSaveImage);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef _WINDOWS64
|
||||||
|
// stash the thumbnail so the save callback can write it to disk once
|
||||||
|
// the storage lib has actually created the folder and saveData.ms
|
||||||
|
if (pbThumbnailData && dwThumbnailDataSize > 0)
|
||||||
|
{
|
||||||
|
s_pendingThumbData = new BYTE[dwThumbnailDataSize];
|
||||||
|
memcpy(s_pendingThumbData, pbThumbnailData, dwThumbnailDataSize);
|
||||||
|
s_pendingThumbSize = dwThumbnailDataSize;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
BYTE bTextMetadata[88];
|
BYTE bTextMetadata[88];
|
||||||
ZeroMemory(bTextMetadata,88);
|
ZeroMemory(bTextMetadata,88);
|
||||||
|
|
||||||
|
|
@ -939,6 +955,62 @@ int ConsoleSaveFileOriginal::SaveSaveDataCallback(LPVOID lpParam,bool bRes)
|
||||||
s_bgSaveActive.store(false, std::memory_order_release);
|
s_bgSaveActive.store(false, std::memory_order_release);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef _WINDOWS64
|
||||||
|
// GetSaveUniqueFilename returns empty on Win64, so find the save folder
|
||||||
|
// by looking for whichever saveData.ms was written most recently
|
||||||
|
if (s_pendingThumbData && s_pendingThumbSize > 0)
|
||||||
|
{
|
||||||
|
WIN32_FIND_DATAA fd;
|
||||||
|
HANDLE hFind = FindFirstFileA("Windows64\\GameHDD\\*", &fd);
|
||||||
|
if (hFind != INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
FILETIME newestTime = {};
|
||||||
|
char newestDir[MAX_PATH] = {};
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
|
||||||
|
fd.cFileName[0] != '.')
|
||||||
|
{
|
||||||
|
// Check saveData.ms modification time in this folder
|
||||||
|
char msPath[MAX_PATH];
|
||||||
|
sprintf_s(msPath, MAX_PATH, "Windows64\\GameHDD\\%s\\saveData.ms", fd.cFileName);
|
||||||
|
WIN32_FIND_DATAA msfd;
|
||||||
|
HANDLE hMs = FindFirstFileA(msPath, &msfd);
|
||||||
|
if (hMs != INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
if (CompareFileTime(&msfd.ftLastWriteTime, &newestTime) > 0)
|
||||||
|
{
|
||||||
|
newestTime = msfd.ftLastWriteTime;
|
||||||
|
strcpy_s(newestDir, MAX_PATH, fd.cFileName);
|
||||||
|
}
|
||||||
|
FindClose(hMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (FindNextFileA(hFind, &fd));
|
||||||
|
FindClose(hFind);
|
||||||
|
|
||||||
|
if (newestDir[0] != '\0')
|
||||||
|
{
|
||||||
|
char thumbPath[MAX_PATH];
|
||||||
|
sprintf_s(thumbPath, MAX_PATH, "Windows64\\GameHDD\\%s\\thumbnail.png", newestDir);
|
||||||
|
HANDLE hFile = CreateFileA(thumbPath, GENERIC_WRITE, 0, nullptr,
|
||||||
|
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||||
|
if (hFile != INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
DWORD bytesWritten = 0;
|
||||||
|
WriteFile(hFile, s_pendingThumbData, s_pendingThumbSize, &bytesWritten, nullptr);
|
||||||
|
CloseHandle(hFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] s_pendingThumbData;
|
||||||
|
s_pendingThumbData = nullptr;
|
||||||
|
s_pendingThumbSize = 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue