mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/4jcraft.git
synced 2026-06-22 10:05:33 +00:00
restructure codebase according to vcproj filters
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include "FileHeader.h"
|
||||
#include "ConsoleSavePath.h"
|
||||
|
||||
enum class SaveFileSeekOrigin { Begin, Current, End };
|
||||
|
||||
class ConsoleSaveFile {
|
||||
public:
|
||||
virtual ~ConsoleSaveFile() {};
|
||||
|
||||
virtual FileEntry* createFile(const ConsoleSavePath& fileName) = 0;
|
||||
virtual void deleteFile(FileEntry* file) = 0;
|
||||
virtual void setFilePointer(FileEntry* file, unsigned int distanceToMove,
|
||||
SaveFileSeekOrigin seekOrigin) = 0;
|
||||
virtual bool writeFile(FileEntry* file, const void* lpBuffer,
|
||||
unsigned int nNumberOfBytesToWrite,
|
||||
unsigned int* lpNumberOfBytesWritten) = 0;
|
||||
virtual bool zeroFile(FileEntry* file, unsigned int nNumberOfBytesToWrite,
|
||||
unsigned int* lpNumberOfBytesWritten) = 0;
|
||||
virtual bool readFile(FileEntry* file, void* lpBuffer,
|
||||
unsigned int nNumberOfBytesToRead,
|
||||
unsigned int* lpNumberOfBytesRead) = 0;
|
||||
virtual bool closeHandle(FileEntry* file) = 0;
|
||||
virtual void finalizeWrite() = 0;
|
||||
virtual void tick() {};
|
||||
|
||||
virtual bool doesFileExist(ConsoleSavePath file) = 0;
|
||||
|
||||
virtual void Flush(bool autosave, bool updateThumbnail = true) = 0;
|
||||
|
||||
#if !defined(_CONTENT_PACKAGE)
|
||||
virtual void DebugFlushToFile(void* compressedData = nullptr,
|
||||
unsigned int compressedDataSize = 0) = 0;
|
||||
#endif
|
||||
virtual unsigned int getSizeOnDisk() = 0;
|
||||
virtual std::wstring getFilename() = 0;
|
||||
virtual std::vector<FileEntry*>* getFilesWithPrefix(
|
||||
const std::wstring& prefix) = 0;
|
||||
virtual std::vector<FileEntry*>* getRegionFilesByDimension(
|
||||
unsigned int dimensionIndex) = 0;
|
||||
|
||||
virtual int getSaveVersion() = 0;
|
||||
virtual int getOriginalSaveVersion() = 0;
|
||||
|
||||
virtual void LockSaveAccess() = 0;
|
||||
virtual void ReleaseSaveAccess() = 0;
|
||||
|
||||
virtual ESavePlatform getSavePlatform() = 0;
|
||||
virtual bool isSaveEndianDifferent() = 0;
|
||||
virtual void setLocalPlatform() = 0;
|
||||
virtual void setPlatform(ESavePlatform plat) = 0;
|
||||
virtual ByteOrder getSaveEndian() = 0;
|
||||
virtual ByteOrder getLocalEndian() = 0;
|
||||
virtual void setEndian(ByteOrder endian) = 0;
|
||||
|
||||
virtual void ConvertRegionFile(File sourceFile) = 0;
|
||||
virtual void ConvertToLocalPlatform() = 0;
|
||||
|
||||
virtual void* getWritePointer(FileEntry* file) { return nullptr; }
|
||||
};
|
||||
@@ -0,0 +1,291 @@
|
||||
#include "../../Header Files/stdafx.h"
|
||||
#include "../../net/minecraft/world/level/chunk/storage/net.minecraft.world.level.chunk.storage.h"
|
||||
#include "../../net/minecraft/world/level/storage/net.minecraft.world.level.storage.h"
|
||||
#include "ConsoleSaveFileIO.h"
|
||||
#include "ConsoleSaveFileConverter.h"
|
||||
#include "../../net/minecraft/util/ProgressListener.h"
|
||||
|
||||
void ConsoleSaveFileConverter::ProcessSimpleFile(ConsoleSaveFile* sourceSave,
|
||||
FileEntry* sourceFileEntry,
|
||||
ConsoleSaveFile* targetSave,
|
||||
FileEntry* targetFileEntry) {
|
||||
unsigned int numberOfBytesRead = 0;
|
||||
unsigned int numberOfBytesWritten = 0;
|
||||
|
||||
std::uint8_t* data = new std::uint8_t[sourceFileEntry->getFileSize()];
|
||||
|
||||
// Read from source
|
||||
sourceSave->readFile(sourceFileEntry, data, sourceFileEntry->getFileSize(),
|
||||
&numberOfBytesRead);
|
||||
|
||||
// Write back to target
|
||||
targetSave->writeFile(targetFileEntry, data, numberOfBytesRead,
|
||||
&numberOfBytesWritten);
|
||||
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
void ConsoleSaveFileConverter::ProcessStandardRegionFile(
|
||||
ConsoleSaveFile* sourceSave, File sourceFile, ConsoleSaveFile* targetSave,
|
||||
File targetFile) {
|
||||
unsigned int numberOfBytesWritten = 0;
|
||||
unsigned int numberOfBytesRead = 0;
|
||||
|
||||
RegionFile sourceRegionFile(sourceSave, &sourceFile);
|
||||
RegionFile targetRegionFile(targetSave, &targetFile);
|
||||
|
||||
for (unsigned int x = 0; x < 32; ++x) {
|
||||
for (unsigned int z = 0; z < 32; ++z) {
|
||||
DataInputStream* dis =
|
||||
sourceRegionFile.getChunkDataInputStream(x, z);
|
||||
|
||||
if (dis) {
|
||||
int read = dis->read();
|
||||
DataOutputStream* dos =
|
||||
targetRegionFile.getChunkDataOutputStream(x, z);
|
||||
while (read != -1) {
|
||||
dos->write(read & 0xff);
|
||||
|
||||
read = dis->read();
|
||||
}
|
||||
dos->close();
|
||||
dos->deleteChildStream();
|
||||
delete dos;
|
||||
}
|
||||
|
||||
delete dis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConsoleSaveFileConverter::ConvertSave(ConsoleSaveFile* sourceSave,
|
||||
ConsoleSaveFile* targetSave,
|
||||
ProgressListener* progress) {
|
||||
// Process level.dat
|
||||
ConsoleSavePath ldatPath(std::wstring(L"level.dat"));
|
||||
FileEntry* sourceLdatFe = sourceSave->createFile(ldatPath);
|
||||
FileEntry* targetLdatFe = targetSave->createFile(ldatPath);
|
||||
printf("Processing level.dat\n");
|
||||
ProcessSimpleFile(sourceSave, sourceLdatFe, targetSave, targetLdatFe);
|
||||
|
||||
// Process game rules
|
||||
{
|
||||
ConsoleSavePath gameRulesPath(GAME_RULE_SAVENAME);
|
||||
if (sourceSave->doesFileExist(gameRulesPath)) {
|
||||
FileEntry* sourceFe = sourceSave->createFile(gameRulesPath);
|
||||
FileEntry* targetFe = targetSave->createFile(gameRulesPath);
|
||||
printf("Processing game rules\n");
|
||||
ProcessSimpleFile(sourceSave, sourceFe, targetSave, targetFe);
|
||||
}
|
||||
}
|
||||
|
||||
// MGH added - find any player data files and copy them across
|
||||
std::vector<FileEntry*>* playerFiles =
|
||||
sourceSave->getFilesWithPrefix(DirectoryLevelStorage::getPlayerDir());
|
||||
|
||||
if (playerFiles != nullptr) {
|
||||
for (int fileIdx = 0; fileIdx < playerFiles->size(); fileIdx++) {
|
||||
ConsoleSavePath sourcePlayerDatPath(
|
||||
playerFiles->at(fileIdx)->data.filename);
|
||||
ConsoleSavePath targetPlayerDatPath(
|
||||
playerFiles->at(fileIdx)->data.filename);
|
||||
{
|
||||
FileEntry* sourceFe =
|
||||
sourceSave->createFile(sourcePlayerDatPath);
|
||||
FileEntry* targetFe =
|
||||
targetSave->createFile(targetPlayerDatPath);
|
||||
wprintf(L"Processing player dat file %ls\n",
|
||||
playerFiles->at(fileIdx)->data.filename);
|
||||
ProcessSimpleFile(sourceSave, sourceFe, targetSave, targetFe);
|
||||
|
||||
targetFe->data.lastModifiedTime =
|
||||
sourceFe->data.lastModifiedTime;
|
||||
}
|
||||
}
|
||||
delete playerFiles;
|
||||
}
|
||||
|
||||
#if defined(SPLIT_SAVES)
|
||||
int xzSize = LEVEL_LEGACY_WIDTH;
|
||||
int hellScale = HELL_LEVEL_LEGACY_SCALE;
|
||||
if (sourceSave->doesFileExist(ldatPath)) {
|
||||
ConsoleSaveFileInputStream fis =
|
||||
ConsoleSaveFileInputStream(sourceSave, ldatPath);
|
||||
CompoundTag* root = NbtIo::readCompressed(&fis);
|
||||
CompoundTag* tag = root->getCompound(L"Data");
|
||||
LevelData ret(tag);
|
||||
|
||||
xzSize = ret.getXZSize();
|
||||
hellScale = ret.getHellScale();
|
||||
|
||||
delete root;
|
||||
}
|
||||
|
||||
RegionFileCache sourceCache;
|
||||
RegionFileCache targetCache;
|
||||
|
||||
if (progress) {
|
||||
progress->progressStage(IDS_SAVETRANSFER_STAGE_CONVERTING);
|
||||
}
|
||||
|
||||
// Overworld
|
||||
{
|
||||
printf("Processing the overworld\n");
|
||||
int halfXZSize = xzSize / 2;
|
||||
|
||||
int progressTarget = (xzSize) * (xzSize);
|
||||
int currentProgress = 0;
|
||||
if (progress)
|
||||
progress->progressStagePercentage((currentProgress * 100) /
|
||||
progressTarget);
|
||||
|
||||
for (int x = -halfXZSize; x < halfXZSize; ++x) {
|
||||
for (int z = -halfXZSize; z < halfXZSize; ++z) {
|
||||
// printf("Processing overworld chunk %d,%d\n",x,z);
|
||||
DataInputStream* dis =
|
||||
sourceCache._getChunkDataInputStream(sourceSave, L"", x, z);
|
||||
|
||||
if (dis) {
|
||||
int read = dis->read();
|
||||
DataOutputStream* dos =
|
||||
targetCache._getChunkDataOutputStream(targetSave, L"",
|
||||
x, z);
|
||||
BufferedOutputStream bos(dos, 1024 * 1024);
|
||||
while (read != -1) {
|
||||
bos.write(read & 0xff);
|
||||
|
||||
read = dis->read();
|
||||
}
|
||||
bos.flush();
|
||||
dos->close();
|
||||
dos->deleteChildStream();
|
||||
delete dos;
|
||||
}
|
||||
|
||||
delete dis;
|
||||
|
||||
++currentProgress;
|
||||
if (progress)
|
||||
progress->progressStagePercentage((currentProgress * 100) /
|
||||
progressTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nether
|
||||
{
|
||||
printf("Processing the nether\n");
|
||||
int hellSize = xzSize / hellScale;
|
||||
int halfXZSize = hellSize / 2;
|
||||
|
||||
int progressTarget = (hellSize) * (hellSize);
|
||||
int currentProgress = 0;
|
||||
if (progress)
|
||||
progress->progressStagePercentage((currentProgress * 100) /
|
||||
progressTarget);
|
||||
|
||||
for (int x = -halfXZSize; x < halfXZSize; ++x) {
|
||||
for (int z = -halfXZSize; z < halfXZSize; ++z) {
|
||||
// printf("Processing nether chunk %d,%d\n",x,z);
|
||||
DataInputStream* dis = sourceCache._getChunkDataInputStream(
|
||||
sourceSave, L"DIM-1", x, z);
|
||||
|
||||
if (dis) {
|
||||
int read = dis->read();
|
||||
DataOutputStream* dos =
|
||||
targetCache._getChunkDataOutputStream(targetSave,
|
||||
L"DIM-1", x, z);
|
||||
BufferedOutputStream bos(dos, 1024 * 1024);
|
||||
while (read != -1) {
|
||||
bos.write(read & 0xff);
|
||||
|
||||
read = dis->read();
|
||||
}
|
||||
bos.flush();
|
||||
dos->close();
|
||||
dos->deleteChildStream();
|
||||
delete dos;
|
||||
}
|
||||
|
||||
delete dis;
|
||||
|
||||
++currentProgress;
|
||||
if (progress)
|
||||
progress->progressStagePercentage((currentProgress * 100) /
|
||||
progressTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// End
|
||||
{
|
||||
printf("Processing the end\n");
|
||||
int halfXZSize = END_LEVEL_MAX_WIDTH / 2;
|
||||
|
||||
int progressTarget = (END_LEVEL_MAX_WIDTH) * (END_LEVEL_MAX_WIDTH);
|
||||
int currentProgress = 0;
|
||||
if (progress)
|
||||
progress->progressStagePercentage((currentProgress * 100) /
|
||||
progressTarget);
|
||||
|
||||
for (int x = -halfXZSize; x < halfXZSize; ++x) {
|
||||
for (int z = -halfXZSize; z < halfXZSize; ++z) {
|
||||
// printf("Processing end chunk %d,%d\n",x,z);
|
||||
DataInputStream* dis = sourceCache._getChunkDataInputStream(
|
||||
sourceSave, L"DIM1/", x, z);
|
||||
|
||||
if (dis) {
|
||||
int read = dis->read();
|
||||
DataOutputStream* dos =
|
||||
targetCache._getChunkDataOutputStream(targetSave,
|
||||
L"DIM1/", x, z);
|
||||
BufferedOutputStream bos(dos, 1024 * 1024);
|
||||
while (read != -1) {
|
||||
bos.write(read & 0xff);
|
||||
|
||||
read = dis->read();
|
||||
}
|
||||
bos.flush();
|
||||
dos->close();
|
||||
dos->deleteChildStream();
|
||||
delete dos;
|
||||
}
|
||||
|
||||
delete dis;
|
||||
|
||||
++currentProgress;
|
||||
if (progress)
|
||||
progress->progressStagePercentage((currentProgress * 100) /
|
||||
progressTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
// 4J Stu - Old version that just changes the compression of chunks, not
|
||||
// usable for XboxOne style split saves or compressed tile formats Process
|
||||
// region files
|
||||
std::vector<FileEntry*>* allFilesInSave =
|
||||
sourceSave->getFilesWithPrefix(std::wstring(L""));
|
||||
for (auto it = allFilesInSave->begin(); it < allFilesInSave->end(); ++it) {
|
||||
FileEntry* fe = *it;
|
||||
if (fe != sourceLdatFe) {
|
||||
std::wstring fName(fe->data.filename);
|
||||
std::wstring suffix(L".mcr");
|
||||
if (fName.compare(fName.length() - suffix.length(), suffix.length(),
|
||||
suffix) == 0) {
|
||||
#if !defined(_CONTENT_PACKAGE)
|
||||
wprintf(L"Processing a region file: %s\n", fe->data.filename);
|
||||
#endif
|
||||
ProcessStandardRegionFile(sourceSave, File(fe->data.filename),
|
||||
targetSave, File(fe->data.filename));
|
||||
} else {
|
||||
#if !defined(_CONTENT_PACKAGE)
|
||||
wprintf(L"%s is not a region file, ignoring\n",
|
||||
fe->data.filename);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include "../../ConsoleJavaLibs/File.h"
|
||||
class FileEntry;
|
||||
class ConsoleSaveFile;
|
||||
class ProgressRenderer;
|
||||
|
||||
// 4J Stu - This code is taken from the standalone save converter tool, and
|
||||
// modified slightly
|
||||
class ConsoleSaveFileConverter {
|
||||
private:
|
||||
static void ProcessSimpleFile(ConsoleSaveFile* sourceSave,
|
||||
FileEntry* sourceFileEntry,
|
||||
ConsoleSaveFile* targetSave,
|
||||
FileEntry* targetFileEntry);
|
||||
static void ProcessStandardRegionFile(ConsoleSaveFile* sourceSave,
|
||||
File sourceFile,
|
||||
ConsoleSaveFile* targetSave,
|
||||
File targetFile);
|
||||
|
||||
public:
|
||||
static void ConvertSave(ConsoleSaveFile* sourceSave,
|
||||
ConsoleSaveFile* targetSave,
|
||||
ProgressListener* progress);
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "FileHeader.h"
|
||||
#include "ConsoleSaveFile.h"
|
||||
#include "ConsoleSaveFileInputStream.h"
|
||||
#include "ConsoleSaveFileOutputStream.h"
|
||||
#include "ConsoleSavePath.h"
|
||||
@@ -0,0 +1,120 @@
|
||||
#include "../../Header Files/stdafx.h"
|
||||
|
||||
#include "../../ConsoleJavaLibs/File.h"
|
||||
#include "ConsoleSaveFile.h"
|
||||
#include "ConsoleSaveFileInputStream.h"
|
||||
|
||||
ConsoleSaveFileInputStream::ConsoleSaveFileInputStream(
|
||||
ConsoleSaveFile* saveFile, const ConsoleSavePath& file) {
|
||||
m_saveFile = saveFile;
|
||||
m_file = m_saveFile->createFile(file);
|
||||
|
||||
m_saveFile->setFilePointer(m_file, 0, SaveFileSeekOrigin::Begin);
|
||||
}
|
||||
|
||||
ConsoleSaveFileInputStream::ConsoleSaveFileInputStream(
|
||||
ConsoleSaveFile* saveFile, FileEntry* file) {
|
||||
m_saveFile = saveFile;
|
||||
m_file = file;
|
||||
|
||||
m_saveFile->setFilePointer(m_file, 0, SaveFileSeekOrigin::Begin);
|
||||
}
|
||||
|
||||
// Reads a byte of data from this input stream. This method blocks if no input
|
||||
// is yet available. Returns: the next byte of data, or -1 if the end of the
|
||||
// file is reached.
|
||||
int ConsoleSaveFileInputStream::read() {
|
||||
std::uint8_t byteRead = static_cast<std::uint8_t>(0);
|
||||
unsigned int numberOfBytesRead;
|
||||
|
||||
bool result =
|
||||
m_saveFile->readFile(m_file,
|
||||
&byteRead, // data buffer
|
||||
1, // number of bytes to read
|
||||
&numberOfBytesRead // number of bytes read
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
// TODO 4J Stu - Some kind of error handling
|
||||
return -1;
|
||||
} else if (numberOfBytesRead == 0) {
|
||||
// File pointer is past the end of the file
|
||||
return -1;
|
||||
}
|
||||
|
||||
return static_cast<int>(byteRead);
|
||||
}
|
||||
|
||||
// Reads up to b.length bytes of data from this input stream into an array of
|
||||
// bytes. This method blocks until some input is available. Parameters: b - the
|
||||
// buffer into which the data is read. Returns: the total number of bytes read
|
||||
// into the buffer, or -1 if there is no more data because the end of the file
|
||||
// has been reached.
|
||||
int ConsoleSaveFileInputStream::read(byteArray b) {
|
||||
unsigned int numberOfBytesRead;
|
||||
|
||||
bool result =
|
||||
m_saveFile->readFile(m_file,
|
||||
&b.data, // data buffer
|
||||
b.length, // number of bytes to read
|
||||
&numberOfBytesRead // number of bytes read
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
// TODO 4J Stu - Some kind of error handling
|
||||
return -1;
|
||||
} else if (numberOfBytesRead == 0) {
|
||||
// File pointer is past the end of the file
|
||||
return -1;
|
||||
}
|
||||
|
||||
return numberOfBytesRead;
|
||||
}
|
||||
|
||||
// Reads up to len bytes of data from this input stream into an array of bytes.
|
||||
// If len is not zero, the method blocks until some input is available;
|
||||
// otherwise, no bytes are read and 0 is returned. Parameters: b - the buffer
|
||||
// into which the data is read. off - the start offset in the destination array
|
||||
// b len - the maximum number of bytes read. Returns: the total number of bytes
|
||||
// read into the buffer, or -1 if there is no more data because the end of the
|
||||
// file has been reached.
|
||||
int ConsoleSaveFileInputStream::read(byteArray b, unsigned int offset,
|
||||
unsigned int length) {
|
||||
// 4J Stu - We don't want to read any more than the array buffer can hold
|
||||
assert(length <= (b.length - offset));
|
||||
|
||||
unsigned int numberOfBytesRead;
|
||||
|
||||
bool result =
|
||||
m_saveFile->readFile(m_file,
|
||||
&b[offset], // data buffer
|
||||
length, // number of bytes to read
|
||||
&numberOfBytesRead // number of bytes read
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
// TODO 4J Stu - Some kind of error handling
|
||||
return -1;
|
||||
} else if (numberOfBytesRead == 0) {
|
||||
// File pointer is past the end of the file
|
||||
return -1;
|
||||
}
|
||||
|
||||
return numberOfBytesRead;
|
||||
}
|
||||
|
||||
// Closes this file input stream and releases any system resources associated
|
||||
// with the stream. If this stream has an associated channel then the channel is
|
||||
// closed as well.
|
||||
void ConsoleSaveFileInputStream::close() {
|
||||
if (m_saveFile != nullptr) {
|
||||
bool result = m_saveFile->closeHandle(m_file);
|
||||
|
||||
if (!result) {
|
||||
// TODO 4J Stu - Some kind of error handling
|
||||
}
|
||||
|
||||
// Stop the dtor from trying to close it again
|
||||
m_saveFile = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
// 4J Stu - Implements the Java InputStream but rather than writing directly to
|
||||
// disc it writes through the save file
|
||||
|
||||
#include "../../ConsoleJavaLibs/InputOutputStream/InputStream.h"
|
||||
|
||||
#include "ConsoleSavePath.h"
|
||||
|
||||
class ConsoleSaveFile;
|
||||
class FileEntry;
|
||||
|
||||
class ConsoleSaveFileInputStream : public InputStream {
|
||||
public:
|
||||
ConsoleSaveFileInputStream(ConsoleSaveFile* saveFile,
|
||||
const ConsoleSavePath& file);
|
||||
ConsoleSaveFileInputStream(ConsoleSaveFile* saveFile, FileEntry* file);
|
||||
virtual int read();
|
||||
virtual int read(byteArray b);
|
||||
virtual int read(byteArray b, unsigned int offset, unsigned int length);
|
||||
virtual void close();
|
||||
virtual int64_t skip(int64_t n) { return n; }
|
||||
|
||||
private:
|
||||
ConsoleSaveFile* m_saveFile;
|
||||
FileEntry* m_file;
|
||||
};
|
||||
@@ -0,0 +1,875 @@
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
#include "../../Header Files/stdafx.h"
|
||||
#include "../StringHelpers.h"
|
||||
#include "../../Header Files/PortableFileIO.h"
|
||||
#include "ConsoleSaveFileOriginal.h"
|
||||
#include "../../ConsoleJavaLibs/File.h"
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <xuiapp.h>
|
||||
#include "../../Header Files/compression.h"
|
||||
#include "../../../Minecraft.Client/net/minecraft/client/Minecraft.h"
|
||||
#include "../../../Minecraft.Client/net/minecraft/server/MinecraftServer.h"
|
||||
#include "../../../Minecraft.Client/net/minecraft/server/level/ServerLevel.h"
|
||||
#include "../../net/minecraft/world/level/net.minecraft.world.level.h"
|
||||
#include "../../net/minecraft/world/level/storage/LevelData.h"
|
||||
#include "../../../Minecraft.Client/Common/Source Files/GameRules/LevelGeneration/LevelGenerationOptions.h"
|
||||
#include "../../net/minecraft/world/level/chunk/storage/net.minecraft.world.level.chunk.storage.h"
|
||||
|
||||
#define RESERVE_ALLOCATION MEM_RESERVE
|
||||
#define COMMIT_ALLOCATION MEM_COMMIT
|
||||
|
||||
unsigned int ConsoleSaveFileOriginal::pagesCommitted = 0;
|
||||
void* ConsoleSaveFileOriginal::pvHeap = nullptr;
|
||||
|
||||
ConsoleSaveFileOriginal::ConsoleSaveFileOriginal(
|
||||
const std::wstring& fileName, void* pvSaveData /*= nullptr*/,
|
||||
unsigned int initialFileSize /*= 0*/, bool forceCleanSave /*= false*/,
|
||||
ESavePlatform plat /*= SAVE_FILE_PLATFORM_LOCAL*/) {
|
||||
// One time initialise of static stuff required for our storage
|
||||
if (pvHeap == nullptr) {
|
||||
// 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.
|
||||
|
||||
// AP - The Vita doesn't have virtual memory so a pretend system has
|
||||
// been implemented in PSVitaStubs.cpp. All access to the memory must be
|
||||
// done via the access function as the pointer returned from
|
||||
// VirtualAlloc can't be used directly.
|
||||
pvHeap = VirtualAlloc(nullptr, MAX_PAGE_COUNT * CSF_PAGE_SIZE,
|
||||
RESERVE_ALLOCATION, PAGE_READWRITE);
|
||||
}
|
||||
|
||||
pvSaveMem = pvHeap;
|
||||
m_fileName = fileName;
|
||||
|
||||
unsigned int fileSize = initialFileSize;
|
||||
|
||||
// Load a save from the game rules
|
||||
bool bLevelGenBaseSave = false;
|
||||
LevelGenerationOptions* levelGen = app.getLevelGenerationOptions();
|
||||
if (pvSaveData == nullptr && levelGen != nullptr &&
|
||||
levelGen->requiresBaseSave()) {
|
||||
pvSaveData = levelGen->getBaseSaveData(fileSize);
|
||||
if (pvSaveData && fileSize != 0) bLevelGenBaseSave = true;
|
||||
}
|
||||
|
||||
if (pvSaveData == nullptr || fileSize == 0)
|
||||
fileSize = StorageManager.GetSaveSize();
|
||||
|
||||
if (forceCleanSave) fileSize = 0;
|
||||
|
||||
unsigned int heapSize = std::max(
|
||||
fileSize,
|
||||
1024u * 1024u * 2u); // 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 == nullptr) {
|
||||
#ifndef _CONTENT_PACKAGE
|
||||
// Out of physical memory
|
||||
__debugbreak();
|
||||
#endif
|
||||
}
|
||||
pagesCommitted = pagesRequired;
|
||||
|
||||
if (fileSize > 0) {
|
||||
if (pvSaveData != nullptr) {
|
||||
memcpy(pvSaveMem, pvSaveData, fileSize);
|
||||
if (bLevelGenBaseSave) {
|
||||
levelGen->deleteBaseSaveData();
|
||||
}
|
||||
} else {
|
||||
unsigned int storageLength;
|
||||
StorageManager.GetSaveData(pvSaveMem, &storageLength);
|
||||
app.DebugPrintf("Filesize - %d, Adjusted size - %d\n", fileSize,
|
||||
storageLength);
|
||||
fileSize = storageLength;
|
||||
}
|
||||
void* pvSourceData = pvSaveMem;
|
||||
int compressed = *(int*)pvSourceData;
|
||||
if (compressed == 0) {
|
||||
unsigned int decompSize = *((int*)pvSourceData + 1);
|
||||
if (isLocalEndianDifferent(plat)) System::ReverseULONG(&decompSize);
|
||||
|
||||
// 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");
|
||||
std::memset(pvSourceData, 0, fileSize);
|
||||
// Clear the first 8 bytes that reference the header
|
||||
header.WriteHeader(pvSourceData);
|
||||
} else {
|
||||
unsigned char* buf = new unsigned char[decompSize];
|
||||
Compression::getCompression()->SetDecompressionType(
|
||||
plat); // if this save is from another platform, set the
|
||||
// correct decompression type
|
||||
Compression::getCompression()->Decompress(
|
||||
buf, &decompSize, (unsigned char*)pvSourceData + 8,
|
||||
fileSize - 8);
|
||||
Compression::getCompression()->SetDecompressionType(
|
||||
SAVE_FILE_PLATFORM_LOCAL); // and then set the
|
||||
// decompression back to the
|
||||
// local machine's standard type
|
||||
|
||||
// Only ReAlloc if we need to (we might already have enough)
|
||||
// and align to 512 byte boundaries
|
||||
unsigned int currentHeapSize = pagesCommitted * CSF_PAGE_SIZE;
|
||||
|
||||
unsigned int 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 == nullptr) {
|
||||
// Out of physical memory
|
||||
__debugbreak();
|
||||
}
|
||||
pagesCommitted = pagesRequired;
|
||||
}
|
||||
memcpy(pvSaveMem, buf, decompSize);
|
||||
delete[] buf;
|
||||
}
|
||||
}
|
||||
|
||||
header.ReadHeader(pvSaveMem, plat);
|
||||
|
||||
} else {
|
||||
// Clear the first 8 bytes that reference the header
|
||||
header.WriteHeader(pvSaveMem);
|
||||
}
|
||||
}
|
||||
|
||||
ConsoleSaveFileOriginal::~ConsoleSaveFileOriginal() {
|
||||
VirtualFree(pvHeap, MAX_PAGE_COUNT * CSF_PAGE_SIZE, MEM_DECOMMIT);
|
||||
pagesCommitted = 0;
|
||||
}
|
||||
|
||||
// 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* ConsoleSaveFileOriginal::createFile(
|
||||
const ConsoleSavePath& fileName) {
|
||||
LockSaveAccess();
|
||||
FileEntry* file = header.AddFile(fileName.getName());
|
||||
ReleaseSaveAccess();
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
void ConsoleSaveFileOriginal::deleteFile(FileEntry* file) {
|
||||
if (file == nullptr) return;
|
||||
|
||||
LockSaveAccess();
|
||||
|
||||
unsigned int numberOfBytesRead = 0;
|
||||
unsigned int numberOfBytesWritten = 0;
|
||||
|
||||
const int bufferSize = 4096;
|
||||
int amountToRead = bufferSize;
|
||||
std::uint8_t buffer[bufferSize];
|
||||
unsigned int 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 ConsoleSaveFileOriginal::setFilePointer(FileEntry* file,
|
||||
unsigned int distanceToMove,
|
||||
SaveFileSeekOrigin seekOrigin) {
|
||||
LockSaveAccess();
|
||||
|
||||
switch (seekOrigin) {
|
||||
case SaveFileSeekOrigin::Current:
|
||||
file->currentFilePointer += distanceToMove;
|
||||
break;
|
||||
case SaveFileSeekOrigin::End:
|
||||
file->currentFilePointer =
|
||||
file->data.startOffset + file->getFileSize() + distanceToMove;
|
||||
break;
|
||||
case SaveFileSeekOrigin::Begin:
|
||||
default:
|
||||
file->currentFilePointer = file->data.startOffset + distanceToMove;
|
||||
break;
|
||||
}
|
||||
|
||||
ReleaseSaveAccess();
|
||||
}
|
||||
|
||||
// If this file needs to grow, move the data after along
|
||||
void ConsoleSaveFileOriginal::PrepareForWrite(
|
||||
FileEntry* file, unsigned int 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
|
||||
MoveDataBeyond(file, bytesToGrowBy);
|
||||
|
||||
// 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 ConsoleSaveFileOriginal::writeFile(FileEntry* file, const void* lpBuffer,
|
||||
unsigned int nNumberOfBytesToWrite,
|
||||
unsigned int* lpNumberOfBytesWritten) {
|
||||
assert(pvSaveMem != nullptr);
|
||||
if (pvSaveMem == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LockSaveAccess();
|
||||
|
||||
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 true;
|
||||
}
|
||||
|
||||
bool ConsoleSaveFileOriginal::zeroFile(FileEntry* file,
|
||||
unsigned int nNumberOfBytesToWrite,
|
||||
unsigned int* lpNumberOfBytesWritten) {
|
||||
assert(pvSaveMem != nullptr);
|
||||
if (pvSaveMem == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LockSaveAccess();
|
||||
|
||||
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 true;
|
||||
}
|
||||
|
||||
bool ConsoleSaveFileOriginal::readFile(FileEntry* file, void* lpBuffer,
|
||||
unsigned int nNumberOfBytesToRead,
|
||||
unsigned int* lpNumberOfBytesRead) {
|
||||
unsigned int actualBytesToRead;
|
||||
assert(pvSaveMem != nullptr);
|
||||
if (pvSaveMem == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LockSaveAccess();
|
||||
|
||||
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 true;
|
||||
}
|
||||
|
||||
bool ConsoleSaveFileOriginal::closeHandle(FileEntry* file) {
|
||||
LockSaveAccess();
|
||||
finalizeWrite();
|
||||
ReleaseSaveAccess();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConsoleSaveFileOriginal::finalizeWrite() {
|
||||
LockSaveAccess();
|
||||
header.WriteHeader(pvSaveMem);
|
||||
ReleaseSaveAccess();
|
||||
}
|
||||
|
||||
void ConsoleSaveFileOriginal::MoveDataBeyond(
|
||||
FileEntry* file, unsigned int nNumberOfBytesToWrite) {
|
||||
unsigned int numberOfBytesRead = 0;
|
||||
unsigned int numberOfBytesWritten = 0;
|
||||
|
||||
const unsigned int bufferSize = 4096;
|
||||
unsigned int amountToRead = bufferSize;
|
||||
// assert( nNumberOfBytesToWrite <= bufferSize );
|
||||
static std::uint8_t buffer1[bufferSize];
|
||||
static std::uint8_t buffer2[bufferSize];
|
||||
unsigned int buffer1Size = 0;
|
||||
unsigned int buffer2Size = 0;
|
||||
|
||||
// Only ReAlloc if we need to (we might already have enough) and align to
|
||||
// 512 byte boundaries
|
||||
unsigned int currentHeapSize = pagesCommitted * CSF_PAGE_SIZE;
|
||||
|
||||
unsigned int 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 == nullptr) {
|
||||
// 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 = static_cast<unsigned int>(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);
|
||||
}
|
||||
|
||||
bool ConsoleSaveFileOriginal::doesFileExist(ConsoleSavePath file) {
|
||||
LockSaveAccess();
|
||||
bool exists = header.fileExists(file.getName());
|
||||
ReleaseSaveAccess();
|
||||
|
||||
return exists;
|
||||
}
|
||||
|
||||
void ConsoleSaveFileOriginal::Flush(bool autosave, bool updateThumbnail) {
|
||||
LockSaveAccess();
|
||||
|
||||
finalizeWrite();
|
||||
|
||||
float fElapsedTime = 0.0f;
|
||||
|
||||
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
|
||||
std::uint8_t* compData =
|
||||
(std::uint8_t*)StorageManager.AllocateSaveData(compLength);
|
||||
|
||||
// If we failed to allocate then compData will be nullptr
|
||||
// Pre-calculate the compressed data size so that we can attempt to allocate
|
||||
// a smaller buffer
|
||||
if (compData == nullptr) {
|
||||
// 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
|
||||
const auto startTime = std::chrono::steady_clock::now();
|
||||
Compression::getCompression()->Compress(nullptr, &compLength, pvSaveMem,
|
||||
fileSize);
|
||||
fElapsedTime = std::chrono::duration<float>(
|
||||
std::chrono::steady_clock::now() - startTime)
|
||||
.count();
|
||||
|
||||
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 = (std::uint8_t*)StorageManager.AllocateSaveData(compLength);
|
||||
}
|
||||
|
||||
if (compData != nullptr) {
|
||||
// Re-compress all save data before we save it to disk
|
||||
PIXBeginNamedEvent(0, "Actual save compression");
|
||||
// Save the start time
|
||||
const auto startTime = std::chrono::steady_clock::now();
|
||||
Compression::getCompression()->Compress(compData + 8, &compLength,
|
||||
pvSaveMem, fileSize);
|
||||
fElapsedTime = std::chrono::duration<float>(
|
||||
std::chrono::steady_clock::now() - startTime)
|
||||
.count();
|
||||
|
||||
app.DebugPrintf("Compress: Elapsed time %f\n", fElapsedTime);
|
||||
PIXEndNamedEvent();
|
||||
|
||||
std::fill_n(compData, 8, std::uint8_t{0});
|
||||
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);
|
||||
|
||||
std::uint8_t* pbThumbnailData = nullptr;
|
||||
unsigned int dwThumbnailDataSize = 0;
|
||||
|
||||
std::uint8_t* pbDataSaveImage = nullptr;
|
||||
unsigned int dwDataSizeSaveImage = 0;
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
app.GetSaveThumbnail(&pbThumbnailData, &dwThumbnailDataSize,
|
||||
&pbDataSaveImage, &dwDataSizeSaveImage);
|
||||
#endif
|
||||
|
||||
std::uint8_t bTextMetadata[88] = {};
|
||||
|
||||
int64_t seed = 0;
|
||||
bool hasSeed = false;
|
||||
if (MinecraftServer::getInstance() != nullptr &&
|
||||
MinecraftServer::getInstance()->levels[0] != nullptr) {
|
||||
seed = MinecraftServer::getInstance()
|
||||
->levels[0]
|
||||
->getLevelData()
|
||||
->getSeed();
|
||||
hasSeed = true;
|
||||
}
|
||||
|
||||
int iTextMetadataBytes = app.CreateImageTextData(
|
||||
bTextMetadata, seed, hasSeed,
|
||||
app.GetGameHostOption(eGameHostOption_All),
|
||||
Minecraft::GetInstance()->getCurrentTexturePackId());
|
||||
|
||||
int32_t saveOrCheckpointId = 0;
|
||||
bool validSave =
|
||||
StorageManager.GetSaveUniqueNumber(&saveOrCheckpointId);
|
||||
TelemetryManager->RecordLevelSaveOrCheckpoint(
|
||||
ProfileManager.GetPrimaryPad(), saveOrCheckpointId, compLength + 8);
|
||||
#ifdef _WINDOWS64
|
||||
// set the icon and save image
|
||||
StorageManager.SetSaveImages(pbThumbnailData, dwThumbnailDataSize,
|
||||
pbDataSaveImage, dwDataSizeSaveImage,
|
||||
bTextMetadata, iTextMetadataBytes);
|
||||
app.DebugPrintf("Save thumbnail size %d\n", dwThumbnailDataSize);
|
||||
|
||||
// save the data
|
||||
StorageManager.SaveSaveData(
|
||||
&ConsoleSaveFileOriginal::SaveSaveDataCallback, this);
|
||||
#ifndef _CONTENT_PACKAGE
|
||||
if (app.DebugSettingsOn()) {
|
||||
if (app.GetWriteSavesToFolderEnabled()) {
|
||||
DebugFlushToFile(compData, compLength + 8);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
ReleaseSaveAccess();
|
||||
#else
|
||||
ReleaseSaveAccess();
|
||||
#endif
|
||||
} else {
|
||||
// We have failed to allocate the memory required to save this file. Now
|
||||
// what?
|
||||
ReleaseSaveAccess();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
|
||||
int ConsoleSaveFileOriginal::SaveSaveDataCallback(void* lpParam, bool bRes) {
|
||||
ConsoleSaveFile* pClass = (ConsoleSaveFile*)lpParam;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef _CONTENT_PACKAGE
|
||||
void ConsoleSaveFileOriginal::DebugFlushToFile(
|
||||
void* compressedData /*= nullptr*/,
|
||||
unsigned int compressedDataSize /*= 0*/) {
|
||||
LockSaveAccess();
|
||||
|
||||
finalizeWrite();
|
||||
|
||||
unsigned int fileSize = header.GetFileSize();
|
||||
|
||||
unsigned int numberOfBytesWritten = 0;
|
||||
File targetFileDir(L"Saves");
|
||||
|
||||
if (!targetFileDir.exists()) targetFileDir.mkdir();
|
||||
|
||||
wchar_t* fileName = new wchar_t[XCONTENT_MAX_FILENAME_LENGTH + 1];
|
||||
|
||||
std::time_t now = std::time(nullptr);
|
||||
std::tm t = *std::gmtime(&now);
|
||||
|
||||
// 14 chars for the digits
|
||||
// 11 chars for the separators + suffix
|
||||
// 25 chars total
|
||||
std::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.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min,
|
||||
t.tm_sec);
|
||||
|
||||
const std::wstring outputPath =
|
||||
targetFileDir.getPath() + std::wstring(fileName);
|
||||
bool writeSucceeded = false;
|
||||
|
||||
if (compressedData != nullptr && compressedDataSize > 0) {
|
||||
writeSucceeded = PortableFileIO::WriteBinaryFile(
|
||||
outputPath, compressedData, compressedDataSize);
|
||||
numberOfBytesWritten = writeSucceeded ? compressedDataSize : 0;
|
||||
assert(numberOfBytesWritten == compressedDataSize);
|
||||
} else {
|
||||
writeSucceeded =
|
||||
PortableFileIO::WriteBinaryFile(outputPath, pvSaveMem, fileSize);
|
||||
numberOfBytesWritten = writeSucceeded ? fileSize : 0;
|
||||
assert(numberOfBytesWritten == fileSize);
|
||||
}
|
||||
|
||||
delete[] fileName;
|
||||
|
||||
ReleaseSaveAccess();
|
||||
}
|
||||
#endif
|
||||
|
||||
unsigned int ConsoleSaveFileOriginal::getSizeOnDisk() {
|
||||
return header.GetFileSize();
|
||||
}
|
||||
|
||||
std::wstring ConsoleSaveFileOriginal::getFilename() { return m_fileName; }
|
||||
|
||||
std::vector<FileEntry*>* ConsoleSaveFileOriginal::getFilesWithPrefix(
|
||||
const std::wstring& prefix) {
|
||||
return header.getFilesWithPrefix(prefix);
|
||||
}
|
||||
|
||||
std::vector<FileEntry*>* ConsoleSaveFileOriginal::getRegionFilesByDimension(
|
||||
unsigned int dimensionIndex) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int ConsoleSaveFileOriginal::getSaveVersion() {
|
||||
return header.getSaveVersion();
|
||||
}
|
||||
|
||||
int ConsoleSaveFileOriginal::getOriginalSaveVersion() {
|
||||
return header.getOriginalSaveVersion();
|
||||
}
|
||||
|
||||
void ConsoleSaveFileOriginal::LockSaveAccess() { m_lock.lock(); }
|
||||
|
||||
void ConsoleSaveFileOriginal::ReleaseSaveAccess() { m_lock.unlock(); }
|
||||
|
||||
ESavePlatform ConsoleSaveFileOriginal::getSavePlatform() {
|
||||
return header.getSavePlatform();
|
||||
}
|
||||
|
||||
bool ConsoleSaveFileOriginal::isSaveEndianDifferent() {
|
||||
return header.isSaveEndianDifferent();
|
||||
}
|
||||
|
||||
void ConsoleSaveFileOriginal::setLocalPlatform() { header.setLocalPlatform(); }
|
||||
|
||||
void ConsoleSaveFileOriginal::setPlatform(ESavePlatform plat) {
|
||||
header.setPlatform(plat);
|
||||
}
|
||||
|
||||
ByteOrder ConsoleSaveFileOriginal::getSaveEndian() {
|
||||
return header.getSaveEndian();
|
||||
}
|
||||
|
||||
ByteOrder ConsoleSaveFileOriginal::getLocalEndian() {
|
||||
return header.getLocalEndian();
|
||||
}
|
||||
|
||||
void ConsoleSaveFileOriginal::setEndian(ByteOrder endian) {
|
||||
header.setEndian(endian);
|
||||
}
|
||||
|
||||
bool ConsoleSaveFileOriginal::isLocalEndianDifferent(ESavePlatform plat) {
|
||||
return getLocalEndian() != header.getEndian(plat);
|
||||
}
|
||||
|
||||
void ConsoleSaveFileOriginal::ConvertRegionFile(File sourceFile) {
|
||||
unsigned int numberOfBytesWritten = 0;
|
||||
unsigned int 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 ConsoleSaveFileOriginal::ConvertToLocalPlatform() {
|
||||
if (getSavePlatform() == SAVE_FILE_PLATFORM_LOCAL) {
|
||||
// already in the correct format
|
||||
return;
|
||||
}
|
||||
// convert each of the region files to the local platform
|
||||
std::vector<FileEntry*>* allFilesInSave =
|
||||
getFilesWithPrefix(std::wstring(L""));
|
||||
for (auto it = allFilesInSave->begin(); it < allFilesInSave->end(); ++it) {
|
||||
FileEntry* fe = *it;
|
||||
std::wstring fName(fe->data.filename);
|
||||
std::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
|
||||
}
|
||||
|
||||
void* ConsoleSaveFileOriginal::getWritePointer(FileEntry* file) {
|
||||
return (char*)pvSaveMem + file->currentFilePointer;
|
||||
;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
#include <mutex>
|
||||
|
||||
#include "FileHeader.h"
|
||||
#include "ConsoleSavePath.h"
|
||||
#include "ConsoleSaveFile.h"
|
||||
|
||||
class ConsoleSaveFileOriginal : public ConsoleSaveFile {
|
||||
private:
|
||||
FileHeader header;
|
||||
|
||||
std::wstring m_fileName;
|
||||
|
||||
// void* hHeap;
|
||||
static void* pvHeap;
|
||||
static unsigned int pagesCommitted;
|
||||
#if defined(_LARGE_WORLDS)
|
||||
static const unsigned int CSF_PAGE_SIZE = 64 * 1024;
|
||||
static const unsigned int MAX_PAGE_COUNT =
|
||||
32 * 1024; // 2GB virtual allocation
|
||||
#else
|
||||
static const unsigned int CSF_PAGE_SIZE = 64 * 1024;
|
||||
static const unsigned int MAX_PAGE_COUNT = 1024;
|
||||
#endif
|
||||
void* pvSaveMem;
|
||||
|
||||
std::recursive_mutex m_lock;
|
||||
|
||||
void PrepareForWrite(FileEntry* file, unsigned int nNumberOfBytesToWrite);
|
||||
void MoveDataBeyond(FileEntry* file, unsigned int nNumberOfBytesToWrite);
|
||||
|
||||
public:
|
||||
#if defined(_WINDOWS64)
|
||||
static int SaveSaveDataCallback(void* lpParam, bool bRes);
|
||||
#endif
|
||||
ConsoleSaveFileOriginal(const std::wstring& fileName,
|
||||
void* pvSaveData = nullptr,
|
||||
unsigned int fileSize = 0,
|
||||
bool forceCleanSave = false,
|
||||
ESavePlatform plat = SAVE_FILE_PLATFORM_LOCAL);
|
||||
virtual ~ConsoleSaveFileOriginal();
|
||||
|
||||
// 4J Stu - Initial implementation is intended to have a similar interface
|
||||
// to the standard Xbox file access functions
|
||||
|
||||
virtual FileEntry* createFile(const ConsoleSavePath& fileName);
|
||||
virtual void deleteFile(FileEntry* file);
|
||||
|
||||
virtual void setFilePointer(FileEntry* file, unsigned int distanceToMove,
|
||||
SaveFileSeekOrigin seekOrigin);
|
||||
virtual bool writeFile(FileEntry* file, const void* lpBuffer,
|
||||
unsigned int nNumberOfBytesToWrite,
|
||||
unsigned int* lpNumberOfBytesWritten);
|
||||
virtual bool zeroFile(FileEntry* file, unsigned int nNumberOfBytesToWrite,
|
||||
unsigned int* lpNumberOfBytesWritten);
|
||||
virtual bool readFile(FileEntry* file, void* lpBuffer,
|
||||
unsigned int nNumberOfBytesToRead,
|
||||
unsigned int* lpNumberOfBytesRead);
|
||||
virtual bool closeHandle(FileEntry* file);
|
||||
|
||||
virtual void finalizeWrite();
|
||||
|
||||
virtual bool doesFileExist(ConsoleSavePath file);
|
||||
|
||||
virtual void Flush(bool autosave, bool updateThumbnail = true);
|
||||
|
||||
#if !defined(_CONTENT_PACKAGE)
|
||||
virtual void DebugFlushToFile(void* compressedData = nullptr,
|
||||
unsigned int compressedDataSize = 0);
|
||||
#endif
|
||||
virtual unsigned int getSizeOnDisk();
|
||||
|
||||
virtual std::wstring getFilename();
|
||||
|
||||
virtual std::vector<FileEntry*>* getFilesWithPrefix(
|
||||
const std::wstring& prefix);
|
||||
virtual std::vector<FileEntry*>* getRegionFilesByDimension(
|
||||
unsigned int dimensionIndex);
|
||||
|
||||
virtual int getSaveVersion();
|
||||
virtual int getOriginalSaveVersion();
|
||||
|
||||
virtual void LockSaveAccess();
|
||||
virtual void ReleaseSaveAccess();
|
||||
|
||||
virtual ESavePlatform getSavePlatform();
|
||||
virtual bool isSaveEndianDifferent();
|
||||
virtual void setLocalPlatform();
|
||||
virtual void setPlatform(ESavePlatform plat);
|
||||
virtual ByteOrder getSaveEndian();
|
||||
virtual ByteOrder getLocalEndian();
|
||||
virtual void setEndian(ByteOrder endian);
|
||||
virtual bool isLocalEndianDifferent(ESavePlatform plat);
|
||||
|
||||
virtual void ConvertRegionFile(File sourceFile);
|
||||
virtual void ConvertToLocalPlatform();
|
||||
|
||||
protected:
|
||||
virtual void* getWritePointer(FileEntry* file);
|
||||
};
|
||||
@@ -0,0 +1,116 @@
|
||||
#include "../../Header Files/stdafx.h"
|
||||
#include "../../ConsoleJavaLibs/File.h"
|
||||
#include "ConsoleSaveFileOutputStream.h"
|
||||
|
||||
#include "ConsoleSaveFile.h"
|
||||
|
||||
// Creates a file output stream to write to the file represented by the
|
||||
// specified File object. A new FileDescriptor object is created to represent
|
||||
// this file connection. First, if there is a security manager, its checkWrite
|
||||
// method is called with the path represented by the file argument as its
|
||||
// argument.
|
||||
//
|
||||
// If the file exists but is a directory rather than a regular file, does not
|
||||
// exist but cannot be created, or cannot be opened for any other reason then a
|
||||
// FileNotFoundException is thrown.
|
||||
//
|
||||
// Parameters:
|
||||
// file - the file to be opened for writing.
|
||||
ConsoleSaveFileOutputStream::ConsoleSaveFileOutputStream(
|
||||
ConsoleSaveFile* saveFile, const ConsoleSavePath& file) {
|
||||
m_saveFile = saveFile;
|
||||
|
||||
m_file = m_saveFile->createFile(file);
|
||||
|
||||
m_saveFile->setFilePointer(m_file, 0, SaveFileSeekOrigin::Begin);
|
||||
}
|
||||
|
||||
ConsoleSaveFileOutputStream::ConsoleSaveFileOutputStream(
|
||||
ConsoleSaveFile* saveFile, FileEntry* file) {
|
||||
m_saveFile = saveFile;
|
||||
|
||||
m_file = file;
|
||||
|
||||
m_saveFile->setFilePointer(m_file, 0, SaveFileSeekOrigin::Begin);
|
||||
}
|
||||
|
||||
// Writes the specified byte to this file output stream. Implements the write
|
||||
// method of OutputStream. Parameters: b - the byte to be written.
|
||||
void ConsoleSaveFileOutputStream::write(unsigned int b) {
|
||||
unsigned int numberOfBytesWritten;
|
||||
|
||||
std::uint8_t value = (std::uint8_t)b;
|
||||
|
||||
bool result =
|
||||
m_saveFile->writeFile(m_file,
|
||||
&value, // data buffer
|
||||
1, // number of bytes to write
|
||||
&numberOfBytesWritten // number of bytes written
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
// TODO 4J Stu - Some kind of error handling
|
||||
} else if (numberOfBytesWritten == 0) {
|
||||
// File pointer is past the end of the file
|
||||
}
|
||||
}
|
||||
|
||||
// Writes b.length bytes from the specified byte array to this file output
|
||||
// stream. Parameters: b - the data.
|
||||
void ConsoleSaveFileOutputStream::write(byteArray b) {
|
||||
unsigned int numberOfBytesWritten;
|
||||
|
||||
bool result =
|
||||
m_saveFile->writeFile(m_file,
|
||||
&b.data, // data buffer
|
||||
b.length, // number of bytes to write
|
||||
&numberOfBytesWritten // number of bytes written
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
// TODO 4J Stu - Some kind of error handling
|
||||
} else if (numberOfBytesWritten == 0 || numberOfBytesWritten != b.length) {
|
||||
// File pointer is past the end of the file
|
||||
}
|
||||
}
|
||||
|
||||
// Writes len bytes from the specified byte array starting at offset off to this
|
||||
// file output stream. Parameters: b - the data. off - the start offset in the
|
||||
// data. len - the number of bytes to write.
|
||||
void ConsoleSaveFileOutputStream::write(byteArray b, unsigned int offset,
|
||||
unsigned int length) {
|
||||
// 4J Stu - We don't want to write any more than the array buffer holds
|
||||
assert(length <= (b.length - offset));
|
||||
|
||||
unsigned int numberOfBytesWritten;
|
||||
|
||||
bool result =
|
||||
m_saveFile->writeFile(m_file,
|
||||
&b[offset], // data buffer
|
||||
length, // number of bytes to write
|
||||
&numberOfBytesWritten // number of bytes written
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
// TODO 4J Stu - Some kind of error handling
|
||||
} else if (numberOfBytesWritten == 0 || numberOfBytesWritten != length) {
|
||||
// File pointer is past the end of the file
|
||||
}
|
||||
}
|
||||
//
|
||||
// Closes this file output stream and releases any system resources associated
|
||||
// with this stream. This file output stream may no longer be used for writing
|
||||
// bytes. If this stream has an associated channel then the channel is closed as
|
||||
// well.
|
||||
void ConsoleSaveFileOutputStream::close() {
|
||||
if (m_saveFile != nullptr) {
|
||||
bool result = m_saveFile->closeHandle(m_file);
|
||||
|
||||
if (!result) {
|
||||
// TODO 4J Stu - Some kind of error handling
|
||||
}
|
||||
|
||||
// Stop the dtor from trying to close it again
|
||||
m_saveFile = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
// 4J Stu - Implements the Java InputStream but rather than writing directly to
|
||||
// disc it writes through the save file
|
||||
|
||||
#include "../../ConsoleJavaLibs/InputOutputStream/OutputStream.h"
|
||||
|
||||
#include "ConsoleSavePath.h"
|
||||
|
||||
class ConsoleSaveFile;
|
||||
class FileEntry;
|
||||
|
||||
class ConsoleSaveFileOutputStream : public OutputStream {
|
||||
public:
|
||||
ConsoleSaveFileOutputStream(ConsoleSaveFile* saveFile,
|
||||
const ConsoleSavePath& file);
|
||||
ConsoleSaveFileOutputStream(ConsoleSaveFile* saveFile, FileEntry* file);
|
||||
virtual void write(unsigned int b);
|
||||
virtual void write(byteArray b);
|
||||
virtual void write(byteArray b, unsigned int offset, unsigned int length);
|
||||
virtual void close();
|
||||
virtual void flush() {}
|
||||
|
||||
private:
|
||||
ConsoleSaveFile* m_saveFile;
|
||||
FileEntry* m_file;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,166 @@
|
||||
#pragma once
|
||||
#include <mutex>
|
||||
|
||||
#include "FileHeader.h"
|
||||
#include "ConsoleSavePath.h"
|
||||
#include "ConsoleSaveFile.h"
|
||||
|
||||
class ProgressRenderer;
|
||||
|
||||
class ConsoleSaveFileSplit : public ConsoleSaveFile {
|
||||
private:
|
||||
FileHeader header;
|
||||
|
||||
static const int WRITE_BANDWIDTH_BYTESPERSECOND =
|
||||
1048576; // Average bytes per second we will cap to when writing region
|
||||
// files during the tick() method
|
||||
static const int WRITE_BANDWIDTH_MEASUREMENT_PERIOD_SECONDS =
|
||||
10; // Time period over which the bytes per second average is
|
||||
// calculated
|
||||
static const int WRITE_TICK_RATE_MS =
|
||||
500; // Time between attempts to work out which regions we should write
|
||||
// during the tick
|
||||
static const int WRITE_MAX_WRITE_PER_TICK =
|
||||
WRITE_BANDWIDTH_BYTESPERSECOND; // Maximum number of bytes we can add
|
||||
// in a single tick
|
||||
|
||||
class WriteHistory {
|
||||
public:
|
||||
std::int64_t writeTime;
|
||||
unsigned int writeSize;
|
||||
};
|
||||
|
||||
class DirtyRegionFile {
|
||||
public:
|
||||
std::int64_t lastWritten;
|
||||
unsigned int fileRef;
|
||||
bool operator<(const DirtyRegionFile& rhs) const {
|
||||
return lastWritten < rhs.lastWritten;
|
||||
}
|
||||
};
|
||||
|
||||
class RegionFileReference {
|
||||
public:
|
||||
RegionFileReference(int index, unsigned int regionIndex,
|
||||
unsigned int length = 0,
|
||||
unsigned char* data = nullptr);
|
||||
~RegionFileReference();
|
||||
void Compress(); // Compress from data to dataCompressed
|
||||
void Decompress(); // Decompress from dataCompressed -> data
|
||||
unsigned int GetCompressedSize(); // Gets byte size for what this
|
||||
// region will compress to
|
||||
void ReleaseCompressed(); // Release dataCompressed
|
||||
FileEntry* fileEntry;
|
||||
unsigned char* data;
|
||||
unsigned char* dataCompressed;
|
||||
unsigned int dataCompressedSize;
|
||||
int index;
|
||||
bool dirty;
|
||||
std::int64_t lastWritten;
|
||||
};
|
||||
std::unordered_map<unsigned int, RegionFileReference*> regionFiles;
|
||||
std::vector<WriteHistory> writeHistory;
|
||||
std::int64_t m_lastTickTime;
|
||||
|
||||
FileEntry* GetRegionFileEntry(unsigned int regionIndex);
|
||||
|
||||
std::wstring m_fileName;
|
||||
bool m_autosave;
|
||||
|
||||
// void* hHeap;
|
||||
static void* pvHeap;
|
||||
static unsigned int pagesCommitted;
|
||||
#if defined(_LARGE_WORLDS)
|
||||
static const unsigned int CSF_PAGE_SIZE = 64 * 1024;
|
||||
static const unsigned int MAX_PAGE_COUNT =
|
||||
32 * 1024; // 2GB virtual allocation
|
||||
#else
|
||||
static const unsigned int CSF_PAGE_SIZE = 64 * 1024;
|
||||
static const unsigned int MAX_PAGE_COUNT = 1024;
|
||||
#endif
|
||||
void* pvSaveMem;
|
||||
|
||||
std::recursive_mutex m_lock;
|
||||
|
||||
void PrepareForWrite(FileEntry* file, unsigned int nNumberOfBytesToWrite);
|
||||
void MoveDataBeyond(FileEntry* file, unsigned int nNumberOfBytesToWrite);
|
||||
|
||||
bool GetNumericIdentifierFromName(const std::wstring& fileName,
|
||||
unsigned int* idOut);
|
||||
std::wstring GetNameFromNumericIdentifier(unsigned int idIn);
|
||||
void processSubfilesForWrite();
|
||||
void processSubfilesAfterWrite();
|
||||
|
||||
public:
|
||||
static int SaveSaveDataCallback(void* lpParam, bool bRes);
|
||||
static int SaveRegionFilesCallback(void* lpParam, bool bRes);
|
||||
|
||||
private:
|
||||
void _init(const std::wstring& fileName, void* pvSaveData,
|
||||
unsigned int fileSize, ESavePlatform plat);
|
||||
|
||||
public:
|
||||
ConsoleSaveFileSplit(const std::wstring& fileName,
|
||||
void* pvSaveData = nullptr, unsigned int fileSize = 0,
|
||||
bool forceCleanSave = false,
|
||||
ESavePlatform plat = SAVE_FILE_PLATFORM_LOCAL);
|
||||
ConsoleSaveFileSplit(ConsoleSaveFile* sourceSave,
|
||||
bool alreadySmallRegions = true,
|
||||
ProgressListener* progress = nullptr);
|
||||
virtual ~ConsoleSaveFileSplit();
|
||||
|
||||
// 4J Stu - Initial implementation is intended to have a similar interface
|
||||
// to the standard Xbox file access functions
|
||||
|
||||
virtual FileEntry* createFile(const ConsoleSavePath& fileName);
|
||||
virtual void deleteFile(FileEntry* file);
|
||||
|
||||
virtual void setFilePointer(FileEntry* file, unsigned int distanceToMove,
|
||||
SaveFileSeekOrigin seekOrigin);
|
||||
virtual bool writeFile(FileEntry* file, const void* lpBuffer,
|
||||
unsigned int nNumberOfBytesToWrite,
|
||||
unsigned int* lpNumberOfBytesWritten);
|
||||
virtual bool zeroFile(FileEntry* file, unsigned int nNumberOfBytesToWrite,
|
||||
unsigned int* lpNumberOfBytesWritten);
|
||||
virtual bool readFile(FileEntry* file, void* lpBuffer,
|
||||
unsigned int nNumberOfBytesToRead,
|
||||
unsigned int* lpNumberOfBytesRead);
|
||||
virtual bool closeHandle(FileEntry* file);
|
||||
|
||||
virtual void finalizeWrite();
|
||||
virtual void tick();
|
||||
|
||||
virtual bool doesFileExist(ConsoleSavePath file);
|
||||
|
||||
virtual void Flush(bool autosave, bool updateThumbnail = true);
|
||||
|
||||
#if !defined(_CONTENT_PACKAGE)
|
||||
virtual void DebugFlushToFile(void* compressedData = nullptr,
|
||||
unsigned int compressedDataSize = 0);
|
||||
#endif
|
||||
virtual unsigned int getSizeOnDisk();
|
||||
|
||||
virtual std::wstring getFilename();
|
||||
|
||||
virtual std::vector<FileEntry*>* getFilesWithPrefix(
|
||||
const std::wstring& prefix);
|
||||
virtual std::vector<FileEntry*>* getRegionFilesByDimension(
|
||||
unsigned int dimensionIndex);
|
||||
|
||||
virtual int getSaveVersion();
|
||||
virtual int getOriginalSaveVersion();
|
||||
|
||||
virtual void LockSaveAccess();
|
||||
virtual void ReleaseSaveAccess();
|
||||
|
||||
virtual ESavePlatform getSavePlatform();
|
||||
virtual bool isSaveEndianDifferent();
|
||||
virtual void setLocalPlatform();
|
||||
virtual void setPlatform(ESavePlatform plat);
|
||||
virtual ByteOrder getSaveEndian();
|
||||
virtual ByteOrder getLocalEndian();
|
||||
virtual void setEndian(ByteOrder endian);
|
||||
|
||||
virtual void ConvertRegionFile(File sourceFile);
|
||||
virtual void ConvertToLocalPlatform();
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
class ConsoleSavePath {
|
||||
private:
|
||||
std::wstring path;
|
||||
|
||||
public:
|
||||
ConsoleSavePath(const std::wstring& newPath) { path = newPath; }
|
||||
|
||||
std::wstring getName() const { return path; }
|
||||
|
||||
std::wstring operator+(std::wstring& b) { return path + b; }
|
||||
};
|
||||
364
Minecraft.World/ConsoleHelpers/ConsoleSaveFileIO/FileHeader.cpp
Normal file
364
Minecraft.World/ConsoleHelpers/ConsoleSaveFileIO/FileHeader.cpp
Normal file
@@ -0,0 +1,364 @@
|
||||
#include "../../Header Files/stdafx.h"
|
||||
#include "FileHeader.h"
|
||||
|
||||
// #define _DEBUG_FILE_HEADER
|
||||
|
||||
extern CConsoleMinecraftApp app;
|
||||
|
||||
FileHeader::FileHeader() {
|
||||
lastFile = nullptr;
|
||||
m_saveVersion = 0;
|
||||
|
||||
// New saves should have an original version set to the latest version. This
|
||||
// will be overridden when we load a save
|
||||
m_originalSaveVersion = SAVE_FILE_VERSION_NUMBER;
|
||||
m_savePlatform = SAVE_FILE_PLATFORM_LOCAL;
|
||||
m_saveEndian = m_localEndian;
|
||||
}
|
||||
|
||||
FileHeader::~FileHeader() {
|
||||
for (unsigned int i = 0; i < fileTable.size(); ++i) {
|
||||
delete fileTable[i];
|
||||
}
|
||||
}
|
||||
|
||||
FileEntry* FileHeader::AddFile(const std::wstring& name,
|
||||
unsigned int length /* = 0 */) {
|
||||
assert(name.length() < 64);
|
||||
|
||||
wchar_t filename[64];
|
||||
memset(&filename, 0, sizeof(wchar_t) * 64);
|
||||
memcpy(&filename, name.c_str(),
|
||||
std::min(sizeof(wchar_t) * 64, sizeof(wchar_t) * name.length()));
|
||||
|
||||
// Would a map be more efficient? Our file tables probably won't be very big
|
||||
// so better to avoid hashing all the time? Does the file exist?
|
||||
for (unsigned int i = 0; i < fileTable.size(); ++i) {
|
||||
if (wcscmp(fileTable[i]->data.filename, filename) == 0) {
|
||||
// If so, return it
|
||||
return fileTable[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Else, add it to our file table
|
||||
fileTable.push_back(new FileEntry(filename, length, GetStartOfNextData()));
|
||||
lastFile = fileTable[fileTable.size() - 1];
|
||||
return lastFile;
|
||||
}
|
||||
|
||||
void FileHeader::RemoveFile(FileEntry* file) {
|
||||
if (file == nullptr) return;
|
||||
|
||||
AdjustStartOffsets(file, file->getFileSize(), true);
|
||||
|
||||
auto it = find(fileTable.begin(), fileTable.end(), file);
|
||||
|
||||
if (it < fileTable.end()) {
|
||||
fileTable.erase(it);
|
||||
}
|
||||
|
||||
#if !defined(_CONTENT_PACKAGE)
|
||||
wprintf(L"Removed file %ls\n", file->data.filename);
|
||||
#endif
|
||||
|
||||
delete file;
|
||||
}
|
||||
|
||||
void FileHeader::WriteHeader(void* saveMem) {
|
||||
unsigned int headerOffset = GetStartOfNextData();
|
||||
|
||||
// 4J Changed for save version 2 to be the number of files rather than the
|
||||
// size in bytes
|
||||
unsigned int headerSize = (int)(fileTable.size());
|
||||
|
||||
// uint32_t numberOfBytesWritten = 0;
|
||||
|
||||
// Write the offset of the header
|
||||
// assert(numberOfBytesWritten == 4);
|
||||
int* begin = (int*)saveMem;
|
||||
*begin = headerOffset;
|
||||
|
||||
// Write the size of the header
|
||||
// assert(numberOfBytesWritten == 4);
|
||||
*(begin + 1) = headerSize;
|
||||
|
||||
short* versions = (short*)(begin + 2);
|
||||
// Write the original version number
|
||||
*versions = m_originalSaveVersion;
|
||||
|
||||
// Write the version number
|
||||
short versionNumber = SAVE_FILE_VERSION_NUMBER;
|
||||
// assert(numberOfBytesWritten == 4);
|
||||
//*(begin + 2) = versionNumber;
|
||||
*(versions + 1) = versionNumber;
|
||||
|
||||
#if defined(_DEBUG_FILE_HEADER)
|
||||
app.DebugPrintf(
|
||||
"Write save file with original version: %d, and current version %d\n",
|
||||
m_originalSaveVersion, versionNumber);
|
||||
#endif
|
||||
|
||||
char* headerPosition = (char*)saveMem + headerOffset;
|
||||
|
||||
#if defined(_DEBUG_FILE_HEADER)
|
||||
app.DebugPrintf("\n\nWrite file Header: Offset = %d, Size = %d\n",
|
||||
headerOffset, headerSize);
|
||||
#endif
|
||||
|
||||
// Write the header
|
||||
for (unsigned int i = 0; i < fileTable.size(); ++i) {
|
||||
// wprintf(L"File: %ls, Start = %d, Length = %d, End = %d\n",
|
||||
// fileTable[i]->data.filename, fileTable[i]->data.startOffset,
|
||||
// fileTable[i]->data.length, fileTable[i]->data.startOffset +
|
||||
// fileTable[i]->data.length);
|
||||
memcpy((void*)headerPosition, &fileTable[i]->data,
|
||||
sizeof(FileEntrySaveData));
|
||||
// assert(numberOfBytesWritten == sizeof(FileEntrySaveData));
|
||||
headerPosition += sizeof(FileEntrySaveData);
|
||||
}
|
||||
}
|
||||
|
||||
void FileHeader::ReadHeader(
|
||||
void* saveMem, ESavePlatform plat /*= SAVE_FILE_PLATFORM_LOCAL */) {
|
||||
unsigned int headerOffset;
|
||||
unsigned int headerSize;
|
||||
|
||||
m_savePlatform = plat;
|
||||
|
||||
switch (m_savePlatform) {
|
||||
case SAVE_FILE_PLATFORM_X360:
|
||||
case SAVE_FILE_PLATFORM_PS3:
|
||||
m_saveEndian = BIGENDIAN;
|
||||
break;
|
||||
case SAVE_FILE_PLATFORM_XBONE:
|
||||
case SAVE_FILE_PLATFORM_WIN64:
|
||||
case SAVE_FILE_PLATFORM_PS4:
|
||||
case SAVE_FILE_PLATFORM_PSVITA:
|
||||
m_saveEndian = LITTLEENDIAN;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
m_savePlatform = SAVE_FILE_PLATFORM_LOCAL;
|
||||
m_saveEndian = m_localEndian;
|
||||
break;
|
||||
}
|
||||
|
||||
// Read the offset of the header
|
||||
// assert(numberOfBytesRead == 4);
|
||||
int* begin = (int*)saveMem;
|
||||
headerOffset = *begin;
|
||||
if (isSaveEndianDifferent()) System::ReverseULONG(&headerOffset);
|
||||
|
||||
// Read the size of the header
|
||||
// assert(numberOfBytesRead == 4);
|
||||
headerSize = *(begin + 1);
|
||||
if (isSaveEndianDifferent()) System::ReverseULONG(&headerSize);
|
||||
|
||||
short* versions = (short*)(begin + 2);
|
||||
// Read the original save version number
|
||||
m_originalSaveVersion = *(versions);
|
||||
if (isSaveEndianDifferent()) System::ReverseSHORT(&m_originalSaveVersion);
|
||||
|
||||
// Read the save version number
|
||||
// m_saveVersion = *(begin + 2);
|
||||
m_saveVersion = *(versions + 1);
|
||||
if (isSaveEndianDifferent()) System::ReverseSHORT(&m_saveVersion);
|
||||
|
||||
#if defined(_DEBUG_FILE_HEADER)
|
||||
app.DebugPrintf(
|
||||
"Read save file with orignal version: %d, and current version %d\n",
|
||||
m_originalSaveVersion, m_saveVersion);
|
||||
app.DebugPrintf("\n\nRead file Header: Offset = %d, Size = %d\n",
|
||||
headerOffset, headerSize);
|
||||
#endif
|
||||
|
||||
char* headerPosition = (char*)saveMem + headerOffset;
|
||||
|
||||
switch (m_saveVersion) {
|
||||
// case SAVE_FILE_VERSION_NUMBER:
|
||||
// case 8: // 4J Stu - SAVE_FILE_VERSION_NUMBER 2,3,4,5,6,7,8 are the
|
||||
// same, but: : Bumped
|
||||
// it to 3 in TU5 to force older builds (ie 0062) to
|
||||
// generate a new world when trying to load new saves
|
||||
// : Bumped it to 4 in TU9 to delete versions of The End that were
|
||||
// generated in builds prior to TU9
|
||||
// : Bumped it to 5 in TU9 to update the map data that was only using 1
|
||||
// bit to determine dimension
|
||||
// : Bumped it to 6 for PS3 v1 to update map data mappings to use larger
|
||||
// PlayerUID : Bumped
|
||||
// it to 7 for Durango v1 to update map data mappings to use string
|
||||
// based PlayerUID
|
||||
// : Bumped it to 8 for Durango v1 when to save the chunks in a
|
||||
// different compressed format
|
||||
case SAVE_FILE_VERSION_COMPRESSED_CHUNK_STORAGE:
|
||||
case SAVE_FILE_VERSION_DURANGO_CHANGE_MAP_DATA_MAPPING_SIZE:
|
||||
case SAVE_FILE_VERSION_CHANGE_MAP_DATA_MAPPING_SIZE:
|
||||
case SAVE_FILE_VERSION_MOVED_STRONGHOLD:
|
||||
case SAVE_FILE_VERSION_NEW_END:
|
||||
case SAVE_FILE_VERSION_POST_LAUNCH:
|
||||
case SAVE_FILE_VERSION_LAUNCH: {
|
||||
// Changes for save file version 2:
|
||||
// headerSize is now a count of elements rather than a count of
|
||||
// bytes The FileEntrySaveData struct has a lastModifiedTime member
|
||||
|
||||
// Read the header
|
||||
FileEntrySaveData* fesdHeaderPosition =
|
||||
(FileEntrySaveData*)headerPosition;
|
||||
for (unsigned int i = 0; i < headerSize; ++i) {
|
||||
FileEntry* entry = new FileEntry();
|
||||
// assert(numberOfBytesRead == sizeof(FileEntrySaveData));
|
||||
|
||||
memcpy(&entry->data, fesdHeaderPosition,
|
||||
sizeof(FileEntrySaveData));
|
||||
|
||||
if (isSaveEndianDifferent()) {
|
||||
// Reverse bytes
|
||||
// System::ReverseWCHARA(entry->data.filename,64);
|
||||
System::ReverseULONG(&entry->data.length);
|
||||
System::ReverseULONG(&entry->data.startOffset);
|
||||
System::ReverseULONGLONG(&entry->data.lastModifiedTime);
|
||||
}
|
||||
|
||||
entry->currentFilePointer = entry->data.startOffset;
|
||||
lastFile = entry;
|
||||
fileTable.push_back(entry);
|
||||
#if defined(_DEBUG_FILE_HEADER)
|
||||
app.DebugPrintf(
|
||||
"File: %ls, Start = %d, Length = %d, End = %d, Timestamp = "
|
||||
"%lld\n",
|
||||
entry->data.filename, entry->data.startOffset,
|
||||
entry->data.length,
|
||||
entry->data.startOffset + entry->data.length,
|
||||
entry->data.lastModifiedTime);
|
||||
#endif
|
||||
|
||||
fesdHeaderPosition++;
|
||||
}
|
||||
} break;
|
||||
|
||||
// Legacy save versions, with updated code to convert the
|
||||
// FileEntrySaveData to the latest version 4J Stu - At time of writing,
|
||||
// the tutorial save is V1 so need to keep this for compatibility
|
||||
case SAVE_FILE_VERSION_PRE_LAUNCH: {
|
||||
// Read the header
|
||||
// We can then make headerPosition a FileEntrySaveData pointer and
|
||||
// just increment by one up to the number
|
||||
unsigned int i = 0;
|
||||
while (i < headerSize) {
|
||||
FileEntry* entry = new FileEntry();
|
||||
// assert(numberOfBytesRead == sizeof(FileEntrySaveData));
|
||||
|
||||
memcpy(&entry->data, headerPosition,
|
||||
sizeof(FileEntrySaveDataV1));
|
||||
|
||||
entry->currentFilePointer = entry->data.startOffset;
|
||||
lastFile = entry;
|
||||
fileTable.push_back(entry);
|
||||
#if defined(_DEBUG_FILE_HEADER)
|
||||
app.DebugPrintf(
|
||||
"File: %ls, Start = %d, Length = %d, End = %d\n",
|
||||
entry->data.filename, entry->data.startOffset,
|
||||
entry->data.length,
|
||||
entry->data.startOffset + entry->data.length);
|
||||
#endif
|
||||
|
||||
i += sizeof(FileEntrySaveDataV1);
|
||||
headerPosition += sizeof(FileEntrySaveDataV1);
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
#if !defined(_CONTENT_PACKAGE)
|
||||
app.DebugPrintf("********** Invalid save version %d\n",
|
||||
m_saveVersion);
|
||||
__debugbreak();
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int FileHeader::GetStartOfNextData() {
|
||||
// The first 4 bytes is the location of the header (the header itself is at
|
||||
// the end of the file) Then 4 bytes for the size of the header Then 2 bytes
|
||||
// for the version number at which this save was first generated Then 2
|
||||
// bytes for the version number that the save should now be at
|
||||
unsigned int totalBytesSoFar = SAVE_FILE_HEADER_SIZE;
|
||||
for (unsigned int i = 0; i < fileTable.size(); ++i) {
|
||||
if (fileTable[i]->getFileSize() > 0)
|
||||
totalBytesSoFar += fileTable[i]->getFileSize();
|
||||
}
|
||||
return totalBytesSoFar;
|
||||
}
|
||||
|
||||
unsigned int FileHeader::GetFileSize() {
|
||||
return GetStartOfNextData() +
|
||||
(sizeof(FileEntrySaveData) * (unsigned int)fileTable.size());
|
||||
}
|
||||
|
||||
void FileHeader::AdjustStartOffsets(FileEntry* file,
|
||||
unsigned int nNumberOfBytesToWrite,
|
||||
bool subtract /*= false*/) {
|
||||
bool found = false;
|
||||
for (unsigned int i = 0; i < fileTable.size(); ++i) {
|
||||
if (found == true) {
|
||||
if (subtract) {
|
||||
fileTable[i]->data.startOffset -= nNumberOfBytesToWrite;
|
||||
fileTable[i]->currentFilePointer -= nNumberOfBytesToWrite;
|
||||
} else {
|
||||
fileTable[i]->data.startOffset += nNumberOfBytesToWrite;
|
||||
fileTable[i]->currentFilePointer += nNumberOfBytesToWrite;
|
||||
}
|
||||
} else if (fileTable[i] == file) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FileHeader::fileExists(const std::wstring& name) {
|
||||
for (unsigned int i = 0; i < fileTable.size(); ++i) {
|
||||
if (wcscmp(fileTable[i]->data.filename, name.c_str()) == 0) {
|
||||
// If so, return it
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<FileEntry*>* FileHeader::getFilesWithPrefix(
|
||||
const std::wstring& prefix) {
|
||||
std::vector<FileEntry*>* files = nullptr;
|
||||
|
||||
for (unsigned int i = 0; i < fileTable.size(); ++i) {
|
||||
if (wcsncmp(fileTable[i]->data.filename, prefix.c_str(),
|
||||
prefix.size()) == 0) {
|
||||
if (files == nullptr) {
|
||||
files = new std::vector<FileEntry*>();
|
||||
}
|
||||
|
||||
files->push_back(fileTable[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
ByteOrder FileHeader::getEndian(ESavePlatform plat) {
|
||||
ByteOrder platEndian;
|
||||
switch (plat) {
|
||||
case SAVE_FILE_PLATFORM_X360:
|
||||
case SAVE_FILE_PLATFORM_PS3:
|
||||
return BIGENDIAN;
|
||||
break;
|
||||
|
||||
case SAVE_FILE_PLATFORM_NONE:
|
||||
case SAVE_FILE_PLATFORM_XBONE:
|
||||
case SAVE_FILE_PLATFORM_PS4:
|
||||
case SAVE_FILE_PLATFORM_PSVITA:
|
||||
case SAVE_FILE_PLATFORM_WIN64:
|
||||
return LITTLEENDIAN;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
return LITTLEENDIAN;
|
||||
}
|
||||
206
Minecraft.World/ConsoleHelpers/ConsoleSaveFileIO/FileHeader.h
Normal file
206
Minecraft.World/ConsoleHelpers/ConsoleSaveFileIO/FileHeader.h
Normal file
@@ -0,0 +1,206 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../ConsoleJavaLibs/System.h"
|
||||
|
||||
// The first 4 bytes is the location of the header (the header itself is at the
|
||||
// end of the file) Then 4 bytes for the size of the header Then 2 bytes for the
|
||||
// version number at which this save was first generated Then 2 bytes for the
|
||||
// version number that the save should now be at ( the rest of the header is
|
||||
// actually a footer )
|
||||
#define SAVE_FILE_HEADER_SIZE 12
|
||||
|
||||
enum ESaveVersions {
|
||||
// Pre-release version
|
||||
SAVE_FILE_VERSION_PRE_LAUNCH = 1,
|
||||
|
||||
// This is the version at which we launched the Xbox360 version
|
||||
SAVE_FILE_VERSION_LAUNCH = 2,
|
||||
|
||||
// This is the version at which we had made changes that broke older saves
|
||||
SAVE_FILE_VERSION_POST_LAUNCH = 3,
|
||||
|
||||
// This is the version at which we introduced the End, and any saves older
|
||||
// than this will have their End data deleted
|
||||
SAVE_FILE_VERSION_NEW_END = 4,
|
||||
|
||||
// This is the version at which we change the stronghold generation, and any
|
||||
// saves older than this should should the original version
|
||||
SAVE_FILE_VERSION_MOVED_STRONGHOLD = 5,
|
||||
|
||||
// This is the version at which we changed the playeruid format for PS3
|
||||
SAVE_FILE_VERSION_CHANGE_MAP_DATA_MAPPING_SIZE = 6,
|
||||
|
||||
// This is the version at which we changed the playeruid format for Xbox One
|
||||
SAVE_FILE_VERSION_DURANGO_CHANGE_MAP_DATA_MAPPING_SIZE = 7,
|
||||
|
||||
// This is the version at which we changed the chunk format to directly save
|
||||
// the compressed storage formats
|
||||
SAVE_FILE_VERSION_COMPRESSED_CHUNK_STORAGE,
|
||||
|
||||
// This is the version at which we added inhabited time to chunk (1.6.4)
|
||||
SAVE_FILE_VERSION_CHUNK_INHABITED_TIME,
|
||||
|
||||
// 4J Stu - If you add a new version here, the save conversion tool will
|
||||
// also need updated to be able to read this new format
|
||||
|
||||
SAVE_FILE_VERSION_NEXT,
|
||||
};
|
||||
|
||||
// This is the version at which we changed the playeruid format for Xbox One
|
||||
#define SAVE_FILE_VERSION_DURANGO_CHANGE_MAP_DATA_MAPPING_SIZE 7
|
||||
|
||||
enum ESavePlatform {
|
||||
SAVE_FILE_PLATFORM_NONE = MAKE_FOURCC('N', 'O', 'N', 'E'),
|
||||
SAVE_FILE_PLATFORM_X360 = MAKE_FOURCC('X', '3', '6', '0'),
|
||||
SAVE_FILE_PLATFORM_XBONE = MAKE_FOURCC('X', 'B', '1', '_'),
|
||||
SAVE_FILE_PLATFORM_PS3 = MAKE_FOURCC('P', 'S', '3', '_'),
|
||||
SAVE_FILE_PLATFORM_PS4 = MAKE_FOURCC('P', 'S', '4', '_'),
|
||||
SAVE_FILE_PLATFORM_PSVITA = MAKE_FOURCC('P', 'S', 'V', '_'),
|
||||
SAVE_FILE_PLATFORM_WIN64 = MAKE_FOURCC('W', 'I', 'N', '_'),
|
||||
|
||||
#if defined(_WINDOWS64)
|
||||
SAVE_FILE_PLATFORM_LOCAL = SAVE_FILE_PLATFORM_WIN64
|
||||
#else
|
||||
// DecalOverdose(HACK + TODO)
|
||||
SAVE_FILE_PLATFORM_LOCAL = SAVE_FILE_PLATFORM_WIN64
|
||||
#endif
|
||||
};
|
||||
#define SAVE_FILE_VERSION_NUMBER (SAVE_FILE_VERSION_NEXT - 1)
|
||||
|
||||
struct FileEntrySaveDataV1 {
|
||||
public:
|
||||
wchar_t filename[64]; // 64 * 2B
|
||||
unsigned int length; // In bytes // 4B
|
||||
|
||||
// This is only valid once the save file has been written/loaded at least
|
||||
// once
|
||||
unsigned int startOffset; // 4B
|
||||
};
|
||||
|
||||
// It's important that we keep the order and size of the data here to smooth
|
||||
// updating 4J Stu - As of writing the tutorial level uses a V1 save file
|
||||
struct FileEntrySaveDataV2 {
|
||||
public:
|
||||
wchar_t filename[64]; // 64 * 2B
|
||||
unsigned int length; // In bytes // 4B
|
||||
|
||||
union {
|
||||
// This is only valid once the save file has been written/loaded at
|
||||
// least once
|
||||
unsigned int startOffset; // 4B
|
||||
// For region files stored via ConsolveSaveFileSplit, these aren't
|
||||
// stored within the normal save file, identified by not having a name
|
||||
// (filename[0] is 0). Note: These won't be read or written as part of a
|
||||
// file header, and should only exist wrapped up in a FileEntry class
|
||||
unsigned int regionIndex; // 4B
|
||||
};
|
||||
|
||||
int64_t lastModifiedTime; // 8B
|
||||
};
|
||||
|
||||
typedef FileEntrySaveDataV2 FileEntrySaveData;
|
||||
|
||||
class FileEntry {
|
||||
public:
|
||||
FileEntrySaveData data;
|
||||
|
||||
unsigned int currentFilePointer;
|
||||
|
||||
FileEntry() { ZeroMemory(&data, sizeof(FileEntrySaveData)); }
|
||||
|
||||
FileEntry(wchar_t name[64], unsigned int length, unsigned int startOffset) {
|
||||
data.length = length;
|
||||
data.startOffset = startOffset;
|
||||
memset(&data.filename, 0, sizeof(wchar_t) * 64);
|
||||
memcpy(&data.filename, name, sizeof(wchar_t) * 64);
|
||||
|
||||
data.lastModifiedTime = 0;
|
||||
|
||||
currentFilePointer = data.startOffset;
|
||||
}
|
||||
|
||||
unsigned int getFileSize() { return data.length; }
|
||||
bool isRegionFile() {
|
||||
return data.filename[0] == 0;
|
||||
} // When using ConsoleSaveFileSplit only
|
||||
unsigned int getRegionFileIndex() {
|
||||
return data.regionIndex;
|
||||
} // When using ConsoleSaveFileSplit only
|
||||
|
||||
void updateLastModifiedTime() {
|
||||
data.lastModifiedTime = System::currentRealTimeMillis();
|
||||
}
|
||||
|
||||
/*
|
||||
Comparison function object that returns true if the first argument goes
|
||||
before the second argument in the specific strict weak ordering it defines,
|
||||
and false otherwise. Used in a call to std::sort in
|
||||
DirectoryLevelStorage.cpp
|
||||
*/
|
||||
static bool newestFirst(FileEntry* a, FileEntry* b) {
|
||||
return a->data.lastModifiedTime > b->data.lastModifiedTime;
|
||||
}
|
||||
};
|
||||
|
||||
// A class the represents the header of the save file
|
||||
class FileHeader {
|
||||
friend class ConsoleSaveFileOriginal;
|
||||
friend class ConsoleSaveFileSplit;
|
||||
|
||||
private:
|
||||
std::vector<FileEntry*> fileTable;
|
||||
ESavePlatform m_savePlatform;
|
||||
ByteOrder m_saveEndian;
|
||||
static const ByteOrder m_localEndian = LITTLEENDIAN;
|
||||
|
||||
short m_saveVersion;
|
||||
short m_originalSaveVersion;
|
||||
|
||||
public:
|
||||
FileEntry* lastFile;
|
||||
|
||||
public:
|
||||
FileHeader();
|
||||
~FileHeader();
|
||||
|
||||
protected:
|
||||
FileEntry* AddFile(const std::wstring& name, unsigned int length = 0);
|
||||
void RemoveFile(FileEntry*);
|
||||
void WriteHeader(void* saveMem);
|
||||
void ReadHeader(void* saveMem,
|
||||
ESavePlatform plat = SAVE_FILE_PLATFORM_LOCAL);
|
||||
|
||||
unsigned int GetStartOfNextData();
|
||||
|
||||
unsigned int GetFileSize();
|
||||
|
||||
void AdjustStartOffsets(FileEntry* file, unsigned int nNumberOfBytesToWrite,
|
||||
bool subtract = false);
|
||||
|
||||
bool fileExists(const std::wstring& name);
|
||||
|
||||
std::vector<FileEntry*>* getFilesWithPrefix(const std::wstring& prefix);
|
||||
|
||||
std::vector<FileEntry*>* getValidPlayerDatFiles();
|
||||
|
||||
void setSaveVersion(int version) { m_saveVersion = version; }
|
||||
int getSaveVersion() { return m_saveVersion; }
|
||||
void setOriginalSaveVersion(int version) {
|
||||
m_originalSaveVersion = version;
|
||||
}
|
||||
int getOriginalSaveVersion() { return m_originalSaveVersion; }
|
||||
ESavePlatform getSavePlatform() { return m_savePlatform; }
|
||||
void setPlatform(ESavePlatform plat) { m_savePlatform = plat; }
|
||||
bool isSaveEndianDifferent() { return m_saveEndian != m_localEndian; }
|
||||
void setLocalPlatform() {
|
||||
m_savePlatform = SAVE_FILE_PLATFORM_LOCAL;
|
||||
m_saveEndian = m_localEndian;
|
||||
}
|
||||
ByteOrder getSaveEndian() { return m_saveEndian; }
|
||||
static ByteOrder getLocalEndian() { return m_localEndian; }
|
||||
void setEndian(ByteOrder endian) { m_saveEndian = endian; }
|
||||
static ByteOrder getEndian(ESavePlatform plat);
|
||||
bool isLocalEndianDifferent(ESavePlatform plat) {
|
||||
return m_localEndian != getEndian(plat);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user