diff --git a/UnleashedRecomp/kernel/io/file_system.cpp b/UnleashedRecomp/kernel/io/file_system.cpp index e12b02c0..6858bc11 100644 --- a/UnleashedRecomp/kernel/io/file_system.cpp +++ b/UnleashedRecomp/kernel/io/file_system.cpp @@ -41,6 +41,7 @@ struct FindHandle : KernelObject if (includeDirs == nullptr) break; + // TODO: Should cache what we find here to save on future lookups. for (auto& includeDir : *includeDirs) addDirectory(includeDir / pathNoPrefix); } diff --git a/UnleashedRecomp/mod/mod_loader.cpp b/UnleashedRecomp/mod/mod_loader.cpp index 78a13ca5..61250148 100644 --- a/UnleashedRecomp/mod/mod_loader.cpp +++ b/UnleashedRecomp/mod/mod_loader.cpp @@ -1,6 +1,11 @@ #include "mod_loader.h" #include "ini_file.h" -#include "../xxHashMap.h" + +#include +#include +#include +#include +#include enum class ModType { @@ -34,6 +39,8 @@ std::filesystem::path ModLoader::RedirectPath(std::string_view path) for (auto& mod : g_mods) { + // TODO: Need to ignore UMM merge archives here. + for (auto& includeDir : mod.includeDirs) { std::filesystem::path modPath = includeDir / path; @@ -107,3 +114,267 @@ void ModLoader::Init() g_mods.emplace_back(std::move(mod)); } } + +// Hedgehog::Database::CDatabaseLoader::ReadArchiveList +PPC_FUNC_IMPL(__imp__sub_82E0D3E8); +PPC_FUNC(sub_82E0D3E8) +{ + if (g_mods.empty()) + { + __imp__sub_82E0D3E8(ctx, base); + return; + } + + const char* arlFilePathU8 = reinterpret_cast(base + PPC_LOAD_U32(ctx.r4.u32)); + std::filesystem::path appendArlFilePath; + + thread_local ankerl::unordered_dense::set fileNames; + thread_local std::vector arlFileData; + fileNames.clear(); + arlFileData.clear(); + + auto parseArlFileData = [&](const uint8_t* arlFileData, size_t arlFileSize) + { + struct ArlHeader + { + uint32_t signature; + uint32_t splitCount; + }; + + auto* arlHeader = reinterpret_cast(arlFileData); + size_t arlHeaderSize = sizeof(ArlHeader) + arlHeader->splitCount * sizeof(uint32_t); + const uint8_t* arlFileNames = arlFileData + arlHeaderSize; + + while (arlFileNames < arlFileData + arlFileSize) + { + uint8_t fileNameSize = *arlFileNames; + ++arlFileNames; + + fileNames.emplace(reinterpret_cast(arlFileNames), fileNameSize); + + arlFileNames += fileNameSize; + } + + return arlHeaderSize; + }; + + auto parseArlFile = [&](const std::filesystem::path& arlFilePath) + { + std::ifstream stream(arlFilePath, std::ios::binary); + if (stream.good()) + { + stream.seekg(0, std::ios::end); + size_t arlFileSize = stream.tellg(); + stream.seekg(0, std::ios::beg); + arlFileData.resize(arlFileSize); + stream.read(reinterpret_cast(arlFileData.data()), arlFileSize); + stream.close(); + + parseArlFileData(arlFileData.data(), arlFileSize); + } + }; + + for (auto& mod : g_mods) + { + // TODO: Handle UMM merge archives! + for (auto& includeDir : mod.includeDirs) + { + if (appendArlFilePath.empty()) + { + std::filesystem::path arlFilePath(std::u8string_view((const char8_t*)arlFilePathU8)); + appendArlFilePath = arlFilePath.parent_path(); + appendArlFilePath /= "+"; + appendArlFilePath += arlFilePath.filename(); + appendArlFilePath += ".arl"; + } + + parseArlFile(includeDir / appendArlFilePath); + } + } + + if (fileNames.empty()) + { + __imp__sub_82E0D3E8(ctx, base); + return; + } + + size_t arlHeaderSize = parseArlFileData(base + ctx.r5.u32, ctx.r6.u32); + size_t arlFileSize = arlHeaderSize; + + for (auto& fileName : fileNames) + { + arlFileSize += 1; + arlFileSize += fileName.size(); + } + + uint8_t* newArlFileData = reinterpret_cast(g_userHeap.Alloc(arlFileSize)); + memcpy(newArlFileData, base + ctx.r5.u32, arlHeaderSize); + + uint8_t* arlFileNames = newArlFileData + arlHeaderSize; + for (auto& fileName : fileNames) + { + *arlFileNames = uint8_t(fileName.size()); + ++arlFileNames; + memcpy(arlFileNames, fileName.data(), fileName.size()); + arlFileNames += fileName.size(); + } + + ctx.r5.u32 = uint32_t(newArlFileData - base); + ctx.r6.u32 = uint32_t(arlFileSize); + + __imp__sub_82E0D3E8(ctx, base); + + g_userHeap.Free(newArlFileData); +} + +// Hedgehog::Database::SLoadElement::SLoadElement +PPC_FUNC_IMPL(__imp__sub_82E140D8); +PPC_FUNC(sub_82E140D8) +{ + // Store archive name as the pretty name to use it later for append archive loading. + // This is always set to an empty string for archives, so it should be safe to replace. + if (!g_mods.empty() && PPC_LOAD_U32(ctx.r5.u32) == 0x8200A621) + ctx.r5.u32 = ctx.r6.u32; + + __imp__sub_82E140D8(ctx, base); +} + +// Hedgehog::Database::CDatabaseLoader::CCreateFromArchive::CreateCallback +PPC_FUNC_IMPL(__imp__sub_82E0B500); +PPC_FUNC(sub_82E0B500) +{ + if (g_mods.empty()) + { + __imp__sub_82E0B500(ctx, base); + return; + } + + auto r3 = ctx.r3; // Callback + auto r4 = ctx.r4; // Database + auto r5 = ctx.r5; // Name + auto r6 = ctx.r6; // Data + auto r7 = ctx.r7; // Size + auto r8 = ctx.r8; // Unknown + + std::u8string_view arFilePathU8(reinterpret_cast(base + PPC_LOAD_U32(ctx.r5.u32))); + size_t index = arFilePathU8.find(u8".ar.00"); + if (index == (arFilePathU8.size() - 6)) + { + arFilePathU8.remove_suffix(3); + } + else + { + index = arFilePathU8.find(u8".ar"); + if (index != (arFilePathU8.size() - 3)) + { + __imp__sub_82E0B500(ctx, base); + return; + } + } + + std::filesystem::path appendArFilePath; + + thread_local std::filesystem::path tempFilePath; + tempFilePath.clear(); + + auto loadArchive = [&](const std::filesystem::path& arFilePath) + { + std::ifstream stream(arFilePath, std::ios::binary); + if (stream.good()) + { + stream.seekg(0, std::ios::end); + size_t arFileSize = stream.tellg(); + + void* arFileData = __HH_ALLOC(arFileSize); + guest_stack_var, be>> arFileDataHolder; + arFileDataHolder->first = g_memory.MapVirtual(arFileData); + GuestToHostFunction(sub_8241F200, &arFileDataHolder->second, arFileData); + + stream.seekg(0, std::ios::beg); + stream.read(reinterpret_cast(arFileData), arFileSize); + stream.close(); + + ctx.r3 = r3; + ctx.r4 = r4; + ctx.r5 = r5; + ctx.r6.u32 = g_memory.MapVirtual(arFileDataHolder.get()); + ctx.r7.u32 = uint32_t(arFileSize); + ctx.r8 = r8; + + __imp__sub_82E0B500(ctx, base); + + return true; + } + + return false; + }; + + auto loadArchives = [&](const std::filesystem::path& arFilePath, bool allowNoArl) + { + tempFilePath = arFilePath; + tempFilePath += "l"; + + std::ifstream stream(tempFilePath, std::ios::binary); + if (stream.good()) + { + // TODO: Should cache this instead of re-opening the file. + uint32_t splitCount{}; + stream.seekg(4, std::ios::beg); + stream.read(reinterpret_cast(&splitCount), sizeof(splitCount)); + stream.close(); + + if (splitCount == 0) + { + loadArchive(arFilePath); + } + else + { + for (uint32_t i = 0; i < splitCount; i++) + { + tempFilePath = arFilePath; + tempFilePath += fmt::format(".{:02}", i); + loadArchive(tempFilePath); + } + } + } + else if (allowNoArl) + { + if (!loadArchive(arFilePath)) + { + for (uint32_t i = 0; ; i++) + { + tempFilePath = arFilePath; + tempFilePath += fmt::format(".{:02}", i); + if (!loadArchive(tempFilePath)) + break; + } + } + } + }; + + for (auto& mod : g_mods) + { + // TODO: Need to check for UMM merge archives here. + for (auto& includeDir : mod.includeDirs) + { + if (appendArFilePath.empty()) + { + std::filesystem::path arFilePath(arFilePathU8); + appendArFilePath = arFilePath.parent_path(); + appendArFilePath /= "+"; + appendArFilePath += arFilePath.filename(); + } + + loadArchives(includeDir / appendArFilePath, false); + } + } + + ctx.r3 = r3; + ctx.r4 = r4; + ctx.r5 = r5; + ctx.r6 = r6; + ctx.r7 = r7; + ctx.r8 = r8; + + __imp__sub_82E0B500(ctx, base); +}