mirror of
https://github.com/hedge-dev/UnleashedRecomp.git
synced 2026-01-05 19:19:43 -06:00
Mod loader implementation. (#66)
* Initial mod loader implementation. * Allow iterating in mod directories. * Initial append archive implementation. * Avoid calling function wrappers when loading append ARs. For some reason they cause issues. Should investigate later. * UMM merge archive support. * Load merge archives without archive lists. * Less thread locals. I shouldn't worry about string allocations this much when the game itself spams them... * Check for read-only UMM archives. TODO: Skip merging as it's currently just doing duplicate loads. * Skip loading merge archives if they are read-only. * Merge only archives. * Implement decompression. * Fix append ARLs not loading. * Initial save file redirection implementation. * Slightly refactor resolved path usage. * Implement save file redirection fallback. * Set a default save file path if none is provided. * Check for enabled option & replace backward slashes with forward ones in mod save file paths. * Convert back slashes to forward ones when iterating directories. * Make CSB limit dynamic. * Cache append/merge archive lookups. * Close stream after reading compressed ARL. * Fix UMM/HMM ARL file path inconsistency.
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
#include <kernel/function.h>
|
||||
#include <cpu/guest_thread.h>
|
||||
#include <os/logger.h>
|
||||
#include <mod/mod_loader.h>
|
||||
|
||||
struct FileHandle : KernelObject
|
||||
{
|
||||
@@ -15,21 +16,62 @@ struct FileHandle : KernelObject
|
||||
struct FindHandle : KernelObject
|
||||
{
|
||||
std::error_code ec;
|
||||
std::filesystem::path searchPath;
|
||||
std::filesystem::directory_iterator iterator;
|
||||
ankerl::unordered_dense::map<std::u8string, std::pair<size_t, bool>> searchResult; // Relative path, file size, is directory
|
||||
decltype(searchResult)::iterator iterator;
|
||||
|
||||
FindHandle(const std::string_view& path)
|
||||
{
|
||||
auto addDirectory = [&](const std::filesystem::path& directory)
|
||||
{
|
||||
for (auto& entry : std::filesystem::directory_iterator(directory, ec))
|
||||
{
|
||||
std::u8string relativePath = entry.path().lexically_relative(directory).u8string();
|
||||
searchResult.emplace(relativePath, std::make_pair(entry.is_directory(ec) ? 0 : entry.file_size(ec), entry.is_directory(ec)));
|
||||
}
|
||||
};
|
||||
|
||||
std::string_view pathNoPrefix = path;
|
||||
size_t index = pathNoPrefix.find(":\\");
|
||||
if (index != std::string_view::npos)
|
||||
pathNoPrefix.remove_prefix(index + 2);
|
||||
|
||||
// Force add a work folder to let the game see the files in mods,
|
||||
// if by some rare chance the user has no DLC or update files.
|
||||
if (pathNoPrefix.empty())
|
||||
searchResult.emplace(u8"work", std::make_pair(0, true));
|
||||
|
||||
// Look for only work folder in mod folders, AR files cause issues.
|
||||
if (pathNoPrefix.starts_with("work"))
|
||||
{
|
||||
std::string pathStr(pathNoPrefix);
|
||||
std::replace(pathStr.begin(), pathStr.end(), '\\', '/');
|
||||
|
||||
for (size_t i = 0; ; i++)
|
||||
{
|
||||
auto* includeDirs = ModLoader::GetIncludeDirectories(i);
|
||||
if (includeDirs == nullptr)
|
||||
break;
|
||||
|
||||
for (auto& includeDir : *includeDirs)
|
||||
addDirectory(includeDir / pathStr);
|
||||
}
|
||||
}
|
||||
|
||||
addDirectory(FileSystem::ResolvePath(path, false));
|
||||
|
||||
iterator = searchResult.begin();
|
||||
}
|
||||
|
||||
void fillFindData(WIN32_FIND_DATAA* lpFindFileData)
|
||||
{
|
||||
if (iterator->is_directory())
|
||||
if (iterator->second.second)
|
||||
lpFindFileData->dwFileAttributes = ByteSwap(FILE_ATTRIBUTE_DIRECTORY);
|
||||
else if (iterator->is_regular_file())
|
||||
else
|
||||
lpFindFileData->dwFileAttributes = ByteSwap(FILE_ATTRIBUTE_NORMAL);
|
||||
|
||||
std::u8string pathU8Str = iterator->path().lexically_relative(searchPath).u8string();
|
||||
uint64_t fileSize = iterator->file_size(ec);
|
||||
strncpy(lpFindFileData->cFileName, (const char *)(pathU8Str.c_str()), sizeof(lpFindFileData->cFileName));
|
||||
lpFindFileData->nFileSizeLow = ByteSwap(uint32_t(fileSize >> 32U));
|
||||
lpFindFileData->nFileSizeHigh = ByteSwap(uint32_t(fileSize));
|
||||
strncpy(lpFindFileData->cFileName, (const char *)(iterator->first.c_str()), sizeof(lpFindFileData->cFileName));
|
||||
lpFindFileData->nFileSizeLow = ByteSwap(uint32_t(iterator->second.first >> 32U));
|
||||
lpFindFileData->nFileSizeHigh = ByteSwap(uint32_t(iterator->second.first));
|
||||
lpFindFileData->ftCreationTime = {};
|
||||
lpFindFileData->ftLastAccessTime = {};
|
||||
lpFindFileData->ftLastWriteTime = {};
|
||||
@@ -50,7 +92,7 @@ SWA_API FileHandle* XCreateFileA
|
||||
assert(((dwShareMode & ~(FILE_SHARE_READ | FILE_SHARE_WRITE)) == 0) && "Unknown share mode bits.");
|
||||
assert(((dwCreationDisposition & ~(CREATE_NEW | CREATE_ALWAYS)) == 0) && "Unknown creation disposition bits.");
|
||||
|
||||
std::filesystem::path filePath = std::u8string_view((const char8_t*)(FileSystem::TransformPath(lpFileName)));
|
||||
std::filesystem::path filePath = FileSystem::ResolvePath(lpFileName, true);
|
||||
std::fstream fileStream;
|
||||
std::ios::openmode fileOpenMode = std::ios::binary;
|
||||
if (dwDesiredAccess & (GENERIC_READ | FILE_READ_DATA))
|
||||
@@ -228,45 +270,35 @@ uint32_t XSetFilePointerEx(FileHandle* hFile, int32_t lDistanceToMove, LARGE_INT
|
||||
|
||||
FindHandle* XFindFirstFileA(const char* lpFileName, WIN32_FIND_DATAA* lpFindFileData)
|
||||
{
|
||||
const char *transformedPath = FileSystem::TransformPath(lpFileName);
|
||||
size_t transformedPathLength = strlen(transformedPath);
|
||||
if (transformedPathLength == 0)
|
||||
return (FindHandle*)GUEST_INVALID_HANDLE_VALUE;
|
||||
|
||||
std::filesystem::path dirPath;
|
||||
if (strstr(transformedPath, "\\*") == (&transformedPath[transformedPathLength - 2]) || strstr(transformedPath, "/*") == (&transformedPath[transformedPathLength - 2]))
|
||||
std::string_view path = lpFileName;
|
||||
if (path.find("\\*") == (path.size() - 2) || path.find("/*") == (path.size() - 2))
|
||||
{
|
||||
dirPath = std::u8string_view((const char8_t*)(transformedPath), transformedPathLength - 2);
|
||||
path.remove_suffix(1);
|
||||
}
|
||||
else if (strstr(transformedPath, "\\*.*") == (&transformedPath[transformedPathLength - 4]) || strstr(transformedPath, "/*.*") == (&transformedPath[transformedPathLength - 4]))
|
||||
else if (path.find("\\*.*") == (path.size() - 4) || path.find("/*.*") == (path.size() - 4))
|
||||
{
|
||||
dirPath = std::u8string_view((const char8_t *)(transformedPath), transformedPathLength - 4);
|
||||
path.remove_suffix(3);
|
||||
}
|
||||
else
|
||||
{
|
||||
dirPath = std::u8string_view((const char8_t *)(transformedPath), transformedPathLength);
|
||||
assert(!dirPath.has_extension() && "Unknown search pattern.");
|
||||
assert(!std::filesystem::path(path).has_extension() && "Unknown search pattern.");
|
||||
}
|
||||
|
||||
if (!std::filesystem::is_directory(dirPath))
|
||||
FindHandle findHandle(path);
|
||||
|
||||
if (findHandle.searchResult.empty())
|
||||
return GetInvalidKernelObject<FindHandle>();
|
||||
|
||||
std::filesystem::directory_iterator dirIterator(dirPath);
|
||||
if (dirIterator == std::filesystem::directory_iterator())
|
||||
return GetInvalidKernelObject<FindHandle>();
|
||||
findHandle.fillFindData(lpFindFileData);
|
||||
|
||||
FindHandle *findHandle = CreateKernelObject<FindHandle>();
|
||||
findHandle->searchPath = std::move(dirPath);
|
||||
findHandle->iterator = std::move(dirIterator);
|
||||
findHandle->fillFindData(lpFindFileData);
|
||||
return findHandle;
|
||||
return CreateKernelObject<FindHandle>(std::move(findHandle));
|
||||
}
|
||||
|
||||
uint32_t XFindNextFileA(FindHandle* Handle, WIN32_FIND_DATAA* lpFindFileData)
|
||||
{
|
||||
Handle->iterator++;
|
||||
|
||||
if (Handle->iterator == std::filesystem::directory_iterator())
|
||||
if (Handle->iterator == Handle->searchResult.end())
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
@@ -305,7 +337,7 @@ uint32_t XReadFileEx(FileHandle* hFile, void* lpBuffer, uint32_t nNumberOfBytesT
|
||||
|
||||
uint32_t XGetFileAttributesA(const char* lpFileName)
|
||||
{
|
||||
std::filesystem::path filePath(std::u8string_view((const char8_t*)(FileSystem::TransformPath(lpFileName))));
|
||||
std::filesystem::path filePath = FileSystem::ResolvePath(lpFileName, true);
|
||||
if (std::filesystem::is_directory(filePath))
|
||||
return FILE_ATTRIBUTE_DIRECTORY;
|
||||
else if (std::filesystem::is_regular_file(filePath))
|
||||
@@ -328,52 +360,41 @@ uint32_t XWriteFile(FileHandle* hFile, const void* lpBuffer, uint32_t nNumberOfB
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void fixSlashes(char *path)
|
||||
std::filesystem::path FileSystem::ResolvePath(const std::string_view& path, bool checkForMods)
|
||||
{
|
||||
while (*path != 0)
|
||||
if (checkForMods)
|
||||
{
|
||||
if (*path == '\\')
|
||||
{
|
||||
*path = '/';
|
||||
}
|
||||
|
||||
path++;
|
||||
std::filesystem::path resolvedPath = ModLoader::ResolvePath(path);
|
||||
if (!resolvedPath.empty())
|
||||
return resolvedPath;
|
||||
}
|
||||
}
|
||||
|
||||
const char* FileSystem::TransformPath(const char* path)
|
||||
{
|
||||
thread_local char builtPath[2048]{};
|
||||
const char* relativePath = strstr(path, ":\\");
|
||||
if (relativePath != nullptr)
|
||||
thread_local std::string builtPath;
|
||||
builtPath.clear();
|
||||
|
||||
size_t index = path.find(":\\");
|
||||
if (index != std::string::npos)
|
||||
{
|
||||
// rooted folder, handle direction
|
||||
const std::string_view root = std::string_view{ path, path + (relativePath - path) };
|
||||
const std::string_view root = path.substr(0, index);
|
||||
const auto newRoot = XamGetRootPath(root);
|
||||
|
||||
if (!newRoot.empty())
|
||||
{
|
||||
strncpy(builtPath, newRoot.data(), newRoot.size());
|
||||
builtPath[newRoot.size()] = '\\';
|
||||
strcpy(builtPath + newRoot.size() + 1, relativePath + 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
strncpy(builtPath, relativePath + 2, sizeof(builtPath));
|
||||
builtPath += newRoot;
|
||||
builtPath += '/';
|
||||
}
|
||||
|
||||
builtPath += path.substr(index + 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
strncpy(builtPath, path, sizeof(builtPath));
|
||||
builtPath += path;
|
||||
}
|
||||
|
||||
fixSlashes(builtPath);
|
||||
return builtPath;
|
||||
}
|
||||
std::replace(builtPath.begin(), builtPath.end(), '\\', '/');
|
||||
|
||||
SWA_API const char* XExpandFilePathA(const char* path)
|
||||
{
|
||||
return FileSystem::TransformPath(path);
|
||||
return std::u8string_view((const char8_t*)builtPath.c_str());
|
||||
}
|
||||
|
||||
GUEST_FUNCTION_HOOK(sub_82BD4668, XCreateFileA);
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
|
||||
struct FileSystem
|
||||
{
|
||||
static const char* TransformPath(const char* path);
|
||||
};
|
||||
static std::filesystem::path ResolvePath(const std::string_view& path, bool checkForMods);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user