From 7416bc340b99c4c206043f71e45d0e4095779414 Mon Sep 17 00:00:00 2001 From: Maschell Date: Fri, 7 Feb 2025 18:25:00 +0100 Subject: [PATCH] Implement FSWrapperReplaceSingleFile --- Dockerfile | 4 +- src/DirInfoEx.h | 13 +- src/FSWrapper.cpp | 12 +- src/FSWrapper.h | 1 + src/FSWrapperReplaceSingleFile.cpp | 247 +++++++++++++++++++++++++++++ src/FSWrapperReplaceSingleFile.h | 52 ++++++ src/export.cpp | 45 +++++- 7 files changed, 363 insertions(+), 11 deletions(-) create mode 100644 src/FSWrapperReplaceSingleFile.cpp create mode 100644 src/FSWrapperReplaceSingleFile.h diff --git a/Dockerfile b/Dockerfile index 98ad2a7..dd17e43 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ -FROM ghcr.io/wiiu-env/devkitppc:20240505 +FROM ghcr.io/wiiu-env/devkitppc:20241128 COPY --from=ghcr.io/wiiu-env/libfunctionpatcher:20230621 /artifacts $DEVKITPRO COPY --from=ghcr.io/wiiu-env/wiiumodulesystem:20240424 /artifacts $DEVKITPRO -COPY --from=ghcr.io/wiiu-env/libcontentredirection:20240428 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/libcontentredirection:1.2-dev-20250207-b11c3c8 /artifacts $DEVKITPRO WORKDIR project diff --git a/src/DirInfoEx.h b/src/DirInfoEx.h index c682d3a..648ac7e 100644 --- a/src/DirInfoEx.h +++ b/src/DirInfoEx.h @@ -3,8 +3,8 @@ #include typedef struct FSDirectoryEntryEx { - FSADirectoryEntry realEntry{}; - bool isMarkedAsDeleted = false; + FSADirectoryEntry realEntry = {}; + bool isMarkedAsDeleted = false; } FSDirectoryEntryEx; struct DirInfoEx final : DirInfo { @@ -12,4 +12,11 @@ struct DirInfoEx final : DirInfo { int readResultCapacity = 0; int readResultNumberOfEntries = 0; FSDirectoryHandle realDirHandle = 0; -}; \ No newline at end of file +}; + +struct DirInfoExSingleFile final : DirInfoBase { + FSADirectoryEntry directoryEntry = {}; + bool entryRead = false; + bool entryReadSuccess = false; + FSDirectoryHandle realDirHandle = 0; +}; diff --git a/src/FSWrapper.cpp b/src/FSWrapper.cpp index 96affaf..cc007a9 100644 --- a/src/FSWrapper.cpp +++ b/src/FSWrapper.cpp @@ -42,11 +42,7 @@ FSError FSWrapper::FSOpenDirWrapper(const char *path, FSDirectoryHandle *handle) dirHandle->path[0] = '\0'; strncat(dirHandle->path, newPath.c_str(), sizeof(dirHandle->path) - 1); - { - std::lock_guard lock(openDirsMutex); - openDirs.push_back(dirHandle); - OSMemoryBarrier(); - } + addDirHandle(dirHandle); } else { auto err = errno; if (err == ENOENT) { @@ -743,6 +739,12 @@ std::shared_ptr FSWrapper::getDirFromHandle(const FSDirectoryHandle return nullptr; } +void FSWrapper::addDirHandle(const std::shared_ptr &dirHandle) { + std::lock_guard lock(openDirsMutex); + openDirs.push_back(dirHandle); + OSMemoryBarrier(); +} + void FSWrapper::deleteDirHandle(FSDirectoryHandle handle) { if (!remove_locked_first_if(openDirsMutex, openDirs, [handle](auto &cur) { return static_cast(cur->handle) == handle; })) { DEBUG_FUNCTION_LINE_ERR("[%s] Delete failed because the handle %08X was not found", getName().c_str(), handle); diff --git a/src/FSWrapper.h b/src/FSWrapper.h index cb052ef..84d5280 100644 --- a/src/FSWrapper.h +++ b/src/FSWrapper.h @@ -114,6 +114,7 @@ protected: bool isValidDirHandle(FSDirectoryHandle handle) override; bool isValidFileHandle(FSFileHandle handle) override; + void addDirHandle(const std::shared_ptr &dirHandle); void deleteDirHandle(FSDirectoryHandle handle) override; void deleteFileHandle(FSFileHandle handle) override; diff --git a/src/FSWrapperReplaceSingleFile.cpp b/src/FSWrapperReplaceSingleFile.cpp new file mode 100644 index 0000000..640ce9d --- /dev/null +++ b/src/FSWrapperReplaceSingleFile.cpp @@ -0,0 +1,247 @@ +#include "FSWrapperReplaceSingleFile.h" +#include "utils/StringTools.h" +#include "utils/logger.h" +#include "utils/utils.h" + +#include +#include +#include + +#include + +FSWrapperReplaceSingleFile::FSWrapperReplaceSingleFile(const std::string &name, + const std::string &fileToReplace, + const std::string &replaceWithPath, + const bool fallbackOnError) : FSWrapper(name, + fileToReplace, + replaceWithPath, + fallbackOnError, + false) { + auto strCpy = fileToReplace; + std::ranges::replace(strCpy, '\\', '/'); + auto asPath = std::filesystem::path(strCpy); + mPathToReplace = asPath.parent_path(); + mFileNameToReplace = asPath.filename(); + mFullPathToReplace = fileToReplace; + + strCpy = replaceWithPath; + std::ranges::replace(strCpy, '\\', '/'); + asPath = std::filesystem::path(strCpy); + mReplacedWithPath = asPath.parent_path(); + mReplacedWithFileName = asPath.filename(); + + FSAInit(); + this->mClientHandle = FSAAddClient(nullptr); + if (mClientHandle < 0) { + DEBUG_FUNCTION_LINE_ERR("[%s] FSAClientHandle failed: %s (%d)", name.c_str(), FSAGetStatusStr(static_cast(mClientHandle)), mClientHandle); + mClientHandle = 0; + } +} + +FSWrapperReplaceSingleFile::~FSWrapperReplaceSingleFile() { + if (mClientHandle) { + if (const FSError res = FSADelClient(mClientHandle); res != FS_ERROR_OK) { + DEBUG_FUNCTION_LINE_ERR("[%s] FSADelClient failed: %s (%d)", FSAGetStatusStr(res), res); + } + mClientHandle = 0; + } +} + +FSError FSWrapperReplaceSingleFile::FSOpenDirWrapper(const char *path, + FSADirectoryHandle *handle) { + if (!IsDirPathToReplace(path)) { + return FS_ERROR_FORCE_PARENT_LAYER; + } + if (handle == nullptr) { + DEBUG_FUNCTION_LINE_ERR("[%s] handle was NULL", getName().c_str()); + return FS_ERROR_INVALID_PARAM; + } + if (const auto dirInfo = make_shared_nothrow()) { + dirInfo->handle = (reinterpret_cast(dirInfo.get()) & 0x0FFFFFFF) | 0x30000000; + *handle = dirInfo->handle; + addDirHandle(dirInfo); + if (!isValidDirHandle(*handle)) { + FSWrapper::FSCloseDirWrapper(*handle); + DEBUG_FUNCTION_LINE_ERR("[%s] No valid dir handle %08X", getName().c_str(), *handle); + return FS_ERROR_INVALID_DIRHANDLE; + } + if (const auto dirHandle = getDirExFromHandle(*handle); dirHandle != nullptr) { + dirHandle->entryRead = false; + dirHandle->entryReadSuccess = false; + dirHandle->directoryEntry = {}; + dirHandle->realDirHandle = 0; + + if (mClientHandle) { + FSADirectoryHandle realHandle = 0; + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Call FSAOpenDir with %s for parent layer", getName().c_str(), path); + if (const FSError err = FSAOpenDir(mClientHandle, path, &realHandle); err == FS_ERROR_OK) { + dirHandle->realDirHandle = realHandle; + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] Failed to open real dir %s. %s (%d)", getName().c_str(), path, FSAGetStatusStr(err), err); + } + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] clientHandle was null", getName().c_str()); + } + OSMemoryBarrier(); + } + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] Failed to alloc dir handle", getName().c_str()); + return FS_ERROR_MAX_DIRS; + } + return FS_ERROR_OK; +} + +FSError FSWrapperReplaceSingleFile::FSReadDirWrapper(const FSADirectoryHandle handle, FSADirectoryEntry *entry) { + if (!isValidDirHandle(handle)) { + return FS_ERROR_FORCE_PARENT_LAYER; + } + const auto dirHandle = getDirExFromHandle(handle); + if (!dirHandle) { + DEBUG_FUNCTION_LINE_ERR("[%s] No valid dir handle %08X", getName().c_str(), handle); + return FS_ERROR_INVALID_DIRHANDLE; + } + FSError res = FS_ERROR_OK; + do { + if (!dirHandle->entryRead) { + dirHandle->entryRead = true; + const auto newPath = GetNewPath(mFullPathToReplace); + + struct stat path_stat {}; + + DEBUG_FUNCTION_LINE_VERBOSE("[%s] dir read of %s (%s)", getName().c_str(), mFullPathToReplace.c_str(), newPath.c_str()); + if (stat(newPath.c_str(), &path_stat) < 0) { + DEBUG_FUNCTION_LINE_WARN("[%s] Path %s (%s) for dir read not found ", getName().c_str(), mFullPathToReplace.c_str(), newPath.c_str()); + dirHandle->entryReadSuccess = false; + continue; + } + translate_stat(&path_stat, &dirHandle->directoryEntry.info); + strncpy(dirHandle->directoryEntry.name, mFileNameToReplace.c_str(), sizeof(dirHandle->directoryEntry.name)); + memcpy(entry, &dirHandle->directoryEntry, sizeof(FSADirectoryEntry)); + + dirHandle->entryReadSuccess = true; + OSMemoryBarrier(); + } else { + // Read the real directory. + if (dirHandle->realDirHandle != 0) { + if (mClientHandle) { + FSADirectoryEntry realDirEntry; + while (true) { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Call FSReadDir with %08X for parent layer", getName().c_str(), dirHandle->realDirHandle); + if (const FSError readDirResult = FSAReadDir(mClientHandle, dirHandle->realDirHandle, &realDirEntry); readDirResult == FS_ERROR_OK) { + // Skip already read new files + if (dirHandle->entryRead && dirHandle->entryReadSuccess && strcmp(dirHandle->directoryEntry.name, realDirEntry.name) == 0) { + continue; + } + + // But use new entries! + memcpy(entry, &realDirEntry, sizeof(FSADirectoryEntry)); + res = FS_ERROR_OK; + break; + } else if (readDirResult == FS_ERROR_END_OF_DIR) { + res = FS_ERROR_END_OF_DIR; + break; + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] real_FSReadDir returned an unexpected error: %s (%d)", getName().c_str(), FSAGetStatusStr(readDirResult), readDirResult); + res = FS_ERROR_END_OF_DIR; + break; + } + } + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] clientHandle was null", getName().c_str()); + } + } + } + return res; + } while (true); +} + +FSError FSWrapperReplaceSingleFile::FSCloseDirWrapper(const FSADirectoryHandle handle) { + if (!isValidDirHandle(handle)) { + return FS_ERROR_FORCE_PARENT_LAYER; + } + const auto dirHandle = getDirExFromHandle(handle); + if (dirHandle->realDirHandle != 0) { + if (mClientHandle) { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Call FSCloseDir with %08X for parent layer", getName().c_str(), dirHandle->realDirHandle); + auto realResult = FSACloseDir(mClientHandle, dirHandle->realDirHandle); + if (realResult == FS_ERROR_OK) { + dirHandle->realDirHandle = 0; + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] Failed to close realDirHandle %d: res %s (%d)", getName().c_str(), dirHandle->realDirHandle, FSAGetStatusStr(realResult), realResult); + return realResult == FS_ERROR_CANCELLED ? FS_ERROR_CANCELLED : FS_ERROR_MEDIA_ERROR; + } + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] clientHandle was null", getName().c_str()); + } + } else { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] dirHandle->realDirHandle was 0", getName().c_str()); + } + dirHandle->entryRead = false; + dirHandle->entryReadSuccess = false; + + OSMemoryBarrier(); + return FS_ERROR_OK; +} + +FSError FSWrapperReplaceSingleFile::FSRewindDirWrapper(const FSADirectoryHandle handle) { + if (!isValidDirHandle(handle)) { + return FS_ERROR_FORCE_PARENT_LAYER; + } + const auto dirHandle = getDirExFromHandle(handle); + dirHandle->entryRead = false; + dirHandle->entryReadSuccess = false; + if (dirHandle->realDirHandle != 0) { + if (mClientHandle) { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Call FSARewindDir with %08X for parent layer", getName().c_str(), dirHandle->realDirHandle); + if (const FSError err = FSARewindDir(mClientHandle, dirHandle->realDirHandle); err != FS_ERROR_OK) { + DEBUG_FUNCTION_LINE_ERR("[%s] Failed to rewind dir for realDirHandle %08X. %s (%d)", getName().c_str(), dirHandle->realDirHandle, FSAGetStatusStr(err), err); + } + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] clientHandle was null", getName().c_str()); + } + } else { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] dirHandle->realDirHandle was 0", getName().c_str()); + } + OSMemoryBarrier(); + + return FS_ERROR_OK; +} + +bool FSWrapperReplaceSingleFile::SkipDeletedFilesInReadDir() { + return false; +} + +bool FSWrapperReplaceSingleFile::IsDirPathToReplace(const std::string_view &path) const { + return starts_with_case_insensitive(path, mPathToReplace); +} + +std::string FSWrapperReplaceSingleFile::GetNewPath(const std::string_view &path) const { + auto pathCpy = std::string(path); + SafeReplaceInString(pathCpy, this->mPathToReplace, this->mReplacedWithPath); + SafeReplaceInString(pathCpy, this->mFileNameToReplace, this->mReplacedWithFileName); + std::ranges::replace(pathCpy, '\\', '/'); + + uint32_t length = pathCpy.size(); + + //! clear path of double slashes + for (uint32_t i = 1; i < length; ++i) { + if (pathCpy[i - 1] == '/' && pathCpy[i] == '/') { + pathCpy.erase(i, 1); + i--; + length--; + } + } + + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Redirect %.*s -> %s", getName().c_str(), int(path.length()), path.data(), pathCpy.c_str()); + return pathCpy; +} + +std::shared_ptr FSWrapperReplaceSingleFile::getDirExFromHandle(FSADirectoryHandle handle) { + auto dir = std::dynamic_pointer_cast(getDirFromHandle(handle)); + + if (!dir) { + DEBUG_FUNCTION_LINE_ERR("[%s] dynamic_pointer_cast(%08X) failed", getName().c_str(), handle); + OSFatal("ContentRedirectionModule: dynamic_pointer_cast failed"); + } + return dir; +} \ No newline at end of file diff --git a/src/FSWrapperReplaceSingleFile.h b/src/FSWrapperReplaceSingleFile.h new file mode 100644 index 0000000..c14dcf5 --- /dev/null +++ b/src/FSWrapperReplaceSingleFile.h @@ -0,0 +1,52 @@ +#pragma once +#include "DirInfoEx.h" +#include "FSWrapper.h" + +#include + +class FSWrapperReplaceSingleFile final : public FSWrapper { +public: + FSWrapperReplaceSingleFile(const std::string &name, + const std::string &fileToReplace, + const std::string &replaceWithPath, + bool fallbackOnError); + + ~FSWrapperReplaceSingleFile() override; + + FSError FSOpenDirWrapper(const char *path, + FSDirectoryHandle *handle) override; + + FSError FSReadDirWrapper(FSDirectoryHandle handle, + FSDirectoryEntry *entry) override; + + FSError FSCloseDirWrapper(FSDirectoryHandle handle) override; + + FSError FSRewindDirWrapper(FSDirectoryHandle handle) override; + + bool SkipDeletedFilesInReadDir() override; + + uint32_t getLayerId() override { + return static_cast(mClientHandle); + } + + +protected: + [[nodiscard]] std::string GetNewPath(const std::string_view &path) const override; + + std::shared_ptr getNewDirInfoHandle() override { + OSFatal("FSWrapperReplaceSingleFile::getNewDirInfoHandle. Not implemented"); + return {}; + } + +private: + bool IsDirPathToReplace(const std::string_view &path) const; + + std::shared_ptr getDirExFromHandle(FSDirectoryHandle handle); + + FSAClientHandle mClientHandle; + std::string mPathToReplace; + std::string mFileNameToReplace; + std::string mFullPathToReplace; + std::string mReplacedWithPath; + std::string mReplacedWithFileName; +}; diff --git a/src/export.cpp b/src/export.cpp index 363ca78..7bab400 100644 --- a/src/export.cpp +++ b/src/export.cpp @@ -6,6 +6,8 @@ #include "utils/StringTools.h" #include "utils/logger.h" #include "utils/utils.h" + +#include #include #include #include @@ -142,6 +144,46 @@ ContentRedirectionApiErrorType CRAddFSLayer(CRLayerHandle *handle, const char *l return CONTENT_REDIRECTION_API_ERROR_NO_MEMORY; } +ContentRedirectionApiErrorType CRAddFSLayerEx(CRLayerHandle *handle, const char *layerName, const char *targetPath, const char *replacementPath, const FSLayerTypeEx layerType) { + if (!handle || layerName == nullptr || replacementPath == nullptr || targetPath == nullptr) { + DEBUG_FUNCTION_LINE_WARN("CONTENT_REDIRECTION_API_ERROR_INVALID_ARG"); + return CONTENT_REDIRECTION_API_ERROR_INVALID_ARG; + } + std::unique_ptr ptr; + switch (layerType) { + case FS_LAYER_TYPE_EX_REPLACE_DIRECTORY: { + DEBUG_FUNCTION_LINE_INFO("[AddFSLayerEx] Redirecting \"%s\" to \"%s\", mode: \"replace\"", targetPath, replacementPath); + ptr = make_unique_nothrow(layerName, targetPath, replacementPath, false, false); + break; + } + case FS_LAYER_TYPE_EX_MERGE_DIRECTORY: { + DEBUG_FUNCTION_LINE_INFO("[AddFSLayerEx] Redirecting \"%s\" to \"%s\", mode: \"merge\"", targetPath, replacementPath); + ptr = make_unique_nothrow(layerName, targetPath, replacementPath, true); + break; + } + case FS_LAYER_TYPE_EX_REPLACE_FILE: { + DEBUG_FUNCTION_LINE_INFO("[AddFSLayerEx] Redirecting file \"%s\" to \"%s\"", targetPath, replacementPath); + ptr = make_unique_nothrow(layerName, targetPath, replacementPath, true); + break; + } + default: { + DEBUG_FUNCTION_LINE_ERR("[AddFSLayerEx] CONTENT_REDIRECTION_API_ERROR_UNKNOWN_LAYER_DIR_TYPE: %s %s %d", layerName, replacementPath, layerType); + return CONTENT_REDIRECTION_API_ERROR_UNKNOWN_FS_LAYER_TYPE; + } + } + + if (ptr) { + DEBUG_FUNCTION_LINE_VERBOSE("[AddFSLayerEx] Added new layer (%s). Target path: %s Replacement dir: %s Type:%d", layerName, targetPath, replacementPath, layerType); + std::lock_guard lock(gFSLayerMutex); + *handle = ptr->getHandle(); + gFSLayers.emplace_back(std::move(ptr)); + return CONTENT_REDIRECTION_API_ERROR_NONE; + } + DEBUG_FUNCTION_LINE_ERR("[AddFSLayerEx] Failed to allocate memory"); + return CONTENT_REDIRECTION_API_ERROR_NO_MEMORY; +} + + ContentRedirectionApiErrorType CRRemoveFSLayer(CRLayerHandle handle) { if (!remove_locked_first_if(gFSLayerMutex, gFSLayers, [handle](auto &cur) { return (CRLayerHandle) cur->getHandle() == handle; })) { DEBUG_FUNCTION_LINE_WARN("CONTENT_REDIRECTION_API_ERROR_LAYER_NOT_FOUND for handle %08X", handle); @@ -167,7 +209,7 @@ ContentRedirectionApiErrorType CRGetVersion(ContentRedirectionVersion *outVersio if (outVersion == nullptr) { return CONTENT_REDIRECTION_API_ERROR_INVALID_ARG; } - *outVersion = 1; + *outVersion = 2; return CONTENT_REDIRECTION_API_ERROR_NONE; } @@ -180,6 +222,7 @@ int CRRemoveDevice(const char *name) { } WUMS_EXPORT_FUNCTION(CRGetVersion); +WUMS_EXPORT_FUNCTION(CRAddFSLayerEx); WUMS_EXPORT_FUNCTION(CRAddFSLayer); WUMS_EXPORT_FUNCTION(CRRemoveFSLayer); WUMS_EXPORT_FUNCTION(CRSetActive);