commit 7c9c0348fee6641a6d1a7ffd8f48a759c79aeecd Author: Maschell Date: Thu Apr 14 22:41:41 2022 +0200 first commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..56cc685 --- /dev/null +++ b/.clang-format @@ -0,0 +1,67 @@ +# Generated from CLion C/C++ Code Style settings +BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: Consecutive +AlignConsecutiveMacros: AcrossEmptyLinesAndComments +AlignOperands: Align +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Always +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Always +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterReturnType: None +AlwaysBreakTemplateDeclarations: Yes +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: true +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +ColumnLimit: 0 +CompactNamespaces: false +ContinuationIndentWidth: 8 +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 4 +KeepEmptyLinesAtTheStartOfBlocks: true +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: All +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PointerAlignment: Right +ReflowComments: false +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +TabWidth: 4 +UseTab: Never diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..14b2964 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,65 @@ +name: CI-Release + +on: + push: + branches: + - main + +jobs: + clang-format: + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v2 + - name: clang-format + run: | + docker run --rm -v ${PWD}:/src wiiuenv/clang-format:13.0.0-2 -r ./src + build-binary: + runs-on: ubuntu-18.04 + needs: clang-format + steps: + - uses: actions/checkout@v2 + - name: build binary + run: | + docker build . -t builder + docker run --rm -v ${PWD}:/project builder make + - uses: actions/upload-artifact@master + with: + name: binary + path: "*.wms" + deploy-binary: + needs: build-binary + runs-on: ubuntu-18.04 + steps: + - name: Get environment variables + id: get_repository_name + run: | + echo REPOSITORY_NAME=$(echo "$GITHUB_REPOSITORY" | awk -F / '{print $2}' | sed -e "s/:refs//") >> $GITHUB_ENV + echo DATETIME=$(echo $(date '+%Y%m%d-%H%M%S')) >> $GITHUB_ENV + - uses: actions/download-artifact@master + with: + name: binary + - name: zip artifact + run: zip -r ${{ env.REPOSITORY_NAME }}_${{ env.DATETIME }}.zip *.wms + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ env.REPOSITORY_NAME }}-${{ env.DATETIME }} + release_name: Nightly-${{ env.REPOSITORY_NAME }}-${{ env.DATETIME }} + draft: false + prerelease: true + body: | + Not a stable release: + ${{ github.event.head_commit.message }} + - name: Upload Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + asset_path: ./${{ env.REPOSITORY_NAME }}_${{ env.DATETIME }}.zip + asset_name: ${{ env.REPOSITORY_NAME }}_${{ env.DATETIME }}.zip + asset_content_type: application/zip \ No newline at end of file diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..ef22559 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,25 @@ +name: CI-PR + +on: [pull_request] + +jobs: + clang-format: + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v2 + - name: clang-format + run: | + docker run --rm -v ${PWD}:/src wiiuenv/clang-format:13.0.0-2 -r ./src + build-binary: + runs-on: ubuntu-18.04 + needs: clang-format + steps: + - uses: actions/checkout@v2 + - name: build binary + run: | + docker build . -t builder + docker run --rm -v ${PWD}:/project builder make + - uses: actions/upload-artifact@master + with: + name: binary + path: "*.wms" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba57c28 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.cbp +*.elf +*.layout +*.rpx +build/ +*.save-failed +.idea/ +cmake-build-debug/ +CMakeLists.txt +*.wms diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c5a094f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM wiiuenv/devkitppc:20220303 + +COPY --from=wiiuenv/libfunctionpatcher:20220211 /artifacts $DEVKITPRO +COPY --from=wiiuenv/wiiumodulesystem:20220204 /artifacts $DEVKITPRO +COPY --from=wiiuenv/libromfs_wiiu:20220414 /artifacts $DEVKITPRO +COPY --from=wiiuenv/libcontentredirection:20220414 /artifacts $DEVKITPRO + +WORKDIR project \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..973636c --- /dev/null +++ b/Makefile @@ -0,0 +1,147 @@ +#------------------------------------------------------------------------------- +.SUFFIXES: +#------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) + +include $(DEVKITPRO)/wums/share/wums_rules + +WUMS_ROOT := $(DEVKITPRO)/wums +WUT_ROOT := $(DEVKITPRO)/wut +#------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +#------------------------------------------------------------------------------- +TARGET := ContentRedirectionModule +BUILD := build +SOURCES := src \ + src/utils +DATA := data +INCLUDES := src + +#------------------------------------------------------------------------------- +# options for code generation +#------------------------------------------------------------------------------- +CFLAGS := -Wall -Wextra -Os -ffunction-sections\ + $(MACHDEP) + +CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ + +CXXFLAGS := $(CFLAGS) -std=c++20 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -g $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) -T$(WUMS_ROOT)/share/libfunctionpatcher.ld $(WUMSSPECS) + +ifeq ($(DEBUG),1) +CXXFLAGS += -DDEBUG -g +CFLAGS += -DDEBUG -g +endif + +LIBS := -lwums -lwut -lfunctionpatcher + +#------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level +# containing include and lib +#------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(WUT_ROOT) $(WUT_ROOT)/usr $(WUMS_ROOT) + +#------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#------------------------------------------------------------------------------- + export LD := $(CC) +#------------------------------------------------------------------------------- +else +#------------------------------------------------------------------------------- + export LD := $(CXX) +#------------------------------------------------------------------------------- +endif +#------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +.PHONY: $(BUILD) clean all + +#------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).wms $(TARGET).elf + +#------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#------------------------------------------------------------------------------- +# main targets +#------------------------------------------------------------------------------- +all : $(OUTPUT).wms + +$(OUTPUT).wms : $(OUTPUT).elf +$(OUTPUT).elf : $(OFILES) + +$(OFILES_SRC) : $(HFILES_BIN) + +#------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +#--------------------------------------------------------------------------------- +%.o: %.s + @echo $(notdir $<) + @$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@ $(ERROR_FILTER) + +-include $(DEPENDS) + +#------------------------------------------------------------------------------- +endif +#------------------------------------------------------------------------------- diff --git a/README.md b/README.md new file mode 100644 index 0000000..751b279 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +[![CI-Release](https://github.com/wiiu-env/ContentRedirectionModule/actions/workflows/ci.yml/badge.svg)](https://github.com/wiiu-env/ContentRedirectionModule/actions/workflows/ci.yml) + +## Usage +(`[ENVIRONMENT]` is a placeholder for the actual environment name.) + +1. Copy the file `ContentRedirectionModule.wms` into `sd:/wiiu/environments/[ENVIRONMENT]/modules`. +2. Requires the [WUMSLoader](https://github.com/wiiu-env/WUMSLoader) in `sd:/wiiu/environments/[ENVIRONMENT]/modules/setup`. +3. Use [libcontentredirection](https://github.com/wiiu-env/libcontentredirection). + +## Building using the Dockerfile + +It's possible to use a docker image for building. This way you don't need anything installed on your host system. + +``` +# Build docker image (only needed once) +docker build . -t contentredirectionmodule-builder + +# make +docker run -it --rm -v ${PWD}:/project contentredirectionmodule-builder make + +# make clean +docker run -it --rm -v ${PWD}:/project contentredirectionmodule-builder make clean +``` + +## Format the code via docker + +`docker run --rm -v ${PWD}:/src wiiuenv/clang-format:13.0.0-2 -r ./src -i` \ No newline at end of file diff --git a/src/DirInfo.h b/src/DirInfo.h new file mode 100644 index 0000000..13fc292 --- /dev/null +++ b/src/DirInfo.h @@ -0,0 +1,10 @@ +#pragma once +#include +#include + +struct DirInfo { + virtual ~DirInfo() = default; + FSDirectoryHandle handle{}; + DIR *dir{}; + char path[0x280]{}; +}; diff --git a/src/DirInfoEx.h b/src/DirInfoEx.h new file mode 100644 index 0000000..cb5d385 --- /dev/null +++ b/src/DirInfoEx.h @@ -0,0 +1,16 @@ +#pragma once +#include "DirInfo.h" +#include + +typedef struct FSDirectoryEntryEx { + FSDirectoryEntry realEntry{}; + bool isMarkedAsDeleted = false; +} FSDirectoryEntryEx; + +struct DirInfoEx : public DirInfo { +public: + FSDirectoryEntryEx *readResult = nullptr; + int readResultCapacity = 0; + int readResultNumberOfEntries = 0; + FSDirectoryHandle realDirHandle = 0; +}; \ No newline at end of file diff --git a/src/FSDirReplacements.cpp b/src/FSDirReplacements.cpp new file mode 100644 index 0000000..4b545f7 --- /dev/null +++ b/src/FSDirReplacements.cpp @@ -0,0 +1,220 @@ +#include "FSDirReplacements.h" +#include "FileUtils.h" +#include "IFSWrapper.h" +#include "utils/logger.h" +#include +#include + +DECL_FUNCTION(FSStatus, FSOpenDir, FSClient *client, FSCmdBlock *block, char *path, FSDirectoryHandle *handle, FSErrorFlag errorMask) { + DEBUG_FUNCTION_LINE_VERBOSE("%s", path); + if (isForceRealFunction(errorMask)) { + return real_FSOpenDir(client, block, path, handle, errorMask); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, p = path](FSErrorFlag realErrorMask) -> FSStatus { + auto res = real_FSOpenDir(c, b, p, h, realErrorMask); + return res; + }, + [f = getFullPathForClient(client, path), h = handle](IFSWrapper *layer) -> FSStatus { + return layer->FSOpenDirWrapper(f.c_str(), h); + }, + SYNC_RESULT_HANDLER); +} + + +DECL_FUNCTION(FSStatus, FSOpenDirAsync, FSClient *client, FSCmdBlock *block, char *path, FSDirectoryHandle *handle, FSErrorFlag errorMask, FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE("%s", path); + if (isForceRealFunction(errorMask)) { + return real_FSOpenDirAsync(client, block, path, handle, getRealErrorFlag(errorMask), asyncData); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, p = path, a = asyncData](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSOpenDirAsync(c, b, p, h, realErrorMask, a); + }, + [f = getFullPathForClient(client, path), h = handle](IFSWrapper *layer) -> FSStatus { + return layer->FSOpenDirWrapper(f.c_str(), h); + }, + ASYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSReadDir, FSClient *client, FSCmdBlock *block, FSDirectoryHandle handle, [[maybe_unused]] FSDirectoryEntry *entry, FSErrorFlag errorMask) { + DEBUG_FUNCTION_LINE_VERBOSE("", errorMask); + if (isForceRealFunction(errorMask)) { + return real_FSReadDir(client, block, handle, entry, errorMask); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, e = entry](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSReadDir(c, b, h, e, realErrorMask); + }, + [h = handle, e = entry](IFSWrapper *layer) -> FSStatus { + return layer->FSReadDirWrapper(h, e); + }, + SYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSReadDirAsync, FSClient *client, FSCmdBlock *block, FSDirectoryHandle handle, FSDirectoryEntry *entry, FSErrorFlag errorMask, FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE(); + if (isForceRealFunction(errorMask)) { + return real_FSReadDirAsync(client, block, handle, entry, getRealErrorFlag(errorMask), asyncData); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, e = entry, a = asyncData](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSReadDirAsync(c, b, h, e, realErrorMask, a); + }, + [h = handle, e = entry](IFSWrapper *layer) -> FSStatus { + return layer->FSReadDirWrapper(h, e); + }, + ASYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSCloseDir, FSClient *client, FSCmdBlock *block, FSDirectoryHandle handle, FSErrorFlag errorMask) { + DEBUG_FUNCTION_LINE_VERBOSE("", errorMask); + if (isForceRealFunction(errorMask)) { + return real_FSCloseDir(client, block, handle, errorMask); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSCloseDir(c, b, h, realErrorMask); + }, + [h = handle](IFSWrapper *layer) -> FSStatus { + return layer->FSCloseDirWrapper(h); + }, + [h = handle, filename = __FILENAME__, func = __FUNCTION__, line = __LINE__](IFSWrapper *layer, FSStatus res) -> FSStatus { + if (layer->isValidDirHandle(h)) { + layer->deleteDirHandle(h); + } else { + DEBUG_FUNCTION_LINE_ERR_LAMBDA(filename, func, line, "Expected to delete dirHandle by %08X but it was not found", h); + } + DEBUG_FUNCTION_LINE_VERBOSE_EX(filename, func, line, "Sync result %d", res); + return res; + }); +} + +DECL_FUNCTION(FSStatus, FSCloseDirAsync, FSClient *client, FSCmdBlock *block, FSDirectoryHandle handle, FSErrorFlag errorMask, FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE(); + if (isForceRealFunction(errorMask)) { + return real_FSCloseDirAsync(client, block, handle, getRealErrorFlag(errorMask), asyncData); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, a = asyncData](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSCloseDirAsync(c, b, h, realErrorMask, a); + }, + [h = handle](IFSWrapper *layer) -> FSStatus { + return layer->FSCloseDirWrapper(h); + }, + [c = client, b = block, h = handle, a = asyncData, filename = __FILENAME__, func = __FUNCTION__, line = __LINE__](IFSWrapper *layer, FSStatus res) -> FSStatus { + if (layer->isValidDirHandle(h)) { + layer->deleteDirHandle(h); + } else { + DEBUG_FUNCTION_LINE_ERR_LAMBDA(filename, func, line, "Expected to delete dirHandle by %08X but it was not found", h); + } + DEBUG_FUNCTION_LINE_VERBOSE_EX(filename, func, line, "Async result %d", res); + return send_result_async(c, b, a, res); + }); +} + +DECL_FUNCTION(FSStatus, FSRewindDir, FSClient *client, FSCmdBlock *block, FSDirectoryHandle handle, FSErrorFlag errorMask) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSRewindDir(client, block, handle, errorMask); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSRewindDir(c, b, h, realErrorMask); + }, + [h = handle](IFSWrapper *layer) -> FSStatus { + return layer->FSRewindDirWrapper(h); + }, + SYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSRewindDirAsync, FSClient *client, FSCmdBlock *block, FSDirectoryHandle handle, FSErrorFlag errorMask, FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE(); + if (isForceRealFunction(errorMask)) { + return real_FSRewindDirAsync(client, block, handle, getRealErrorFlag(errorMask), asyncData); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, a = asyncData](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSRewindDirAsync(c, b, h, realErrorMask, a); + }, + [h = handle](IFSWrapper *layer) -> FSStatus { + return layer->FSRewindDirWrapper(h); + }, + ASYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSMakeDir, FSClient *client, FSCmdBlock *block, char *path, FSErrorFlag errorMask) { + DEBUG_FUNCTION_LINE_VERBOSE("%s", path); + if (isForceRealFunction(errorMask)) { + return real_FSMakeDir(client, block, path, errorMask); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, p = path](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSMakeDir(c, b, p, realErrorMask); + }, + [f = getFullPathForClient(client, path)](IFSWrapper *layer) -> FSStatus { + return layer->FSMakeDirWrapper(f.c_str()); + }, + SYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSMakeDirAsync, FSClient *client, FSCmdBlock *block, char *path, FSErrorFlag errorMask, FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE("%s", path); + if (isForceRealFunction(errorMask)) { + return real_FSMakeDirAsync(client, block, path, getRealErrorFlag(errorMask), asyncData); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, p = path, a = asyncData](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSMakeDirAsync(c, b, p, realErrorMask, a); + }, + [f = getFullPathForClient(client, path)](IFSWrapper *layer) -> FSStatus { + return layer->FSMakeDirWrapper(f.c_str()); + }, + ASYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSChangeDirAsync, FSClient *client, FSCmdBlock *block, const char *path, FSErrorFlag errorMask, FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE("FSChangeDirAsync %s", path); + setWorkingDir(client, path); + return real_FSChangeDirAsync(client, block, path, errorMask, asyncData); +} + +function_replacement_data_t fs_dir_function_replacements[] = { + REPLACE_FUNCTION(FSOpenDir, LIBRARY_COREINIT, FSOpenDir), + REPLACE_FUNCTION(FSOpenDirAsync, LIBRARY_COREINIT, FSOpenDirAsync), + + REPLACE_FUNCTION(FSReadDir, LIBRARY_COREINIT, FSReadDir), + REPLACE_FUNCTION(FSReadDirAsync, LIBRARY_COREINIT, FSReadDirAsync), + + REPLACE_FUNCTION(FSCloseDir, LIBRARY_COREINIT, FSCloseDir), + REPLACE_FUNCTION(FSCloseDirAsync, LIBRARY_COREINIT, FSCloseDirAsync), + + REPLACE_FUNCTION(FSRewindDir, LIBRARY_COREINIT, FSRewindDir), + REPLACE_FUNCTION(FSRewindDirAsync, LIBRARY_COREINIT, FSRewindDirAsync), + + REPLACE_FUNCTION(FSMakeDir, LIBRARY_COREINIT, FSMakeDir), + REPLACE_FUNCTION(FSMakeDirAsync, LIBRARY_COREINIT, FSMakeDirAsync), + + REPLACE_FUNCTION(FSChangeDirAsync, LIBRARY_COREINIT, FSChangeDirAsync), +}; +uint32_t fs_dir_function_replacements_size = sizeof(fs_dir_function_replacements) / sizeof(function_replacement_data_t); diff --git a/src/FSDirReplacements.h b/src/FSDirReplacements.h new file mode 100644 index 0000000..262271d --- /dev/null +++ b/src/FSDirReplacements.h @@ -0,0 +1,11 @@ +#pragma once + +#include "IFSWrapper.h" +#include +#include +#include +#include + + +extern function_replacement_data_t fs_dir_function_replacements[]; +extern uint32_t fs_dir_function_replacements_size; diff --git a/src/FSFileReplacements.cpp b/src/FSFileReplacements.cpp new file mode 100644 index 0000000..05e0dbe --- /dev/null +++ b/src/FSFileReplacements.cpp @@ -0,0 +1,579 @@ +#include "FSFileReplacements.h" +#include "FileUtils.h" +#include "utils/logger.h" +#include +#include + +DECL_FUNCTION(FSStatus, FSOpenFile, FSClient *client, FSCmdBlock *block, char *path, const char *mode, FSFileHandle *handle, FSErrorFlag errorMask) { + DEBUG_FUNCTION_LINE_VERBOSE("%s", path); + if (isForceRealFunction(errorMask)) { + return real_FSOpenFile(client, block, path, mode, handle, errorMask); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, p = path, m = mode, h = handle](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSOpenFile(c, b, p, m, h, realErrorMask); + }, + [f = getFullPathForClient(client, path), m = mode, h = handle](IFSWrapper *layer) -> FSStatus { + return layer->FSOpenFileWrapper(f.c_str(), m, h); + }, + SYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSOpenFileAsync, FSClient *client, FSCmdBlock *block, char *path, const char *mode, FSFileHandle *handle, FSErrorFlag errorMask, FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE("%s", path); + if (isForceRealFunction(errorMask)) { + return real_FSOpenFileAsync(client, block, path, mode, handle, getRealErrorFlag(errorMask), asyncData); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, p = path, m = mode, h = handle, a = asyncData](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSOpenFileAsync(c, b, p, m, h, realErrorMask, a); + }, + [p = getFullPathForClient(client, path), m = mode, h = handle](IFSWrapper *layer) -> FSStatus { + return layer->FSOpenFileWrapper(p.c_str(), m, h); + }, + ASYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSCloseFile, FSClient *client, FSCmdBlock *block, FSFileHandle handle, FSErrorFlag errorMask) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSCloseFile(client, block, handle, errorMask); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSCloseFile(c, b, h, realErrorMask); + }, + [h = handle](IFSWrapper *layer) -> FSStatus { + return layer->FSCloseFileWrapper(h); + }, + [h = handle, filename = __FILENAME__, func = __FUNCTION__, line = __LINE__](IFSWrapper *layer, FSStatus res) -> FSStatus { + if (layer->isValidFileHandle(h)) { + layer->deleteFileHandle(h); + } else { + DEBUG_FUNCTION_LINE_ERR_LAMBDA(filename, func, line, "Expected to delete fileHandle by handle %08X but it was not found", h); + } + DEBUG_FUNCTION_LINE_VERBOSE_EX(filename, func, line, "Sync result %d", res); + return res; + }); +} + +DECL_FUNCTION(FSStatus, FSCloseFileAsync, FSClient *client, FSCmdBlock *block, FSFileHandle handle, FSErrorFlag errorMask, FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSCloseFileAsync(client, block, handle, getRealErrorFlag(errorMask), asyncData); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, a = asyncData](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSCloseFileAsync(c, b, h, realErrorMask, a); + }, + [h = handle](IFSWrapper *layer) -> FSStatus { + return layer->FSCloseFileWrapper(h); + }, + [c = client, b = block, h = handle, a = asyncData, filename = __FILENAME__, func = __FUNCTION__, line = __LINE__](IFSWrapper *layer, FSStatus res) -> FSStatus { + if (layer->isValidFileHandle(h)) { + layer->deleteFileHandle(h); + } else { + DEBUG_FUNCTION_LINE_ERR_LAMBDA(filename, func, line, "Expected to delete fileHandle by handle %08X but it was not found", h); + } + DEBUG_FUNCTION_LINE_VERBOSE_EX(filename, func, line, "Async result %d", res); + return send_result_async(c, b, a, res); + }); +} + +DECL_FUNCTION(FSStatus, FSGetStat, FSClient *client, FSCmdBlock *block, char *path, FSStat *stats, FSErrorFlag errorMask) { + DEBUG_FUNCTION_LINE_VERBOSE("%s", path); + if (isForceRealFunction(errorMask)) { + return real_FSGetStat(client, block, path, stats, errorMask); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, p = path, s = stats](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSGetStat(c, b, p, s, realErrorMask); + }, + [p = getFullPathForClient(client, path), s = stats](IFSWrapper *layer) -> FSStatus { + return layer->FSGetStatWrapper(p.c_str(), s); + }, + SYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSGetStatAsync, FSClient *client, FSCmdBlock *block, char *path, FSStat *stats, FSErrorFlag errorMask, FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE("%s", path); + if (isForceRealFunction(errorMask)) { + return real_FSGetStatAsync(client, block, path, stats, getRealErrorFlag(errorMask), asyncData); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, p = path, s = stats, a = asyncData](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSGetStatAsync(c, b, p, s, realErrorMask, a); + }, + [p = getFullPathForClient(client, path), s = stats](IFSWrapper *layer) -> FSStatus { + return layer->FSGetStatWrapper(p.c_str(), s); + }, + ASYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSGetStatFile, FSClient *client, FSCmdBlock *block, FSFileHandle handle, FSStat *stats, FSErrorFlag errorMask) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSGetStatFile(client, block, handle, stats, getRealErrorFlag(errorMask)); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, s = stats](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSGetStatFile(c, b, h, s, realErrorMask); + }, + [h = handle, s = stats](IFSWrapper *layer) -> FSStatus { + return layer->FSGetStatFileWrapper(h, s); + }, + SYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSGetStatFileAsync, FSClient *client, FSCmdBlock *block, FSFileHandle handle, FSStat *stats, FSErrorFlag errorMask, FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSGetStatFileAsync(client, block, handle, stats, getRealErrorFlag(errorMask), asyncData); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, s = stats, a = asyncData](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSGetStatFileAsync(c, b, h, s, realErrorMask, a); + }, + [h = handle, s = stats](IFSWrapper *layer) -> FSStatus { + return layer->FSGetStatFileWrapper(h, s); + }, + ASYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSReadFile, FSClient *client, FSCmdBlock *block, void *buffer, uint32_t size, uint32_t count, FSFileHandle handle, uint32_t unk1, FSErrorFlag errorMask) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSReadFile(client, block, buffer, size, count, handle, unk1, errorMask); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, s = size, co = count, bu = buffer, u = unk1](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSReadFile(c, b, bu, s, co, h, u, realErrorMask); + }, + [b = buffer, s = size, c = count, h = handle, u = unk1](IFSWrapper *layer) -> FSStatus { + return layer->FSReadFileWrapper(b, s, c, h, u); + }, + SYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSReadFileAsync, FSClient *client, FSCmdBlock *block, void *buffer, uint32_t size, uint32_t count, FSFileHandle handle, uint32_t unk1, FSErrorFlag errorMask, + FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSReadFileAsync(client, block, buffer, size, count, handle, unk1, getRealErrorFlag(errorMask), asyncData); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, s = size, co = count, bu = buffer, u = unk1, a = asyncData](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSReadFileAsync(c, b, bu, s, co, h, u, realErrorMask, a); + }, + [b = buffer, s = size, c = count, h = handle, u = unk1](IFSWrapper *layer) -> FSStatus { + return layer->FSReadFileWrapper(b, s, c, h, u); + }, + ASYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSReadFileWithPos, FSClient *client, FSCmdBlock *block, void *buffer, uint32_t size, uint32_t count, uint32_t pos, FSFileHandle handle, uint32_t unk1, FSErrorFlag errorMask) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSReadFileWithPos(client, block, buffer, size, count, pos, handle, unk1, errorMask); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, s = size, co = count, p = pos, bu = buffer, u = unk1](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSReadFileWithPos(c, b, bu, s, co, p, h, u, realErrorMask); + }, + [b = buffer, s = size, c = count, p = pos, h = handle, u = unk1](IFSWrapper *layer) -> FSStatus { + return layer->FSReadFileWithPosWrapper(b, s, c, p, h, u); + }, + SYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSReadFileWithPosAsync, FSClient *client, FSCmdBlock *block, void *buffer, uint32_t size, uint32_t count, uint32_t pos, FSFileHandle handle, int32_t unk1, FSErrorFlag errorMask, + FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSReadFileWithPosAsync(client, block, buffer, size, count, pos, handle, unk1, getRealErrorFlag(errorMask), asyncData); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, s = size, co = count, p = pos, bu = buffer, u = unk1, a = asyncData](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSReadFileWithPosAsync(c, b, bu, s, co, p, h, u, realErrorMask, a); + }, + [b = buffer, s = size, c = count, p = pos, h = handle, u = unk1](IFSWrapper *layer) -> FSStatus { + return layer->FSReadFileWithPosWrapper(b, s, c, p, h, u); + }, + ASYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSSetPosFile, FSClient *client, FSCmdBlock *block, FSFileHandle handle, uint32_t pos, FSErrorFlag errorMask) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSSetPosFile(client, block, handle, pos, errorMask); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, p = pos](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSSetPosFile(c, b, h, p, realErrorMask); + }, + [h = handle, p = pos](IFSWrapper *layer) -> FSStatus { + return layer->FSSetPosFileWrapper(h, p); + }, + SYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSSetPosFileAsync, FSClient *client, FSCmdBlock *block, FSFileHandle handle, uint32_t pos, FSErrorFlag errorMask, FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSSetPosFileAsync(client, block, handle, pos, getRealErrorFlag(errorMask), asyncData); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, p = pos, a = asyncData](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSSetPosFileAsync(c, b, h, p, realErrorMask, a); + }, + [h = handle, p = pos](IFSWrapper *layer) -> FSStatus { + return layer->FSSetPosFileWrapper(h, p); + }, + ASYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSGetPosFile, FSClient *client, FSCmdBlock *block, FSFileHandle handle, uint32_t *pos, FSErrorFlag errorMask) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSGetPosFile(client, block, handle, pos, errorMask); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, p = pos](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSGetPosFile(c, b, h, p, realErrorMask); + }, + [h = handle, p = pos](IFSWrapper *layer) -> FSStatus { + return layer->FSGetPosFileWrapper(h, p); + }, + SYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSGetPosFileAsync, FSClient *client, FSCmdBlock *block, FSFileHandle handle, uint32_t *pos, FSErrorFlag errorMask, FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSGetPosFileAsync(client, block, handle, pos, getRealErrorFlag(errorMask), asyncData); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, p = pos, a = asyncData](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSGetPosFileAsync(c, b, h, p, realErrorMask, a); + }, + [h = handle, p = pos](IFSWrapper *layer) -> FSStatus { + return layer->FSGetPosFileWrapper(h, p); + }, + ASYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSIsEof, FSClient *client, FSCmdBlock *block, FSFileHandle handle, FSErrorFlag errorMask) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSIsEof(client, block, handle, errorMask); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSIsEof(c, b, h, realErrorMask); + }, + [h = handle](IFSWrapper *layer) -> FSStatus { + return layer->FSIsEofWrapper(h); + }, + SYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSIsEofAsync, FSClient *client, FSCmdBlock *block, FSFileHandle handle, FSErrorFlag errorMask, FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSIsEofAsync(client, block, handle, getRealErrorFlag(errorMask), asyncData); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, a = asyncData](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSIsEofAsync(c, b, h, realErrorMask, a); + }, + [h = handle](IFSWrapper *layer) -> FSStatus { + return layer->FSIsEofWrapper(h); + }, + ASYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSWriteFile, FSClient *client, FSCmdBlock *block, uint8_t *buffer, uint32_t size, uint32_t count, FSFileHandle handle, uint32_t unk1, FSErrorFlag errorMask) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSWriteFile(client, block, buffer, size, count, handle, unk1, errorMask); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, bu = buffer, s = size, co = count, h = handle, u = unk1](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSWriteFile(c, b, bu, s, co, h, u, realErrorMask); + }, + [b = buffer, s = size, c = count, h = handle, u = unk1](IFSWrapper *layer) -> FSStatus { + return layer->FSWriteFileWrapper(b, s, c, h, u); + }, + SYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSWriteFileAsync, FSClient *client, FSCmdBlock *block, uint8_t *buffer, uint32_t size, uint32_t count, FSFileHandle handle, uint32_t unk1, FSErrorFlag errorMask, + FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSWriteFileAsync(client, block, buffer, size, count, handle, unk1, getRealErrorFlag(errorMask), asyncData); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, bu = buffer, s = size, co = count, h = handle, u = unk1, a = asyncData](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSWriteFileAsync(c, b, bu, s, co, h, u, realErrorMask, a); + }, + [b = buffer, s = size, c = count, h = handle, u = unk1](IFSWrapper *layer) -> FSStatus { + return layer->FSWriteFileWrapper(b, s, c, h, u); + }, + ASYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSTruncateFile, FSClient *client, FSCmdBlock *block, FSFileHandle handle, FSErrorFlag errorMask) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSTruncateFile(client, block, handle, errorMask); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSTruncateFile(c, b, h, realErrorMask); + }, + [h = handle](IFSWrapper *layer) -> FSStatus { + return layer->FSTruncateFileWrapper(h); + }, + SYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSTruncateFileAsync, FSClient *client, FSCmdBlock *block, FSFileHandle handle, FSErrorFlag errorMask, FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSTruncateFileAsync(client, block, handle, getRealErrorFlag(errorMask), asyncData); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, a = asyncData](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSTruncateFileAsync(c, b, h, realErrorMask, a); + }, + [h = handle](IFSWrapper *layer) -> FSStatus { + return layer->FSTruncateFileWrapper(h); + }, + ASYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSRemove, FSClient *client, FSCmdBlock *block, char *path, FSErrorFlag errorMask) { + DEBUG_FUNCTION_LINE_VERBOSE("%s", path); + if (isForceRealFunction(errorMask)) { + return real_FSRemove(client, block, path, errorMask); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, p = path](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSRemove(c, b, p, realErrorMask); + }, + [p = getFullPathForClient(client, path)](IFSWrapper *layer) -> FSStatus { + return layer->FSRemoveWrapper(p.c_str()); + }, + SYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSRemoveAsync, FSClient *client, FSCmdBlock *block, char *path, [[maybe_unused]] FSErrorFlag errorMask, FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE("%s", path); + if (isForceRealFunction(errorMask)) { + return real_FSRemoveAsync(client, block, path, getRealErrorFlag(errorMask), asyncData); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, p = path, a = asyncData](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSRemoveAsync(c, b, p, realErrorMask, a); + }, + [p = getFullPathForClient(client, path)](IFSWrapper *layer) -> FSStatus { + return layer->FSRemoveWrapper(p.c_str()); + }, + ASYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSRename, FSClient *client, FSCmdBlock *block, char *oldPath, char *newPath, FSErrorFlag errorMask) { + DEBUG_FUNCTION_LINE_VERBOSE("%s -> %s", oldPath, newPath); + if (isForceRealFunction(errorMask)) { + return real_FSRename(client, block, oldPath, newPath, getRealErrorFlag(errorMask)); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, oP = oldPath, nP = newPath](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSRename(c, b, oP, nP, realErrorMask); + }, + [oP = getFullPathForClient(client, oldPath), nP = getFullPathForClient(client, newPath)](IFSWrapper *layer) -> FSStatus { + return layer->FSRenameWrapper(oP.c_str(), nP.c_str()); + }, + SYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSRenameAsync, + FSClient *client, + FSCmdBlock *block, + char *oldPath, + char *newPath, + FSErrorFlag errorMask, + FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE("%s -> %s", oldPath, newPath); + if (isForceRealFunction(errorMask)) { + return real_FSRenameAsync(client, block, oldPath, newPath, getRealErrorFlag(errorMask), asyncData); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, oP = oldPath, nP = newPath, a = asyncData](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSRenameAsync(c, b, oP, nP, realErrorMask, a); + }, + [oP = getFullPathForClient(client, oldPath), nP = getFullPathForClient(client, newPath)](IFSWrapper *layer) -> FSStatus { + return layer->FSRenameWrapper(oP.c_str(), nP.c_str()); + }, + ASYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSFlushFile, FSClient *client, FSCmdBlock *block, [[maybe_unused]] FSFileHandle handle, [[maybe_unused]] FSErrorFlag errorMask) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSFlushFile(client, block, handle, errorMask); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSFlushFile(c, b, h, realErrorMask); + }, + [h = handle](IFSWrapper *layer) -> FSStatus { + return layer->FSFlushFileWrapper(h); + }, + SYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSFlushFileAsync, FSClient *client, FSCmdBlock *block, [[maybe_unused]] FSFileHandle handle, [[maybe_unused]] FSErrorFlag errorMask, FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_VERBOSE(""); + if (isForceRealFunction(errorMask)) { + return real_FSFlushFileAsync(client, block, handle, getRealErrorFlag(errorMask), asyncData); + } + return doForLayer( + client, + errorMask, + [c = client, b = block, h = handle, a = asyncData](FSErrorFlag realErrorMask) -> FSStatus { + return real_FSFlushFileAsync(c, b, h, realErrorMask, a); + }, + [h = handle](IFSWrapper *layer) -> FSStatus { + return layer->FSFlushFileWrapper(h); + }, + ASYNC_RESULT_HANDLER); +} + +DECL_FUNCTION(FSStatus, FSChangeModeAsync, + FSClient *client, + FSCmdBlock *block, + char *path, + [[maybe_unused]] FSMode mode, + [[maybe_unused]] FSMode modeMask, + [[maybe_unused]] FSErrorFlag errorMask, + FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_ERR("NOT IMPLEMENTED FSChangeModeAsync %s mode: %08X", path, mode); + return real_FSChangeModeAsync(client, block, path, mode, modeMask, errorMask, asyncData); +} + +DECL_FUNCTION(FSStatus, FSGetFreeSpaceSizeAsync, FSClient *client, FSCmdBlock *block, char *path, [[maybe_unused]] uint64_t *outSize, [[maybe_unused]] FSErrorFlag errorMask, FSAsyncData *asyncData) { + DEBUG_FUNCTION_LINE_ERR("NOT IMPLEMENTED FSGetFreeSpaceSizeAsync %s", path); + return real_FSGetFreeSpaceSizeAsync(client, block, path, outSize, errorMask, asyncData); +} + +DECL_FUNCTION(void, OSSetExceptionCallbackEx, void) { + return; +} + +function_replacement_data_t fs_file_function_replacements[] = { + REPLACE_FUNCTION(FSOpenFile, LIBRARY_COREINIT, FSOpenFile), + REPLACE_FUNCTION(FSOpenFileAsync, LIBRARY_COREINIT, FSOpenFileAsync), + + REPLACE_FUNCTION(FSCloseFile, LIBRARY_COREINIT, FSCloseFile), + REPLACE_FUNCTION(FSCloseFileAsync, LIBRARY_COREINIT, FSCloseFileAsync), + + REPLACE_FUNCTION(FSGetStat, LIBRARY_COREINIT, FSGetStat), + REPLACE_FUNCTION(FSGetStatAsync, LIBRARY_COREINIT, FSGetStatAsync), + + REPLACE_FUNCTION(FSGetStatFile, LIBRARY_COREINIT, FSGetStatFile), + REPLACE_FUNCTION(FSGetStatFileAsync, LIBRARY_COREINIT, FSGetStatFileAsync), + + REPLACE_FUNCTION(FSReadFile, LIBRARY_COREINIT, FSReadFile), + REPLACE_FUNCTION(FSReadFileAsync, LIBRARY_COREINIT, FSReadFileAsync), + + REPLACE_FUNCTION(FSReadFileWithPos, LIBRARY_COREINIT, FSReadFileWithPos), + REPLACE_FUNCTION(FSReadFileWithPosAsync, LIBRARY_COREINIT, FSReadFileWithPosAsync), + + REPLACE_FUNCTION(FSSetPosFile, LIBRARY_COREINIT, FSSetPosFile), + REPLACE_FUNCTION(FSSetPosFileAsync, LIBRARY_COREINIT, FSSetPosFileAsync), + + REPLACE_FUNCTION(FSGetPosFile, LIBRARY_COREINIT, FSGetPosFile), + REPLACE_FUNCTION(FSGetPosFileAsync, LIBRARY_COREINIT, FSGetPosFileAsync), + + REPLACE_FUNCTION(FSIsEof, LIBRARY_COREINIT, FSIsEof), + REPLACE_FUNCTION(FSIsEofAsync, LIBRARY_COREINIT, FSIsEofAsync), + + REPLACE_FUNCTION(FSWriteFile, LIBRARY_COREINIT, FSWriteFile), + REPLACE_FUNCTION(FSWriteFileAsync, LIBRARY_COREINIT, FSWriteFileAsync), + + REPLACE_FUNCTION(FSTruncateFile, LIBRARY_COREINIT, FSTruncateFile), + REPLACE_FUNCTION(FSTruncateFileAsync, LIBRARY_COREINIT, FSTruncateFileAsync), + + REPLACE_FUNCTION(FSRemove, LIBRARY_COREINIT, FSRemove), + REPLACE_FUNCTION(FSRemoveAsync, LIBRARY_COREINIT, FSRemoveAsync), + + REPLACE_FUNCTION(FSRename, LIBRARY_COREINIT, FSRename), + REPLACE_FUNCTION(FSRenameAsync, LIBRARY_COREINIT, FSRenameAsync), + + REPLACE_FUNCTION(FSFlushFile, LIBRARY_COREINIT, FSFlushFile), + REPLACE_FUNCTION(FSFlushFileAsync, LIBRARY_COREINIT, FSFlushFileAsync), + + REPLACE_FUNCTION(FSChangeModeAsync, LIBRARY_COREINIT, FSChangeModeAsync), + REPLACE_FUNCTION(OSSetExceptionCallbackEx, LIBRARY_COREINIT, OSSetExceptionCallbackEx), + + //REPLACE_FUNCTION(FSGetFreeSpaceSizeAsync, LIBRARY_COREINIT, FSGetFreeSpaceSizeAsync), + //REPLACE_FUNCTION_VIA_ADDRESS(FSGetFreeSpaceSizeAsync, LIBRARY_COREINIT, 0x0A000000 + 0x0256079c), +}; + +uint32_t fs_file_function_replacements_size = sizeof(fs_file_function_replacements) / sizeof(function_replacement_data_t); \ No newline at end of file diff --git a/src/FSFileReplacements.h b/src/FSFileReplacements.h new file mode 100644 index 0000000..5727633 --- /dev/null +++ b/src/FSFileReplacements.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern function_replacement_data_t fs_file_function_replacements[]; +extern uint32_t fs_file_function_replacements_size; + +extern function_replacement_data_t test_function_replacements[]; +extern uint32_t test_function_replacements_size; + +#ifdef __cplusplus +} +#endif diff --git a/src/FSWrapper.cpp b/src/FSWrapper.cpp new file mode 100644 index 0000000..6dfcd30 --- /dev/null +++ b/src/FSWrapper.cpp @@ -0,0 +1,714 @@ +#include "FSWrapper.h" +#include "FileUtils.h" +#include "utils/StringTools.h" +#include "utils/logger.h" +#include +#include +#include +#include +#include +#include +#include +#include + +FSStatus FSWrapper::FSOpenDirWrapper(const char *path, FSDirectoryHandle *handle) { + if (!IsPathToReplace(path)) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + if (handle == nullptr) { + DEBUG_FUNCTION_LINE_ERR("[%s] handle was nullptr", getName().c_str()); + return FS_STATUS_FATAL_ERROR; + } + + FSStatus result = FS_STATUS_OK; + + auto *dirHandle = getNewDirHandle(); + if (dirHandle != nullptr) { + DIR *dir; + auto newPath = GetNewPath(path); + + if ((dir = opendir(newPath.c_str()))) { + + dirHandle->dir = dir; + dirHandle->handle = (((uint32_t) dirHandle) & 0x0FFFFFFF) | 0x30000000; + *handle = dirHandle->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(); + } + } else { + delete dirHandle; + result = FS_STATUS_NOT_FOUND; + } + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] Failed to alloc dir handle", getName().c_str()); + result = FS_STATUS_MAX; + } + return result; +} + +FSStatus FSWrapper::FSReadDirWrapper(FSDirectoryHandle handle, FSDirectoryEntry *entry) { + if (!isValidDirHandle(handle)) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + auto *dirHandle = getDirFromHandle(handle); + + DIR *dir = dirHandle->dir; + + FSStatus result = FS_STATUS_END; + DEBUG_FUNCTION_LINE_VERBOSE("[%s] readdir %08X (handle %08X)", getName().c_str(), dir, handle); + do { + struct dirent *entry_ = readdir(dir); + + if (entry_) { + if (SkipDeletedFilesInReadDir() && std::string_view(entry_->d_name).starts_with(deletePrefix)) { + DEBUG_FUNCTION_LINE_ERR("Skip file file name %s because of the prefix", entry_->d_name); + continue; + } + entry->name[0] = '\0'; + strncat(entry->name, entry_->d_name, sizeof(entry->name) - 1); + entry->info.mode = (FSMode) FS_MODE_READ_OWNER; + if (entry_->d_type == DT_DIR) { + entry->info.flags = (FSStatFlags) ((uint32_t) FS_STAT_DIRECTORY); + entry->info.size = 0; + } else { + entry->info.flags = (FSStatFlags) 0; + if (strcmp(entry_->d_name, ".") == 0 || strcmp(entry_->d_name, "..") == 0) { + entry->info.size = 0; + } else { + struct stat sb {}; + auto strLen = strlen(dirHandle->path) + 1 + strlen(entry_->d_name) + 1; + char path[strLen]; + snprintf(path, sizeof(path), "%s/%s", dirHandle->path, entry_->d_name); + if (stat(path, &sb) >= 0) { + entry->info.size = sb.st_size; + entry->info.flags = (FSStatFlags) 0; + entry->info.owner = sb.st_uid; + entry->info.group = sb.st_gid; + } + } + } + result = FS_STATUS_OK; + } + break; + } while (true); + return result; +} + +FSStatus FSWrapper::FSCloseDirWrapper(FSDirectoryHandle handle) { + if (!isValidDirHandle(handle)) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + auto *dirHandle = getDirFromHandle(handle); + + DIR *dir = dirHandle->dir; + + FSStatus result = FS_STATUS_OK; + DEBUG_FUNCTION_LINE_VERBOSE("[%s] closedir %08X (handle %08X)", getName().c_str(), dir, handle); + if (closedir(dir) != 0) { + DEBUG_FUNCTION_LINE_ERR("[%s] Failed to close dir %08X (handle %08X)", getName().c_str(), dir, handle); + result = FS_STATUS_MEDIA_ERROR; + } + dirHandle->dir = nullptr; + + return result; +} + +FSStatus FSWrapper::FSRewindDirWrapper(FSDirectoryHandle handle) { + if (!isValidDirHandle(handle)) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + auto *dirHandle = getDirFromHandle(handle); + + DIR *dir = dirHandle->dir; + + DEBUG_FUNCTION_LINE_VERBOSE("[%s] rewindir %08X (handle %08X)", getName().c_str(), dir, handle); + rewinddir(dir); + + return FS_STATUS_OK; +} + +FSStatus FSWrapper::FSMakeDirWrapper(const char *path) { + if (!IsPathToReplace(path)) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + if (!pIsWriteable) { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Tried to create dir %s but layer is not writeable", getName().c_str(), path); + return FS_STATUS_ACCESS_ERROR; + } + DEBUG_FUNCTION_LINE_ERR("NOT IMPLEMENTED MAKE DIR"); + return FS_STATUS_FATAL_ERROR; +} + +FSStatus FSWrapper::FSOpenFileWrapper(const char *path, const char *mode, FSFileHandle *handle) { + if (!IsPathToReplace(path)) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + if (path == nullptr) { + DEBUG_FUNCTION_LINE_ERR("[%s] path was nullptr", getName().c_str()); + return FS_STATUS_FATAL_ERROR; + } + + if (mode == nullptr || handle == nullptr) { + DEBUG_FUNCTION_LINE_ERR("[%s] mode or handle was nullptr", getName().c_str()); + return FS_STATUS_FATAL_ERROR; + } + + auto newPath = GetNewPath(path); + + if (pCheckIfDeleted && CheckFileShouldBeIgnored(newPath)) { + return static_cast((FS_STATUS_NOT_FOUND & 0x0000FFFF) | FS_STATUS_FORCE_NO_FALLBACK); + } + + auto result = FS_STATUS_OK; + int _mode; + // Map flags to open modes + if (!IsFileModeAllowed(mode)) { + OSReport("## WARN ## [%s] Given mode is not allowed %s", getName().c_str(), mode); + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Given mode is not allowed %s", getName().c_str(), mode); + return FS_STATUS_ACCESS_ERROR; + } + + if (strcmp(mode, "r") == 0 || strcmp(mode, "rb") == 0) { + _mode = 0x000; + } else if (strcmp(mode, "r+") == 0) { + _mode = 0x002; + } else if (strcmp(mode, "w") == 0) { + _mode = 0x601; + } else if (strcmp(mode, "w+") == 0) { + _mode = 0x602; + } else if (strcmp(mode, "a") == 0) { + _mode = 0x209; + } else if (strcmp(mode, "a+") == 0) { + _mode = 0x20A; + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] mode \"%s\" was allowed but is unsupported", getName().c_str(), mode); + return FS_STATUS_ACCESS_ERROR; + } + + + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Open %s (as %s) mode %s,", getName().c_str(), path, newPath.c_str(), mode); + int32_t fd = open(newPath.c_str(), _mode); + if (fd >= 0) { + auto *fileHandle = getNewFileHandle(); + if (fileHandle) { + std::lock_guard lock(openFilesMutex); + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Opened %s (as %s) mode %s, fd %d (%08X)", getName().c_str(), path, newPath.c_str(), mode, fd, handle); + + fileHandle->handle = (((uint32_t) fileHandle) & 0x0FFFFFFF) | 0x30000000; + *handle = fileHandle->handle; + fileHandle->fd = fd; + + openFiles.push_back(fileHandle); + + OSMemoryBarrier(); + } else { + close(fd); + DEBUG_FUNCTION_LINE_ERR("[%s] Failed to alloc new fileHandle", getName().c_str()); + result = FS_STATUS_MAX; + } + } else { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] File not found %s (%s)", getName().c_str(), path, newPath.c_str()); + result = FS_STATUS_NOT_FOUND; + } + + return result; +} + +FSStatus FSWrapper::FSCloseFileWrapper(FSFileHandle handle) { + if (!isValidFileHandle(handle)) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + auto *fileHandle = getFileFromHandle(handle); + + int real_fd = fileHandle->fd; + + FSStatus result = FS_STATUS_OK; + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Close %d (handle %08X)", getName().c_str(), real_fd, handle); + if (close(real_fd) != 0) { + DEBUG_FUNCTION_LINE_ERR("[%s] Failed to close %d (handle %08X) ", getName().c_str(), real_fd, handle); + result = FS_STATUS_MEDIA_ERROR; + } + fileHandle->fd = -1; + return result; +} + +bool FSWrapper::CheckFileShouldBeIgnored(std::string &path) { + auto asPath = std::filesystem::path(path); + + if (std::string(asPath.filename().c_str()).starts_with(deletePrefix)) { + DEBUG_FUNCTION_LINE_VERBOSE("Ignore %s, filename starts with %s", path.c_str(), deletePrefix.c_str()); + return true; + } + + auto newDelPath = asPath.replace_filename(deletePrefix + asPath.filename().c_str()); + struct stat buf {}; + if (stat(newDelPath.c_str(), &buf) == 0) { + DEBUG_FUNCTION_LINE_VERBOSE("Ignore %s, file %s exists", path.c_str(), newDelPath.c_str()); + return true; + } + return false; +} + +FSStatus FSWrapper::FSGetStatWrapper(const char *path, FSStat *stats) { + if (path == nullptr || stats == nullptr) { + DEBUG_FUNCTION_LINE_ERR("[%s] path was or stats nullptr", getName().c_str()); + return FS_STATUS_FATAL_ERROR; + } + + if (!IsPathToReplace(path)) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + auto newPath = GetNewPath(path); + + struct stat path_stat {}; + + if (pCheckIfDeleted && CheckFileShouldBeIgnored(newPath)) { + return static_cast((FS_STATUS_NOT_FOUND & 0x0000FFFF) | FS_STATUS_FORCE_NO_FALLBACK); + } + + FSStatus result = FS_STATUS_OK; + + + DEBUG_FUNCTION_LINE_VERBOSE("[%s] stat of %s (%s)", getName().c_str(), path, newPath.c_str()); + if (stat(newPath.c_str(), &path_stat) < 0) { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Path %s (%s) not found ", getName().c_str(), path, newPath.c_str()); + result = FS_STATUS_NOT_FOUND; + } else { + memset(&(stats->flags), 0, sizeof(stats->flags)); + if (S_ISDIR(path_stat.st_mode)) { + stats->flags = (FSStatFlags) ((uint32_t) FS_STAT_DIRECTORY); + } else { + stats->size = path_stat.st_size; + stats->mode = (FSMode) FS_MODE_READ_OWNER; + stats->flags = (FSStatFlags) 0; + stats->owner = path_stat.st_uid; + stats->group = path_stat.st_gid; + } + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Stats file for %s (%s), size %016llX", getName().c_str(), path, newPath.c_str(), stats->size); + } + return result; +} + +FSStatus FSWrapper::FSGetStatFileWrapper(FSFileHandle handle, FSStat *stats) { + if (!isValidFileHandle(handle)) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + auto *fileHandle = getFileFromHandle(handle); + + int real_fd = fileHandle->fd; + + struct stat path_stat {}; + + FSStatus result = FS_STATUS_OK; + DEBUG_FUNCTION_LINE_VERBOSE("[%s] fstat of fd %d (FSFileHandle %08X)", getName().c_str(), real_fd, handle); + if (fstat(real_fd, &path_stat) < 0) { + DEBUG_FUNCTION_LINE_ERR("[%s] fstat of fd %d (FSFileHandle %08X) failed", getName().c_str(), real_fd, handle); + result = FS_STATUS_MEDIA_ERROR; + } else { + memset(&(stats->flags), 0, sizeof(stats->flags)); + + stats->size = path_stat.st_size; + stats->mode = (FSMode) FS_MODE_READ_OWNER; + stats->flags = (FSStatFlags) 0; + stats->owner = path_stat.st_uid; + stats->group = path_stat.st_gid; + } + + return result; +} + +FSStatus FSWrapper::FSReadFileWrapper(void *buffer, uint32_t size, uint32_t count, FSFileHandle handle, [[maybe_unused]] uint32_t unk1) { + if (!isValidFileHandle(handle)) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + if (size * count == 0) { + return FS_STATUS_OK; + } + + if (buffer == nullptr) { + DEBUG_FUNCTION_LINE_ERR("[%s] buffer is null but size * count is not 0 (It's: %d)", getName().c_str(), size * count); + return FS_STATUS_FATAL_ERROR; + } + + auto *fileHandle = getFileFromHandle(handle); + int real_fd = fileHandle->fd; + + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Read %u bytes of fd %08X (FSFileHandle %08X) to buffer %08X", getName().c_str(), size * count, real_fd, handle, buffer); + int32_t read = readIntoBuffer(real_fd, buffer, size, count); + + FSStatus result; + if (read < 0) { + DEBUG_FUNCTION_LINE_ERR("[%s] read %u bytes of fd %d (FSFileHandle %08X) failed", getName().c_str(), size * count, real_fd, handle); + result = FS_STATUS_MEDIA_ERROR; + } else { + result = static_cast((uint32_t) (read & 0xFFFFFFFF) / size); + } + + return result; +} + +FSStatus FSWrapper::FSReadFileWithPosWrapper(void *buffer, uint32_t size, uint32_t count, uint32_t pos, FSFileHandle handle, int32_t unk1) { + if (!isValidFileHandle(handle)) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Read from with position.", getName().c_str()); + FSStatus result; + if ((result = this->FSSetPosFileWrapper(handle, pos)) != FS_STATUS_OK) { + return result; + } + + result = this->FSReadFileWrapper(buffer, size, count, handle, unk1); + + return result; +} + +FSStatus FSWrapper::FSSetPosFileWrapper(FSFileHandle handle, uint32_t pos) { + if (!isValidFileHandle(handle)) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + auto *fileHandle = getFileFromHandle(handle); + + FSStatus result = FS_STATUS_OK; + + int real_fd = fileHandle->fd; + + DEBUG_FUNCTION_LINE_VERBOSE("[%s] lseek fd %d (FSFileHandle %08X) to get current position for truncation", getName().c_str(), real_fd, handle); + if (lseek(real_fd, (off_t) pos, SEEK_SET) != pos) { + DEBUG_FUNCTION_LINE_ERR("[%s] lseek fd %d (FSFileHandle %08X) to position %u failed", getName().c_str(), real_fd, handle); + result = FS_STATUS_MEDIA_ERROR; + } else { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] pos set to %u for fd %d (FSFileHandle %08X)", getName().c_str(), pos, real_fd, handle); + } + + return result; +} + +FSStatus FSWrapper::FSGetPosFileWrapper(FSFileHandle handle, uint32_t *pos) { + if (!isValidFileHandle(handle)) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + auto *fileHandle = getFileFromHandle(handle); + + FSStatus result = FS_STATUS_OK; + + int real_fd = fileHandle->fd; + + DEBUG_FUNCTION_LINE_VERBOSE("[%s] lseek fd %08X (FSFileHandle %08X) to get current position for truncation", getName().c_str(), real_fd, handle); + off_t currentPos = lseek(real_fd, (off_t) 0, SEEK_CUR); + if (currentPos == -1) { + DEBUG_FUNCTION_LINE_ERR("[%s] Failed to get current position (res: %lld) of fd (handle %08X) to check EoF", getName().c_str(), currentPos, real_fd, handle); + result = FS_STATUS_MEDIA_ERROR; + } else { + *pos = currentPos; + } + return result; +} + +FSStatus FSWrapper::FSIsEofWrapper(FSFileHandle handle) { + if (!isValidFileHandle(handle)) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + auto *fileHandle = getFileFromHandle(handle); + + FSStatus result; + + int real_fd = fileHandle->fd; + + DEBUG_FUNCTION_LINE_VERBOSE("[%s] lseek fd %08X (FSFileHandle %08X) to get current position for truncation", getName().c_str(), real_fd, handle); + off_t currentPos = lseek(real_fd, (off_t) 0, SEEK_CUR); + DEBUG_FUNCTION_LINE_VERBOSE("[%s] lseek fd %08X (FSFileHandle %08X) to get end position for truncation", getName().c_str(), real_fd, handle); + off_t endPos = lseek(real_fd, (off_t) 0, SEEK_END); + + if (currentPos == -1 || endPos == -1) { + DEBUG_FUNCTION_LINE_ERR("[%s] Failed to get current position (res: %lld) or endPos (res: %lld) of fd (handle %08X) to check EoF", getName().c_str(), currentPos, endPos, real_fd, handle); + result = FS_STATUS_MEDIA_ERROR; + } else if (currentPos == endPos) { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] FSIsEof END for %d\n", getName().c_str(), real_fd); + result = FS_STATUS_END; + } else { + lseek(real_fd, currentPos, SEEK_CUR); + DEBUG_FUNCTION_LINE_VERBOSE("[%s] FSIsEof OK for %d\n", getName().c_str(), real_fd); + result = FS_STATUS_OK; + } + + return result; +} + +FSStatus FSWrapper::FSTruncateFileWrapper(FSFileHandle handle) { + if (!isValidFileHandle(handle)) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + if (!pIsWriteable) { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Tried to truncate fd %d (handle %08X) but layer is not writeable", getName().c_str(), getFileFromHandle(handle)->fd, handle); + return FS_STATUS_ACCESS_ERROR; + } + + auto *fileHandle = getFileFromHandle(handle); + + FSStatus result = FS_STATUS_OK; + + int real_fd = fileHandle->fd; + + DEBUG_FUNCTION_LINE_VERBOSE("[%s] lseek fd %08X (FSFileHandle %08X) to get current position for truncation", getName().c_str(), real_fd, handle); + off_t currentPos = lseek(real_fd, (off_t) 0, SEEK_CUR); + if (currentPos == -1) { + DEBUG_FUNCTION_LINE_ERR("[%s] Failed to get current position of fd (handle %08X) to truncate file", getName().c_str(), real_fd, handle); + result = FS_STATUS_MEDIA_ERROR; + } else { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Truncate fd %08X (FSFileHandle %08X) to %lld bytes ", getName().c_str(), real_fd, handle, currentPos); + if (ftruncate(real_fd, currentPos) < 0) { + DEBUG_FUNCTION_LINE_ERR("[%s] ftruncate failed for fd %08X (FSFileHandle %08X) errno %d", getName().c_str(), real_fd, handle, errno); + result = FS_STATUS_MEDIA_ERROR; + } + } + + return result; +} + +FSStatus FSWrapper::FSWriteFileWrapper(uint8_t *buffer, uint32_t size, uint32_t count, FSFileHandle handle, [[maybe_unused]] uint32_t unk1) { + if (!isValidFileHandle(handle)) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + if (!pIsWriteable) { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Tried to write to fd %d (handle %08X) but layer is not writeable", getName().c_str(), getFileFromHandle(handle)->fd, handle); + return FS_STATUS_ACCESS_ERROR; + } + auto *fileHandle = getFileFromHandle(handle); + + FSStatus result; + + int real_fd = fileHandle->fd; + + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Write %u bytes to fd %08X (FSFileHandle %08X) from buffer %08X", getName().c_str(), count * size, real_fd, handle, buffer); + auto writeRes = writeFromBuffer(real_fd, buffer, size, count); + if (writeRes < 0) { + auto err = errno; + DEBUG_FUNCTION_LINE_ERR("[%s] Write failed %u bytes to fd %08X (FSFileHandle %08X) from buffer %08X errno %d", getName().c_str(), count * size, real_fd, handle, buffer, err); + if (err == EFBIG) { + result = FS_STATUS_FILE_TOO_BIG; + } else if (err == EACCES) { + result = FS_STATUS_ACCESS_ERROR; + } else { + result = FS_STATUS_MEDIA_ERROR; + } + } else { + result = static_cast((uint32_t) (writeRes & 0xFFFFFFFF) / size); + } + + return result; +} + +FSStatus FSWrapper::FSRemoveWrapper(const char *path) { + if (!IsPathToReplace(path)) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + if (!pIsWriteable) { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Tried to remove %s but layer is not writeable", getName().c_str(), path); + return FS_STATUS_ACCESS_ERROR; + } + auto newPath = GetNewPath(path); + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Remove %s (%s)", getName().c_str(), path, newPath.c_str()); + if (remove(newPath.c_str()) < 0) { + auto err = errno; + DEBUG_FUNCTION_LINE_ERR("[%s] rename failed %s (%s) errno %d", getName().c_str(), path, newPath.c_str(), err); + if (err == ENOTDIR) { + return FS_STATUS_NOT_DIR; + } else if (err == EACCES) { + return FS_STATUS_ACCESS_ERROR; + } else if (err == EISDIR) { + return FS_STATUS_NOT_FILE; + } else if (err == EPERM) { + return FS_STATUS_PERMISSION_ERROR; + } + return FS_STATUS_MEDIA_ERROR; + } + return FS_STATUS_OK; +} + +FSStatus FSWrapper::FSRenameWrapper(const char *oldPath, const char *newPath) { + if (!IsPathToReplace(oldPath) || !IsPathToReplace(newPath)) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + if (!pIsWriteable) { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Tried to rename %s to %s but layer is not writeable", getName().c_str(), oldPath, newPath); + return FS_STATUS_ACCESS_ERROR; + } + auto oldPathRedirect = GetNewPath(oldPath); + auto newPathRedirect = GetNewPath(newPath); + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Rename %s (%s) -> %s (%s)", getName().c_str(), oldPath, oldPathRedirect.c_str(), newPath, newPathRedirect.c_str()); + if (rename(oldPathRedirect.c_str(), newPathRedirect.c_str()) < 0) { + auto err = errno; + DEBUG_FUNCTION_LINE_ERR("[%s] Rename failed %s (%s) -> %s (%s). errno %d", getName().c_str(), oldPath, oldPathRedirect.c_str(), newPath, newPathRedirect.c_str(), err); + if (err == ENOTDIR) { + return FS_STATUS_NOT_DIR; + } else if (err == EACCES) { + return FS_STATUS_ACCESS_ERROR; + } else if (err == EISDIR) { + return FS_STATUS_NOT_FILE; + } else if (err == EPERM) { + return FS_STATUS_PERMISSION_ERROR; + } + return FS_STATUS_MEDIA_ERROR; + } + return FS_STATUS_OK; +} + +FSStatus FSWrapper::FSFlushFileWrapper(FSFileHandle handle) { + if (!isValidFileHandle(handle)) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + if (!pIsWriteable) { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Tried to fsync fd %d (handle %08X)) but layer is not writeable", getName().c_str(), getFileFromHandle(handle)->fd, handle); + return FS_STATUS_ACCESS_ERROR; + } + + auto *fileHandle = getFileFromHandle(handle); + int real_fd = fileHandle->fd; + + DEBUG_FUNCTION_LINE_VERBOSE("[%s] fsync fd %08X (FSFileHandle %08X)", real_fd, handle); + FSStatus result = FS_STATUS_OK; + if (fsync(real_fd) < 0) { + DEBUG_FUNCTION_LINE_ERR("[%s] fsync failed for fd %08X (FSFileHandle %08X)", getName().c_str(), real_fd, handle); + result = FS_STATUS_MEDIA_ERROR; + } + return result; +} + +bool FSWrapper::IsFileModeAllowed(const char *mode) { + if (strcmp(mode, "r") == 0 || strcmp(mode, "rb") == 0) { + return true; + } + + if (pIsWriteable && (strcmp(mode, "r+") == 0 || + strcmp(mode, "w") == 0 || + strcmp(mode, "w+") == 0 || + strcmp(mode, "a") == 0 || + strcmp(mode, "a+") == 0)) { + return true; + } + + return false; +} + +bool FSWrapper::IsPathToReplace(const std::string_view &path) { + return path.starts_with(pPathToReplace); +} + +std::string FSWrapper::GetNewPath(const std::string_view &path) { + auto subStr = path.substr(this->pPathToReplace.length()); + auto res = string_format("%s%.*s", this->pReplacePathWith.c_str(), int(subStr.length()), subStr.data()); + + std::replace(res.begin(), res.end(), '/', '/'); + + uint32_t length = res.size(); + + //! clear path of double slashes + for (uint32_t i = 1; i < length; ++i) { + if (res[i - 1] == '/' && res[i] == '/') { + res.erase(i, 1); + i--; + length--; + } + } + + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Redirect %.*s -> %s", getName().c_str(), int(path.length()), path.data(), res.c_str()); + return res; +} + +bool FSWrapper::isValidFileHandle(FSFileHandle handle) { + std::lock_guard lock(openFilesMutex); + return std::ranges::any_of(openFiles, [handle](FileInfo *val) { return val->handle == handle; }); +} + +bool FSWrapper::isValidDirHandle(FSDirectoryHandle handle) { + std::lock_guard lock(openDirsMutex); + return std::ranges::any_of(openDirs, [handle](DirInfo *val) { return val->handle == handle; }); +} + +FileInfo *FSWrapper::getNewFileHandle() { + return new (std::nothrow) FileInfo; +} + +DirInfo *FSWrapper::getNewDirHandle() { + return new (std::nothrow) DirInfo; +} + +FileInfo *FSWrapper::getFileFromHandle(FSFileHandle handle) { + std::lock_guard lock(openFilesMutex); + for (auto &file : openFiles) { + if (file->handle == handle) { + return file; + } + } + DEBUG_FUNCTION_LINE_ERR("[%s] FileInfo for handle %08X was not found. isValidFileHandle check missing?", getName().c_str(), handle); + OSFatal("Failed to find file handle"); + return nullptr; +} + +DirInfo *FSWrapper::getDirFromHandle(FSDirectoryHandle handle) { + std::lock_guard lock(openDirsMutex); + for (auto &dir : openDirs) { + if (dir->handle == handle) { + return dir; + } + } + DEBUG_FUNCTION_LINE_ERR("[%s] DirInfo for handle %08X was not found. isValidDirHandle check missing?", getName().c_str(), handle); + OSFatal("Failed to find dir handle"); + return nullptr; +} + +void FSWrapper::deleteDirHandle(FSDirectoryHandle handle) { + std::lock_guard lock(openDirsMutex); + bool found = false; + int count = 0; + DirInfo *info; + for (auto &val : openDirs) { + if ((FSDirectoryHandle) val->handle == handle) { + found = true; + info = val; + break; + } + count++; + } + if (found) { + openDirs.erase(openDirs.begin() + count); + delete info; + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] Delete failed because the handle %08X was not found", getName().c_str(), handle); + } +} + +void FSWrapper::deleteFileHandle(FSFileHandle handle) { + std::lock_guard lock(openFilesMutex); + bool found = false; + auto count = 0; + FileInfo *info; + for (auto &val : openFiles) { + if ((FSFileHandle) val->handle == handle) { + found = true; + info = val; + break; + } + count++; + } + if (found) { + openFiles.erase(openFiles.begin() + count); + delete info; + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] Delete failed because the handle %08X was not found", getName().c_str(), handle); + } +} + +bool FSWrapper::SkipDeletedFilesInReadDir() { + return true; +} diff --git a/src/FSWrapper.h b/src/FSWrapper.h new file mode 100644 index 0000000..8acdb83 --- /dev/null +++ b/src/FSWrapper.h @@ -0,0 +1,139 @@ +#pragma once +#include "DirInfo.h" +#include "FileInfo.h" +#include "IFSWrapper.h" +#include "utils/logger.h" +#include +#include +#include +#include + +class FSWrapper : public IFSWrapper { +public: + FSWrapper(const std::string &name, const std::string &pathToReplace, const std::string &replacePathWith, bool fallbackOnError, bool isWriteable) { + this->pName = name; + this->pPathToReplace = pathToReplace; + this->pReplacePathWith = replacePathWith; + this->pFallbackOnError = fallbackOnError; + this->pIsWriteable = isWriteable; + this->pCheckIfDeleted = fallbackOnError; + + std::replace(pPathToReplace.begin(), pPathToReplace.end(), '\\', '/'); + std::replace(pReplacePathWith.begin(), pReplacePathWith.end(), '\\', '/'); + } + ~FSWrapper() override { + if (!openFiles.empty()) { + DEBUG_FUNCTION_LINE_ERR("Clean leaked fileInfos"); + for (auto &file : openFiles) { + delete file; + } + openFiles.clear(); + } + if (!openDirs.empty()) { + DEBUG_FUNCTION_LINE_ERR("Clean leaked dirInfos"); + for (auto &dir : openDirs) { + delete dir; + } + openDirs.clear(); + } + } + + FSStatus FSOpenDirWrapper(const char *path, + FSDirectoryHandle *handle) override; + + + FSStatus FSReadDirWrapper(FSDirectoryHandle handle, + FSDirectoryEntry *entry) override; + + FSStatus FSCloseDirWrapper(FSDirectoryHandle handle) override; + + + FSStatus FSMakeDirWrapper(const char *path) override; + + + FSStatus FSRewindDirWrapper(FSDirectoryHandle handle) override; + + + FSStatus FSOpenFileWrapper(const char *path, + const char *mode, + FSFileHandle *handle) override; + + FSStatus FSCloseFileWrapper(FSFileHandle handle) override; + + FSStatus FSGetStatWrapper(const char *path, FSStat *stats) override; + + FSStatus FSGetStatFileWrapper(FSFileHandle handle, + FSStat *stats) override; + + FSStatus FSReadFileWrapper(void *buffer, + uint32_t size, + uint32_t count, + FSFileHandle handle, + uint32_t unk1) override; + + FSStatus FSReadFileWithPosWrapper(void *buffer, + uint32_t size, + uint32_t count, + uint32_t pos, + FSFileHandle handle, + int32_t unk1) override; + + FSStatus FSSetPosFileWrapper(FSFileHandle handle, + uint32_t pos) override; + + FSStatus FSGetPosFileWrapper(FSFileHandle handle, + uint32_t *pos) override; + + FSStatus FSIsEofWrapper(FSFileHandle handle) override; + + FSStatus FSTruncateFileWrapper(FSFileHandle handle) override; + + FSStatus FSWriteFileWrapper(uint8_t *buffer, + uint32_t size, + uint32_t count, + FSFileHandle handle, + uint32_t unk1) override; + + FSStatus FSRemoveWrapper(const char *path) override; + + FSStatus FSRenameWrapper(const char *oldPath, + const char *newPath) override; + + FSStatus FSFlushFileWrapper(FSFileHandle handle) override; + +protected: + virtual bool IsFileModeAllowed(const char *mode); + + virtual bool IsPathToReplace(const std::string_view &path); + + std::string GetNewPath(const std::string_view &path); + + DirInfo *getDirFromHandle(FSDirectoryHandle handle); + FileInfo *getFileFromHandle(FSFileHandle handle); + + bool isValidDirHandle(FSDirectoryHandle handle) override; + bool isValidFileHandle(FSFileHandle handle) override; + + void deleteDirHandle(FSDirectoryHandle handle) override; + void deleteFileHandle(FSFileHandle handle) override; + + virtual bool CheckFileShouldBeIgnored(std::string &path); + + virtual FileInfo *getNewFileHandle(); + virtual DirInfo *getNewDirHandle(); + + virtual bool SkipDeletedFilesInReadDir(); + + bool pCheckIfDeleted = false; + + std::string deletePrefix = ".deleted_"; + +private: + std::string pPathToReplace; + std::string pReplacePathWith; + bool pIsWriteable = false; + std::mutex openFilesMutex; + std::mutex openDirsMutex; + std::vector openFiles; + std::vector openDirs; +}; diff --git a/src/FSWrapperMergeDirsWithParent.cpp b/src/FSWrapperMergeDirsWithParent.cpp new file mode 100644 index 0000000..209fb09 --- /dev/null +++ b/src/FSWrapperMergeDirsWithParent.cpp @@ -0,0 +1,257 @@ +#include "FSWrapperMergeDirsWithParent.h" +#include "utils/logger.h" +#include +#include +#include +#include + +FSStatus FSWrapperMergeDirsWithParent::FSOpenDirWrapper(const char *path, + FSDirectoryHandle *handle) { + auto res = FSWrapper::FSOpenDirWrapper(path, handle); + if (res == FS_STATUS_OK) { + if (handle == nullptr || !isValidDirHandle(*handle)) { + DEBUG_FUNCTION_LINE_ERR("[%s] No valid dir handle %08X", getName().c_str(), *handle); + return FS_STATUS_FATAL_ERROR; + } + auto *dirHandle = getDirExFromHandle(*handle); + if (dirHandle != nullptr) { + dirHandle->readResultCapacity = 0; + dirHandle->readResultNumberOfEntries = 0; + dirHandle->realDirHandle = 0; + + if (pFSClient && pCmdBlock) { + FSDirectoryHandle realHandle = 0; + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Call real_FSOpenDir for %s with error_flag %08X", getName().c_str(), path, (uint32_t) this); + // Call FSOpen with "this" as errorFlag call FSOpen for "parent" layers only. + if (FSOpenDir(pFSClient, pCmdBlock, path, &realHandle, (FSErrorFlag) (uint32_t) this) == FS_STATUS_OK) { + dirHandle->realDirHandle = realHandle; + } else { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Failed to open real dir %s", getName().c_str(), path); + } + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] Global FSClient or FSCmdBlock were null", getName().c_str()); + } + OSMemoryBarrier(); + } + } + return res; +} + +bool FSWrapperMergeDirsWithParent::SkipDeletedFilesInReadDir() { + return false; +} + +FSStatus FSWrapperMergeDirsWithParent::FSReadDirWrapper(FSDirectoryHandle handle, FSDirectoryEntry *entry) { + do { + auto res = FSWrapper::FSReadDirWrapper(handle, entry); + if (res == FS_STATUS_OK || res == FS_STATUS_END) { + if (!isValidDirHandle(handle)) { + DEBUG_FUNCTION_LINE_ERR("[%s] No valid dir handle %08X", getName().c_str(), handle); + return FS_STATUS_FATAL_ERROR; + } + auto *dirHandle = getDirExFromHandle(handle); + if (res == FS_STATUS_OK) { + if (dirHandle->readResultCapacity == 0) { + dirHandle->readResult = (FSDirectoryEntryEx *) malloc(sizeof(FSDirectoryEntryEx)); + if (dirHandle->readResult == nullptr) { + DEBUG_FUNCTION_LINE_ERR("[%s] Failed to alloc memory for %08X (handle %08X)", getName().c_str(), dirHandle, handle); + OSFatal("Failed to alloc memory for read result"); + } + dirHandle->readResultCapacity = 1; + } + + if (dirHandle->readResultNumberOfEntries >= dirHandle->readResultCapacity) { + auto newCapacity = dirHandle->readResultCapacity * 2; + dirHandle->readResult = (FSDirectoryEntryEx *) realloc(dirHandle->readResult, newCapacity * sizeof(FSDirectoryEntryEx)); + dirHandle->readResultCapacity = newCapacity; + if (dirHandle->readResult == nullptr) { + DEBUG_FUNCTION_LINE_ERR("[%s] Failed to realloc memory for %08X (handle %08X)", getName().c_str(), dirHandle, handle); + OSFatal("Failed to alloc memory for read result"); + } + } + + memcpy(&dirHandle->readResult[dirHandle->readResultNumberOfEntries].realEntry, entry, sizeof(FSDirectoryEntry)); + dirHandle->readResultNumberOfEntries++; + + /** + * Read the next entry if this enntry starts with deletePrefix. We keep the entry but mark it as deleted. + */ + if (std::string_view(entry->name).starts_with(deletePrefix)) { + dirHandle->readResult[dirHandle->readResultNumberOfEntries].isMarkedAsDeleted = true; + + OSMemoryBarrier(); + continue; + } + + OSMemoryBarrier(); + + } else if (res == FS_STATUS_END) { + // Read the real directory. + if (dirHandle->realDirHandle != 0) { + if (pFSClient && pCmdBlock) { + FSDirectoryEntry realDirEntry; + FSStatus readDirResult; + while (true) { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Call real_FSReadDir for %08X with error_flag %08X", getName().c_str(), dirHandle->realDirHandle, (uint32_t) this); + readDirResult = FSReadDir(pFSClient, pCmdBlock, dirHandle->realDirHandle, &realDirEntry, (FSErrorFlag) (uint32_t) this); + if (readDirResult == FS_STATUS_OK) { + bool found = false; + auto nameDeleted = deletePrefix + realDirEntry.name; + for (int i = 0; i < dirHandle->readResultNumberOfEntries; i++) { + auto curResult = &dirHandle->readResult[i]; + + // Don't return file that are "deleted" + if (strcmp(curResult->realEntry.name, nameDeleted.c_str()) == 0) { + found = true; + break; + } + // Check if this is a new result + if (strcmp(curResult->realEntry.name, realDirEntry.name) == 0 && !curResult->isMarkedAsDeleted) { + found = true; + break; + } + } + // If it's new we can use it :) + if (!found) { + memcpy(entry, &realDirEntry, sizeof(FSDirectoryEntry)); + res = FS_STATUS_OK; + break; + } + } else if (readDirResult == FS_STATUS_END) { + res = FS_STATUS_END; + break; + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] real_FSReadDir returned an unexpected error: %08X", getName().c_str(), readDirResult); + res = FS_STATUS_END; + break; + } + } + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] Global FSClient or FSCmdBlock were null", getName().c_str()); + } + } + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] Unexpected result %d", getName().c_str(), res); + } + } + return res; + } while (true); +} + +FSStatus FSWrapperMergeDirsWithParent::FSCloseDirWrapper(FSDirectoryHandle handle) { + auto res = FSWrapper::FSCloseDirWrapper(handle); + + if (res == FS_STATUS_OK) { + if (!isValidDirHandle(handle)) { + DEBUG_FUNCTION_LINE_ERR("[%s] No valid dir handle %08X", getName().c_str(), handle); + return FS_STATUS_FATAL_ERROR; + } + auto *dirHandle = getDirExFromHandle(handle); + if (dirHandle->realDirHandle != 0) { + if (pFSClient && pCmdBlock) { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Call FSCloseDir for %08X with error_flag %08X (this)", getName().c_str(), dirHandle->realDirHandle, (uint32_t) this); + auto realResult = FSCloseDir(pFSClient, pCmdBlock, dirHandle->realDirHandle, (FSErrorFlag) (uint32_t) this); + if (realResult == FS_STATUS_OK) { + dirHandle->realDirHandle = 0; + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] Failed to close realDirHandle %d: res %d", getName().c_str(), dirHandle->realDirHandle, realResult); + return realResult; + } + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] Global FSClient or FSCmdBlock were null", getName().c_str()); + } + } + + if (dirHandle->readResult != nullptr) { + free(dirHandle->readResult); + dirHandle->readResult = nullptr; + dirHandle->readResultCapacity = 0; + dirHandle->readResultNumberOfEntries = 0; + } + + OSMemoryBarrier(); + } + return res; +} + +FSStatus FSWrapperMergeDirsWithParent::FSRewindDirWrapper(FSDirectoryHandle handle) { + auto res = FSWrapper::FSRewindDirWrapper(handle); + if (res == FS_STATUS_OK) { + if (!isValidDirHandle(handle)) { + DEBUG_FUNCTION_LINE_ERR("[%s] No valid dir handle %08X", getName().c_str(), handle); + return FS_STATUS_FATAL_ERROR; + } + auto *dirHandle = getDirExFromHandle(handle); + if (dirHandle->readResult != nullptr) { + dirHandle->readResultNumberOfEntries = 0; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wclass-memaccess" + memset(dirHandle->readResult, 0, sizeof(FSDirectoryEntryEx) * dirHandle->readResultCapacity); +#pragma GCC diagnostic pop + } + + if (dirHandle->realDirHandle != 0) { + if (pFSClient && pCmdBlock) { + DEBUG_FUNCTION_LINE_VERBOSE("[%s] Call real_FSRewindDir for %08X with error_flag %08X (this)", getName().c_str(), dirHandle->realDirHandle, (uint32_t) this); + if (FSRewindDir(pFSClient, pCmdBlock, dirHandle->realDirHandle, (FSErrorFlag) (uint32_t) this) == FS_STATUS_OK) { + dirHandle->realDirHandle = 0; + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] Failed to rewind dir for realDirHandle %08X", getName().c_str(), dirHandle->realDirHandle); + } + } else { + DEBUG_FUNCTION_LINE_ERR("[%s] Global FSClient or FSCmdBlock were null", getName().c_str()); + } + } + OSMemoryBarrier(); + } + return res; +} + +FSWrapperMergeDirsWithParent::FSWrapperMergeDirsWithParent(const std::string &name, + const std::string &pathToReplace, + const std::string &replaceWithPath, + bool fallbackOnError) : FSWrapper(name, + pathToReplace, + replaceWithPath, + fallbackOnError, + false) { + pFSClient = new (std::nothrow) FSClient; + pCmdBlock = new (std::nothrow) FSCmdBlock; + if (pFSClient == nullptr || pCmdBlock == nullptr) { + DEBUG_FUNCTION_LINE_ERR("[%s] Failed to alloc client or cmdBlock", name.c_str()); + freeFSClient(); + } + if (FSAddClient(pFSClient, FS_ERROR_FLAG_ALL) != FS_STATUS_OK) { + DEBUG_FUNCTION_LINE_ERR("[%s] Failed to addClient"); + freeFSClient(); + } + FSInitCmdBlock(pCmdBlock); +} + +FSWrapperMergeDirsWithParent::~FSWrapperMergeDirsWithParent() { + freeFSClient(); +} + +void FSWrapperMergeDirsWithParent::freeFSClient() { + if (pFSClient) { + FSDelClient(pFSClient, FS_ERROR_FLAG_ALL); + } + delete pFSClient; + delete pCmdBlock; + pFSClient = nullptr; + pCmdBlock = nullptr; +} + +DirInfoEx *FSWrapperMergeDirsWithParent::getDirExFromHandle(FSDirectoryHandle handle) { + auto *dir = getDirFromHandle(handle); + auto res = dynamic_cast(dir); + if (res == nullptr) { + DEBUG_FUNCTION_LINE_ERR("[%s] dynamic_cast(%08X)", getName().c_str(), handle); + OSFatal("dynamic_cast failed"); + } + return res; +} + +DirInfo *FSWrapperMergeDirsWithParent::getNewDirHandle() { + return new (std::nothrow) DirInfoEx; +} diff --git a/src/FSWrapperMergeDirsWithParent.h b/src/FSWrapperMergeDirsWithParent.h new file mode 100644 index 0000000..fe62ccb --- /dev/null +++ b/src/FSWrapperMergeDirsWithParent.h @@ -0,0 +1,36 @@ +#pragma once +#include "DirInfoEx.h" +#include "FSWrapper.h" +#include +#include + +class FSWrapperMergeDirsWithParent : public FSWrapper { +public: + FSWrapperMergeDirsWithParent(const std::string &name, + const std::string &pathToReplace, + const std::string &replaceWithPath, + bool fallbackOnError); + + ~FSWrapperMergeDirsWithParent() override; + + FSStatus FSOpenDirWrapper(const char *path, + FSDirectoryHandle *handle) override; + + FSStatus FSReadDirWrapper(FSDirectoryHandle handle, + FSDirectoryEntry *entry) override; + + FSStatus FSCloseDirWrapper(FSDirectoryHandle handle) override; + + FSStatus FSRewindDirWrapper(FSDirectoryHandle handle) override; + + DirInfo *getNewDirHandle() override; + + bool SkipDeletedFilesInReadDir() override; + +private: + void freeFSClient(); + FSClient *pFSClient; + FSCmdBlock *pCmdBlock; + + DirInfoEx *getDirExFromHandle(FSDirectoryHandle handle); +}; diff --git a/src/FileInfo.h b/src/FileInfo.h new file mode 100644 index 0000000..1f6e7e8 --- /dev/null +++ b/src/FileInfo.h @@ -0,0 +1,8 @@ +#pragma once +#include + +struct FileInfo { +public: + FSFileHandle handle; + int fd; +}; diff --git a/src/FileUtils.cpp b/src/FileUtils.cpp new file mode 100644 index 0000000..5ef7574 --- /dev/null +++ b/src/FileUtils.cpp @@ -0,0 +1,270 @@ +#include "FileUtils.h" +#include "FSWrapper.h" +#include "IFSWrapper.h" +#include "utils/StringTools.h" +#include "utils/logger.h" +#include "utils/utils.h" +#include +#include +#include +#include +#include + +std::mutex workingDirMutex; +std::map workingDirs; + +std::mutex fsLayerMutex; +std::vector fsLayers; + +std::string getFullPathForClient(FSClient *pClient, char *path) { + std::string res; + + if (path[0] != '/' && path[0] != '\\') { + if (workingDirs.count(pClient) > 0) { + res = string_format("%s%s", workingDirs.at(pClient).c_str(), path); + } else { + DEBUG_FUNCTION_LINE_ERR("Failed to find working dir for client"); + } + } else { + res = path; + } + + std::replace(res.begin(), res.end(), '\\', '/'); + + return res; +} + +void setWorkingDir(FSClient *client, const char *path) { + std::lock_guard workingDirLock(workingDirMutex); + + workingDirs[client] = path; + OSMemoryBarrier(); +} + +void clearFSLayer() { + { + std::lock_guard workingDirLock(workingDirMutex); + workingDirs.clear(); + } + { + std::lock_guard layerlock(fsLayerMutex); + for (auto &layer : fsLayers) { + delete layer; + } + fsLayers.clear(); + } +} + +// FUN_0204cc20 +#define fsClientHandleFatalErrorAndBlock ((void (*)(FSClientBody *, uint32_t))(0x101C400 + 0x4cc20)) + +FSStatus doForLayer(FSClient *client, + FSErrorFlag errorMask, + const std::function &real_function, + const std::function &layer_callback, + const std::function &result_handler) { + FSErrorFlag realErrorMask = errorMask; + + std::lock_guard lock(fsLayerMutex); + if (!fsLayers.empty()) { + uint32_t startIndex = fsLayers.size(); + for (uint32_t i = fsLayers.size(); i > 0; i--) { + if ((uint32_t) fsLayers[i - 1] == errorMask) { + startIndex = i - 1; + realErrorMask = FS_ERROR_FLAG_ALL; + break; + } + } + + if (startIndex > 0) { + for (uint32_t i = startIndex; i > 0; i--) { + auto layer = fsLayers[i - 1]; + if (!layer->isActive()) { + continue; + } + auto result = layer_callback(layer); + + if (result != FS_STATUS_FORCE_PARENT_LAYER) { + if (result < FS_STATUS_OK && result != FS_STATUS_END && result != FS_STATUS_CANCELLED) { + if (layer->fallbackOnError()) { + // Only fallback if FS_STATUS_FORCE_NO_FALLBACK flag is not set. + if (static_cast(result & 0xFFFF0000) != FS_STATUS_FORCE_NO_FALLBACK) { + continue; + } else { + // Remove FS_STATUS_FORCE_NO_FALLBACK flag. + result = static_cast((result & 0x0000FFFF) | 0xFFFF0000); + } + } + } + + if (result >= FS_STATUS_OK || result == FS_STATUS_END || result == FS_STATUS_CANCELLED) { + DEBUG_FUNCTION_LINE_VERBOSE("Returned %08X by %s", result, layer->getName().c_str()); + return result_handler(layer, result); + } + + FSErrorFlag errorFlags = FS_ERROR_FLAG_NONE; + bool forceError = false; + + switch ((int) result) { + case FS_STATUS_MAX: + errorFlags = FS_ERROR_FLAG_MAX; + break; + case FS_STATUS_ALREADY_OPEN: + errorFlags = FS_ERROR_FLAG_ALREADY_OPEN; + break; + case FS_STATUS_EXISTS: + errorFlags = FS_ERROR_FLAG_EXISTS; + break; + case FS_STATUS_NOT_FOUND: + errorFlags = FS_ERROR_FLAG_NOT_FOUND; + break; + case FS_STATUS_NOT_FILE: + errorFlags = FS_ERROR_FLAG_NOT_FILE; + break; + case FS_STATUS_NOT_DIR: + errorFlags = FS_ERROR_FLAG_NOT_DIR; + break; + case FS_STATUS_ACCESS_ERROR: + errorFlags = FS_ERROR_FLAG_ACCESS_ERROR; + break; + case FS_STATUS_PERMISSION_ERROR: + errorFlags = FS_ERROR_FLAG_PERMISSION_ERROR; + break; + case FS_STATUS_FILE_TOO_BIG: + errorFlags = FS_ERROR_FLAG_FILE_TOO_BIG; + break; + case FS_STATUS_STORAGE_FULL: + errorFlags = FS_ERROR_FLAG_STORAGE_FULL; + break; + case FS_STATUS_JOURNAL_FULL: + errorFlags = FS_ERROR_FLAG_JOURNAL_FULL; + break; + case FS_STATUS_UNSUPPORTED_CMD: + errorFlags = FS_ERROR_FLAG_UNSUPPORTED_CMD; + break; + case FS_STATUS_MEDIA_NOT_READY: + case FS_STATUS_MEDIA_ERROR: + case FS_STATUS_CORRUPTED: + case FS_STATUS_FATAL_ERROR: + forceError = true; + break; + case FS_STATUS_OK: + break; + } + + if (forceError || (realErrorMask != FS_ERROR_FLAG_NONE && (errorFlags & realErrorMask) == 0)) { + DEBUG_FUNCTION_LINE_ERR("Transit to Fatal Error"); + auto clientBody = fsClientGetBody(client); + + fsClientHandleFatalErrorAndBlock(clientBody, clientBody->lastError); + return FS_STATUS_FATAL_ERROR; + } + DEBUG_FUNCTION_LINE_VERBOSE("%08X Returned %08X by %s ", errorMask, result, layer->getName().c_str()); + return result_handler(layer, result); + } + } + } + } + + auto mask = static_cast((realErrorMask & FS_ERROR_FLAG_REAL_MASK) | FS_ERROR_FLAG_FORCE_REAL); + return real_function(mask); +} + +FSCmdBlockBody *fsCmdBlockGetBody(FSCmdBlock *cmdBlock) { + if (!cmdBlock) { + return nullptr; + } + + auto body = (FSCmdBlockBody *) (ROUNDUP((uint32_t) cmdBlock, 0x40)); + return body; +} + +FSClientBody *fsClientGetBody(FSClient *client) { + if (!client) { + return nullptr; + } + + auto body = (FSClientBody *) (ROUNDUP((uint32_t) client, 0x40)); + body->client = client; + return body; +} + + +FSStatus send_result_async(FSClient *client, FSCmdBlock *block, FSAsyncData *asyncData, FSStatus status) { + if (asyncData->callback != nullptr) { + if (asyncData->ioMsgQueue != nullptr) { + DEBUG_FUNCTION_LINE_ERR("callback and ioMsgQueue both set."); + return FS_STATUS_FATAL_ERROR; + } + // userCallbacks are called in the DefaultAppIOQueue. + asyncData->ioMsgQueue = OSGetDefaultAppIOQueue(); + //DEBUG_FUNCTION_LINE("Force to OSGetDefaultAppIOQueue (%08X)", asyncData->ioMsgQueue); + } + + if (asyncData->ioMsgQueue != nullptr) { +#pragma GCC diagnostic ignored "-Waddress-of-packed-member" + FSAsyncResult *result = &(fsCmdBlockGetBody(block)->asyncResult); + //DEBUG_FUNCTION_LINE("Send result %d to ioMsgQueue (%08X)", status, asyncData->ioMsgQueue); + result->asyncData.callback = asyncData->callback; + result->asyncData.param = asyncData->param; + result->asyncData.ioMsgQueue = asyncData->ioMsgQueue; + memset(&result->ioMsg, 0, sizeof(result->ioMsg)); + result->ioMsg.data = result; + result->ioMsg.type = OS_FUNCTION_TYPE_FS_CMD_ASYNC; + result->client = client; + result->block = block; + result->status = status; + + while (!OSSendMessage(asyncData->ioMsgQueue, (OSMessage *) &(result->ioMsg), OS_MESSAGE_FLAGS_NONE)) { + DEBUG_FUNCTION_LINE_ERR("Failed to send message"); + } + } + return FS_STATUS_OK; +} + +int64_t readIntoBuffer(int32_t handle, void *buffer, size_t size, size_t count) { + auto sizeToRead = size * count; + /* + // https://github.com/decaf-emu/decaf-emu/blob/131aeb14fccff8461a5fd9f2aa5c040ba3880ef5/src/libdecaf/src/cafe/libraries/coreinit/coreinit_fs_cmd.cpp#L2346 + if (sizeToRead > 0x100000) { + sizeToRead = 0x100000; + }*/ + void *newBuffer = buffer; + int32_t curResult; + int64_t totalSize = 0; + while (sizeToRead > 0) { + curResult = read(handle, newBuffer, sizeToRead); + if (curResult < 0) { + DEBUG_FUNCTION_LINE_ERR("Reading %08X bytes from handle %08X failed. result %08X errno: %d ", size * count, handle, curResult, errno); + return -1; + } + if (curResult == 0) { + break; + } + newBuffer = (void *) (((uint32_t) newBuffer) + curResult); + totalSize += curResult; + sizeToRead -= curResult; + } + return totalSize; +} + +int64_t writeFromBuffer(int32_t handle, void *buffer, size_t size, size_t count) { + auto sizeToWrite = size * count; + void *ptr = buffer; + int32_t curResult; + int64_t totalSize = 0; + while (sizeToWrite > 0) { + curResult = write(handle, ptr, sizeToWrite); + if (curResult < 0) { + DEBUG_FUNCTION_LINE_ERR("Writing %08X bytes from handle %08X failed. result %08X errno: %d ", size * count, handle, curResult, errno); + return -1; + } + if (curResult == 0) { + break; + } + ptr = (void *) (((uint32_t) ptr) + curResult); + totalSize += curResult; + sizeToWrite -= curResult; + } + return totalSize; +} \ No newline at end of file diff --git a/src/FileUtils.h b/src/FileUtils.h new file mode 100644 index 0000000..4535dc7 --- /dev/null +++ b/src/FileUtils.h @@ -0,0 +1,85 @@ +#pragma once + +#include "IFSWrapper.h" +#include +#include +#include +#include +#include + +extern std::mutex fsLayerMutex; +extern std::vector fsLayers; + +#define SYNC_RESULT_HANDLER [filename = __FILENAME__, func = __FUNCTION__, line = __LINE__]([[maybe_unused]] IFSWrapper *layer, FSStatus res) -> FSStatus { \ + DEBUG_FUNCTION_LINE_VERBOSE_EX(filename, func, line, "Sync result was %d", res); \ + return res; \ +} + + +#define ASYNC_RESULT_HANDLER [c = client, b = block, a = asyncData, filename = __FILENAME__, func = __FUNCTION__, line = __LINE__]([[maybe_unused]] IFSWrapper *layer, FSStatus res) -> FSStatus { \ + DEBUG_FUNCTION_LINE_VERBOSE_EX(filename, func, line, "Async result was %d", res); \ + return send_result_async(c, b, a, res); \ +} + +#define FS_ERROR_FLAG_EXTRA_MASK (FSErrorFlag) 0xFFFF0000 +#define FS_ERROR_FLAG_REAL_MASK (FSErrorFlag) 0x0000FFFF +#define FS_ERROR_FLAG_FORCE_REAL (FSErrorFlag) 0xFEDC0000 + +static inline FSErrorFlag getRealErrorFlag(FSErrorFlag flag) { + auto res = flag & FS_ERROR_FLAG_REAL_MASK; + if (res == 0x0000FFFF) { + return FS_ERROR_FLAG_ALL; + } + return static_cast(res); +} + +static inline FSErrorFlag isForceRealFunction(FSErrorFlag flag) { + return static_cast((flag & FS_ERROR_FLAG_EXTRA_MASK) == FS_ERROR_FLAG_FORCE_REAL); +} + +void clearFSLayer(); + +struct WUT_PACKED FSCmdBlockBody { //! FSAsyncResult object used for this command. + + WUT_UNKNOWN_BYTES(0x96C); + FSAsyncResult asyncResult; +}; +WUT_CHECK_OFFSET(FSCmdBlockBody, 0x96C, asyncResult); +WUT_CHECK_SIZE(FSCmdBlockBody, 0x96C + 0x28); + +struct WUT_PACKED FSClientBody { + WUT_UNKNOWN_BYTES(0x1448); + void *fsm; + WUT_UNKNOWN_BYTES(0x15E8 - 0x1448 - 0x04); + uint32_t lastError; + BOOL isLastErrorWithoutVolume; + WUT_UNKNOWN_BYTES(0x161C - 0x15E8 - 0x4 - 0x4); + FSClient *client; +}; +WUT_CHECK_OFFSET(FSClientBody, 0x1448, fsm); +WUT_CHECK_OFFSET(FSClientBody, 0x15E8, lastError); +WUT_CHECK_OFFSET(FSClientBody, 0x15EC, isLastErrorWithoutVolume); +WUT_CHECK_OFFSET(FSClientBody, 0x161C, client); +WUT_CHECK_SIZE(FSClientBody, 0x161C + 0x4); + +std::string getFullPathForClient(FSClient *pClient, char *path); + +void setWorkingDir(FSClient *client, const char *path); + +FSStatus doForLayer(FSClient *client, + FSErrorFlag errorMask, + const std::function &real_function, + const std::function &layer_callback, + const std::function &result_handler); + +FSCmdBlockBody *fsCmdBlockGetBody(FSCmdBlock *cmdBlock); + +FSClientBody *fsClientGetBody(FSClient *client); + +FSStatus send_result_async(FSClient *client, FSCmdBlock *block, FSAsyncData *asyncData, FSStatus result); + +int64_t readIntoBuffer(int32_t handle, void *buffer, size_t size, size_t count); + +int64_t writeFromBuffer(int32_t handle, void *buffer, size_t size, size_t count); + +int32_t CreateSubfolder(const char *fullpath); \ No newline at end of file diff --git a/src/IFSWrapper.h b/src/IFSWrapper.h new file mode 100644 index 0000000..5415ae2 --- /dev/null +++ b/src/IFSWrapper.h @@ -0,0 +1,146 @@ +#pragma once +#include +#include +#include + +#define FS_STATUS_FORCE_PARENT_LAYER (FSStatus) 0xFFFF0000 +#define FS_STATUS_FORCE_NO_FALLBACK (FSStatus) 0xFFFE0000 + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" + +class IFSWrapper { +public: + virtual ~IFSWrapper() = default; + virtual FSStatus FSOpenDirWrapper(const char *path, + FSDirectoryHandle *handle) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + virtual FSStatus FSReadDirWrapper(FSDirectoryHandle handle, + FSDirectoryEntry *entry) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + virtual FSStatus FSCloseDirWrapper(FSDirectoryHandle handle) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + + virtual FSStatus FSRewindDirWrapper(FSDirectoryHandle handle) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + virtual FSStatus FSMakeDirWrapper(const char *path) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + virtual FSStatus FSOpenFileWrapper(const char *path, + const char *mode, + FSFileHandle *handle) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + virtual FSStatus FSCloseFileWrapper(FSFileHandle handle) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + virtual FSStatus FSGetStatWrapper(const char *path, + FSStat *stats) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + + virtual FSStatus FSGetStatFileWrapper(FSFileHandle handle, + FSStat *stats) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + virtual FSStatus FSReadFileWrapper(void *buffer, + uint32_t size, + uint32_t count, + FSFileHandle handle, + uint32_t unk1) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + virtual FSStatus FSReadFileWithPosWrapper(void *buffer, + uint32_t size, + uint32_t count, + uint32_t pos, + FSFileHandle handle, + int32_t unk1) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + virtual FSStatus FSSetPosFileWrapper(FSFileHandle handle, + uint32_t pos) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + virtual FSStatus FSGetPosFileWrapper(FSFileHandle handle, + uint32_t *pos) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + virtual FSStatus FSIsEofWrapper(FSFileHandle handle) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + virtual FSStatus FSTruncateFileWrapper(FSFileHandle handle) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + virtual FSStatus FSWriteFileWrapper(uint8_t *buffer, + uint32_t size, + uint32_t count, + FSFileHandle handle, + uint32_t unk1) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + virtual FSStatus FSRemoveWrapper(const char *path) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + virtual FSStatus FSRenameWrapper(const char *oldPath, + const char *newPath) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + virtual FSStatus FSFlushFileWrapper(FSFileHandle handle) { + return FS_STATUS_FORCE_PARENT_LAYER; + } + + virtual bool fallbackOnError() { + return pFallbackOnError; + } + + virtual bool isActive() { + return pIsActive; + } + + virtual void setActive(bool newValue) { + pIsActive = newValue; + } + + [[nodiscard]] virtual std::string getName() const { + return pName; + } + + virtual bool isValidDirHandle(FSDirectoryHandle handle) = 0; + + virtual bool isValidFileHandle(FSFileHandle handle) = 0; + + virtual void deleteDirHandle(FSDirectoryHandle handle) = 0; + + virtual void deleteFileHandle(FSFileHandle handle) = 0; + +private: + bool pIsActive = true; + +protected: + bool pFallbackOnError = false; + std::string pName; +}; +#pragma GCC diagnostic pop diff --git a/src/export.cpp b/src/export.cpp new file mode 100644 index 0000000..e0ffd40 --- /dev/null +++ b/src/export.cpp @@ -0,0 +1,96 @@ +#include "FSWrapper.h" +#include "FSWrapperMergeDirsWithParent.h" +#include "FileUtils.h" +#include "IFSWrapper.h" +#include "utils/logger.h" +#include +#include +#include +#include + +ContentRedirectionApiErrorType CRAddFSLayer(CRLayerHandle *handle, const char *layerName, const char *replacementDir, FSLayerType layerType) { + if (!handle || layerName == nullptr || replacementDir == nullptr) { + DEBUG_FUNCTION_LINE_ERR("CONTENT_REDIRECTION_API_ERROR_INVALID_ARG"); + return CONTENT_REDIRECTION_API_ERROR_INVALID_ARG; + } + IFSWrapper *ptr; + if (layerType == FS_LAYER_TYPE_CONTENT_REPLACE) { + ptr = new (std::nothrow) FSWrapper(layerName, "/vol/content", replacementDir, false, false); + } else if (layerType == FS_LAYER_TYPE_CONTENT_MERGE) { + ptr = new (std::nothrow) FSWrapperMergeDirsWithParent(layerName, "/vol/content", replacementDir, true); + } else if (layerType == FS_LAYER_TYPE_SAVE_REPLACE) { + ptr = new (std::nothrow) FSWrapper(layerName, "/vol/save", replacementDir, false, true); + } else { + DEBUG_FUNCTION_LINE_ERR("CONTENT_REDIRECTION_API_ERROR_UNKNOWN_LAYER_DIR_TYPE: %s %s %d", layerName, replacementDir, layerType); + return CONTENT_REDIRECTION_API_ERROR_UNKNOWN_FS_LAYER_TYPE; + } + if (ptr) { + DEBUG_FUNCTION_LINE_VERBOSE("Added new layer (%s). Replacement dir: %s Type:%d", layerName, replacementDir, layerType); + std::lock_guard lock(fsLayerMutex); + fsLayers.push_back(ptr); + *handle = (CRLayerHandle) ptr; + return CONTENT_REDIRECTION_API_ERROR_NONE; + } + DEBUG_FUNCTION_LINE_ERR("Failed to allocate memory"); + return CONTENT_REDIRECTION_API_ERROR_NO_MEMORY; +} + +ContentRedirectionApiErrorType CRRemoveFSLayer(CRLayerHandle handle) { + std::lock_guard lock(fsLayerMutex); + auto count = 0; + auto found = false; + IFSWrapper *layer; + for (auto &cur : fsLayers) { + if ((CRLayerHandle) cur == handle) { + found = true; + layer = cur; + break; + } + count++; + } + if (!found) { + DEBUG_FUNCTION_LINE_ERR("CONTENT_REDIRECTION_API_ERROR_LAYER_NOT_FOUND for handle %08X", handle); + return CONTENT_REDIRECTION_API_ERROR_LAYER_NOT_FOUND; + } + fsLayers.erase(fsLayers.begin() + count); + delete layer; + return CONTENT_REDIRECTION_API_ERROR_NONE; +} + +ContentRedirectionApiErrorType CRSetActive(CRLayerHandle handle, bool active) { + std::lock_guard lock(fsLayerMutex); + auto found = false; + IFSWrapper *layer; + for (auto &cur : fsLayers) { + if ((CRLayerHandle) cur == handle) { + found = true; + layer = cur; + break; + } + } + if (!found) { + DEBUG_FUNCTION_LINE_ERR("CONTENT_REDIRECTION_API_ERROR_LAYER_NOT_FOUND for handle %08X", handle); + return CONTENT_REDIRECTION_API_ERROR_LAYER_NOT_FOUND; + } + layer->setActive(active); + return CONTENT_REDIRECTION_API_ERROR_NONE; +} + +uint32_t CRGetVersion() { + return CONTENT_REDIRECT_MODULE_VERSION; +} + +int CRAddDevice(const devoptab_t *device) { + return AddDevice(device); +} + +int CRRemoveDevice(const char *name) { + return RemoveDevice(name); +} + +WUMS_EXPORT_FUNCTION(CRGetVersion); +WUMS_EXPORT_FUNCTION(CRAddFSLayer); +WUMS_EXPORT_FUNCTION(CRRemoveFSLayer); +WUMS_EXPORT_FUNCTION(CRSetActive); +WUMS_EXPORT_FUNCTION(CRAddDevice); +WUMS_EXPORT_FUNCTION(CRRemoveDevice); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..e0f380c --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,30 @@ +#include "FSDirReplacements.h" +#include "FSFileReplacements.h" +#include "FileUtils.h" +#include "utils/logger.h" +#include +#include +#include +#include + +WUMS_MODULE_EXPORT_NAME("homebrew_content_redirection"); +WUMS_USE_WUT_DEVOPTAB(); + +WUMS_INITIALIZE() { + initLogging(); + DEBUG_FUNCTION_LINE("Patch functions"); + // we only patch static functions, we don't need re-patch them at every launch + FunctionPatcherPatchFunction(fs_file_function_replacements, fs_file_function_replacements_size); + FunctionPatcherPatchFunction(fs_dir_function_replacements, fs_dir_function_replacements_size); + DEBUG_FUNCTION_LINE("Patch functions finished"); + deinitLogging(); +} + +WUMS_APPLICATION_STARTS() { + initLogging(); +} + +WUMS_APPLICATION_ENDS() { + clearFSLayer(); + deinitLogging(); +} \ No newline at end of file diff --git a/src/utils/StringTools.h b/src/utils/StringTools.h new file mode 100644 index 0000000..e825367 --- /dev/null +++ b/src/utils/StringTools.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include +#include + +template +std::string string_format(const std::string &format, Args... args) { + int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0' + auto size = static_cast(size_s); + auto buf = std::make_unique(size); + std::snprintf(buf.get(), size, format.c_str(), args...); + return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside +} \ No newline at end of file diff --git a/src/utils/logger.c b/src/utils/logger.c new file mode 100644 index 0000000..f700806 --- /dev/null +++ b/src/utils/logger.c @@ -0,0 +1,36 @@ +#ifdef DEBUG +#include +#include +#include +#include + +uint32_t moduleLogInit = false; +uint32_t cafeLogInit = false; +uint32_t udpLogInit = false; +#endif // DEBUG + +void initLogging() { +#ifdef DEBUG + if (!(moduleLogInit = WHBLogModuleInit())) { + cafeLogInit = WHBLogCafeInit(); + udpLogInit = WHBLogUdpInit(); + } +#endif // DEBUG +} + +void deinitLogging() { +#ifdef DEBUG + if (moduleLogInit) { + WHBLogModuleDeinit(); + moduleLogInit = false; + } + if (cafeLogInit) { + WHBLogCafeDeinit(); + cafeLogInit = false; + } + if (udpLogInit) { + WHBLogUdpDeinit(); + udpLogInit = false; + } +#endif // DEBUG +} \ No newline at end of file diff --git a/src/utils/logger.h b/src/utils/logger.h new file mode 100644 index 0000000..d394030 --- /dev/null +++ b/src/utils/logger.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define __FILENAME_X__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILENAME_X__) + +// #define VERBOSE_DEBUG + +#ifdef DEBUG + +#ifdef VERBOSE_DEBUG +#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) \ + do { \ + WHBLogPrintf("[%23s]%30s@L%04d: " FMT "", __FILENAME__, __FUNCTION__, __LINE__, ##ARGS); \ + } while (0) + +#define DEBUG_FUNCTION_LINE_VERBOSE_EX(FILENAME, FUNCTION, LINE, FMT, ARGS...) \ + do { \ + WHBLogPrintf("[%23s]%30s@L%04d: " FMT "", FILENAME, FUNCTION, LINE, ##ARGS); \ + } while (0) +#else + +#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0) + +#define DEBUG_FUNCTION_LINE_VERBOSE_EX(FILENAME, FUNCTION, LINE, FMT, ARGS...) while (0) +#endif + +#define DEBUG_FUNCTION_LINE(FMT, ARGS...) \ + do { \ + WHBLogPrintf("[%23s]%30s@L%04d: " FMT "", __FILENAME__, __FUNCTION__, __LINE__, ##ARGS); \ + } while (0) + +#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) \ + do { \ + WHBLogWritef("[%23s]%30s@L%04d: " FMT "", __FILENAME__, __FUNCTION__, __LINE__, ##ARGS); \ + } while (0) + +#else + +#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0) + +#define DEBUG_FUNCTION_LINE_VERBOSE_EX(FILENAME, FUNCTION, LINE, FMT, ARGS...) while (0) + +#define DEBUG_FUNCTION_LINE(FMT, ARGS...) while (0) + +#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) while (0) + +#endif + +#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) \ + do { \ + OSReport("## ERROR ## [%23s]%30s@L%04d: ##ERROR## " FMT "\n", __FILENAME__, __FUNCTION__, __LINE__, ##ARGS); \ + } while (0) + +#define DEBUG_FUNCTION_LINE_ERR_LAMBDA(FILENAME, FUNCTION, LINE, FMT, ARGS...) \ + do { \ + OSReport("[%23s]%30s@L%04d: ##ERROR## " FMT "\n", FILENAME, FUNCTION, LINE, ##ARGS); \ + } while (0) + +void initLogging(); + +void deinitLogging(); + +#ifdef __cplusplus +} +#endif diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp new file mode 100644 index 0000000..bbe9a07 --- /dev/null +++ b/src/utils/utils.cpp @@ -0,0 +1,39 @@ +#include "utils/logger.h" +#include +#include + +#define PRINTF_BUFFER_LENGTH 2048 + +// https://gist.github.com/ccbrown/9722406 +void dumpHex(const void *data, size_t size) { + char ascii[17]; + size_t i, j; + ascii[16] = '\0'; + DEBUG_FUNCTION_LINE("0x%08X (0x0000): ", data); + for (i = 0; i < size; ++i) { + WHBLogWritef("%02X ", ((unsigned char *) data)[i]); + if (((unsigned char *) data)[i] >= ' ' && ((unsigned char *) data)[i] <= '~') { + ascii[i % 16] = ((unsigned char *) data)[i]; + } else { + ascii[i % 16] = '.'; + } + if ((i + 1) % 8 == 0 || i + 1 == size) { + WHBLogWritef(" "); + if ((i + 1) % 16 == 0) { + WHBLogPrintf("| %s ", ascii); + if (i + 1 < size) { + DEBUG_FUNCTION_LINE("0x%08X (0x%04X); ", ((uint32_t) data) + i + 1, i + 1); + } + } else if (i + 1 == size) { + ascii[(i + 1) % 16] = '\0'; + if ((i + 1) % 16 <= 8) { + WHBLogWritef(" "); + } + for (j = (i + 1) % 16; j < 16; ++j) { + WHBLogWritef(" "); + } + WHBLogPrintf("| %s ", ascii); + } + } + } +} \ No newline at end of file diff --git a/src/utils/utils.h b/src/utils/utils.h new file mode 100644 index 0000000..81cbf5e --- /dev/null +++ b/src/utils/utils.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define LIMIT(x, min, max) \ + ({ \ + typeof(x) _x = x; \ + typeof(min) _min = min; \ + typeof(max) _max = max; \ + (((_x) < (_min)) ? (_min) : ((_x) > (_max)) ? (_max) \ + : (_x)); \ + }) + +#define DegToRad(a) ((a) *0.01745329252f) +#define RadToDeg(a) ((a) *57.29577951f) + +#define ALIGN4(x) (((x) + 3) & ~3) +#define ALIGN32(x) (((x) + 31) & ~31) + +// those work only in powers of 2 +#define ROUNDDOWN(val, align) ((val) & ~(align - 1)) +#define ROUNDUP(val, align) ROUNDDOWN(((val) + (align - 1)), align) + + +#define le16(i) ((((uint16_t) ((i) &0xFF)) << 8) | ((uint16_t) (((i) &0xFF00) >> 8))) +#define le32(i) ((((uint32_t) le16((i) &0xFFFF)) << 16) | ((uint32_t) le16(((i) &0xFFFF0000) >> 16))) +#define le64(i) ((((uint64_t) le32((i) &0xFFFFFFFFLL)) << 32) | ((uint64_t) le32(((i) &0xFFFFFFFF00000000LL) >> 32))) + +//Needs to have log_init() called beforehand. +void dumpHex(const void *data, size_t size); + +#ifdef __cplusplus +} +#endif \ No newline at end of file