diff --git a/Minecraft.Client/ArchiveFile.cpp b/Minecraft.Client/ArchiveFile.cpp index defef24e..9914cecd 100644 --- a/Minecraft.Client/ArchiveFile.cpp +++ b/Minecraft.Client/ArchiveFile.cpp @@ -4,6 +4,7 @@ #include "../Minecraft.World/compression.h" #include "ArchiveFile.h" +#include "lce_filesystem/FolderFile.h" void ArchiveFile::_readHeader(DataInputStream *dis) { @@ -28,10 +29,19 @@ void ArchiveFile::_readHeader(DataInputStream *dis) } } -ArchiveFile::ArchiveFile(File file) +ArchiveFile::ArchiveFile(File file, bool allowFolder) { m_cachedData = nullptr; + m_folderFile = nullptr; + m_useFolder = false; m_sourcefile = file; + + if(allowFolder && file.exists() && file.isDirectory()) + { + m_folderFile = new FolderFile(file.getPath()); + m_useFolder = true; + return; + } app.DebugPrintf("Loading archive file...\n"); #ifndef _CONTENT_PACKAGE char buf[256]; @@ -72,10 +82,15 @@ ArchiveFile::ArchiveFile(File file) ArchiveFile::~ArchiveFile() { delete m_cachedData; + delete m_folderFile; } vector *ArchiveFile::getFileList() { + if(m_useFolder) + { + return m_folderFile->getFileList(); + } vector *out = new vector(); for ( const auto& it : m_index ) @@ -86,16 +101,28 @@ vector *ArchiveFile::getFileList() bool ArchiveFile::hasFile(const wstring &filename) { + if(m_useFolder) + { + return m_folderFile->hasFile(filename); + } return m_index.find(filename) != m_index.end(); } int ArchiveFile::getFileSize(const wstring &filename) { + if(m_useFolder) + { + return m_folderFile->getFileSize(filename); + } return hasFile(filename) ? m_index.at(filename)->filesize : -1; } byteArray ArchiveFile::getFile(const wstring &filename) { + if(m_useFolder) + { + return m_folderFile->getFile(filename); + } byteArray out; auto it = m_index.find(filename); diff --git a/Minecraft.Client/ArchiveFile.h b/Minecraft.Client/ArchiveFile.h index f529e806..9e21e337 100644 --- a/Minecraft.Client/ArchiveFile.h +++ b/Minecraft.Client/ArchiveFile.h @@ -8,11 +8,15 @@ using namespace std; +class FolderFile; + class ArchiveFile { protected: File m_sourcefile; BYTE *m_cachedData; + FolderFile *m_folderFile; + bool m_useFolder; typedef struct _MetaData { @@ -28,7 +32,7 @@ protected: public: void _readHeader(DataInputStream *dis); - ArchiveFile(File file); + ArchiveFile(File file, bool allowFolder = false); ~ArchiveFile(); vector *getFileList(); diff --git a/Minecraft.Client/Common/DLC/DLCManager.cpp b/Minecraft.Client/Common/DLC/DLCManager.cpp index 8f35b1d1..c5d89882 100644 --- a/Minecraft.Client/Common/DLC/DLCManager.cpp +++ b/Minecraft.Client/Common/DLC/DLCManager.cpp @@ -4,9 +4,56 @@ #include "DLCPack.h" #include "DLCFile.h" #include "../../../Minecraft.World/StringHelpers.h" +#include "../../../Minecraft.World/File.h" #include "../../Minecraft.h" #include "../../TexturePackRepository.h" #include "Common/UI/UI.h" +#include "lce_filesystem/FolderFile.h" + +static bool hasPckFolderFallback(const wstring &path, wstring &folderPath) +{ + wstring lowerPath = toLower(path); + const wstring pckSuffix = L".pck"; + if(lowerPath.size() <= pckSuffix.size()) + { + return false; + } + if(lowerPath.compare(lowerPath.size() - pckSuffix.size(), pckSuffix.size(), pckSuffix) != 0) + { + return false; + } + + if(!(lowerPath.find(L"x16Data.pck") != wstring::npos + || lowerPath.find(L"x32Data.pck") != wstring::npos)) + { + return false; + } + + folderPath = path.substr(0, path.size() - pckSuffix.size()); + return true; +} + +static DLCManager::EDLCType getFolderFileType(const wstring &path) +{ + wstring lowerPath = toLower(path); + if(lowerPath == L"colours.col" || lowerPath.rfind(L"/colours.col") != wstring::npos) + { + return DLCManager::e_DLCType_ColourTable; + } + if(lowerPath.rfind(L"/languages.loc") != wstring::npos || lowerPath.rfind(L".loc") != wstring::npos) + { + return DLCManager::e_DLCType_LocalisationData; + } + if(lowerPath.rfind(L".xzp") != wstring::npos) + { + return DLCManager::e_DLCType_UIData; + } + if(lowerPath.rfind(L".grf") != wstring::npos) + { + return DLCManager::e_DLCType_GameRulesHeader; + } + return DLCManager::e_DLCType_Texture; +} const WCHAR *DLCManager::wchTypeNamesA[]= { @@ -328,13 +375,47 @@ bool DLCManager::readDLCDataFile(DWORD &dwFilesProcessed, const string &path, DL } else if (fromArchive) return false; + wstring finalPathW = wPath; #ifdef _WINDOWS64 string finalPath = StorageManager.GetMountedPath(path.c_str()); if(finalPath.size() == 0) finalPath = path; - HANDLE file = CreateFile(finalPath.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + finalPathW = convStringToWstring(finalPath); #elif defined(_DURANGO) wstring finalPath = StorageManager.GetMountedPath(wPath.c_str()); if(finalPath.size() == 0) finalPath = wPath; + finalPathW = finalPath; +#endif + + if(!fromArchive) + { + wstring folderPath; + if(hasPckFolderFallback(finalPathW, folderPath)) + { + wstring resolvedFolderPath = folderPath; +#ifdef _WINDOWS64 + const char *folderPathA = wstringtofilename(folderPath); + string mountedFolderPath = StorageManager.GetMountedPath(folderPathA); + if(mountedFolderPath.size() > 0) + { + resolvedFolderPath = convStringToWstring(mountedFolderPath); + } +#elif defined(_DURANGO) + wstring mountedFolderPath = StorageManager.GetMountedPath(folderPath.c_str()); + if(mountedFolderPath.size() > 0) + { + resolvedFolderPath = mountedFolderPath; + } +#endif + if(readDLCDataFolder(dwFilesProcessed, resolvedFolderPath, pack)) + { + return true; + } + } + } + +#ifdef _WINDOWS64 + HANDLE file = CreateFile(finalPath.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); +#elif defined(_DURANGO) HANDLE file = CreateFile(finalPath.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); #else HANDLE file = CreateFile(path.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); @@ -371,6 +452,81 @@ bool DLCManager::readDLCDataFile(DWORD &dwFilesProcessed, const string &path, DL return processDLCDataFile(dwFilesProcessed, pbData, bytesRead, pack); } +bool DLCManager::readDLCDataFolder(DWORD &dwFilesProcessed, const wstring &path, DLCPack *pack) +{ + File folder(path); + if(!folder.exists() || !folder.isDirectory()) + { + return false; + } + + FolderFile folderFile(path); + vector *fileList = folderFile.getFileList(); + if(fileList == nullptr || fileList->empty()) + { + delete fileList; + return false; + } + + struct FolderEntry + { + wstring rawPath; + wstring normalizedPath; + int size; + }; + vector entries; + entries.reserve(fileList->size()); + + unsigned int totalBytes = 0; + for(const auto &rawPath : *fileList) + { + wstring normalized = replaceAll(rawPath, L"\\", L"/"); + if(normalized.empty() || normalized == L"0") + { + continue; + } + int size = folderFile.getFileSize(rawPath); + if(size <= 0) + { + continue; + } + entries.push_back({ rawPath, normalized, size }); + totalBytes += static_cast(size); + } + delete fileList; + + if(entries.empty() || totalBytes == 0) + { + return false; + } + + PBYTE dataBuffer = new BYTE[totalBytes]; + pack->SetDataPointer(dataBuffer); + + unsigned int offset = 0; + for(const auto &entry : entries) + { + byteArray data = folderFile.getFile(entry.rawPath); + if(data.data == nullptr || data.length == 0) + { + continue; + } + + DLCManager::EDLCType type = getFolderFileType(entry.normalizedPath); + DLCFile *dlcFile = pack->addFile(type, entry.normalizedPath); + if(dlcFile != nullptr) + { + memcpy(dataBuffer + offset, data.data, data.length); + dlcFile->addData(dataBuffer + offset, data.length); + offset += data.length; + ++dwFilesProcessed; + } + delete [] data.data; + } + + return dwFilesProcessed > 0; +} + bool DLCManager::processDLCDataFile(DWORD &dwFilesProcessed, PBYTE pbData, DWORD dwLength, DLCPack *pack) { unordered_map parameterMapping; diff --git a/Minecraft.Client/Common/DLC/DLCManager.h b/Minecraft.Client/Common/DLC/DLCManager.h index f114bd07..dc3d1a02 100644 --- a/Minecraft.Client/Common/DLC/DLCManager.h +++ b/Minecraft.Client/Common/DLC/DLCManager.h @@ -93,6 +93,7 @@ public: bool readDLCDataFile(DWORD &dwFilesProcessed, const wstring &path, DLCPack *pack, bool fromArchive = false); bool readDLCDataFile(DWORD &dwFilesProcessed, const string &path, DLCPack *pack, bool fromArchive = false); + bool readDLCDataFolder(DWORD &dwFilesProcessed, const wstring &path, DLCPack *pack); DWORD retrievePackIDFromDLCDataFile(const string &path, DLCPack *pack); static unsigned short SwapInt16(unsigned short value) { diff --git a/Minecraft.Client/Common/Network/GameNetworkManager.cpp b/Minecraft.Client/Common/Network/GameNetworkManager.cpp index 0e83c8b2..1a3a7bd7 100644 --- a/Minecraft.Client/Common/Network/GameNetworkManager.cpp +++ b/Minecraft.Client/Common/Network/GameNetworkManager.cpp @@ -988,6 +988,13 @@ int CGameNetworkManager::RunNetworkGameThreadProc( void* lpParameter ) int CGameNetworkManager::ServerThreadProc( void* lpParameter ) { int64_t seed = 0; + + Compression::UseDefaultThreadStorage(); + if (Compression::getCompression() == nullptr) + { + Compression::CreateNewThreadStorage(); + } + if (lpParameter != nullptr) { NetworkGameInitData *param = static_cast(lpParameter); @@ -1027,6 +1034,7 @@ int CGameNetworkManager::ServerThreadProc( void* lpParameter ) AABB::ReleaseThreadStorage(); Vec3::ReleaseThreadStorage(); IntCache::ReleaseThreadStorage(); + Compression::ReleaseThreadStorage(); Level::destroyLightingCache(); if(lpParameter != nullptr) delete lpParameter; diff --git a/Minecraft.Client/DLCTexturePack.cpp b/Minecraft.Client/DLCTexturePack.cpp index 3cb68521..19327ece 100644 --- a/Minecraft.Client/DLCTexturePack.cpp +++ b/Minecraft.Client/DLCTexturePack.cpp @@ -144,9 +144,27 @@ InputStream *DLCTexturePack::getResourceImplementation(const wstring &name) //th bool DLCTexturePack::hasFile(const wstring &name) { - bool hasFile = false; - if(m_dlcDataPack != nullptr) hasFile = m_dlcDataPack->doesPackContainFile(DLCManager::e_DLCType_Texture, name); - return hasFile; + if(m_dlcDataPack == nullptr) + { + return false; + } + + wstring normalized = replaceAll(name, L"\\", L"/"); + if(!normalized.empty() && normalized[0] == L'/') + { + normalized = normalized.substr(1); + } + if(normalized.rfind(L"res/", 0) != 0) + { + normalized = L"res/" + normalized; + } + + if(m_dlcDataPack->doesPackContainFile(DLCManager::e_DLCType_Texture, normalized)) + { + return true; + } + + return false; } bool DLCTexturePack::isTerrainUpdateCompatible() @@ -163,10 +181,23 @@ wstring DLCTexturePack::getAnimationString(const wstring &textureName, const wst { wstring result = L""; - wstring fullpath = L"res/" + path + textureName + L".png"; - if(hasFile(fullpath)) + wstring fullpath = path; + if(!fullpath.empty() && fullpath[0] == L'/') { - result = m_dlcDataPack->getFile(DLCManager::e_DLCType_Texture, fullpath)->getParameterAsString(DLCManager::e_DLCParamType_Anim); + fullpath = fullpath.substr(1); + } + if(fullpath.rfind(L"res/", 0) != 0) + { + fullpath = L"res/" + fullpath; + } + fullpath += textureName + L".png"; + if(m_dlcDataPack != nullptr && m_dlcDataPack->doesPackContainFile(DLCManager::e_DLCType_Texture, fullpath)) + { + DLCFile *dlcFile = m_dlcDataPack->getFile(DLCManager::e_DLCType_Texture, fullpath); + if(dlcFile != nullptr) + { + result = dlcFile->getParameterAsString(DLCManager::e_DLCParamType_Anim); + } } return result; @@ -174,8 +205,33 @@ wstring DLCTexturePack::getAnimationString(const wstring &textureName, const wst BufferedImage *DLCTexturePack::getImageResource(const wstring& File, bool filenameHasExtension /*= false*/, bool bTitleUpdateTexture /*=false*/, const wstring &drive /*=L""*/) { - if(m_dlcDataPack) return new BufferedImage(m_dlcDataPack, L"/" + File, filenameHasExtension); - else return fallback->getImageResource(File, filenameHasExtension, bTitleUpdateTexture, drive); + if(m_dlcDataPack != nullptr) + { + wstring dlcPath = File; + if(!dlcPath.empty() && dlcPath[0] == L'/') + { + dlcPath = dlcPath.substr(1); + } + if(dlcPath.rfind(L"res/", 0) != 0) + { + dlcPath = L"res/" + dlcPath; + } + bool hasTexture = m_dlcDataPack->doesPackContainFile(DLCManager::e_DLCType_Texture, dlcPath); + if(!hasTexture && !filenameHasExtension) + { + hasTexture = m_dlcDataPack->doesPackContainFile(DLCManager::e_DLCType_Texture, dlcPath + L".png"); + } + if(!hasTexture && filenameHasExtension && dlcPath.size() > 4) + { + wstring noExt = dlcPath.substr(0, dlcPath.size() - 4); + hasTexture = m_dlcDataPack->doesPackContainFile(DLCManager::e_DLCType_Texture, noExt + L".png"); + } + if(hasTexture) + { + return new BufferedImage(m_dlcDataPack, L"/" + File, filenameHasExtension); + } + } + return fallback->getImageResource(File, filenameHasExtension, bTitleUpdateTexture, drive); } DLCPack * DLCTexturePack::getDLCPack() @@ -354,8 +410,16 @@ int DLCTexturePack::packMounted(LPVOID pParam,int iPad,DWORD dwErr,DWORD dwLicen } } #else - File archivePath(getFilePath(texturePack->m_dlcInfoPack->GetPackID(), wstring(L"media.arc") ) ); - if(archivePath.exists()) texturePack->m_archiveFile = new ArchiveFile(archivePath); + File mediaFolder(getFilePath(texturePack->m_dlcInfoPack->GetPackID(), wstring(L"media"))); + if(mediaFolder.exists() && mediaFolder.isDirectory()) + { + texturePack->m_archiveFile = new ArchiveFile(mediaFolder, true); + } + else + { + File archivePath(getFilePath(texturePack->m_dlcInfoPack->GetPackID(), wstring(L"media.arc") ) ); + if(archivePath.exists()) texturePack->m_archiveFile = new ArchiveFile(archivePath); + } #endif /** @@ -364,16 +428,73 @@ int DLCTexturePack::packMounted(LPVOID pParam,int iPad,DWORD dwErr,DWORD dwLicen */ DLCPack *pack = texturePack->m_dlcInfoPack->GetParentPack(); LevelGenerationOptions *levelGen = app.getLevelGenerationOptions(); - if (levelGen != nullptr && !levelGen->hasLoadedData()) + bool shouldLoadGameRules = levelGen != nullptr && levelGen->isFromDLC(); + if (shouldLoadGameRules) { - int gameRulesCount = pack->getDLCItemsCount(DLCManager::e_DLCType_GameRulesHeader); - for(int i = 0; i < gameRulesCount; ++i) + bool needsReload = !levelGen->hasLoadedData() + || levelGen->getRequiredTexturePackId() != texturePack->getDLCParentPackId(); + if(needsReload) { - DLCGameRulesHeader *dlcFile = static_cast(pack->getFile(DLCManager::e_DLCType_GameRulesHeader, i)); - - if (!dlcFile->getGrfPath().empty()) + int gameRulesCount = pack->getDLCItemsCount(DLCManager::e_DLCType_GameRulesHeader); + for(int i = 0; i < gameRulesCount; ++i) { - File grf( getFilePath(texturePack->m_dlcInfoPack->GetPackID(), dlcFile->getGrfPath() ) ); + DLCGameRulesHeader *dlcFile = static_cast(pack->getFile(DLCManager::e_DLCType_GameRulesHeader, i)); + + if (!dlcFile->getGrfPath().empty()) + { + File grf( getFilePath(texturePack->m_dlcInfoPack->GetPackID(), dlcFile->getGrfPath() ) ); + if (grf.exists()) + { +#ifdef _UNICODE + wstring path = grf.getPath(); + const WCHAR *pchFilename=path.c_str(); + HANDLE fileHandle = CreateFile( + pchFilename, // file name + GENERIC_READ, // access mode + 0, // share mode // TODO 4J Stu - Will we need to share file? Probably not but... + nullptr, // Unused + OPEN_EXISTING , // how to create // TODO 4J Stu - Assuming that the file already exists if we are opening to read from it + FILE_FLAG_SEQUENTIAL_SCAN, // file attributes + nullptr // Unsupported + ); +#else + const char *pchFilename=wstringtofilename(grf.getPath()); + HANDLE fileHandle = CreateFile( + pchFilename, // file name + GENERIC_READ, // access mode + 0, // share mode // TODO 4J Stu - Will we need to share file? Probably not but... + nullptr, // Unused + OPEN_EXISTING , // how to create // TODO 4J Stu - Assuming that the file already exists if we are opening to read from it + FILE_FLAG_SEQUENTIAL_SCAN, // file attributes + nullptr // Unsupported + ); +#endif + + if( fileHandle != INVALID_HANDLE_VALUE ) + { + DWORD dwFileSize = grf.length(); + DWORD bytesRead; + PBYTE pbData = (PBYTE) new BYTE[dwFileSize]; + BOOL bSuccess = ReadFile(fileHandle,pbData,dwFileSize,&bytesRead,nullptr); + if(bSuccess==FALSE) + { + app.FatalLoadError(); + } + CloseHandle(fileHandle); + + // 4J-PB - is it possible that we can get here after a read fail and it's not an error? + dlcFile->setGrfData(pbData, dwFileSize, texturePack->m_stringTable); + + delete [] pbData; + + app.m_gameRules.setLevelGenerationOptions( dlcFile->lgo ); + } + } + } + } + if(levelGen->requiresBaseSave() && !levelGen->getBaseSavePath().empty() ) + { + File grf(getFilePath(texturePack->m_dlcInfoPack->GetPackID(), levelGen->getBaseSavePath() )); if (grf.exists()) { #ifdef _UNICODE @@ -403,8 +524,7 @@ int DLCTexturePack::packMounted(LPVOID pParam,int iPad,DWORD dwErr,DWORD dwLicen if( fileHandle != INVALID_HANDLE_VALUE ) { - DWORD dwFileSize = grf.length(); - DWORD bytesRead; + DWORD bytesRead,dwFileSize = GetFileSize(fileHandle,nullptr); PBYTE pbData = (PBYTE) new BYTE[dwFileSize]; BOOL bSuccess = ReadFile(fileHandle,pbData,dwFileSize,&bytesRead,nullptr); if(bSuccess==FALSE) @@ -414,61 +534,11 @@ int DLCTexturePack::packMounted(LPVOID pParam,int iPad,DWORD dwErr,DWORD dwLicen CloseHandle(fileHandle); // 4J-PB - is it possible that we can get here after a read fail and it's not an error? - dlcFile->setGrfData(pbData, dwFileSize, texturePack->m_stringTable); - - delete [] pbData; - - app.m_gameRules.setLevelGenerationOptions( dlcFile->lgo ); + levelGen->setBaseSaveData(pbData, dwFileSize); } } } } - if(levelGen->requiresBaseSave() && !levelGen->getBaseSavePath().empty() ) - { - File grf(getFilePath(texturePack->m_dlcInfoPack->GetPackID(), levelGen->getBaseSavePath() )); - if (grf.exists()) - { -#ifdef _UNICODE - wstring path = grf.getPath(); - const WCHAR *pchFilename=path.c_str(); - HANDLE fileHandle = CreateFile( - pchFilename, // file name - GENERIC_READ, // access mode - 0, // share mode // TODO 4J Stu - Will we need to share file? Probably not but... - nullptr, // Unused - OPEN_EXISTING , // how to create // TODO 4J Stu - Assuming that the file already exists if we are opening to read from it - FILE_FLAG_SEQUENTIAL_SCAN, // file attributes - nullptr // Unsupported - ); -#else - const char *pchFilename=wstringtofilename(grf.getPath()); - HANDLE fileHandle = CreateFile( - pchFilename, // file name - GENERIC_READ, // access mode - 0, // share mode // TODO 4J Stu - Will we need to share file? Probably not but... - nullptr, // Unused - OPEN_EXISTING , // how to create // TODO 4J Stu - Assuming that the file already exists if we are opening to read from it - FILE_FLAG_SEQUENTIAL_SCAN, // file attributes - nullptr // Unsupported - ); -#endif - - if( fileHandle != INVALID_HANDLE_VALUE ) - { - DWORD bytesRead,dwFileSize = GetFileSize(fileHandle,nullptr); - PBYTE pbData = (PBYTE) new BYTE[dwFileSize]; - BOOL bSuccess = ReadFile(fileHandle,pbData,dwFileSize,&bytesRead,nullptr); - if(bSuccess==FALSE) - { - app.FatalLoadError(); - } - CloseHandle(fileHandle); - - // 4J-PB - is it possible that we can get here after a read fail and it's not an error? - levelGen->setBaseSaveData(pbData, dwFileSize); - } - } - } }