aboutsummaryrefslogtreecommitdiff
path: root/Minecraft.World/ConsoleSaveFileSplit.cpp
diff options
context:
space:
mode:
authorLoki Rautio <lokirautio@gmail.com>2026-03-04 03:56:03 -0600
committerLoki Rautio <lokirautio@gmail.com>2026-03-04 03:56:03 -0600
commit42aec6dac53dffa6afe072560a7e1d4986112538 (patch)
tree0836426857391df1b6a83f6368a183f83ec9b104 /Minecraft.World/ConsoleSaveFileSplit.cpp
parentc9d58eeac7c72f0b3038e084667b4d89a6249fce (diff)
parentef9b6fd500dfabd9463267b0dd9e29577eea8a2b (diff)
Merge branch 'main' into pr/win64-world-saves
# Conflicts: # Minecraft.Client/MinecraftServer.cpp # README.md
Diffstat (limited to 'Minecraft.World/ConsoleSaveFileSplit.cpp')
-rw-r--r--Minecraft.World/ConsoleSaveFileSplit.cpp1711
1 files changed, 0 insertions, 1711 deletions
diff --git a/Minecraft.World/ConsoleSaveFileSplit.cpp b/Minecraft.World/ConsoleSaveFileSplit.cpp
deleted file mode 100644
index 2d83f217..00000000
--- a/Minecraft.World/ConsoleSaveFileSplit.cpp
+++ /dev/null
@@ -1,1711 +0,0 @@
-#include "stdafx.h"
-#include "StringHelpers.h"
-#include "ConsoleSaveFileSplit.h"
-#include "ConsoleSaveFileConverter.h"
-#include "File.h"
-#include <xuiapp.h>
-#include "compression.h"
-#include "..\Minecraft.Client\Minecraft.h"
-#include "..\Minecraft.Client\MinecraftServer.h"
-#include "..\Minecraft.Client\ServerLevel.h"
-#include "..\Minecraft.World\net.minecraft.world.level.h"
-#include "..\Minecraft.World\LevelData.h"
-#include "..\Minecraft.Client\Common\GameRules\LevelGenerationOptions.h"
-#include "..\Minecraft.World\net.minecraft.world.level.chunk.storage.h"
-
-#define RESERVE_ALLOCATION MEM_RESERVE
-#define COMMIT_ALLOCATION MEM_COMMIT
-
-unsigned int ConsoleSaveFileSplit::pagesCommitted = 0;
-void *ConsoleSaveFileSplit::pvHeap = NULL;
-
-ConsoleSaveFileSplit::RegionFileReference::RegionFileReference(int index, unsigned int regionIndex, unsigned int length/*=0*/, unsigned char *data/*=NULL*/)
-{
- fileEntry = new FileEntry();
- fileEntry->currentFilePointer = 0;
- fileEntry->data.length = 0;
- fileEntry->data.regionIndex = regionIndex;
- this->data = 0;
- this->index = index;
- this->dirty = false;
- this->dataCompressed = data;
- this->dataCompressedSize = length;
- this->lastWritten = 0;
-}
-
-ConsoleSaveFileSplit::RegionFileReference::~RegionFileReference()
-{
- free(data);
- delete fileEntry;
-}
-
-// Compress from data to dataCompressed. Uses a special compression method that is designed just to efficiently store runs of zeros, with little overhead on other stuff.
-// Compresed format is a 4 byte uncompressed size, followed by data as follows:
-//
-// Byte value Meaning
-//
-// 1 - 255 Normal data
-// 0 followed by 1 - 255 Run of 1 - 255 0s
-// 0 followed by 0, followed by 256 to 65791 (as 2 bytes) Run of 256 to 65791 zeros
-
-void ConsoleSaveFileSplit::RegionFileReference::Compress()
-{
- unsigned char *dataIn = data;
- unsigned char *dataInLast = data + fileEntry->data.length;
-
-// int64_t startTime = System::currentTimeMillis();
-
- // One pass through to work out storage space required for compressed data
- unsigned int outputSize = 4; // 4 bytes required to store the uncompressed size for faster decompression
- unsigned int runLength = 0;
- while( dataIn != dataInLast )
- {
- unsigned char thisByte = *dataIn++;
- if( ( thisByte != 0 ) || ( runLength == ( 65535 + 256 ) ) )
- {
- // We've got a non-zero value, or we've hit our maximum run length.
- // If there was a preceeding run of zeros, encode that nwo
- if( runLength != 0 )
- {
- if( runLength < 256 )
- {
- // Runs of 1 to 255 encoded as 0 followed by one byte of run length
- outputSize += 2;
- }
- else
- {
- // Runs of 256 to 65791 encoded as two 0s followed by two bytes of run length - 256
- outputSize += 4;
- }
- // Run is now processed
- runLength = 0;
- }
- // Now handle the current byte
- if( thisByte == 0 )
- {
- runLength++;
- }
- else
- {
- // Non-zero, just copy over to output
- outputSize++;
- }
- }
- else
- {
- // It's a zero - keep counting size of the run
- runLength++;
- }
- }
- // Handle any outstanding run
- if ( runLength != 0 )
- {
- if( runLength < 256 )
- {
- // Runs of 1 to 255 encoded as 0 followed by one byte of run length
- outputSize += 2;
- }
- else
- {
- // Runs of 256 to 65791 encoded as two 0s followed by two bytes of run length - 256
- outputSize += 4;
- }
- // Run is now processed
- runLength = 0;
- }
-
- // Now actually allocate & write the compress data. First 4 bytes store the uncompressed size
- dataCompressed = (unsigned char *)malloc(outputSize);
- *((unsigned int *)dataCompressed) = fileEntry->data.length;
- unsigned char *dataOut = dataCompressed + 4;
- dataIn = data;
-
- // Now same process as before, but actually writing
- while( dataIn != dataInLast )
- {
- unsigned char thisByte = *dataIn++;
- if( ( thisByte != 0 ) || ( runLength == ( 65535 + 256 ) ) )
- {
- // We've got a non-zero value, or we've hit our maximum run length.
- // If there was a preceeding run of zeros, encode that nwo
- if( runLength != 0 )
- {
- if( runLength < 256 )
- {
- // Runs of 1 to 255 encoded as 0 followed by one byte of run length
- *dataOut++ = 0;
- *dataOut++ = runLength;
- }
- else
- {
- // Runs of 256 to 65791 encoded as two 0s followed by two bytes of run length - 256
- *dataOut++ = 0;
- *dataOut++ = 0;
- unsigned int largeRunLength = runLength - 256;
- *dataOut++ = ( largeRunLength >> 8 ) & 0xff;
- *dataOut++ = ( largeRunLength ) & 0xff;
- }
- // Run is now processed
- runLength = 0;
- }
- // Now handle the current byte
- if( thisByte == 0 )
- {
- runLength++;
- }
- else
- {
- // Non-zero, just copy over to output
- *dataOut++ = thisByte;
- }
- }
- else
- {
- // It's a zero - keep counting size of the run
- runLength++;
- }
- }
- // Handle any outstanding run
- if( runLength != 0 )
- {
- if( runLength < 256 )
- {
- // Runs of 1 to 255 encoded as 0 followed by one byte of run length
- *dataOut++ = 0;
- *dataOut++ = runLength;
- }
- else
- {
- // Runs of 256 to 65791 encoded as two 0s followed by two bytes of run length - 256
- *dataOut++ = 0;
- *dataOut++ = 0;
- unsigned int largeRunLength = runLength - 256;
- *dataOut++ = ( largeRunLength >> 8 ) & 0xff;
- *dataOut++ = ( largeRunLength ) & 0xff;
- }
- // Run is now processed
- runLength = 0;
- }
- assert(( dataOut - dataCompressed ) == outputSize );
- dataCompressedSize = outputSize;
-// int64_t endTime = System::currentTimeMillis();
-// app.DebugPrintf("Compressing region file 0x%.8x from %d to %d bytes - %dms\n", fileEntry->data.regionIndex, fileEntry->data.length, dataCompressedSize, endTime - startTime);
-}
-
-// Decompress from dataCompressed -> data. See comment in Compress method for format
-void ConsoleSaveFileSplit::RegionFileReference::Decompress()
-{
-// int64_t startTime = System::currentTimeMillis();
- fileEntry->data.length = *((unsigned int *)dataCompressed);
-
- // If this is unusually large, then test how big it would be when expanded before trying to allocate. Matching the expanded size
- // is (currently) our means of knowing that this file is ok
- if( fileEntry->data.length > 1 * 1024 * 1024 )
- {
- unsigned int uncompressedSize = 0;
- unsigned char *dataIn = dataCompressed + 4;
- unsigned char *dataInLast = dataCompressed + dataCompressedSize;
-
- while (dataIn != dataInLast)
- {
- unsigned char thisByte = *dataIn++;
- if( thisByte == 0 )
- {
- thisByte = *dataIn++;
- if( thisByte == 0 )
- {
- unsigned int runLength = (*dataIn++) << 8;
- runLength |= (*dataIn++);
- runLength += 256;
- uncompressedSize += runLength;
- }
- else
- {
- unsigned int runLength = thisByte;
- uncompressedSize += runLength;
- }
- }
- else
- {
- uncompressedSize++;
- }
- }
-
- if( fileEntry->data.length != uncompressedSize )
- {
- // Treat as if it was an empty region file
- fileEntry->data.length = 0;
- assert(0);
- return;
- }
- }
-
-
- data = (unsigned char *)malloc(fileEntry->data.length);
- unsigned char *dataIn = dataCompressed + 4;
- unsigned char *dataInLast = dataCompressed + dataCompressedSize;
- unsigned char *dataOut = data;
-
- while (dataIn != dataInLast)
- {
- unsigned char thisByte = *dataIn++;
- if( thisByte == 0 )
- {
- thisByte = *dataIn++;
- if( thisByte == 0 )
- {
- unsigned int runLength = (*dataIn++) << 8;
- runLength |= (*dataIn++);
- runLength += 256;
- for( unsigned int i = 0; i < runLength; i++ )
- {
- *dataOut++ = 0;
- }
- }
- else
- {
- unsigned int runLength = thisByte;
- for( unsigned int i = 0; i < runLength; i++ )
- {
- *dataOut++ = 0;
- }
- }
- }
- else
- {
- *dataOut++ = thisByte;
- }
- }
- // If we failed to correctly decompress, then treat as if it was an empty region file
- if( ( dataOut - data ) != fileEntry->data.length )
- {
- free(data);
- fileEntry->data.length = 0;
- data = NULL;
- assert(0);
- }
-// int64_t endTime = System::currentTimeMillis();
-// app.DebugPrintf("Decompressing region file from 0x%.8x %d to %d bytes - %dms\n", fileEntry->data.regionIndex, dataCompressedSize, fileEntry->data.length, endTime - startTime);//
-}
-
-unsigned int ConsoleSaveFileSplit::RegionFileReference::GetCompressedSize()
-{
- unsigned char *dataIn = data;
- unsigned char *dataInLast = data + fileEntry->data.length;
-
- unsigned int outputSize = 4; // 4 bytes required to store the uncompressed size for faster decompression
- unsigned int runLength = 0;
- while( dataIn != dataInLast )
- {
- unsigned char thisByte = *dataIn++;
- if( ( thisByte != 0 ) || ( runLength == ( 65535 + 256 ) ) )
- {
- // We've got a non-zero value, or we've hit our maximum run length.
- // If there was a preceeding run of zeros, encode that nwo
- if( runLength != 0 )
- {
- if( runLength < 256 )
- {
- // Runs of 1 to 255 encoded as 0 followed by one byte of run length
- outputSize += 2;
- }
- else
- {
- // Runs of 256 to 65791 encoded as two 0s followed by two bytes of run length - 256
- outputSize += 4;
- }
- // Run is now processed
- runLength = 0;
- }
- // Now handle the current byte
- if( thisByte == 0 )
- {
- runLength++;
- }
- else
- {
- // Non-zero, just copy over to output
- outputSize++;
- }
- }
- else
- {
- // It's a zero - keep counting size of the run
- runLength++;
- }
- }
- // Handle any outstanding run
- if ( runLength != 0 )
- {
- if( runLength < 256 )
- {
- // Runs of 1 to 255 encoded as 0 followed by one byte of run length
- outputSize += 2;
- }
- else
- {
- // Runs of 256 to 65791 encoded as two 0s followed by two bytes of run length - 256
- outputSize += 4;
- }
- // Run is now processed
- runLength = 0;
- }
- return outputSize;
-}
-
-// Release dataCompressed
-void ConsoleSaveFileSplit::RegionFileReference::ReleaseCompressed()
-{
-// app.DebugPrintf("Releasing compressed data for region file from 0x%.8x\n", fileEntry->data.regionIndex );
- free(dataCompressed);
- dataCompressed = NULL;
- dataCompressedSize = NULL;
-}
-
-FileEntry *ConsoleSaveFileSplit::GetRegionFileEntry(unsigned int regionIndex)
-{
- // Is a region file - determine if we've got it as a separate file
- AUTO_VAR(it, regionFiles.find(regionIndex) );
- if( it != regionFiles.end() )
- {
- // Already got it
- return it->second->fileEntry;
- }
-
- int index = StorageManager.AddSubfile(regionIndex);
- RegionFileReference *newRef = new RegionFileReference(index, regionIndex);
- regionFiles[regionIndex] = newRef;
-
- return newRef->fileEntry;
-}
-
-ConsoleSaveFileSplit::ConsoleSaveFileSplit(const wstring &fileName, LPVOID pvSaveData /*= NULL*/, DWORD dFileSize /*= 0*/, bool forceCleanSave /*= false*/, ESavePlatform plat /*= SAVE_FILE_PLATFORM_LOCAL*/)
-{
- DWORD fileSize = dFileSize;
-
- // Load a save from the game rules
- bool bLevelGenBaseSave = false;
- LevelGenerationOptions *levelGen = app.getLevelGenerationOptions();
- if( pvSaveData == NULL && levelGen != NULL && levelGen->requiresBaseSave())
- {
- pvSaveData = levelGen->getBaseSaveData(fileSize);
- if(pvSaveData && fileSize != 0) bLevelGenBaseSave = true;
- }
-
- if( pvSaveData == NULL || fileSize == 0)
- fileSize = StorageManager.GetSaveSize();
-
- if( forceCleanSave )
- fileSize = 0;
-
- _init(fileName, pvSaveData, fileSize, plat);
-
- if(bLevelGenBaseSave)
- {
- levelGen->deleteBaseSaveData();
- }
-}
-
-ConsoleSaveFileSplit::ConsoleSaveFileSplit(ConsoleSaveFile *sourceSave, bool alreadySmallRegions, ProgressListener *progress)
-{
- _init(sourceSave->getFilename(), NULL, 0, sourceSave->getSavePlatform());
-
- header.setOriginalSaveVersion(sourceSave->getOriginalSaveVersion());
- header.setSaveVersion(sourceSave->getSaveVersion());
-
- if(alreadySmallRegions)
- {
-
- vector<FileEntry *> *sourceFiles = sourceSave->getFilesWithPrefix(L"");
-
- DWORD bytesWritten;
- for(AUTO_VAR(it, sourceFiles->begin()); it != sourceFiles->end(); ++it)
- {
- FileEntry *sourceEntry = *it;
- sourceSave->setFilePointer(sourceEntry,0,NULL,FILE_BEGIN);
-
- FileEntry *targetEntry = createFile(ConsoleSavePath(sourceEntry->data.filename));
-
- writeFile(targetEntry, sourceSave->getWritePointer(sourceEntry), sourceEntry->getFileSize(), &bytesWritten);
- }
-
- delete sourceFiles;
- }
- else
- {
- ConsoleSaveFileConverter::ConvertSave(sourceSave, this, progress);
- }
-}
-
-void ConsoleSaveFileSplit::_init(const wstring &fileName, LPVOID pvSaveData, DWORD fileSize, ESavePlatform plat)
-{
- InitializeCriticalSectionAndSpinCount(&m_lock,5120);
-
- m_lastTickTime = 0;
-
- // One time initialise of static stuff required for our storage
- if( pvHeap == NULL )
- {
- // Reserve a chunk of 64MB of virtual address space for our saves, using 64KB pages.
- // We'll only be committing these as required to grow the storage we need, which will
- // the storage to grow without having to use realloc.
- pvHeap = VirtualAlloc(NULL, MAX_PAGE_COUNT * CSF_PAGE_SIZE, RESERVE_ALLOCATION, PAGE_READWRITE );
- }
-
- pvSaveMem = pvHeap;
- m_fileName = fileName;
-
- // Get details of region files. From this point on we are responsible for the memory that the storage manager initially allocated for them
- unsigned int regionCount = StorageManager.GetSubfileCount();
- for( unsigned int i = 0; i < regionCount; i++ )
- {
- unsigned int regionIndex;
- unsigned char *regionDataCompressed;
- unsigned int regionSizeCompressed;
-
- StorageManager.GetSubfileDetails(i, &regionIndex, &regionDataCompressed, &regionSizeCompressed);
-
- RegionFileReference *regionFileRef = new RegionFileReference(i, regionIndex, regionSizeCompressed, regionDataCompressed);
- if( regionSizeCompressed > 0 )
- {
- regionFileRef->Decompress();
- }
- else
- {
- regionFileRef->fileEntry->data.length = 0;
- }
- regionFileRef->ReleaseCompressed();
- regionFiles[regionIndex] = regionFileRef;
- }
-
- DWORD heapSize = max( fileSize, (DWORD)(1024 * 1024 * 2)); // 4J Stu - Our files are going to be bigger than 2MB so allocate high to start with
-
- // Initially committ enough room to store headSize bytes (using CSF_PAGE_SIZE pages, so rounding up here). We should only ever have one save file at a time,
- // and the pages should be decommitted in the dtor, so pages committed should always be zero at this point.
- if( pagesCommitted != 0 )
- {
-#ifndef _CONTENT_PACKAGE
- __debugbreak();
-#endif
- }
-
- unsigned int pagesRequired = ( heapSize + (CSF_PAGE_SIZE - 1 ) ) / CSF_PAGE_SIZE;
-
- void *pvRet = VirtualAlloc(pvHeap, pagesRequired * CSF_PAGE_SIZE, COMMIT_ALLOCATION, PAGE_READWRITE);
- if( pvRet == NULL )
- {
-#ifndef _CONTENT_PACKAGE
- // Out of physical memory
- __debugbreak();
-#endif
- }
- pagesCommitted = pagesRequired;
-
- if( fileSize > 0)
- {
- if(pvSaveData != NULL)
- {
- memcpy(pvSaveMem, pvSaveData, fileSize);
- }
- else
- {
- unsigned int storageLength;
- StorageManager.GetSaveData( pvSaveMem, &storageLength );
- app.DebugPrintf("Filesize - %d, Adjusted size - %d\n",fileSize,storageLength);
- fileSize = storageLength;
- }
-
- int compressed = *(int*)pvSaveMem;
- if( compressed == 0 )
- {
- unsigned int decompSize = *( (int*)pvSaveMem+1 );
-
- // An invalid save, so clear the memory and start from scratch
- if(decompSize == 0)
- {
- // 4J Stu - Saves created between 2/12/2011 and 7/12/2011 will have this problem
- app.DebugPrintf("Invalid save data format\n");
- ZeroMemory( pvSaveMem, fileSize );
- // Clear the first 8 bytes that reference the header
- header.WriteHeader( pvSaveMem );
- }
- else
- {
- unsigned char *buf = new unsigned char[decompSize];
-
- if( Compression::getCompression()->Decompress(buf, &decompSize, (unsigned char *)pvSaveMem+8, fileSize-8 ) == S_OK)
- {
-
- // Only ReAlloc if we need to (we might already have enough) and align to 512 byte boundaries
- DWORD currentHeapSize = pagesCommitted * CSF_PAGE_SIZE;
-
- DWORD desiredSize = decompSize;
-
- if( desiredSize > currentHeapSize )
- {
- unsigned int pagesRequired = ( desiredSize + (CSF_PAGE_SIZE - 1 ) ) / CSF_PAGE_SIZE;
- void *pvRet = VirtualAlloc(pvHeap, pagesRequired * CSF_PAGE_SIZE, COMMIT_ALLOCATION, PAGE_READWRITE);
- if( pvRet == NULL )
- {
- // Out of physical memory
- __debugbreak();
- }
- pagesCommitted = pagesRequired;
- }
-
- memcpy(pvSaveMem, buf, decompSize);
- }
- else
- {
- // Corrupt save, although most of the terrain should actually be ok
- app.DebugPrintf("Failed to decompress save data!\n");
-#ifndef _CONTENT_PACKAGE
- __debugbreak();
-#endif
- ZeroMemory( pvSaveMem, fileSize );
- // Clear the first 8 bytes that reference the header
- header.WriteHeader( pvSaveMem );
- }
-
- delete[] buf;
- }
- }
-
- header.ReadHeader( pvSaveMem, plat );
-
- }
- else
- {
- // Clear the first 8 bytes that reference the header
- header.WriteHeader( pvSaveMem );
- }
-}
-
-ConsoleSaveFileSplit::~ConsoleSaveFileSplit()
-{
- VirtualFree( pvHeap, MAX_PAGE_COUNT * CSF_PAGE_SIZE, MEM_DECOMMIT );
- pagesCommitted = 0;
- // Make sure we don't have any thumbnail data still waiting round - we can't need it now we've destroyed the save file anyway
-#if defined _XBOX
- app.GetSaveThumbnail(NULL,NULL);
-#elif defined __PS3__
- app.GetSaveThumbnail(NULL,NULL, NULL,NULL);
-#endif
-
- for(AUTO_VAR(it,regionFiles.begin()); it != regionFiles.end(); it++ )
- {
- delete it->second;
- }
-
- StorageManager.ResetSubfiles();
- DeleteCriticalSection(&m_lock);
-}
-
-// Add the file to our table of internal files if not already there
-// Open our actual save file ready for reading/writing, and the set the file pointer to the start of this file
-FileEntry *ConsoleSaveFileSplit::createFile( const ConsoleSavePath &fileName )
-{
- LockSaveAccess();
-
- // Determine if the file is a region file that should be split off into its own file
- unsigned int regionFileIndex;
- bool isRegionFile = GetNumericIdentifierFromName(fileName.getName(), &regionFileIndex);
- if( isRegionFile )
- {
- // First, for backwards compatibility, check if it is already in the main file - will just use that if so
- if( !header.fileExists( fileName.getName() ) )
- {
- // Find or create a new region file
- FileEntry *file = GetRegionFileEntry(regionFileIndex);
- ReleaseSaveAccess();
- return file;
- }
- }
-
- FileEntry *file = header.AddFile( fileName.getName() );
- ReleaseSaveAccess();
-
- return file;
-}
-
-void ConsoleSaveFileSplit::deleteFile( FileEntry *file )
-{
- if( file == NULL ) return;
-
- assert( file->isRegionFile() == false );
-
- LockSaveAccess();
-
- DWORD numberOfBytesRead = 0;
- DWORD numberOfBytesWritten = 0;
-
- const int bufferSize = 4096;
- int amountToRead = bufferSize;
- byte buffer[bufferSize];
- DWORD bufferDataSize = 0;
-
-
- char *readStartOffset = (char *)pvSaveMem + file->data.startOffset + file->getFileSize();
-
- char *writeStartOffset = (char *)pvSaveMem + file->data.startOffset;
-
- char *endOfDataOffset = (char *)pvSaveMem + header.GetStartOfNextData();
-
- while(true)
- {
- // Fill buffer from file
- if( readStartOffset + bufferSize > endOfDataOffset )
- {
- amountToRead = (int)(endOfDataOffset - readStartOffset);
- }
- else
- {
- amountToRead = bufferSize;
- }
-
- if( amountToRead == 0 )
- break;
-
- memcpy( buffer, readStartOffset, amountToRead );
- numberOfBytesRead = amountToRead;
-
- bufferDataSize = amountToRead;
- readStartOffset += numberOfBytesRead;
-
- // Write buffer to file
- memcpy( (void *)writeStartOffset, buffer, bufferDataSize );
- numberOfBytesWritten = bufferDataSize;
-
- writeStartOffset += numberOfBytesWritten;
- }
-
- header.RemoveFile( file );
-
- finalizeWrite();
-
- ReleaseSaveAccess();
-}
-
-void ConsoleSaveFileSplit::setFilePointer(FileEntry *file,LONG lDistanceToMove,PLONG lpDistanceToMoveHigh,DWORD dwMoveMethod)
-{
- LockSaveAccess();
-
- if( file->isRegionFile() )
- {
- file->currentFilePointer = lDistanceToMove;
- }
- else
- {
- file->currentFilePointer = file->data.startOffset + lDistanceToMove;
- }
-
- if( dwMoveMethod == FILE_END)
- {
- file->currentFilePointer += file->getFileSize();
- }
-
- ReleaseSaveAccess();
-}
-
-// If this file needs to grow, move the data after along
-void ConsoleSaveFileSplit::PrepareForWrite( FileEntry *file, DWORD nNumberOfBytesToWrite )
-{
- int bytesToGrowBy = ( (file->currentFilePointer - file->data.startOffset) + nNumberOfBytesToWrite) - file->getFileSize();
- if( bytesToGrowBy <= 0 )
- return;
-
- // 4J Stu - Not forcing a minimum size, it is up to the caller to write data in sensible amounts
- // This lets us keep some of the smaller files small
- //if( bytesToGrowBy < 1024 )
- // bytesToGrowBy = 1024;
-
- // Move all the data beyond us
- PIXBeginNamedEvent(0,"Growing file by %d bytes", bytesToGrowBy);
- MoveDataBeyond(file, bytesToGrowBy);
- PIXEndNamedEvent();
-
- // Update our length
- if( file->data.length < 0 )
- file->data.length = 0;
- file->data.length += bytesToGrowBy;
-
- // Write the header with the updated data
- finalizeWrite();
-}
-
-BOOL ConsoleSaveFileSplit::writeFile(FileEntry *file,LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten)
-{
- assert( pvSaveMem != NULL );
- if( pvSaveMem == NULL )
- {
- return 0;
- }
-
- LockSaveAccess();
-
- if( file->isRegionFile() )
- {
- unsigned int sizeRequired = file->currentFilePointer + nNumberOfBytesToWrite;
- RegionFileReference *fileRef = regionFiles[file->data.regionIndex];
- if( sizeRequired > file->getFileSize() )
- {
- fileRef->data = (unsigned char *)realloc(fileRef->data, sizeRequired);
- file->data.length = sizeRequired;
- }
-
- memcpy( fileRef->data + file->currentFilePointer, lpBuffer, nNumberOfBytesToWrite );
-
-// app.DebugPrintf(">>>>>>>>>>>>>> writing a region file's data 0x%.8x, 0x%x offset %d of %d bytes (writing %d bytes)\n",file->data.regionIndex,fileRef->data,file->currentFilePointer, file->getFileSize(), nNumberOfBytesToWrite);
-
- file->currentFilePointer += nNumberOfBytesToWrite;
- file->updateLastModifiedTime();
- fileRef->dirty = true;
- }
- else
- {
- PrepareForWrite( file, nNumberOfBytesToWrite );
-
- char *writeStartOffset = (char *)pvSaveMem + file->currentFilePointer;
- //printf("Write: pvSaveMem = %0xd, currentFilePointer = %d, writeStartOffset = %0xd\n", pvSaveMem, file->currentFilePointer, writeStartOffset);
-
- memcpy( (void *)writeStartOffset, lpBuffer, nNumberOfBytesToWrite );
- *lpNumberOfBytesWritten = nNumberOfBytesToWrite;
-
- if(file->data.length < 0)
- file->data.length = 0;
-
- file->currentFilePointer += *lpNumberOfBytesWritten;
-
- //wprintf(L"Wrote %d bytes to %s, new file pointer is %I64d\n", *lpNumberOfBytesWritten, file->data.filename, file->currentFilePointer);
-
- file->updateLastModifiedTime();
- }
-
- ReleaseSaveAccess();
-
- return 1;
-}
-
-BOOL ConsoleSaveFileSplit::zeroFile(FileEntry *file, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten)
-{
- assert( pvSaveMem != NULL );
- if( pvSaveMem == NULL )
- {
- return 0;
- }
-
- LockSaveAccess();
-
- if( file->isRegionFile() )
- {
- unsigned int sizeRequired = file->currentFilePointer + nNumberOfBytesToWrite;
- RegionFileReference *fileRef = regionFiles[file->data.regionIndex];
- if( sizeRequired > file->getFileSize() )
- {
- fileRef->data = (unsigned char *)realloc(fileRef->data, sizeRequired);
- file->data.length = sizeRequired;
- }
-
- memset( fileRef->data + file->currentFilePointer, 0, nNumberOfBytesToWrite );
-
-// app.DebugPrintf(">>>>>>>>>>>>>> writing a region file's data 0x%.8x, 0x%x offset %d of %d bytes (writing %d bytes)\n",file->data.regionIndex,fileRef->data,file->currentFilePointer, file->getFileSize(), nNumberOfBytesToWrite);
-
- file->currentFilePointer += nNumberOfBytesToWrite;
- file->updateLastModifiedTime();
- fileRef->dirty = true;
- }
- else
- {
- PrepareForWrite( file, nNumberOfBytesToWrite );
-
- char *writeStartOffset = (char *)pvSaveMem + file->currentFilePointer;
- //printf("Write: pvSaveMem = %0xd, currentFilePointer = %d, writeStartOffset = %0xd\n", pvSaveMem, file->currentFilePointer, writeStartOffset);
-
- memset( (void *)writeStartOffset, 0, nNumberOfBytesToWrite );
- *lpNumberOfBytesWritten = nNumberOfBytesToWrite;
-
- if(file->data.length < 0)
- file->data.length = 0;
-
- file->currentFilePointer += *lpNumberOfBytesWritten;
-
- //wprintf(L"Wrote %d bytes to %s, new file pointer is %I64d\n", *lpNumberOfBytesWritten, file->data.filename, file->currentFilePointer);
-
- file->updateLastModifiedTime();
- }
-
- ReleaseSaveAccess();
-
- return 1;
-}
-
-BOOL ConsoleSaveFileSplit::readFile( FileEntry *file, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead)
-{
- DWORD actualBytesToRead;
- assert( pvSaveMem != NULL );
- if( pvSaveMem == NULL )
- {
- return 0;
- }
-
- LockSaveAccess();
-
- if( file->isRegionFile() )
- {
- actualBytesToRead = nNumberOfBytesToRead;
- if( file->currentFilePointer + nNumberOfBytesToRead > file->data.length )
- {
- actualBytesToRead = file->data.length - file->currentFilePointer;
- }
- RegionFileReference *fileRef = regionFiles[file->data.regionIndex];
- memcpy( lpBuffer, fileRef->data + file->currentFilePointer, actualBytesToRead );
- *lpNumberOfBytesRead = actualBytesToRead;
-
- file->currentFilePointer += actualBytesToRead;
- }
- else
- {
- char *readStartOffset = (char *)pvSaveMem + file->currentFilePointer;
- //printf("Read: pvSaveMem = %0xd, currentFilePointer = %d, readStartOffset = %0xd\n", pvSaveMem, file->currentFilePointer, readStartOffset);
-
- assert( nNumberOfBytesToRead <= file->getFileSize() );
-
- actualBytesToRead = nNumberOfBytesToRead;
- if( file->currentFilePointer + nNumberOfBytesToRead > file->data.startOffset + file->data.length )
- {
- actualBytesToRead = (file->data.startOffset + file->data.length) - file->currentFilePointer;
- }
-
- memcpy( lpBuffer, readStartOffset, actualBytesToRead );
- *lpNumberOfBytesRead = actualBytesToRead;
-
- file->currentFilePointer += *lpNumberOfBytesRead;
-
- //wprintf(L"Read %d bytes from %s, new file pointer is %I64d\n", *lpNumberOfBytesRead, file->data.filename, file->currentFilePointer);
- }
-
-
-
- ReleaseSaveAccess();
-
- return 1;
-}
-
-BOOL ConsoleSaveFileSplit::closeHandle( FileEntry *file )
-{
- LockSaveAccess();
- finalizeWrite();
- ReleaseSaveAccess();
-
- return TRUE;
-}
-
-// In this method, attempt to write any dirty region files, subject to maintaining a maximum write output rate. Writing is prioritised by time since the region was last written.
-void ConsoleSaveFileSplit::tick()
-{
- int64_t currentTime = System::currentTimeMillis();
-
- // Don't do anything if the save system is up to something...
- if( StorageManager.GetSaveState() != C4JStorage::ESaveGame_Idle )
- {
- return;
- }
-
- // ...or we shouldn't be saving...
- if( StorageManager.GetSaveDisabled() )
- {
- return;
- }
-
- // ... or we haven't passed the required time since last assessing what to do
- if( ( currentTime - m_lastTickTime ) < WRITE_TICK_RATE_MS )
- {
- return;
- }
-
- LockSaveAccess();
-
- m_lastTickTime = currentTime;
-
- // Get total amount of data written over the time period we are interested in averaging over. Remove any older data.
- unsigned int bytesWritten = 0;
- for( AUTO_VAR(it, writeHistory.begin()); it != writeHistory.end(); )
- {
- if( ( currentTime - it->writeTime ) > ( WRITE_BANDWIDTH_MEASUREMENT_PERIOD_SECONDS * 1000 ) )
- {
- it = writeHistory.erase(it);
- }
- else
- {
- bytesWritten += it->writeSize;
- it++;
- }
- }
-
- // Compile a vector of dirty regions.
- vector<DirtyRegionFile> dirtyRegions;
- for( AUTO_VAR(it, regionFiles.begin()); it != regionFiles.end(); it++ )
- {
- DirtyRegionFile dirtyRegion;
-
- if( it->second->dirty )
- {
- dirtyRegion.fileRef = it->second->fileEntry->getRegionFileIndex();
- dirtyRegion.lastWritten = it->second->lastWritten;
- dirtyRegions.push_back( dirtyRegion );
- }
- }
-
- // Sort into ascending order, by lastWritten time. First elements will therefore be the ones least recently saved
- std::sort( dirtyRegions.begin(), dirtyRegions.end() );
-
- bool writeRequired = false;
- unsigned int bytesInTimePeriod = bytesWritten;
- unsigned int bytesAddedThisTick = 0;
- for( int i = 0; i < dirtyRegions.size(); i++ )
- {
- RegionFileReference *regionRef = regionFiles[dirtyRegions[i].fileRef];
- unsigned int compressedSize = regionRef->GetCompressedSize();
- bytesInTimePeriod += compressedSize;
- bytesAddedThisTick += compressedSize;
-
- // Always consider at least one item for writing, even if it breaks the rule on the maximum number of bytes we would like to send per tick
- if( ( i > 0 ) && ( bytesAddedThisTick > WRITE_MAX_WRITE_PER_TICK ) )
- {
- break;
- }
-
- // Could we add this without breaking our bytes per second cap?
- if ( ( bytesInTimePeriod / WRITE_BANDWIDTH_MEASUREMENT_PERIOD_SECONDS ) > WRITE_BANDWIDTH_BYTESPERSECOND )
- {
- break;
- }
-
- // Can add for writing
- WriteHistory writeEvent;
- writeEvent.writeSize = compressedSize;
- writeEvent.writeTime = System::currentTimeMillis();
- writeHistory.push_back(writeEvent);
-
- regionRef->Compress();
-// app.DebugPrintf("Tick: Writing region 0x%.8x, compressed as %d bytes\n",regionRef->fileEntry->getRegionFileIndex(), regionRef->dataCompressedSize);
- StorageManager.UpdateSubfile(regionRef->index, regionRef->dataCompressed, regionRef->dataCompressedSize);
- regionRef->dirty = false;
- regionRef->lastWritten = System::currentTimeMillis();
-
- writeRequired = true;
- }
-#ifndef _CONTENT_PACKAGE
- {
- unsigned int totalDirty = 0;
- unsigned int totalDirtyBytes = 0;
- __int64 oldestDirty = currentTime;
- for( AUTO_VAR(it, regionFiles.begin()); it != regionFiles.end(); it++ )
- {
- if( it->second->dirty )
- {
- if( it->second->lastWritten < oldestDirty )
- {
- oldestDirty = it->second->lastWritten;
- }
- totalDirty++;
- totalDirtyBytes += it->second->fileEntry->getFileSize();
- }
- }
-#ifdef _DURANGO
- PIXReportCounter(L"Dirty regions", (float)totalDirty);
- PIXReportCounter(L"Dirty MB", (float)totalDirtyBytes / ( 1024 * 1024) );
- PIXReportCounter(L"Dirty oldest age", ((float) currentTime - oldestDirty ) );
- PIXReportCounter(L"Region writing bandwidth",((float)bytesInTimePeriod/ WRITE_BANDWIDTH_MEASUREMENT_PERIOD_SECONDS) / ( 1024 * 1024));
-#endif
- }
-#endif
-
- if( writeRequired )
- {
- StorageManager.SaveSubfiles(SaveRegionFilesCallback, this);
- }
-
- ReleaseSaveAccess();
-}
-
-void ConsoleSaveFileSplit::finalizeWrite()
-{
- LockSaveAccess();
- header.WriteHeader( pvSaveMem );
- ReleaseSaveAccess();
-}
-
-void ConsoleSaveFileSplit::MoveDataBeyond(FileEntry *file, DWORD nNumberOfBytesToWrite)
-{
- DWORD numberOfBytesRead = 0;
- DWORD numberOfBytesWritten = 0;
-
- const DWORD bufferSize = 4096;
- DWORD amountToRead = bufferSize;
- //assert( nNumberOfBytesToWrite <= bufferSize );
- static byte buffer1[bufferSize];
- static byte buffer2[bufferSize];
- DWORD buffer1Size = 0;
- DWORD buffer2Size = 0;
-
- // Only ReAlloc if we need to (we might already have enough) and align to 512 byte boundaries
- DWORD currentHeapSize = pagesCommitted * CSF_PAGE_SIZE;
-
- DWORD desiredSize = header.GetFileSize() + nNumberOfBytesToWrite;
-
- if( desiredSize > currentHeapSize )
- {
- unsigned int pagesRequired = ( desiredSize + (CSF_PAGE_SIZE - 1 ) ) / CSF_PAGE_SIZE;
- void *pvRet = VirtualAlloc(pvHeap, pagesRequired * CSF_PAGE_SIZE, COMMIT_ALLOCATION, PAGE_READWRITE);
- if( pvRet == NULL )
- {
- // Out of physical memory
- __debugbreak();
- }
- pagesCommitted = pagesRequired;
- }
-
- // This is the start of where we want the space to be, and the start of the data that we need to move
- char *spaceStartOffset = (char *)pvSaveMem + file->data.startOffset + file->getFileSize();
-
- // This is the end of where we want the space to be
- char *spaceEndOffset = spaceStartOffset + nNumberOfBytesToWrite;
-
- // This is the current end of the data that we want to move
- char *beginEndOfDataOffset = (char *)pvSaveMem + header.GetStartOfNextData();
-
- // This is where the end of the data is going to be
- char *finishEndOfDataOffset = beginEndOfDataOffset + nNumberOfBytesToWrite;
-
- // This is where we are going to read from (with the amount we want to read subtracted before we read)
- char *readStartOffset = beginEndOfDataOffset;
-
- // This is where we can safely write to (with the amount we want write subtracted before we write)
- char *writeStartOffset = finishEndOfDataOffset;
-
- //printf("\n******* MOVEDATABEYOND *******\n");
- //printf("Space start: %d, space end: %d\n", spaceStartOffset - (char *)pvSaveMem, spaceEndOffset - (char *)pvSaveMem);
- //printf("Current end of data: %d, new end of data: %d\n", beginEndOfDataOffset - (char *)pvSaveMem, finishEndOfDataOffset - (char *)pvSaveMem);
-
- // Optimisation for things that are being moved in whole region file sector (4K chunks). We could generalise this a bit more but seems safest at the moment to identify this particular type
- // of move and code explicitly for this situation
- if( ( nNumberOfBytesToWrite & 4095 ) == 0 )
- {
- if( nNumberOfBytesToWrite > 0 )
- {
- // Get addresses for start & end of the region we are copying from as uintptr_t, for easier maths
- uintptr_t uiFromStart = (uintptr_t)spaceStartOffset;
- uintptr_t uiFromEnd = (uintptr_t)beginEndOfDataOffset;
-
- // Round both of these values to get 4096 byte chunks that we will need to at least partially move
- uintptr_t uiFromStartChunk = uiFromStart & ~((uintptr_t)4095);
- uintptr_t uiFromEndChunk = (uiFromEnd - 1 ) & ~((uintptr_t)4095);
-
- // Loop through all the affected source 4096 chunks, going backwards so we don't overwrite anything we'll need in the future
- for( uintptr_t uiCurrentChunk = uiFromEndChunk; uiCurrentChunk >= uiFromStartChunk; uiCurrentChunk -= 4096 )
- {
- // Establish chunk we'll need to copy
- uintptr_t uiCopyStart = uiCurrentChunk;
- uintptr_t uiCopyEnd = uiCurrentChunk + 4096;
- // Clamp chunk to the bounds of the full region we are trying to copy
- if( uiCopyStart < uiFromStart )
- {
- // Needs to be clampged against the start of our region
- uiCopyStart = uiFromStart;
- }
- if ( uiCopyEnd > uiFromEnd )
- {
- // Needs to be clamped to the end of our region
- uiCopyEnd = uiFromEnd;
- }
- XMemCpy( (void *)(uiCopyStart + nNumberOfBytesToWrite), ( void *)uiCopyStart, uiCopyEnd - uiCopyStart );
- }
- }
- }
- else
- {
- while(true)
- {
- // Copy buffer 1 to buffer 2
- memcpy( buffer2, buffer1, buffer1Size);
- buffer2Size = buffer1Size;
-
- // Fill buffer 1 from file
- if( (readStartOffset - bufferSize) < spaceStartOffset )
- {
- amountToRead = (DWORD)(readStartOffset - spaceStartOffset);
- }
- else
- {
- amountToRead = bufferSize;
- }
-
- // Push the read point back by the amount of bytes that we are going to read
- readStartOffset -= amountToRead;
-
- //printf("About to read %u from %d\n", amountToRead, readStartOffset - (char *)pvSaveMem );
-
- memcpy( buffer1, readStartOffset, amountToRead );
- numberOfBytesRead = amountToRead;
-
- buffer1Size = amountToRead;
-
- // Move back the write pointer by the amount of bytes we are going to write
- writeStartOffset -= buffer2Size;
-
- // Write buffer 2 to file
- if( (writeStartOffset + buffer2Size) <= finishEndOfDataOffset)
- {
- //printf("About to write %u to %d\n", buffer2Size, writeStartOffset - (char *)pvSaveMem );
- memcpy( (void *)writeStartOffset, buffer2, buffer2Size );
- numberOfBytesWritten = buffer2Size;
- }
- else
- {
- assert((writeStartOffset + buffer2Size) <= finishEndOfDataOffset);
- numberOfBytesWritten = 0;
- }
-
- if( numberOfBytesRead == 0 )
- {
- //printf("\n************** MOVE COMPLETED *************** \n\n");
- assert( writeStartOffset == spaceEndOffset );
- break;
- }
- }
- }
-
- header.AdjustStartOffsets( file, nNumberOfBytesToWrite );
-}
-
-// Attempt to convert a filename into a numeric identifier, which we use for region files. File names supported are of the form:
-//
-// Filename Encoded as
-//
-// r.x.z.mcr 00 00 xx zz
-// DIM-1r.x.z.mcr 00 01 xx zz
-// DIM1/r.x.z.mcr 00 02 xx zz
-
-bool ConsoleSaveFileSplit::GetNumericIdentifierFromName(const wstring &fileName, unsigned int *idOut)
-{
- // Determine whether it is one of our region file names if the file extension is ".mbr"
- if( fileName.length() < 4 ) return false;
- wstring extension = fileName.substr(fileName.length()-4,4);
- if( extension != wstring(L".mcr") ) return false;
-
- unsigned int id = 0;
- int x, z;
-
- const wchar_t *cstr = fileName.c_str();
- const wchar_t *body = cstr + 2;
-
- // If this filename starts with a "r" then assume it is of the format "r.x.z.mcr" - don't do anything as default value we've set are correct
- if( cstr[0] != L'r' )
- {
- // Must be prefixed by "DIM-1r." or "DIM1/r."
- body = cstr + 7;
- // Differentiate between these 2 options
- if( cstr[3] == L'-' )
- {
- // "DIM-1r."
- id = 0x00010000;
- }
- else
- {
- // "DIM/1r."
- id = 0x00020000;
- }
- }
- // Get x/z coords
- swscanf_s(body, L"%d.%d.mcr", &x, &z );
-
- // Pack full id
- id |= ( ( x << 8 ) & 0x0000ff00 );
- id |= ( z & 0x000000ff );
-
- *idOut = id;
-
- return true;
-}
-
-// Convert a numeric file identifier (for region files) back into a normal filename. See comment above.
-
-wstring ConsoleSaveFileSplit::GetNameFromNumericIdentifier(unsigned int idIn)
-{
- wstring prefix;
-
- switch(idIn & 0x00ff0000 )
- {
- case 0:
- prefix = L"";
- break;
- case 1:
- prefix = L"DIM-1";
- break;
- case 2:
- prefix = L"DIM1/";
- break;
- }
- signed char regionX = ( idIn >> 8 ) & 255;
- signed char regionZ = idIn & 255;
- wstring region = ( prefix + wstring(L"r.") + _toString(regionX) + L"." + _toString(regionZ) + L".mcr" );
-
- return region;
-}
-
-// Compress any dirty region files, and tell the storage manager about them so that it will process them when we ask it to save sub files
-void ConsoleSaveFileSplit::processSubfilesForWrite()
-{
-#if 0
- // 4J Stu - There are debug reasons where we want to force a save of all regions
- StorageManager.ResetSubfiles();
- for(AUTO_VAR(it,regionFiles.begin()); it != regionFiles.end(); it++ )
- {
- RegionFileReference* region = it->second;
- int index = StorageManager.AddSubfile(region->fileEntry->data.regionIndex);
- //if( region->dirty )
- {
- region->Compress();
- StorageManager.UpdateSubfile(index, region->dataCompressed, region->dataCompressedSize);
- region->dirty = false;
- region->lastWritten = System::currentTimeMillis();
- }
- }
-#else
- for(AUTO_VAR(it,regionFiles.begin()); it != regionFiles.end(); it++ )
- {
- RegionFileReference* region = it->second;
- if( region->dirty )
- {
- region->Compress();
- StorageManager.UpdateSubfile(region->index, region->dataCompressed, region->dataCompressedSize);
- region->dirty = false;
- region->lastWritten = System::currentTimeMillis();
- }
- }
-#endif
-}
-
-// Clean up any memory allocated for compressed data when we have finished writing
-void ConsoleSaveFileSplit::processSubfilesAfterWrite()
-{
- // This is called from the StorageManager.Tick() which should always be on the main thread
- for(AUTO_VAR(it,regionFiles.begin()); it != regionFiles.end(); it++ )
- {
- RegionFileReference* region = it->second;
- region->ReleaseCompressed();
- }
-}
-
-bool ConsoleSaveFileSplit::doesFileExist(ConsoleSavePath file)
-{
- LockSaveAccess();
- bool exists = header.fileExists( file.getName() );
- ReleaseSaveAccess();
-
- return exists;
-}
-
-void ConsoleSaveFileSplit::Flush(bool autosave, bool updateThumbnail)
-{
- LockSaveAccess();
-
-#ifdef _XBOX_ONE
- MinecraftServer *server = MinecraftServer::getInstance();
-#endif
-
- // The storage manage might potentially be busy doing a sub-file write initiated from the tick. Wait until this is totally processed.
- while( StorageManager.GetSaveState() != C4JStorage::ESaveGame_Idle )
- {
-#ifdef _XBOX_ONE
- if (server && server->IsSuspending())
- {
- // If the server is mid-suspend we need to tick the storage manager ourselves
- StorageManager.Tick();
- }
-#endif
-
- app.DebugPrintf("Flush wait\n");
- Sleep(10);
- }
-
- finalizeWrite();
-
- m_autosave = autosave;
- if(!m_autosave) processSubfilesForWrite();
-
- // Get the frequency of the timer
- LARGE_INTEGER qwTicksPerSec, qwTime, qwNewTime, qwDeltaTime;
- float fElapsedTime = 0.0f;
- QueryPerformanceFrequency( &qwTicksPerSec );
- float fSecsPerTick = 1.0f / (float)qwTicksPerSec.QuadPart;
-
- unsigned int fileSize = header.GetFileSize();
-
- // Assume that the compression will make it smaller so initially attempt to allocate the current file size
- // We add 4 bytes to the start so that we can signal compressed data
- // And another 4 bytes to store the decompressed data size
- unsigned int compLength = fileSize+8;
-
- // 4J Stu - Added TU-1 interim
-
- // Attempt to allocate the required memory
- // We do not own this, it belongs to the StorageManager
- byte *compData = (byte *)StorageManager.AllocateSaveData( compLength );
-
- // If we failed to allocate then compData will be NULL
- // Pre-calculate the compressed data size so that we can attempt to allocate a smaller buffer
- if(compData == NULL)
- {
- // Length should be 0 here so that the compression call knows that we want to know the length back
- compLength = 0;
-
- // Pre-calculate the buffer size required for the compressed data
- PIXBeginNamedEvent(0,"Pre-calc save compression");
- // Save the start time
- QueryPerformanceCounter( &qwTime );
- Compression::getCompression()->Compress(NULL,&compLength,pvSaveMem,fileSize);
- QueryPerformanceCounter( &qwNewTime );
-
- qwDeltaTime.QuadPart = qwNewTime.QuadPart - qwTime.QuadPart;
- fElapsedTime = fSecsPerTick * ((FLOAT)(qwDeltaTime.QuadPart));
-
- app.DebugPrintf("Check buffer size: Elapsed time %f\n", fElapsedTime);
- PIXEndNamedEvent();
-
- // We add 4 bytes to the start so that we can signal compressed data
- // And another 4 bytes to store the decompressed data size
- compLength = compLength+8;
-
- // Attempt to allocate the required memory
- compData = (byte *)StorageManager.AllocateSaveData( compLength );
- }
-
- if(compData != NULL)
- {
- // Re-compress all save data before we save it to disk
- PIXBeginNamedEvent(0,"Actual save compression");
- // Save the start time
- QueryPerformanceCounter( &qwTime );
- Compression::getCompression()->Compress(compData+8,&compLength,pvSaveMem,fileSize);
- QueryPerformanceCounter( &qwNewTime );
-
- qwDeltaTime.QuadPart = qwNewTime.QuadPart - qwTime.QuadPart;
- fElapsedTime = fSecsPerTick * ((FLOAT)(qwDeltaTime.QuadPart));
-
- app.DebugPrintf("Compress: Elapsed time %f\n", fElapsedTime);
- PIXEndNamedEvent();
-
- ZeroMemory(compData,8);
- int saveVer = 0;
- memcpy( compData, &saveVer, sizeof(int) );
- memcpy( compData+4, &fileSize, sizeof(int) );
-
- app.DebugPrintf("Save data compressed from %d to %d\n", fileSize, compLength);
-
- if(updateThumbnail)
- {
- PBYTE pbThumbnailData=NULL;
- DWORD dwThumbnailDataSize=0;
-
- PBYTE pbDataSaveImage=NULL;
- DWORD dwDataSizeSaveImage=0;
-
-#if ( defined _XBOX || defined _DURANGO )
- app.GetSaveThumbnail(&pbThumbnailData,&dwThumbnailDataSize);
-#elif ( defined __PS3__ || defined __ORBIS__ )
- app.GetSaveThumbnail(&pbThumbnailData,&dwThumbnailDataSize,&pbDataSaveImage,&dwDataSizeSaveImage);
-#endif
-
- BYTE bTextMetadata[88];
- ZeroMemory(bTextMetadata,88);
-
- __int64 seed = 0;
- bool hasSeed = false;
- if(MinecraftServer::getInstance()!= NULL && MinecraftServer::getInstance()->levels[0]!=NULL)
- {
- seed = MinecraftServer::getInstance()->levels[0]->getLevelData()->getSeed();
- hasSeed = true;
- }
-
- int iTextMetadataBytes = app.CreateImageTextData(bTextMetadata, seed, hasSeed, app.GetGameHostOption(eGameHostOption_All), Minecraft::GetInstance()->getCurrentTexturePackId());
-
- // set the icon and save image
- StorageManager.SetSaveImages(pbThumbnailData,dwThumbnailDataSize,pbDataSaveImage,dwDataSizeSaveImage,bTextMetadata,iTextMetadataBytes);
- app.DebugPrintf("Save thumbnail size %d\n",dwThumbnailDataSize);
-
- }
-
- INT saveOrCheckpointId = 0;
- bool validSave = StorageManager.GetSaveUniqueNumber(&saveOrCheckpointId);
- TelemetryManager->RecordLevelSaveOrCheckpoint(ProfileManager.GetPrimaryPad(), saveOrCheckpointId, compLength+8);
-
- // save the data
- StorageManager.SaveSaveData( &ConsoleSaveFileSplit::SaveSaveDataCallback, this );
-#ifndef _CONTENT_PACKAGE
- if( app.DebugSettingsOn())
- {
- if(app.GetWriteSavesToFolderEnabled() )
- {
- DebugFlushToFile(compData, compLength+8);
- }
- }
-#endif
- ReleaseSaveAccess();
- }
-}
-
-int ConsoleSaveFileSplit::SaveSaveDataCallback(LPVOID lpParam,bool bRes)
-{
- ConsoleSaveFileSplit *pClass=(ConsoleSaveFileSplit *)lpParam;
-
- // Don't save sub files on autosave (their always being saved anyway)
- if (!pClass->m_autosave)
- {
- // This is called from the StorageManager.Tick() which should always be on the main thread
- StorageManager.SaveSubfiles(SaveRegionFilesCallback, pClass);
- }
- return 0;
-}
-
-int ConsoleSaveFileSplit::SaveRegionFilesCallback(LPVOID lpParam,bool bRes)
-{
- ConsoleSaveFileSplit *pClass=(ConsoleSaveFileSplit *)lpParam;
-
- // This is called from the StorageManager.Tick() which should always be on the main thread
- pClass->processSubfilesAfterWrite();
-
- return 0;
-}
-
-#ifndef _CONTENT_PACKAGE
-void ConsoleSaveFileSplit::DebugFlushToFile(void *compressedData /*= NULL*/, unsigned int compressedDataSize /*= 0*/)
-{
- LockSaveAccess();
-
- finalizeWrite();
-
- unsigned int fileSize = header.GetFileSize();
-
- DWORD numberOfBytesWritten = 0;
-
- File targetFileDir(L"Saves");
-
- if(!targetFileDir.exists())
- targetFileDir.mkdir();
-
- wchar_t *fileName = new wchar_t[XCONTENT_MAX_FILENAME_LENGTH+1];
-
- SYSTEMTIME t;
- GetSystemTime( &t );
-
- //14 chars for the digits
- //11 chars for the separators + suffix
- //25 chars total
- wstring cutFileName = m_fileName;
- if(m_fileName.length() > XCONTENT_MAX_FILENAME_LENGTH - 25)
- {
- cutFileName = m_fileName.substr(0, XCONTENT_MAX_FILENAME_LENGTH - 25);
- }
- swprintf(fileName, XCONTENT_MAX_FILENAME_LENGTH+1, L"\\v%04d-%ls%02d.%02d.%02d.%02d.%02d.mcs",VER_PRODUCTBUILD,cutFileName.c_str(), t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond);
-
-#ifdef _UNICODE
- wstring wtemp = targetFileDir.getPath() + wstring(fileName);
- LPCWSTR lpFileName = wtemp.c_str();
-#else
- LPCSTR lpFileName = wstringtofilename( targetFileDir.getPath() + wstring(fileName) );
-#endif
-
- HANDLE hSaveFile = CreateFile( lpFileName, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_FLAG_RANDOM_ACCESS, NULL);
-
- if(compressedData != NULL && compressedDataSize > 0)
- {
- WriteFile(hSaveFile,compressedData,compressedDataSize,&numberOfBytesWritten,NULL);
- assert(numberOfBytesWritten == compressedDataSize);
- }
- else
- {
- WriteFile(hSaveFile,pvSaveMem,fileSize,&numberOfBytesWritten,NULL);
- assert(numberOfBytesWritten == fileSize);
- }
- CloseHandle( hSaveFile );
-
- delete[] fileName;
-
- ReleaseSaveAccess();
-}
-#endif
-
-unsigned int ConsoleSaveFileSplit::getSizeOnDisk()
-{
- return header.GetFileSize();
-}
-
-wstring ConsoleSaveFileSplit::getFilename()
-{
- return m_fileName;
-}
-
-vector<FileEntry *> *ConsoleSaveFileSplit::getFilesWithPrefix(const wstring &prefix)
-{
- return header.getFilesWithPrefix( prefix );
-}
-
-vector<FileEntry *> *ConsoleSaveFileSplit::getRegionFilesByDimension(unsigned int dimensionIndex)
-{
- vector<FileEntry *> *files = NULL;
-
- for( AUTO_VAR(it,regionFiles.begin()); it != regionFiles.end(); ++it )
- {
- unsigned int entryDimension = ( (it->first) >> 16) & 0xFF;
-
- if(entryDimension == dimensionIndex)
- {
- if( files == NULL )
- {
- files = new vector<FileEntry *>();
- }
-
- files->push_back(it->second->fileEntry);
- }
- }
-
- return files;
-}
-
-#if defined(__PS3__) || defined(__ORBIS__)
-wstring ConsoleSaveFileSplit::getPlayerDataFilenameForLoad(const PlayerUID& pUID)
-{
- return header.getPlayerDataFilenameForLoad( pUID );
-}
-wstring ConsoleSaveFileSplit::getPlayerDataFilenameForSave(const PlayerUID& pUID)
-{
- return header.getPlayerDataFilenameForSave( pUID );
-}
-vector<FileEntry *> *ConsoleSaveFileSplit::getValidPlayerDatFiles()
-{
- return header.getValidPlayerDatFiles();
-}
-#endif
-
-int ConsoleSaveFileSplit::getSaveVersion()
-{
- return header.getSaveVersion();
-}
-
-int ConsoleSaveFileSplit::getOriginalSaveVersion()
-{
- return header.getOriginalSaveVersion();
-}
-
-void ConsoleSaveFileSplit::LockSaveAccess()
-{
- EnterCriticalSection(&m_lock);
-}
-
-void ConsoleSaveFileSplit::ReleaseSaveAccess()
-{
- LeaveCriticalSection(&m_lock);
-}
-
-ESavePlatform ConsoleSaveFileSplit::getSavePlatform()
-{
- return header.getSavePlatform();
-}
-
-bool ConsoleSaveFileSplit::isSaveEndianDifferent()
-{
- return header.isSaveEndianDifferent();
-}
-
-void ConsoleSaveFileSplit::setLocalPlatform()
-{
- header.setLocalPlatform();
-}
-
-void ConsoleSaveFileSplit::setPlatform(ESavePlatform plat)
-{
- header.setPlatform(plat);
-}
-
-ByteOrder ConsoleSaveFileSplit::getSaveEndian()
-{
- return header.getSaveEndian();
-}
-
-ByteOrder ConsoleSaveFileSplit::getLocalEndian()
-{
- return header.getLocalEndian();
-}
-
-void ConsoleSaveFileSplit::setEndian(ByteOrder endian)
-{
- header.setEndian(endian);
-}
-
-void ConsoleSaveFileSplit::ConvertRegionFile(File sourceFile)
-{
- DWORD numberOfBytesWritten = 0;
- DWORD numberOfBytesRead = 0;
-
- RegionFile sourceRegionFile(this, &sourceFile);
-
- for(unsigned int x = 0; x < 32; ++x)
- {
- for(unsigned int z = 0; z < 32; ++z)
- {
- DataInputStream *dis = sourceRegionFile.getChunkDataInputStream(x,z);
-
- if(dis)
- {
- byteArray inData(1024*1024);
- int read = dis->read(inData);
- dis->close();
- dis->deleteChildStream();
- delete dis;
-
- DataOutputStream *dos = sourceRegionFile.getChunkDataOutputStream(x,z);
- dos->write(inData, 0, read);
-
-
- dos->close();
- dos->deleteChildStream();
- delete dos;
- delete inData.data;
-
- }
-
- }
- }
- sourceRegionFile.writeAllOffsets(); // saves all the endian swapped offsets back out to the file (not all of these are written in the above processing).
-
-}
-
-void ConsoleSaveFileSplit::ConvertToLocalPlatform()
-{
- if(getSavePlatform() == SAVE_FILE_PLATFORM_LOCAL)
- {
- // already in the correct format
- return;
- }
- // convert each of the region files to the local platform
- vector<FileEntry *> *allFilesInSave = getFilesWithPrefix(wstring(L""));
- for(AUTO_VAR(it, allFilesInSave->begin()); it < allFilesInSave->end(); ++it)
- {
- FileEntry *fe = *it;
- wstring fName( fe->data.filename );
- wstring suffix(L".mcr");
- if( fName.compare(fName.length() - suffix.length(), suffix.length(), suffix) == 0 )
- {
- app.DebugPrintf("Processing a region file: %ls\n",fName.c_str());
- ConvertRegionFile(File(fe->data.filename) );
- }
- else
- {
- app.DebugPrintf("%ls is not a region file, ignoring\n", fName.c_str());
- }
- }
-
- setLocalPlatform(); // set the platform of this save to the local platform, now that it's been coverted
-}