diff options
Diffstat (limited to 'Minecraft.World/FileHeader.cpp')
| -rw-r--r-- | Minecraft.World/FileHeader.cpp | 681 |
1 files changed, 681 insertions, 0 deletions
diff --git a/Minecraft.World/FileHeader.cpp b/Minecraft.World/FileHeader.cpp new file mode 100644 index 00000000..59e97d9b --- /dev/null +++ b/Minecraft.World/FileHeader.cpp @@ -0,0 +1,681 @@ +#include "stdafx.h" +#include "FileHeader.h" + +//#define _DEBUG_FILE_HEADER + +FileHeader::FileHeader() +{ + lastFile = NULL; + 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 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(), 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 == NULL ) return; + + AdjustStartOffsets(file, file->getFileSize(), true); + + AUTO_VAR(it, find(fileTable.begin(), fileTable.end(),file)); + + if( it < fileTable.end() ) + { + fileTable.erase( it ); + } + +#ifndef _CONTENT_PACKAGE + wprintf(L"Removed file %ls\n", file->data.filename); +#endif + + delete file; +} + +void FileHeader::WriteHeader( LPVOID 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()); + + //DWORD numberOfBytesWritten = 0; + + // Write the offset of the header + //assert(numberOfBytesWritten == 4); + int *begin = (int *)saveMem; +#ifdef __PSVITA__ + VirtualCopyTo(begin, &headerOffset, sizeof(headerOffset)); +#else + *begin = headerOffset; +#endif + + // Write the size of the header + //assert(numberOfBytesWritten == 4); +#ifdef __PSVITA__ + VirtualCopyTo(begin + 1, &headerSize, sizeof(headerSize)); +#else + *(begin + 1) = headerSize; +#endif + + short *versions = (short *)(begin + 2); + // Write the original version number +#ifdef __PSVITA__ + VirtualCopyTo(versions, &m_originalSaveVersion, sizeof(m_originalSaveVersion)); +#else + *versions = m_originalSaveVersion; +#endif + + // Write the version number + short versionNumber = SAVE_FILE_VERSION_NUMBER; + //assert(numberOfBytesWritten == 4); + //*(begin + 2) = versionNumber; +#ifdef __PSVITA__ + VirtualCopyTo(versions + 1, &versionNumber, sizeof(versionNumber)); +#else + *(versions + 1) = versionNumber; +#endif + +#ifdef _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; + +#ifdef _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); +#ifdef __PSVITA__ + VirtualCopyTo((void *)headerPosition, &fileTable[i]->data, sizeof(FileEntrySaveData)); +#else + memcpy( (void *)headerPosition, &fileTable[i]->data, sizeof(FileEntrySaveData) ); +#endif + //assert(numberOfBytesWritten == sizeof(FileEntrySaveData)); + headerPosition += sizeof(FileEntrySaveData); + } +} + +void FileHeader::ReadHeader( LPVOID 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; +#ifdef __PSVITA__ + VirtualCopyFrom(&headerOffset, begin, sizeof(headerOffset)); +#else + headerOffset = *begin; +#endif + if(isSaveEndianDifferent()) System::ReverseULONG(&headerOffset); + + // Read the size of the header + //assert(numberOfBytesRead == 4); +#ifdef __PSVITA__ + VirtualCopyFrom(&headerSize, begin + 1, sizeof(headerSize)); +#else + headerSize = *(begin + 1); +#endif + if(isSaveEndianDifferent()) System::ReverseULONG(&headerSize); + + + short *versions = (short *)(begin + 2); + // Read the original save version number +#ifdef __PSVITA__ + VirtualCopyFrom(&m_originalSaveVersion, versions, sizeof(m_originalSaveVersion)); +#else + m_originalSaveVersion = *(versions); +#endif + if(isSaveEndianDifferent()) System::ReverseSHORT(&m_originalSaveVersion); + + // Read the save version number + //m_saveVersion = *(begin + 2); +#ifdef __PSVITA__ + VirtualCopyFrom(&m_saveVersion, versions + 1, sizeof(m_saveVersion)); +#else + m_saveVersion = *(versions + 1); +#endif + if(isSaveEndianDifferent()) System::ReverseSHORT(&m_saveVersion); + +#ifdef _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)); + +#ifdef __PSVITA__ + VirtualCopyFrom( &entry->data, fesdHeaderPosition, sizeof(FileEntrySaveData) ); +#else + memcpy( &entry->data, fesdHeaderPosition, sizeof(FileEntrySaveData) ); +#endif + + 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 ); +#ifdef _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)); + +#ifdef __PSVITA__ + VirtualCopyFrom( &entry->data, headerPosition, sizeof(FileEntrySaveDataV1) ); +#else + memcpy( &entry->data, headerPosition, sizeof(FileEntrySaveDataV1) ); +#endif + + entry->currentFilePointer = entry->data.startOffset; + lastFile = entry; + fileTable.push_back( entry ); +#ifdef _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: +#ifndef _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, DWORD 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 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; +} + +vector<FileEntry *> *FileHeader::getFilesWithPrefix(const wstring &prefix) +{ + vector<FileEntry *> *files = NULL; + + for( unsigned int i = 0; i < fileTable.size(); ++i ) + { + if( wcsncmp( fileTable[i]->data.filename, prefix.c_str(), prefix.size() ) == 0 ) + { + if( files == NULL ) + { + files = new vector<FileEntry *>(); + } + + files->push_back(fileTable[i]); + } + } + + return files; +} + +#if defined(__PS3__) || defined(__ORBIS__) || defined(__PSVITA__) + +static bool isHexChar(wchar_t wc) +{ + if(wc >= L'0' && wc <= L'9') + return true; + if(wc >= L'a' && wc <= L'f') + return true; + return false; +} +static bool isHexString(wchar_t* ws, int size) +{ + for(int i=0;i<size;i++) + { + if(!isHexChar(ws[i])) + return false; + } + return true; +} +static bool isDecimalChar(wchar_t wc) +{ + if(wc >= L'0' && wc <= L'9') + return true; + return false; +} +static bool isDecimalString(wchar_t* ws, int size) +{ + for(int i=0;i<size;i++) + { + if(!isDecimalChar(ws[i])) + return false; + } + return true; +} +static wchar_t* findFilenameStart(wchar_t* str) +{ + // find the last forward slash in the filename, and return the pointer to the following character + wchar_t* filenameStart= str; + int len = wcslen(str); + for(int i=0;i<len;i++) + if(str[i] == L'/') + filenameStart = &str[i+1]; + return filenameStart; +} + +wstring FileHeader::getPlayerDataFilenameForLoad(const PlayerUID& pUID) +{ + wstring retVal = L""; + vector<FileEntry*>* pFiles = getDatFilesWithOnlineID(pUID); + if(!pFiles) + { + // we didn't find a matching online dat file, so look for an offline version + pFiles = getDatFilesWithMacAndUserID(pUID); + } + if(!pFiles) + { + // and we didn't find an offline file, so check if we're the primary user, and grab the primary file if we are + if(pUID.isPrimaryUser()) + { + pFiles= getDatFilesWithPrimaryUser(); + } + } + if(pFiles) + { + // we've got something to load +// assert(pFiles->size() == 1); + retVal = pFiles->at(0)->data.filename; + delete pFiles; + } + return retVal; +} + +wstring FileHeader::getPlayerDataFilenameForSave(const PlayerUID& pUID) +{ + // check if we're online first + if(pUID.isSignedIntoPSN() == false) + { + // OK, we're not online, see if we can find another data file with matching mac and userID + vector<FileEntry*>* pFiles = getDatFilesWithMacAndUserID(pUID); + if(pFiles) + { + // we've found a previous save, use the filename from it, as it might have the online part too +// assert(pFiles->size() == 1); + wstring retVal = pFiles->at(0)->data.filename; + delete pFiles; + return retVal; + } + } + + // we're either online, or we can't find a previous save, so use the standard filename + wstring retVal = pUID.toString() + L".dat"; + return retVal; +} + + +vector<FileEntry *> *FileHeader::getValidPlayerDatFiles() +{ + vector<FileEntry *> *files = NULL; + + // find filenames that match this pattern + // P_5e7ff8372ea9_00000004_Mark_4J + + for( unsigned int i = 0; i < fileTable.size(); ++i ) + { + wchar_t* filenameOnly = findFilenameStart(fileTable[i]->data.filename); + + int nameLen = wcslen(filenameOnly); + if(nameLen <= 4) + continue; + + // make sure it's a ".dat" file + if( wcsncmp( &filenameOnly[nameLen-4], L".dat", 4 ) != 0 ) + continue; + // make sure we start with "P_" or "N_" + if( ( wcsncmp(&filenameOnly[0], L"P_", 2) != 0 ) && ( wcsncmp( &filenameOnly[0], L"N_", 2 ) != 0 ) ) + continue; + //check the next 12 chars are hex + if(!isHexString(&filenameOnly[2], 12)) + continue; + // make sure character 14 is '_' + if(filenameOnly[14] != L'_') + continue; + //check the next 8 chars are decimal + if(!isDecimalString(&filenameOnly[15], 8)) + continue; + + // if we get here, it must be a valid filename + if( files == NULL ) + { + files = new vector<FileEntry *>(); + } + files->push_back(fileTable[i]); + } + + return files; +} + + + +vector<FileEntry *> *FileHeader::getDatFilesWithOnlineID(const PlayerUID& pUID) +{ + if(pUID.isSignedIntoPSN() == false) + return NULL; + + vector<FileEntry *>* datFiles = getValidPlayerDatFiles(); + if(datFiles == NULL) + return NULL; + + // we're looking for the online name from the pUID in these types of filenames - + // P_5e7ff8372ea9_00000004_Mark_4J + wchar_t onlineIDW[64]; + mbstowcs(onlineIDW, pUID.getOnlineID(), 64); + + vector<FileEntry *> *files = NULL; + int onlineIDSize = wcslen(onlineIDW); + if(onlineIDSize == 0) + return NULL; + + wcscat(onlineIDW, L".dat"); + +#ifdef __ORBIS__ + onlineIDSize = wcslen(onlineIDW); +#else + static const int onlineIDStart = 24; // 24 characters into the filename +#endif + + char tempStr[128]; + for( unsigned int i = 0; i < datFiles->size(); ++i ) + { + wchar_t* filenameOnly = findFilenameStart(datFiles->at(i)->data.filename); + wcstombs(tempStr,filenameOnly, 128); + app.DebugPrintf("file : %s\n", tempStr); + +#ifdef __ORBIS__ + int onlineIDStart = wcslen(filenameOnly) - onlineIDSize; + if(onlineIDStart > 0) +#else + if(wcslen(filenameOnly) > onlineIDStart) +#endif + { + if(wcsncmp(&filenameOnly[onlineIDStart], onlineIDW, onlineIDSize) == 0) + { + if( files == NULL ) + { + files = new vector<FileEntry *>(); + } + files->push_back(datFiles->at(i)); + } + } + } + delete datFiles; + + if(files) sort(files->begin(), files->end(), FileEntry::newestFirst ); + return files; +} + +vector<FileEntry *> *FileHeader::getDatFilesWithMacAndUserID(const PlayerUID& pUID) +{ + + vector<FileEntry *>* datFiles = getValidPlayerDatFiles(); + if(datFiles == NULL) + return NULL; + + // we're looking for the mac address and userIDfrom the pUID in these types of filenames - + // P_5e7ff8372ea9_00000004_Mark_4J + std::wstring macStr = pUID.macAddressStr(); + std::wstring userStr = pUID.userIDStr(); + const wchar_t* pMacStr = macStr.c_str(); + const wchar_t* pUserStr = userStr.c_str(); + + vector<FileEntry *> *files = NULL; + static const int macAddrStart = 2; // 2 characters into the filename + static const int userIDStart = 15; // 15 characters into the filename + + char tempStr[128]; + for( unsigned int i = 0; i < datFiles->size(); ++i ) + { + wchar_t* filenameOnly = findFilenameStart(datFiles->at(i)->data.filename); + wcstombs(tempStr,filenameOnly, 128); + app.DebugPrintf("file : %s\n", tempStr); + + // check the mac address matches + if(wcsncmp(&filenameOnly[macAddrStart], pMacStr, macStr.size()) == 0) + { + // check the userID matches + if(wcsncmp(&filenameOnly[userIDStart], pUserStr, userStr.size()) == 0) + { + if( files == NULL ) + { + files = new vector<FileEntry *>(); + } + files->push_back(datFiles->at(i)); + } + } + } + delete datFiles; + if(files) sort(files->begin(), files->end(), FileEntry::newestFirst ); + return files; +} + + +vector<FileEntry *> *FileHeader::getDatFilesWithPrimaryUser() +{ + + vector<FileEntry *>* datFiles = getValidPlayerDatFiles(); + if(datFiles == NULL) + return NULL; + + // we're just looking for filenames starting with "P_" in these types of filenames - + // P_5e7ff8372ea9_00000004_Mark_4J + vector<FileEntry *> *files = NULL; + + char tempStr[128]; + for( unsigned int i = 0; i < datFiles->size(); ++i ) + { + wchar_t* filenameOnly = findFilenameStart(datFiles->at(i)->data.filename); + wcstombs(tempStr,filenameOnly, 128); + app.DebugPrintf("file : %s\n", tempStr); + + // check for "P_" prefix + if(wcsncmp(&filenameOnly[0], L"P_", 2) == 0) + { + if( files == NULL ) + { + files = new vector<FileEntry *>(); + } + files->push_back(datFiles->at(i)); + } + } + delete datFiles; + if(files) sort(files->begin(), files->end(), FileEntry::newestFirst ); + return files; +} +#endif // __PS3__ || __ORBIS__ + +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; +}
\ No newline at end of file |
