commit 55b2c3da3297f49fe49846d7890d4ca9f852388c Author: Maschell Date: Fri Apr 15 18:13:21 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..00cbb07 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM wiiuenv/devkitppc:20220303 + +COPY --from=wiiuenv/wiiumodulesystem:20220204 /artifacts $DEVKITPRO +COPY --from=wiiuenv/libromfs_wiiu:20220305 /artifacts $DEVKITPRO +COPY --from=wiiuenv/libwuhbutils:20220415 /artifacts $DEVKITPRO + +WORKDIR project \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5b7c0b4 --- /dev/null +++ b/Makefile @@ -0,0 +1,155 @@ +#------------------------------------------------------------------------------- +.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 := WUHBUtilsModule +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) $(WUMSSPECS) + +ifeq ($(DEBUG),1) +CXXFLAGS += -DDEBUG -g +CFLAGS += -DDEBUG -g +endif + +LIBS := -lwums -lwut -lromfs -lz + +#------------------------------------------------------------------------------- +# 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))) +DEFFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.def))) +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 := $(DEFFILES:.def=.o) $(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 +#------------------------------------------------------------------------------- +%.o: %.def + $(SILENTMSG) $(notdir $<) + $(SILENTCMD)rplimportgen $< $*.s $*.ld $(ERROR_FILTER) + $(SILENTCMD)$(CC) -x assembler-with-cpp $(ASFLAGS) -c $*.s -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%_bin.h %.bin.o : %.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..277738c --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +[![CI-Release](https://github.com/wiiu-env/WUHBUtilsModule/actions/workflows/ci.yml/badge.svg)](https://github.com/wiiu-env/WUHBUtilsModule/actions/workflows/ci.yml) + +## Usage +(`[ENVIRONMENT]` is a placeholder for the actual environment name.) + +1. Copy the file `WUHBUtils.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 [libwuhbutils](https://github.com/wiiu-env/libwuhbutils). + +## 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 wuhbutils-builder + +# make +docker run -it --rm -v ${PWD}:/project wuhbutils-builder make + +# make clean +docker run -it --rm -v ${PWD}:/project wuhbutils-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/FileUtils.cpp b/src/FileUtils.cpp new file mode 100644 index 0000000..c218092 --- /dev/null +++ b/src/FileUtils.cpp @@ -0,0 +1,79 @@ +#include "FileUtils.h" +#include "export.h" +#include "utils/logger.h" +#include +#include +#include + +int32_t CheckFile(const char *filepath) { + if (!filepath) { + return 0; + } + + struct stat filestat {}; + + char dirnoslash[strlen(filepath) + 2]; + snprintf(dirnoslash, sizeof(dirnoslash), "%s", filepath); + + while (dirnoslash[strlen(dirnoslash) - 1] == '/') { + dirnoslash[strlen(dirnoslash) - 1] = '\0'; + } + + char *notRoot = strrchr(dirnoslash, '/'); + if (!notRoot) { + strcat(dirnoslash, "/"); + } + + if (stat(dirnoslash, &filestat) == 0) { + return 1; + } + + return 0; +} + +WUHBRPXInfoResultCode getRPXInfoForPath(const std::string &path, BundleSource source, WUHBRPXInfo *outFileInfo) { + auto realSource = RomfsSource_FileDescriptor; + switch (source) { + case BundleSource_FileDescriptor: + realSource = RomfsSource_FileDescriptor; + break; + case BundleSource_FileDescriptor_CafeOS: + realSource = RomfsSource_FileDescriptor_CafeOS; + break; + } + if (romfsMount("wuu_rpxinfo", path.c_str(), realSource) < 0) { + return WUHB_UTILS_RPX_INFO_MOUNT_FAILED; + } + DIR *dir; + struct dirent *entry; + + if (!(dir = opendir("wuu_rpxinfo:/code/"))) { + romfsUnmount("wuu_rpxinfo"); + return WUHB_UTILS_RPX_INFO_OPENDIR_FAILED; + } + bool found = false; + WUHBRPXInfoResultCode res; + while ((entry = readdir(dir)) != nullptr) { + if (std::string_view(entry->d_name).ends_with(".rpx")) { + romfs_fileInfo info; + if (romfsGetFileInfoPerPath("wuu_rpxinfo", (std::string("code/") + entry->d_name).c_str(), &info) >= 0) { + found = true; + res = WUHB_UTILS_RPX_INFO_SUCCESS; + outFileInfo->length = info.length; + outFileInfo->offset = info.offset; + } else { + DEBUG_FUNCTION_LINE_ERR("Failed to get info for %s", entry->d_name); + } + break; + } + } + + closedir(dir); + + romfsUnmount("wuu_rpxinfo"); + + if (!found) { + return WUHB_UTILS_RPX_INFO_NO_RPX_FOUND; + } + return res; +} \ No newline at end of file diff --git a/src/FileUtils.h b/src/FileUtils.h new file mode 100644 index 0000000..76a4a59 --- /dev/null +++ b/src/FileUtils.h @@ -0,0 +1,11 @@ +#pragma once +#include "export.h" +#include +#include +#include +#include +#include + +WUHBRPXInfoResultCode getRPXInfoForPath(const std::string &path, BundleSource source, WUHBRPXInfo *outFileInfo); + +int32_t CheckFile(const char *filepath); \ No newline at end of file diff --git a/src/export.cpp b/src/export.cpp new file mode 100644 index 0000000..6eae426 --- /dev/null +++ b/src/export.cpp @@ -0,0 +1,172 @@ +#include "FileUtils.h" +#include "utils/FileReader.h" +#include "utils/FileReaderCompressed.h" +#include +#include +#include +#include + +std::vector openFiles; +std::map mountedWUHB; +std::mutex mutex; + +void WUHBUtils_CleanUp() { + std::lock_guard lock(mutex); + for (auto &file : openFiles) { + delete file; + } + openFiles.clear(); + + for (const auto &[name, path] : mountedWUHB) { + romfsUnmount(name.c_str()); + } + mountedWUHB.clear(); +} + +WUHBUtilsApiErrorType WUU_MountBundle(const char *name, const char *path, BundleSource source, int32_t *outRes) { + if (!name || !path || (source != BundleSource_FileDescriptor && source != BundleSource_FileDescriptor_CafeOS)) { + return WUHB_UTILS_API_ERROR_INVALID_ARG; + } + std::lock_guard lock(mutex); + for (const auto &[key, value] : mountedWUHB) { + if (key == name) { + if (value == path) { + *outRes = 0; + return WUHB_UTILS_API_ERROR_NONE; + } + return WUHB_UTILS_API_ERROR_MOUNT_NAME_TAKEN; + } + } + + auto res = romfsMount(name, path, (RomfsSource) source); + if (res == 0) { + mountedWUHB[name] = path; + *outRes = res; + } + + return WUHB_UTILS_API_ERROR_NONE; +} + +WUHBUtilsApiErrorType WUU_UnmountBundle(const char *name, int32_t *outRes) { + if (!name) { + return WUHB_UTILS_API_ERROR_INVALID_ARG; + } + std::lock_guard lock(mutex); + if (mountedWUHB.count(name) > 0) { + auto res = romfsUnmount(name); + if (outRes) { + *outRes = res; + } + mountedWUHB.erase(name); + return WUHB_UTILS_API_ERROR_NONE; + } + return WUHB_UTILS_API_ERROR_MOUNT_NOT_FOUND; +} + +WUHBUtilsApiErrorType WUU_FileOpen(const char *name, uint32_t *outHandle) { + if (!outHandle || !name) { + return WUHB_UTILS_API_ERROR_INVALID_ARG; + } + std::lock_guard lock(mutex); + FileReader *reader; + std::string path = std::string(name); + std::string pathGZ = path + ".gz"; + + if (CheckFile(path.c_str())) { + reader = new (std::nothrow) FileReader(path); + } else if (CheckFile(pathGZ.c_str())) { + reader = new (std::nothrow) FileReaderCompressed(pathGZ); + } else { + return WUHB_UTILS_API_ERROR_FILE_NOT_FOUND; + } + if (reader == nullptr) { + return WUHB_UTILS_API_ERROR_NO_MEMORY; + } + openFiles.push_back(reader); + *outHandle = (uint32_t) reader; + return WUHB_UTILS_API_ERROR_NONE; +} + +WUHBUtilsApiErrorType WUU_FileRead(uint32_t handle, uint8_t *buffer, uint32_t size, int32_t *outRes) { + if (!buffer | !outRes) { + return WUHB_UTILS_API_ERROR_INVALID_ARG; + } + std::lock_guard lock(mutex); + auto found = false; + FileReader *reader; + for (auto &cur : openFiles) { + if ((uint32_t) cur == handle) { + found = true; + reader = cur; + break; + } + } + if (!found) { + return WUHB_UTILS_API_ERROR_FILE_HANDLE_NOT_FOUND; + } + + *outRes = (int32_t) reader->read(buffer, size); + + return WUHB_UTILS_API_ERROR_NONE; +} + +WUHBUtilsApiErrorType WUU_FileClose(uint32_t handle) { + std::lock_guard lock(mutex); + auto count = 0; + auto found = false; + FileReader *reader; + for (auto &cur : openFiles) { + if ((uint32_t) cur == handle) { + found = true; + reader = cur; + break; + } + count++; + } + if (!found) { + return WUHB_UTILS_API_ERROR_FILE_HANDLE_NOT_FOUND; + } + openFiles.erase(openFiles.begin() + count); + delete reader; + return WUHB_UTILS_API_ERROR_NONE; +} + +WUHBUtilsApiErrorType WUU_FileExists(const char *name, int32_t *outRes) { + if (!outRes || !name) { + return WUHB_UTILS_API_ERROR_INVALID_ARG; + } + std::lock_guard lock(mutex); + std::string checkgz = std::string(name) + ".gz"; + *outRes = CheckFile(name) || CheckFile(checkgz.c_str()); + return WUHB_UTILS_API_ERROR_NONE; +} + +WUHBUtilsApiErrorType WUU_GetRPXInfo(const char *path, BundleSource source, WUHBRPXInfo *outFileInfo) { + if (!outFileInfo || !path || (source != BundleSource_FileDescriptor && source != BundleSource_FileDescriptor_CafeOS)) { + return WUHB_UTILS_API_ERROR_INVALID_ARG; + } + std::lock_guard lock(mutex); + + auto res = getRPXInfoForPath(path, source, outFileInfo); + + if (res == WUHB_UTILS_RPX_INFO_MOUNT_FAILED) { + return WUHB_UTILS_API_ERROR_MOUNT_FAILED; + } else if (res == WUHB_UTILS_RPX_INFO_NO_RPX_FOUND || res == WUHB_UTILS_RPX_INFO_OPENDIR_FAILED) { + return WUHB_UTILS_API_ERROR_FILE_NOT_FOUND; + } + + return WUHB_UTILS_API_ERROR_NONE; +} + +uint32_t WUU_GetVersion() { + return WUHB_UTILS_MODULE_VERSION; +} + +WUMS_EXPORT_FUNCTION(WUU_MountBundle); +WUMS_EXPORT_FUNCTION(WUU_UnmountBundle); +WUMS_EXPORT_FUNCTION(WUU_FileOpen); +WUMS_EXPORT_FUNCTION(WUU_FileRead); +WUMS_EXPORT_FUNCTION(WUU_FileClose); +WUMS_EXPORT_FUNCTION(WUU_FileExists); +WUMS_EXPORT_FUNCTION(WUU_GetRPXInfo); +WUMS_EXPORT_FUNCTION(WUU_GetVersion); \ No newline at end of file diff --git a/src/export.h b/src/export.h new file mode 100644 index 0000000..d430565 --- /dev/null +++ b/src/export.h @@ -0,0 +1,10 @@ +#pragma once + +typedef enum WUHBRPXInfoResultCode { + WUHB_UTILS_RPX_INFO_SUCCESS = 0, + WUHB_UTILS_RPX_INFO_MOUNT_FAILED = -1, + WUHB_UTILS_RPX_INFO_OPENDIR_FAILED = -2, + WUHB_UTILS_RPX_INFO_NO_RPX_FOUND = -3, +} WUHBRPXInfoResultCode; + +void WUHBUtils_CleanUp(); diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..3d2d445 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,16 @@ +#include "export.h" +#include "utils/logger.h" +#include + +WUMS_MODULE_EXPORT_NAME("homebrew_wuhb_utils"); + +WUMS_USE_WUT_DEVOPTAB(); + +WUMS_APPLICATION_STARTS() { + initLogging(); +} + +WUMS_APPLICATION_ENDS() { + WUHBUtils_CleanUp(); + deinitLogging(); +} diff --git a/src/utils/FileReader.cpp b/src/utils/FileReader.cpp new file mode 100644 index 0000000..3754dad --- /dev/null +++ b/src/utils/FileReader.cpp @@ -0,0 +1,50 @@ +#include "FileReader.h" +#include "logger.h" +#include + +int64_t FileReader::read(uint8_t *buffer, uint32_t size) { + if (isReadFromBuffer) { + if (input_buffer == nullptr) { + return -1; + } + uint32_t toRead = size; + if (toRead > input_size - input_pos) { + toRead = input_size - input_pos; + } + if (toRead == 0) { + return 0; + } + memcpy(buffer, &input_buffer[input_pos], toRead); + input_pos += toRead; + return toRead; + } else if (isReadFromFile) { + int res = ::read(file_fd, buffer, size); + return res; + } + return -2; +} + +FileReader::FileReader(std::string &path) { + int fd; + if ((fd = open(path.c_str(), O_RDONLY)) >= 0) { + this->isReadFromFile = true; + this->isReadFromBuffer = false; + this->file_fd = fd; + } else { + DEBUG_FUNCTION_LINE("## INFO ## Failed to open file %s", path.c_str()); + } +} + +FileReader::~FileReader() { + if (isReadFromFile) { + ::close(this->file_fd); + } +} + +FileReader::FileReader(uint8_t *buffer, uint32_t size) { + this->input_buffer = buffer; + this->input_size = size; + this->input_pos = 0; + this->isReadFromBuffer = true; + this->isReadFromFile = false; +} diff --git a/src/utils/FileReader.h b/src/utils/FileReader.h new file mode 100644 index 0000000..b6c019a --- /dev/null +++ b/src/utils/FileReader.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include +#include + +class FileReader { + +public: + FileReader(uint8_t *buffer, uint32_t size); + + explicit FileReader(std::string &path); + + virtual ~FileReader(); + + virtual int64_t read(uint8_t *buffer, uint32_t size); + +private: + bool isReadFromBuffer = false; + uint8_t *input_buffer = nullptr; + uint32_t input_size = 0; + uint32_t input_pos = 0; + + bool isReadFromFile = false; + int file_fd = 0; +}; diff --git a/src/utils/FileReaderCompressed.cpp b/src/utils/FileReaderCompressed.cpp new file mode 100644 index 0000000..4e43a40 --- /dev/null +++ b/src/utils/FileReaderCompressed.cpp @@ -0,0 +1,89 @@ +#include "FileReaderCompressed.h" + +int64_t FileReaderCompressed::read(uint8_t *buffer, uint32_t size) { + if (!initDone) { + return -11; + } + uint32_t startValue = this->strm.total_out; + uint32_t newSize = 0; + int ret; + do { + uint32_t nextOut = BUFFER_SIZE; + if (nextOut > size) { + nextOut = size; + } + if (this->strm.avail_in == 0) { + auto read_res = FileReader::read(this->zlib_in_buf, BUFFER_SIZE); + if (read_res <= 0) { + break; + } + this->strm.avail_in = read_res; + this->strm.next_in = this->zlib_in_buf; + } + /* run inflate() on input until output buffer not full */ + do { + if (nextOut > size - newSize) { + nextOut = size - newSize; + } + + this->strm.avail_out = nextOut; + this->strm.next_out = buffer + newSize; + ret = inflate(&this->strm, Z_NO_FLUSH); + + if (ret == Z_STREAM_ERROR) { + DEBUG_FUNCTION_LINE_ERR("Z_STREAM_ERROR"); + return 0; + } + + switch (ret) { + case Z_NEED_DICT: + DEBUG_FUNCTION_LINE_ERR("Z_NEED_DICT"); + ret = Z_DATA_ERROR; + [[fallthrough]]; /* and fall through */ + case Z_DATA_ERROR: + case Z_MEM_ERROR: + DEBUG_FUNCTION_LINE_ERR("Z_MEM_ERROR or Z_DATA_ERROR"); + (void) inflateEnd(&this->strm); + return ret; + default: + break; + } + + newSize = this->strm.total_out - startValue; + if (newSize == size) { + break; + } + nextOut = BUFFER_SIZE; + if (newSize + nextOut >= (size)) { + nextOut = (size) -newSize; + } + } while (this->strm.avail_out == 0 && newSize < (size)); + + /* done when inflate() says it's done */ + } while (ret != Z_STREAM_END && newSize < size); + + return newSize; +} + +FileReaderCompressed::FileReaderCompressed(std::string &file) : FileReader(file) { + this->initCompressedData(); +} + +void FileReaderCompressed::initCompressedData() { + /* allocate inflate state */ + this->strm.zalloc = Z_NULL; + this->strm.zfree = Z_NULL; + this->strm.opaque = Z_NULL; + this->strm.avail_in = 0; + this->strm.next_in = Z_NULL; + int ret = inflateInit2(&this->strm, MAX_WBITS | 16); //gzip + if (ret != Z_OK) { + DEBUG_FUNCTION_LINE_ERR("inflateInit2 failed: %d", ret); + return; + } + initDone = true; +} + +[[maybe_unused]] FileReaderCompressed::FileReaderCompressed(uint8_t *buffer, uint32_t size) : FileReader(buffer, size) { + this->initCompressedData(); +} \ No newline at end of file diff --git a/src/utils/FileReaderCompressed.h b/src/utils/FileReaderCompressed.h new file mode 100644 index 0000000..2e76c91 --- /dev/null +++ b/src/utils/FileReaderCompressed.h @@ -0,0 +1,25 @@ +#pragma once + +#include "FileReader.h" +#include "logger.h" +#include + +#define BUFFER_SIZE 0x20000 + +class FileReaderCompressed : public FileReader { +public: + [[maybe_unused]] FileReaderCompressed(uint8_t *buffer, uint32_t size); + + explicit FileReaderCompressed(std::string &file); + + ~FileReaderCompressed() override = default; + + int64_t read(uint8_t *buffer, uint32_t size) override; + +private: + bool initDone = false; + alignas(0x40) uint8_t zlib_in_buf[BUFFER_SIZE]{}; + z_stream strm{}; + + void initCompressedData(); +}; 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..a3538e1 --- /dev/null +++ b/src/utils/logger.h @@ -0,0 +1,58 @@ +#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) +#else +#define DEBUG_FUNCTION_LINE_VERBOSE(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(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) + +void initLogging(); + +void deinitLogging(); + +#ifdef __cplusplus +} +#endif