From bb04d465db92b99ef85315580aed37835dd2374f Mon Sep 17 00:00:00 2001 From: "George V." Date: Fri, 10 Apr 2026 20:59:46 +0300 Subject: [PATCH] feat: replace ArchiveFile with FolderFile for media asset loading Replace the ArchiveFile-based media asset loading system with a new FolderFile implementation that reads files directly from a folder structure instead of from compressed .arc archives. This change simplifies asset management and eliminates the need for pre-packaged media archives. Key changes: - Added FolderFile class that indexes and reads files from a folder - Updated Consoles_App to use FolderFile instead of ArchiveFile - Modified CMake asset copy configuration to exclude platform-specific media folders instead of .arc files - Updated platform-specific media path references to point to folders instead of .arc files This enables easier development and debugging by allowing direct access to media files without requiring archive extraction or repackaging. --- Minecraft.Client/Common/Consoles_App.cpp | 12 +- Minecraft.Client/Common/Consoles_App.h | 3 +- Minecraft.Client/FolderFile.cpp | 181 ++++++++++++++++++++ Minecraft.Client/FolderFile.h | 27 +++ Minecraft.Client/cmake/sources/Common.cmake | 4 +- cmake/CopyAssets.cmake | 28 +-- 6 files changed, 233 insertions(+), 22 deletions(-) create mode 100644 Minecraft.Client/FolderFile.cpp create mode 100644 Minecraft.Client/FolderFile.h diff --git a/Minecraft.Client/Common/Consoles_App.cpp b/Minecraft.Client/Common/Consoles_App.cpp index 6dd4fd9e..a4c340b9 100644 --- a/Minecraft.Client/Common/Consoles_App.cpp +++ b/Minecraft.Client/Common/Consoles_App.cpp @@ -4560,20 +4560,20 @@ void CMinecraftApp::loadMediaArchive() wstring mediapath = L""; #ifdef __PS3__ - mediapath = L"Common\\Media\\MediaPS3.arc"; + mediapath = L"Common\\Media\\MediaPS3"; #elif _WINDOWS64 - mediapath = L"Common\\Media\\MediaWindows64.arc"; + mediapath = L"Common\\Media\\MediaWindows64"; #elif __ORBIS__ - mediapath = L"Common\\Media\\MediaOrbis.arc"; + mediapath = L"Common\\Media\\MediaOrbis"; #elif _DURANGO - mediapath = L"Common\\Media\\MediaDurango.arc"; + mediapath = L"Common\\Media\\MediaDurango"; #elif __PSVITA__ - mediapath = L"Common\\Media\\MediaPSVita.arc"; + mediapath = L"Common\\Media\\MediaPSVita"; #endif if (!mediapath.empty()) { - m_mediaArchive = new ArchiveFile( File(mediapath) ); + m_mediaArchive = new FolderFile(mediapath); } #if 0 string path = "Common\\media.arc"; diff --git a/Minecraft.Client/Common/Consoles_App.h b/Minecraft.Client/Common/Consoles_App.h index 24daa174..83264732 100644 --- a/Minecraft.Client/Common/Consoles_App.h +++ b/Minecraft.Client/Common/Consoles_App.h @@ -22,6 +22,7 @@ using namespace std; #include "./GameRules/GameRuleManager.h" #include "../SkinBox.h" #include "../ArchiveFile.h" +#include "../FolderFile.h" typedef struct _JoinFromInviteData { @@ -433,7 +434,7 @@ public: void loadStringTable(); protected: - ArchiveFile *m_mediaArchive; + FolderFile *m_mediaArchive; StringTable *m_stringTable; public: diff --git a/Minecraft.Client/FolderFile.cpp b/Minecraft.Client/FolderFile.cpp new file mode 100644 index 00000000..88a89887 --- /dev/null +++ b/Minecraft.Client/FolderFile.cpp @@ -0,0 +1,181 @@ +#include "stdafx.h" + +#include "../Minecraft.World/StringHelpers.h" +#include "../Minecraft.World/compression.h" + +#include "FolderFile.h" + +void FolderFile::_buildFileIndex() +{ + wstring searchPath = m_folderPath + L"\\*"; + WIN32_FIND_DATAW findData; + HANDLE hFind = FindFirstFileW(searchPath.c_str(), &findData); + + if (hFind == INVALID_HANDLE_VALUE) + { + app.DebugPrintf("Failed to open folder: %ls\n", m_folderPath.c_str()); + return; + } + + do + { + if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + wstring filename = findData.cFileName; + wstring fullPath = m_folderPath + L"\\" + filename; + + // Store filename without path for compatibility with existing code + m_filePaths[filename] = fullPath; + } + } while (FindNextFileW(hFind, &findData)); + + FindClose(hFind); + + app.DebugPrintf("Indexed %d files from folder\n", (int)m_filePaths.size()); +} + +FolderFile::FolderFile(wstring folderPath) +{ + m_folderPath = folderPath; + app.DebugPrintf("Loading folder file...\n"); + +#ifndef _CONTENT_PACKAGE + char buf[256]; + wcstombs(buf, folderPath.c_str(), 256); + app.DebugPrintf("folder path - %s\n", buf); +#endif + + // Check if folder exists + DWORD attrib = GetFileAttributesW(folderPath.c_str()); + if (attrib == INVALID_FILE_ATTRIBUTES || !(attrib & FILE_ATTRIBUTE_DIRECTORY)) + { + app.DebugPrintf("Failed to load folder - directory doesn't exist!\n"); + app.FatalLoadError(); + } + + _buildFileIndex(); + app.DebugPrintf("Finished loading folder file\n"); +} + +FolderFile::~FolderFile() +{ + +} + +vector* FolderFile::getFileList() +{ + vector* out = new vector(); + + for (const auto& it : m_filePaths) + { + out->push_back(it.first); + } + + return out; +} + +bool FolderFile::hasFile(const wstring &filename) +{ + return m_filePaths.find(filename) != m_filePaths.end(); +} + +int FolderFile::getFileSize(const wstring &filename) +{ + auto it = m_filePaths.find(filename); + if (it == m_filePaths.end()) + { + return -1; + } + + WIN32_FILE_ATTRIBUTE_DATA fileData; + if (GetFileAttributesExW(it->second.c_str(), GetFileExInfoStandard, &fileData)) + { + return (int)fileData.nFileSizeLow; + } + + return -1; +} + +byteArray FolderFile::getFile(const wstring &filename) +{ + byteArray out; + auto it = m_filePaths.find(filename); + + if (it == m_filePaths.end()) + { + app.DebugPrintf("Couldn't find file in folder\n"); + app.DebugPrintf("Failed to find file '%ls' in folder\n", filename.c_str()); +#ifndef _CONTENT_PACKAGE + __debugbreak(); +#endif + app.FatalLoadError(); + return out; + } + + HANDLE hFile = CreateFileW( + it->second.c_str(), + GENERIC_READ, + FILE_SHARE_READ, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr + ); + + if (hFile == INVALID_HANDLE_VALUE) + { + app.DebugPrintf("Failed to open file: %ls\n", it->second.c_str()); + app.FatalLoadError(); + return out; + } + + DWORD fileSize = GetFileSize(hFile, nullptr); + if (fileSize == INVALID_FILE_SIZE) + { + app.DebugPrintf("Failed to get file size: %ls\n", it->second.c_str()); + CloseHandle(hFile); + app.FatalLoadError(); + return out; + } + + PBYTE pData = new BYTE[fileSize]; + DWORD bytesRead = 0; + + BOOL success = ReadFile(hFile, pData, fileSize, &bytesRead, nullptr); + CloseHandle(hFile); + + if (!success || bytesRead != fileSize) + { + app.DebugPrintf("Failed to read file: %ls\n", it->second.c_str()); + delete[] pData; + app.FatalLoadError(); + return out; + } + + out = byteArray(pData, fileSize); + + // Compressed filenames are preceeded with an asterisk. + if (filename[0] == L'*' && out.data != nullptr) + { + /* 4J-JEV: + * If a compressed file is accessed before compression object is + * initialized it will crash here (Compression::getCompression). + */ + ///4 279 553 556 + + ByteArrayInputStream bais(out); + DataInputStream dis(&bais); + unsigned int decompressedSize = dis.readInt(); + dis.close(); + + PBYTE uncompressedBuffer = new BYTE[decompressedSize]; + Compression::getCompression()->Decompress(uncompressedBuffer, &decompressedSize, out.data + 4, out.length - 4); + + delete[] out.data; + + out.data = uncompressedBuffer; + out.length = decompressedSize; + } + + return out; +} diff --git a/Minecraft.Client/FolderFile.h b/Minecraft.Client/FolderFile.h new file mode 100644 index 00000000..7c4cdecb --- /dev/null +++ b/Minecraft.Client/FolderFile.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +#include "../Minecraft.World/ArrayWithLength.h" + +using namespace std; + +class FolderFile +{ +private: + wstring m_folderPath; + unordered_map m_filePaths; // filename -> full path + + void _buildFileIndex(); + +public: + FolderFile(wstring folderPath); + ~FolderFile(); + + vector* getFileList(); + bool hasFile(const wstring &filename); + int getFileSize(const wstring &filename); + byteArray getFile(const wstring &filename); +}; diff --git a/Minecraft.Client/cmake/sources/Common.cmake b/Minecraft.Client/cmake/sources/Common.cmake index ba5260f2..b8d26ac9 100644 --- a/Minecraft.Client/cmake/sources/Common.cmake +++ b/Minecraft.Client/cmake/sources/Common.cmake @@ -277,7 +277,9 @@ source_group("Common/UI" FILES ${_MINECRAFT_CLIENT_COMMON_COMMON_UI}) set(_MINECRAFT_CLIENT_COMMON_COMMON_UI_ALL_PLATFORMS "${CMAKE_CURRENT_SOURCE_DIR}/ArchiveFile.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/ArchiveFile.h" + "${CMAKE_CURRENT_SOURCE_DIR}/ArchiveFile.h" + "${CMAKE_CURRENT_SOURCE_DIR}/FolderFile.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/FolderFile.h" "${BASE_DIR}/UI/IUIController.h" "${BASE_DIR}/UI/IUIScene_AbstractContainerMenu.cpp" "${BASE_DIR}/UI/IUIScene_AbstractContainerMenu.h" diff --git a/cmake/CopyAssets.cmake b/cmake/CopyAssets.cmake index a78c9170..a3366b9e 100644 --- a/cmake/CopyAssets.cmake +++ b/cmake/CopyAssets.cmake @@ -7,8 +7,8 @@ function(setup_asset_folder_copy TARGET_NAME ASSET_FOLDER_PAIRS) "*.xml" "*.lang" "*.bat" "*.cmd" "*.msscmp" "*.binka" # Old audio formats - "*.swf" # These are built into the .arc - "*.resx" "*.loc" + #"*.swf" # These are built into the .arc + "*.resx" #"*.loc" "*.wav" # Unsupported audio format "*.xui" ) @@ -18,20 +18,20 @@ function(setup_asset_folder_copy TARGET_NAME ASSET_FOLDER_PAIRS) "Graphics" ) - # Exclude platform-specific arc media files - set(PLATFORM_MEDIA_FILES - "MediaWindows64.arc" - "MediaDurango.arc" - "MediaOrbis.arc" - "MediaPS3.arc" - "MediaPSVita.arc" - "MediaXbox.arc" # Seems to be missing? + # Exclude platform-specific media folders + set(PLATFORM_MEDIA_FOLDERS + "MediaWindows64" + "MediaDurango" + "MediaOrbis" + "MediaPS3" + "MediaPSVita" + "MediaXbox" # Seems to be missing? ) - # Exclude all platform media files except the one for the current platform - foreach(media_file IN LISTS PLATFORM_MEDIA_FILES) - if(NOT media_file MATCHES "Media${PLATFORM_NAME}\\.arc") - list(APPEND ASSET_EXCLUDE_FILES "${media_file}") + # Exclude all platform media folders except the one for the current platform + foreach(media_folder IN LISTS PLATFORM_MEDIA_FOLDERS) + if(NOT media_folder MATCHES "Media${PLATFORM_NAME}") + list(APPEND ASSET_EXCLUDE_FOLDERS "${media_folder}") endif() endforeach()