feat: game can now save and display thumbnails, code rebuilt from the xbox one edition of the game
fix: Renderer::CaptureThumbnail now can capture thumbnails correctly fix: renderTargetViews and renderTargetShaderResourceViews are no longer null fix: texture saving functions were using BGRA instead of RGBA format
This commit is contained in:
@@ -470,7 +470,9 @@ public:
|
|||||||
std::unordered_map<int, ID3D11RasterizerState *> managedRasterizerStates;
|
std::unordered_map<int, ID3D11RasterizerState *> managedRasterizerStates;
|
||||||
bool m_bShouldScreenGrabNextFrame;
|
bool m_bShouldScreenGrabNextFrame;
|
||||||
bool m_bSuspended;
|
bool m_bSuspended;
|
||||||
BYTE paddingAfterSuspendState[2];
|
|
||||||
|
// @Patoke add
|
||||||
|
ID3D11Texture2D *m_backBufferTexture = NULL;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Singleton
|
// Singleton
|
||||||
|
|||||||
@@ -263,6 +263,14 @@ void Renderer::CaptureThumbnail(ImageFileBuffer *pngOut)
|
|||||||
{
|
{
|
||||||
Renderer::Context &c = getContext();
|
Renderer::Context &c = getContext();
|
||||||
|
|
||||||
|
// @Patoke fix: bind render target to a proper backbuffer texture
|
||||||
|
ID3D11Resource *actualBackBuffer = NULL;
|
||||||
|
renderTargetView->GetResource(&actualBackBuffer);
|
||||||
|
|
||||||
|
// copy the backbuffer contents
|
||||||
|
c.m_pDeviceContext->CopyResource(m_backBufferTexture, actualBackBuffer);
|
||||||
|
actualBackBuffer->Release();
|
||||||
|
|
||||||
float left;
|
float left;
|
||||||
float bottom;
|
float bottom;
|
||||||
float right;
|
float right;
|
||||||
@@ -331,25 +339,25 @@ void Renderer::CaptureThumbnail(ImageFileBuffer *pngOut)
|
|||||||
float aspectRatio = IsWidescreen() ? (16.0f / 9.0f) : (4.0f / 3.0f);
|
float aspectRatio = IsWidescreen() ? (16.0f / 9.0f) : (4.0f / 3.0f);
|
||||||
|
|
||||||
right *= aspectRatio;
|
right *= aspectRatio;
|
||||||
left *= aspectRatio;
|
left *= aspectRatio;
|
||||||
|
|
||||||
float width = right - left;
|
float width = right - left;
|
||||||
float height = top - bottom;
|
float height = top - bottom;
|
||||||
|
|
||||||
if (height > width)
|
if (height > width)
|
||||||
{
|
{
|
||||||
float diff = (height - width) * 0.5f;
|
float diff = (height - width) * 0.5f;
|
||||||
bottom += diff;
|
bottom += diff;
|
||||||
top -= diff;
|
top -= diff;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
float diff = (width - height) * 0.5f;
|
float diff = (width - height) * 0.5f;
|
||||||
left += diff;
|
left += diff;
|
||||||
right -= diff;
|
right -= diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
left /= aspectRatio;
|
left /= aspectRatio;
|
||||||
right /= aspectRatio;
|
right /= aspectRatio;
|
||||||
|
|
||||||
ID3D11BlendState *blendState = NULL;
|
ID3D11BlendState *blendState = NULL;
|
||||||
@@ -410,7 +418,10 @@ void Renderer::CaptureThumbnail(ImageFileBuffer *pngOut)
|
|||||||
blendState->Release();
|
blendState->Release();
|
||||||
depthState->Release();
|
depthState->Release();
|
||||||
rasterizerState->Release();
|
rasterizerState->Release();
|
||||||
c.m_pDeviceContext->PSSetShaderResources(0, 0, NULL);
|
|
||||||
|
// @Patoke add: just to make sure, set the render target shader resource to null to avoid any potential read/write hazards
|
||||||
|
ID3D11ShaderResourceView *nullSRV[1] = {nullptr};
|
||||||
|
c.m_pDeviceContext->PSSetShaderResources(0, 1, nullSRV);
|
||||||
|
|
||||||
for (UINT i = 0; i < MAX_MIP_LEVELS - 1; ++i)
|
for (UINT i = 0; i < MAX_MIP_LEVELS - 1; ++i)
|
||||||
{
|
{
|
||||||
@@ -422,6 +433,8 @@ void Renderer::CaptureThumbnail(ImageFileBuffer *pngOut)
|
|||||||
viewport.MinDepth = 0.0f;
|
viewport.MinDepth = 0.0f;
|
||||||
viewport.MaxDepth = 1.0f;
|
viewport.MaxDepth = 1.0f;
|
||||||
|
|
||||||
|
c.m_pDeviceContext->PSSetShaderResources(0, 1, nullSRV);
|
||||||
|
|
||||||
c.m_pDeviceContext->OMSetRenderTargets(1, &renderTargetViews[i], NULL);
|
c.m_pDeviceContext->OMSetRenderTargets(1, &renderTargetViews[i], NULL);
|
||||||
c.m_pDeviceContext->RSSetViewports(1, &viewport);
|
c.m_pDeviceContext->RSSetViewports(1, &viewport);
|
||||||
|
|
||||||
@@ -433,23 +446,31 @@ void Renderer::CaptureThumbnail(ImageFileBuffer *pngOut)
|
|||||||
D3D11_MAPPED_SUBRESOURCE mapped = {};
|
D3D11_MAPPED_SUBRESOURCE mapped = {};
|
||||||
c.m_pDeviceContext->Map(c.m_thumbnailBoundsBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped);
|
c.m_pDeviceContext->Map(c.m_thumbnailBoundsBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped);
|
||||||
|
|
||||||
float* constants = static_cast<float*>(mapped.pData);
|
float *constants = static_cast<float *>(mapped.pData);
|
||||||
|
// @Patoke fix: the shader code zooms by 2x every iteration, so we keep zooming in over and over again if we use 1.0f, so we adjust by
|
||||||
|
// doing 2.0f like a boss
|
||||||
if (i == 0)
|
if (i == 0)
|
||||||
{
|
{
|
||||||
constants[0] = left;
|
constants[0] = left;
|
||||||
constants[1] = bottom;
|
constants[1] = bottom;
|
||||||
constants[2] = right - left;
|
constants[2] = (right - left) * 2.0f;
|
||||||
constants[3] = top - bottom;
|
constants[3] = (top - bottom) * 2.0f;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
constants[0] = 0.0f;
|
constants[0] = 0.0f;
|
||||||
constants[1] = 0.0f;
|
constants[1] = 0.0f;
|
||||||
constants[2] = 1.0f;
|
constants[2] = 2.0f;
|
||||||
constants[3] = 1.0f;
|
constants[3] = 2.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
c.m_pDeviceContext->Unmap(c.m_thumbnailBoundsBuffer, 0);
|
c.m_pDeviceContext->Unmap(c.m_thumbnailBoundsBuffer, 0);
|
||||||
|
|
||||||
|
// @Patoke fix: the shader expects the bounds buffer to be at slot 9 for vertex shader and slot 0 for pixel shader, so we need to set it there
|
||||||
|
// instead of the usual slot 0
|
||||||
|
c.m_pDeviceContext->VSSetConstantBuffers(9, 1, &c.m_thumbnailBoundsBuffer);
|
||||||
|
c.m_pDeviceContext->PSSetConstantBuffers(0, 1, &c.m_thumbnailBoundsBuffer);
|
||||||
|
|
||||||
c.m_pDeviceContext->Draw(4, 0);
|
c.m_pDeviceContext->Draw(4, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,26 +490,21 @@ void Renderer::CaptureThumbnail(ImageFileBuffer *pngOut)
|
|||||||
c.m_pDeviceContext->CopyResource(stagingTexture, renderTargetTextures[MAX_MIP_LEVELS - 2]);
|
c.m_pDeviceContext->CopyResource(stagingTexture, renderTargetTextures[MAX_MIP_LEVELS - 2]);
|
||||||
|
|
||||||
D3D11_MAPPED_SUBRESOURCE mapped = {};
|
D3D11_MAPPED_SUBRESOURCE mapped = {};
|
||||||
c.m_pDeviceContext->Map(stagingTexture, 0, D3D11_MAP_READ_WRITE, 0, &mapped);
|
if (SUCCEEDED(c.m_pDeviceContext->Map(stagingTexture, 0, D3D11_MAP_READ, 0, &mapped)))
|
||||||
const unsigned char* src = static_cast<const unsigned char*>(mapped.pData);
|
|
||||||
unsigned char* dst = linearData;
|
|
||||||
|
|
||||||
for (UINT y = 0; y < kThumbnailSize; ++y)
|
|
||||||
{
|
{
|
||||||
std::memcpy(dst, src, stride);
|
for (UINT y = 0; y < kThumbnailSize; ++y)
|
||||||
|
|
||||||
unsigned char* alpha = dst + 3;
|
|
||||||
for (UINT x = 0; x < kThumbnailSize; ++x)
|
|
||||||
{
|
{
|
||||||
*alpha = 0xFF;
|
unsigned char *dstRow = linearData + (y * stride);
|
||||||
alpha += 4;
|
const unsigned char *srcRow = reinterpret_cast<const unsigned char *>(mapped.pData) + (y * mapped.RowPitch);
|
||||||
|
|
||||||
|
std::memcpy(dstRow, srcRow, stride);
|
||||||
|
for (UINT x = 0; x < kThumbnailSize; ++x)
|
||||||
|
{
|
||||||
|
dstRow[(x * 4) + 3] = 0xFF;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
c.m_pDeviceContext->Unmap(stagingTexture, 0);
|
||||||
src += mapped.RowPitch;
|
|
||||||
dst += stride;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.m_pDeviceContext->Unmap(stagingTexture, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ConvertLinearToPng(pngOut, linearData, kThumbnailSize, kThumbnailSize);
|
ConvertLinearToPng(pngOut, linearData, kThumbnailSize, kThumbnailSize);
|
||||||
@@ -708,10 +724,24 @@ void Renderer::Initialise(ID3D11Device *pDevice, IDXGISwapChain *pSwapChain)
|
|||||||
backBufferTexture->GetDesc(&backDesc);
|
backBufferTexture->GetDesc(&backDesc);
|
||||||
backBufferWidth = backDesc.Width;
|
backBufferWidth = backDesc.Width;
|
||||||
backBufferHeight = backDesc.Height;
|
backBufferHeight = backDesc.Height;
|
||||||
renderTargetTextures[0] = backBufferTexture;
|
|
||||||
|
|
||||||
m_pDevice->CreateRenderTargetView(backBufferTexture, &rtvDesc, &renderTargetView);
|
// @Patoke fix: we can't bind the backbuffer directly as a shader resource, so we create a new texture with the same dimensions and format and
|
||||||
m_pDevice->CreateShaderResourceView(backBufferTexture, &srvDesc, &renderTargetShaderResourceView);
|
// copy the backbuffer contents to it, then we can bind that texture as a shader resource for the thumbnail generation
|
||||||
|
D3D11_TEXTURE2D_DESC safeDesc = backDesc;
|
||||||
|
safeDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||||
|
safeDesc.Usage = D3D11_USAGE_DEFAULT;
|
||||||
|
safeDesc.SampleDesc.Count = 1; // no MSAA
|
||||||
|
safeDesc.SampleDesc.Quality = 0;
|
||||||
|
|
||||||
|
m_pDevice->CreateTexture2D(&safeDesc, NULL, &m_backBufferTexture);
|
||||||
|
|
||||||
|
m_pDevice->CreateShaderResourceView(m_backBufferTexture, NULL, &renderTargetShaderResourceView);
|
||||||
|
|
||||||
|
renderTargetTextures[0] = m_backBufferTexture;
|
||||||
|
|
||||||
|
//m_pDevice->CreateRenderTargetView(backBufferTexture, &rtvDesc, &renderTargetView);
|
||||||
|
|
||||||
|
backBufferTexture->Release();
|
||||||
backBufferResource->Release();
|
backBufferResource->Release();
|
||||||
|
|
||||||
D3D11_TEXTURE2D_DESC desc = {};
|
D3D11_TEXTURE2D_DESC desc = {};
|
||||||
@@ -731,9 +761,10 @@ void Renderer::Initialise(ID3D11Device *pDevice, IDXGISwapChain *pSwapChain)
|
|||||||
desc.Width = s_auiWidths[i + 1];
|
desc.Width = s_auiWidths[i + 1];
|
||||||
desc.Height = s_auiHeights[i + 1];
|
desc.Height = s_auiHeights[i + 1];
|
||||||
|
|
||||||
|
// @Patoke fix: before these would fail and our views would be nullptrs
|
||||||
m_pDevice->CreateTexture2D(&desc, NULL, &renderTargetTextures[i]);
|
m_pDevice->CreateTexture2D(&desc, NULL, &renderTargetTextures[i]);
|
||||||
m_pDevice->CreateRenderTargetView(renderTargetTextures[i], &rtvDesc, &renderTargetViews[i]);
|
m_pDevice->CreateRenderTargetView(renderTargetTextures[i], NULL, &renderTargetViews[i]);
|
||||||
m_pDevice->CreateShaderResourceView(renderTargetTextures[i], &srvDesc, &renderTargetShaderResourceViews[i]);
|
m_pDevice->CreateShaderResourceView(renderTargetTextures[i], NULL, &renderTargetShaderResourceViews[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::memset(m_textures, 0, sizeof(m_textures));
|
std::memset(m_textures, 0, sizeof(m_textures));
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ HRESULT Renderer::SaveTextureData(const char* szFilename, D3DXIMAGE_INFO* pSrcIn
|
|||||||
image.width = pSrcInfo->Width;
|
image.width = pSrcInfo->Width;
|
||||||
image.height = pSrcInfo->Height;
|
image.height = pSrcInfo->Height;
|
||||||
image.version = PNG_IMAGE_VERSION;
|
image.version = PNG_IMAGE_VERSION;
|
||||||
image.format = PNG_FORMAT_BGRA;
|
image.format = PNG_FORMAT_RGBA;
|
||||||
|
|
||||||
png_image_write_to_file(&image, szFilename, NULL, ppDataOut, NULL, NULL);
|
png_image_write_to_file(&image, szFilename, NULL, ppDataOut, NULL, NULL);
|
||||||
return S_OK;
|
return S_OK;
|
||||||
@@ -111,7 +111,7 @@ HRESULT Renderer::SaveTextureDataToMemory(void* pOutput, int outputCapacity, int
|
|||||||
image.height = height;
|
image.height = height;
|
||||||
dataEnd = (BYTE *)pOutput + outputCapacity;
|
dataEnd = (BYTE *)pOutput + outputCapacity;
|
||||||
image.version = PNG_IMAGE_VERSION;
|
image.version = PNG_IMAGE_VERSION;
|
||||||
image.format = PNG_FORMAT_BGRA;
|
image.format = PNG_FORMAT_RGBA;
|
||||||
dataStart = (BYTE*)pOutput;
|
dataStart = (BYTE*)pOutput;
|
||||||
dataCurr = (BYTE*)pOutput;
|
dataCurr = (BYTE*)pOutput;
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ SOFTWARE.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "STO_SaveGame.h"
|
#include "STO_SaveGame.h"
|
||||||
|
#include "STO_Main.h"
|
||||||
|
|
||||||
|
// @Patoke add
|
||||||
|
char CSaveGame::szPNGHeader[] = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A";
|
||||||
|
|
||||||
CSaveGame::CSaveGame()
|
CSaveGame::CSaveGame()
|
||||||
{
|
{
|
||||||
@@ -43,6 +47,12 @@ CSaveGame::CSaveGame()
|
|||||||
GetCurrentDirectoryA(sizeof(dirName), dirName);
|
GetCurrentDirectoryA(sizeof(dirName), dirName);
|
||||||
sprintf(curDir, "%s/Windows64/GameHDD/", dirName);
|
sprintf(curDir, "%s/Windows64/GameHDD/", dirName);
|
||||||
CreateDirectoryA(curDir, 0);
|
CreateDirectoryA(curDir, 0);
|
||||||
|
|
||||||
|
// @Patoke add
|
||||||
|
this->m_pbThumbnailData = nullptr;
|
||||||
|
this->m_uiThumbnailSize = 0;
|
||||||
|
this->m_pbImageData = nullptr;
|
||||||
|
this->m_uiImageSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSaveGame::SetSaveDisabled(bool bDisable)
|
void CSaveGame::SetSaveDisabled(bool bDisable)
|
||||||
@@ -124,7 +134,14 @@ C4JStorage::ESaveGameState CSaveGame::GetSavesInfo(int iPad, int (*Func)(LPVOID
|
|||||||
sprintf(fileName, "%s\\Windows64\\GameHDD\\%s\\saveData.ms", dirName, findFileData.cFileName);
|
sprintf(fileName, "%s\\Windows64\\GameHDD\\%s\\saveData.ms", dirName, findFileData.cFileName);
|
||||||
|
|
||||||
GetFileAttributesExA(fileName, GetFileExInfoStandard, &fileInfoBuffer);
|
GetFileAttributesExA(fileName, GetFileExInfoStandard, &fileInfoBuffer);
|
||||||
m_pSaveDetails->SaveInfoA[i++].metaData.dataSize = fileInfoBuffer.nFileSizeLow;
|
m_pSaveDetails->SaveInfoA[i].metaData.dataSize = fileInfoBuffer.nFileSizeLow;
|
||||||
|
|
||||||
|
char thumbName[280];
|
||||||
|
sprintf(thumbName, "%s\\Windows64\\GameHDD\\%s\\thumbData.png", dirName, findFileData.cFileName);
|
||||||
|
|
||||||
|
GetFileAttributesExA(thumbName, GetFileExInfoStandard, &fileInfoBuffer);
|
||||||
|
m_pSaveDetails->SaveInfoA[i++].metaData.thumbnailSize = fileInfoBuffer.nFileSizeLow;
|
||||||
|
|
||||||
m_pSaveDetails->iSaveC++;
|
m_pSaveDetails->iSaveC++;
|
||||||
}
|
}
|
||||||
} while (FindNextFileA(fi, &findFileData));
|
} while (FindNextFileA(fi, &findFileData));
|
||||||
@@ -165,9 +182,46 @@ void CSaveGame::ClearSavesInfo()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Patoke add
|
||||||
C4JStorage::ESaveGameState CSaveGame::LoadSaveDataThumbnail(PSAVE_INFO pSaveInfo,
|
C4JStorage::ESaveGameState CSaveGame::LoadSaveDataThumbnail(PSAVE_INFO pSaveInfo,
|
||||||
int (*Func)(LPVOID lpParam, PBYTE pbThumbnail, DWORD dwThumbnailBytes), LPVOID lpParam)
|
int (*Func)(LPVOID lpParam, PBYTE pbThumbnail, DWORD dwThumbnailBytes), LPVOID lpParam)
|
||||||
{
|
{
|
||||||
|
if (pSaveInfo == nullptr)
|
||||||
|
{
|
||||||
|
return C4JStorage::ESaveGame_Idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD thumbSize = pSaveInfo->metaData.thumbnailSize;
|
||||||
|
if (thumbSize > 0 && pSaveInfo->thumbnailData == nullptr)
|
||||||
|
{
|
||||||
|
char curDir[256];
|
||||||
|
GetCurrentDirectoryA(sizeof(curDir), curDir);
|
||||||
|
|
||||||
|
const char *saveName = (const char *)pSaveInfo;
|
||||||
|
|
||||||
|
char thumbPath[512];
|
||||||
|
sprintf(thumbPath, "%s/Windows64/GameHDD/%s/thumbData.ms", curDir, saveName);
|
||||||
|
|
||||||
|
HANDLE hThumb = CreateFileA(thumbPath, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
|
||||||
|
|
||||||
|
if (hThumb != INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
pSaveInfo->thumbnailData = new BYTE[thumbSize];
|
||||||
|
|
||||||
|
DWORD bytesRead = 0;
|
||||||
|
BOOL res = ReadFile(hThumb, pSaveInfo->thumbnailData, thumbSize, &bytesRead, 0);
|
||||||
|
|
||||||
|
// If the read fails or is incomplete, clean up to prevent corrupted image data
|
||||||
|
if (!res || bytesRead != thumbSize)
|
||||||
|
{
|
||||||
|
delete[] pSaveInfo->thumbnailData;
|
||||||
|
pSaveInfo->thumbnailData = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(hThumb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Func(lpParam, pSaveInfo->thumbnailData, pSaveInfo->metaData.thumbnailSize);
|
Func(lpParam, pSaveInfo->thumbnailData, pSaveInfo->metaData.thumbnailSize);
|
||||||
return C4JStorage::ESaveGame_GetSaveThumbnail;
|
return C4JStorage::ESaveGame_GetSaveThumbnail;
|
||||||
}
|
}
|
||||||
@@ -259,9 +313,118 @@ PVOID CSaveGame::AllocateSaveData(unsigned int uiBytes)
|
|||||||
return m_pSaveData;
|
return m_pSaveData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Patoke add
|
||||||
void CSaveGame::SetSaveImages(PBYTE pbThumbnail, DWORD dwThumbnailBytes, PBYTE pbImage, DWORD dwImageBytes, PBYTE pbTextData, DWORD dwTextDataBytes)
|
void CSaveGame::SetSaveImages(PBYTE pbThumbnail, DWORD dwThumbnailBytes, PBYTE pbImage, DWORD dwImageBytes, PBYTE pbTextData, DWORD dwTextDataBytes)
|
||||||
{
|
{
|
||||||
;
|
if (this->m_pbThumbnailData)
|
||||||
|
{
|
||||||
|
free(this->m_pbThumbnailData);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->m_pbImageData = pbImage;
|
||||||
|
this->m_uiImageSize = dwImageBytes;
|
||||||
|
|
||||||
|
DWORD dwNewThumbnailBytes = dwThumbnailBytes;
|
||||||
|
if (dwTextDataBytes > 0)
|
||||||
|
{
|
||||||
|
// add extra bytes to the allocation for the text chunk (4 bytes for size, 4 bytes for type, 4 bytes for checksum)
|
||||||
|
dwNewThumbnailBytes += dwTextDataBytes + 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate the thumbnail
|
||||||
|
this->m_pbThumbnailData = static_cast<PBYTE>(malloc(dwNewThumbnailBytes));
|
||||||
|
this->m_uiThumbnailSize = dwNewThumbnailBytes;
|
||||||
|
memset(this->m_pbThumbnailData, 0, dwNewThumbnailBytes);
|
||||||
|
|
||||||
|
// copy original thumbnail data to new buffer
|
||||||
|
memcpy(this->m_pbThumbnailData, pbThumbnail, dwThumbnailBytes);
|
||||||
|
|
||||||
|
// inject text metadata into the thumbnail if it exists
|
||||||
|
if (dwTextDataBytes > 0)
|
||||||
|
{
|
||||||
|
this->AddTextFieldToPNG(reinterpret_cast<unsigned __int8 *>(this->m_pbThumbnailData), dwThumbnailBytes, pbTextData, dwTextDataBytes,
|
||||||
|
dwNewThumbnailBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Patoke add
|
||||||
|
void CSaveGame::AddTextFieldToPNG(PBYTE pbImageData, DWORD dwImageBytes, PBYTE pbTextData, DWORD dwTextBytes, DWORD dwTotalSizeAllocated)
|
||||||
|
{
|
||||||
|
if (dwImageBytes == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < 8; ++j)
|
||||||
|
{
|
||||||
|
if (CSaveGame::szPNGHeader[j] != pbImageData[j])
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int offset = 8;
|
||||||
|
while (offset < dwImageBytes)
|
||||||
|
{
|
||||||
|
unsigned int chunkStart = offset;
|
||||||
|
|
||||||
|
unsigned int chunkLength = this->ReverseBytes(*reinterpret_cast<unsigned int *>(&pbImageData[offset]));
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
unsigned int chunkType = this->ReverseBytes(*reinterpret_cast<unsigned int *>(&pbImageData[offset]));
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
if (chunkType == 'IEND')
|
||||||
|
{
|
||||||
|
offset = chunkStart;
|
||||||
|
|
||||||
|
// write the tEXt chunk before the IEND chunk
|
||||||
|
*reinterpret_cast<unsigned int *>(&pbImageData[offset]) = this->ReverseBytes(static_cast<unsigned int>(dwTextBytes));
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
unsigned __int8 *textTypeStart = &pbImageData[offset];
|
||||||
|
*reinterpret_cast<unsigned int *>(&pbImageData[offset]) = this->ReverseBytes('tEXt');
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
memcpy(&pbImageData[offset], pbTextData, dwTextBytes);
|
||||||
|
offset += dwTextBytes;
|
||||||
|
|
||||||
|
unsigned int textCrc = InternalStorageManager.CRC(textTypeStart, dwTextBytes + 4);
|
||||||
|
*reinterpret_cast<unsigned int *>(&pbImageData[offset]) = this->ReverseBytes(textCrc);
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
// add a new IEND chunk
|
||||||
|
*reinterpret_cast<unsigned int *>(&pbImageData[offset]) = 0;
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
unsigned __int8 *iendTypeStart = &pbImageData[offset];
|
||||||
|
*reinterpret_cast<unsigned int *>(&pbImageData[offset]) = this->ReverseBytes('IEND');
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
unsigned int iendCrc = InternalStorageManager.CRC(iendTypeStart, 4);
|
||||||
|
*reinterpret_cast<unsigned int *>(&pbImageData[offset]) = this->ReverseBytes(iendCrc);
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
assert("uiCount <= dwTotalSizeAllocated");
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// not 'IEND' chunk, continue to next chunk
|
||||||
|
offset += chunkLength + 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Patoke add
|
||||||
|
unsigned int CSaveGame::ReverseBytes(unsigned int uiValue)
|
||||||
|
{
|
||||||
|
unsigned int uiReturn = 0;
|
||||||
|
|
||||||
|
uiReturn = (uiValue << 24) | ((uiValue << 8) & 0x00FF0000) | ((uiValue >> 8) & 0x0000FF00) | (uiValue >> 24);
|
||||||
|
|
||||||
|
return uiReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
C4JStorage::ESaveGameState CSaveGame::SaveSaveData(int (*Func)(LPVOID, const bool), LPVOID lpParam)
|
C4JStorage::ESaveGameState CSaveGame::SaveSaveData(int (*Func)(LPVOID, const bool), LPVOID lpParam)
|
||||||
@@ -269,6 +432,8 @@ C4JStorage::ESaveGameState CSaveGame::SaveSaveData(int (*Func)(LPVOID, const boo
|
|||||||
char dirName[256];
|
char dirName[256];
|
||||||
char curDir[256];
|
char curDir[256];
|
||||||
char fileName[280];
|
char fileName[280];
|
||||||
|
char thumbName[280];
|
||||||
|
|
||||||
GetCurrentDirectoryA(sizeof(curDir), curDir);
|
GetCurrentDirectoryA(sizeof(curDir), curDir);
|
||||||
sprintf(dirName, "%s/Windows64/GameHDD/%s", curDir, m_szSaveUniqueName);
|
sprintf(dirName, "%s/Windows64/GameHDD/%s", curDir, m_szSaveUniqueName);
|
||||||
CreateDirectoryA(dirName, 0);
|
CreateDirectoryA(dirName, 0);
|
||||||
@@ -282,6 +447,20 @@ C4JStorage::ESaveGameState CSaveGame::SaveSaveData(int (*Func)(LPVOID, const boo
|
|||||||
|
|
||||||
CloseHandle(h);
|
CloseHandle(h);
|
||||||
|
|
||||||
|
// @Patoke add
|
||||||
|
if (this->m_pbThumbnailData != nullptr && this->m_uiThumbnailSize > 0)
|
||||||
|
{
|
||||||
|
sprintf(thumbName, "%s/thumbData.png", dirName);
|
||||||
|
|
||||||
|
HANDLE hThumb = CreateFileA(thumbName, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
|
||||||
|
|
||||||
|
DWORD thumbBytesWritten = 0;
|
||||||
|
BOOL thumbRes = WriteFile(hThumb, this->m_pbThumbnailData, this->m_uiThumbnailSize, &thumbBytesWritten, 0);
|
||||||
|
_ASSERT(thumbRes && thumbBytesWritten == this->m_uiThumbnailSize);
|
||||||
|
|
||||||
|
CloseHandle(hThumb);
|
||||||
|
}
|
||||||
|
|
||||||
Func(lpParam, true);
|
Func(lpParam, true);
|
||||||
|
|
||||||
return C4JStorage::ESaveGame_Idle;
|
return C4JStorage::ESaveGame_Idle;
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ public:
|
|||||||
|
|
||||||
void CreateSaveUniqueName(void);
|
void CreateSaveUniqueName(void);
|
||||||
|
|
||||||
|
// @Patoke add: definition taken from the Xbox One binaries
|
||||||
|
void AddTextFieldToPNG(PBYTE pbImageData, DWORD dwImageBytes, PBYTE pbTextData, DWORD dwTextBytes, DWORD dwTotalSizeAllocated);
|
||||||
|
unsigned int ReverseBytes(unsigned int uiValue);
|
||||||
|
|
||||||
void *m_pSaveData;
|
void *m_pSaveData;
|
||||||
unsigned int m_uiSaveSize;
|
unsigned int m_uiSaveSize;
|
||||||
char m_szSaveUniqueName[32];
|
char m_szSaveUniqueName[32];
|
||||||
@@ -60,4 +64,13 @@ public:
|
|||||||
bool m_bIsSafeDisabled;
|
bool m_bIsSafeDisabled;
|
||||||
bool m_bHasSaveDetails;
|
bool m_bHasSaveDetails;
|
||||||
SAVE_DETAILS *m_pSaveDetails;
|
SAVE_DETAILS *m_pSaveDetails;
|
||||||
|
|
||||||
|
// @Patoke add
|
||||||
|
PBYTE m_pbThumbnailData;
|
||||||
|
unsigned int m_uiThumbnailSize;
|
||||||
|
|
||||||
|
PBYTE m_pbImageData;
|
||||||
|
unsigned int m_uiImageSize;
|
||||||
|
|
||||||
|
static char szPNGHeader[];
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user