From 54750ac330ae06a84a7534ced9c0f71b008e73c9 Mon Sep 17 00:00:00 2001 From: Maschell Date: Tue, 6 Apr 2021 16:58:19 +0200 Subject: [PATCH] first commit --- .github/workflows/ci.yml | 58 +++++++++++ .github/workflows/pr.yml | 17 +++ .gitignore | 11 ++ Dockerfile | 7 ++ Makefile | 139 +++++++++++++++++++++++++ README.md | 54 ++++++++++ src/fs/DirList.cpp | 213 ++++++++++++++++++++++++++++++++++++++ src/fs/DirList.h | 118 +++++++++++++++++++++ src/main.cpp | 18 ++++ src/main.h | 15 +++ src/modpackSelector.cpp | 199 +++++++++++++++++++++++++++++++++++ src/modpackSelector.h | 15 +++ src/utils/StringTools.cpp | 210 +++++++++++++++++++++++++++++++++++++ src/utils/StringTools.h | 88 ++++++++++++++++ src/utils/logger.h | 27 +++++ 15 files changed, 1189 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/pr.yml create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 README.md create mode 100644 src/fs/DirList.cpp create mode 100644 src/fs/DirList.h create mode 100644 src/main.cpp create mode 100644 src/main.h create mode 100644 src/modpackSelector.cpp create mode 100644 src/modpackSelector.h create mode 100644 src/utils/StringTools.cpp create mode 100644 src/utils/StringTools.h create mode 100644 src/utils/logger.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6459e92 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,58 @@ +name: CI-Release + +on: + push: + branches: + - main + +jobs: + build-binary: + runs-on: ubuntu-18.04 + 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: "*.wps" + 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 + path: wiiu/plugins + - name: zip artifact + run: zip -r ${{ env.REPOSITORY_NAME }}_${{ env.DATETIME }}.zip wiiu + - 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/unknown \ No newline at end of file diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..f21e109 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,17 @@ +name: CI-PR + +on: [pull_request] + +jobs: + build-binary: + runs-on: ubuntu-18.04 + 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: "*.wps" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e81da7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +build/* +*.mod +sysapp.layout +sysapp.cbp +sysapp.cscope_file_list +*.wps +*.elf +cmake-build-debug/ +.idea/ +*.rpx +*.txt diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9d1b267 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM wiiuenv/devkitppc:20210101 + +COPY --from=wiiuenv/libmappedmemory:20210403 /artifacts $DEVKITPRO +COPY --from=wiiuenv/librpxloader:20210406 /artifacts $DEVKITPRO +COPY --from=wiiuenv/wiiupluginsystem:20210316 /artifacts $DEVKITPRO + +WORKDIR project \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..33ebf90 --- /dev/null +++ b/Makefile @@ -0,0 +1,139 @@ +#------------------------------------------------------------------------------- +.SUFFIXES: +#------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) + +include $(DEVKITPRO)/wups/share/wups_rules + +WUT_ROOT := $(DEVKITPRO)/wut +WUMS_ROOT := $(DEVKITPRO)/wums + +#------------------------------------------------------------------------------- +# 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 := sdcafiine +BUILD := build +SOURCES := src \ + src/fs \ + src/utils +DATA := data +INCLUDES := src + +#------------------------------------------------------------------------------- +# options for code generation +#------------------------------------------------------------------------------- +CFLAGS := -g -Wall -O2 -ffunction-sections \ + $(MACHDEP) + +CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ -D__WUPS__ + +CXXFLAGS := $(CFLAGS) -std=gnu++17 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -g $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) -T$(WUMS_ROOT)/share/libmappedmemory.ld -T$(WUMS_ROOT)/share/librpxloader.ld $(WUPSSPECS) + +LIBS := -lwups -lwut -lmappedmemory -lrpxloader + +#------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level +# containing include and lib +#------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(WUPS_ROOT) $(WUT_ROOT) $(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).wps $(TARGET).elf + +#------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#------------------------------------------------------------------------------- +# main targets +#------------------------------------------------------------------------------- +all : $(OUTPUT).wps + +$(OUTPUT).wps : $(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) + +-include $(DEPENDS) + +#------------------------------------------------------------------------------- +endif +#------------------------------------------------------------------------------- diff --git a/README.md b/README.md new file mode 100644 index 0000000..93be92a --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# SDCafiine Plugin + +## What is SDCafiine +The main feature of this application is the **on-the-fly replacing of files**, which can be used used to loaded modified content from external media (**SD**). It hooks into the file system functions of the WiiU. Whenever a file is accessed, SDCafiine checks if a (modified) version of it present on the SD device, and redirect the file operations if needed. + +## Dependecies +Requires the [RPXLoadingModule](https://github.com/wiiu-env/RPXLoadingModule) to be loaded. + + +### Installation of the mods +Before the mods can be loaded, they need to be copied to a SD device. +**In the following "root:/" is corresponding to the root of your SD device**. The basic filepath structure is this: + +``` +root:/sdcafiine/[TITLEID]/[MODPACK]/content/ <-- for game files. Maps to /vol/content/ +``` +Replace the following: +- "[TITLEID]" need to be replaced the TitleID of the games that should be modded. A list of can be found [here](http://wiiubrew.org/w/index.php?title=Title_database#00050000:_eShop_and_disc_titles) (without the "-"). Example for SSBU "0005000010145000". Make sure to use the ID of the fullgame and not the update title ID. +- "[MODPACK]" name of the modpack. This folder name can be everything but "content" or "aoc". + +Example path for the EUR version of SuperSmashBros for Wii U: +``` +root:/sdcafiine/0005000010145000/SpecialChars/content/ <-- for game files. Maps to /vol/content/ +``` + +For replacing the file /vol/content/movie/intro.mp4, put a modified file into: +``` +root:/sdcafiine/0005000010145000/SpecialChars/content/movie/intro.mp4 +``` + +*NOTES: paths like "root:/sdcafiine/0005000010145000/content/" are still supported for compatibility, but **not recommended** * + +### Handling multiple mod packs +SDCafiine supports multiple different mods for a single game on the same SDCard. Each mod has an own subfolder. +Example: +``` +sd:/sdcafiine/0005000010145000/ModPack1/content/ +sd:/sdcafiine/0005000010145000/ModPack2/content/ +``` + +## 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 sdcafiineplugin-builder + +# make +docker run -it --rm -v ${PWD}:/project sdcafiineplugin-builder make + +# make clean +docker run -it --rm -v ${PWD}:/project sdcafiineplugin-builder make clean +``` diff --git a/src/fs/DirList.cpp b/src/fs/DirList.cpp new file mode 100644 index 0000000..b92437f --- /dev/null +++ b/src/fs/DirList.cpp @@ -0,0 +1,213 @@ +/**************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * DirList Class + * for WiiXplorer 2010 + ***************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +DirList::DirList() { + Flags = 0; + Filter = 0; + Depth = 0; +} + +DirList::DirList(const std::string &path, const char *filter, uint32_t flags, uint32_t maxDepth) { + this->LoadPath(path, filter, flags, maxDepth); + this->SortList(); +} + +DirList::~DirList() { + ClearList(); +} + +BOOL DirList::LoadPath(const std::string &folder, const char *filter, uint32_t flags, uint32_t maxDepth) { + if (folder.empty()) return false; + + Flags = flags; + Filter = filter; + Depth = maxDepth; + + std::string folderpath(folder); + uint32_t length = folderpath.size(); + + //! clear path of double slashes + StringTools::RemoveDoubleSlashs(folderpath); + + //! remove last slash if exists + if (length > 0 && folderpath[length - 1] == '/') + folderpath.erase(length - 1); + + //! add root slash if missing + if (folderpath.find('/') == std::string::npos) { + folderpath += '/'; + } + + return InternalLoadPath(folderpath); +} + +BOOL DirList::InternalLoadPath(std::string &folderpath) { + if (folderpath.size() < 3) + return false; + + struct dirent *dirent = NULL; + DIR *dir = NULL; + + dir = opendir(folderpath.c_str()); + if (dir == NULL) + return false; + + while ((dirent = readdir(dir)) != 0) { + BOOL isDir = dirent->d_type & DT_DIR; + const char *filename = dirent->d_name; + + if (isDir) { + if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) + continue; + + if ((Flags & CheckSubfolders) && (Depth > 0)) { + int32_t length = folderpath.size(); + if (length > 2 && folderpath[length - 1] != '/') { + folderpath += '/'; + } + folderpath += filename; + + Depth--; + InternalLoadPath(folderpath); + folderpath.erase(length); + Depth++; + } + + if (!(Flags & Dirs)) + continue; + } else if (!(Flags & Files)) { + continue; + } + + if (Filter) { + char *fileext = strrchr(filename, '.'); + if (!fileext) + continue; + + if (StringTools::strtokcmp(fileext, Filter, ",") == 0) + AddEntrie(folderpath, filename, isDir); + } else { + AddEntrie(folderpath, filename, isDir); + } + } + closedir(dir); + + return true; +} + +void DirList::AddEntrie(const std::string &filepath, const char *filename, BOOL isDir) { + if (!filename) + return; + + int32_t pos = FileInfo.size(); + + FileInfo.resize(pos + 1); + + FileInfo[pos].FilePath = (char *) malloc(filepath.size() + strlen(filename) + 2); + if (!FileInfo[pos].FilePath) { + FileInfo.resize(pos); + return; + } + + sprintf(FileInfo[pos].FilePath, "%s/%s", filepath.c_str(), filename); + FileInfo[pos].isDir = isDir; +} + +void DirList::ClearList() { + for (uint32_t i = 0; i < FileInfo.size(); ++i) { + if (FileInfo[i].FilePath) { + free(FileInfo[i].FilePath); + FileInfo[i].FilePath = NULL; + } + } + + FileInfo.clear(); + std::vector().swap(FileInfo); +} + +const char *DirList::GetFilename(int32_t ind) const { + if (!valid(ind)) + return ""; + + return StringTools::FullpathToFilename(FileInfo[ind].FilePath); +} + +static BOOL SortCallback(const DirEntry &f1, const DirEntry &f2) { + if (f1.isDir && !(f2.isDir)) return true; + if (!(f1.isDir) && f2.isDir) return false; + + if (f1.FilePath && !f2.FilePath) return true; + if (!f1.FilePath) return false; + + if (strcasecmp(f1.FilePath, f2.FilePath) > 0) + return false; + + return true; +} + +void DirList::SortList() { + if (FileInfo.size() > 1) + std::sort(FileInfo.begin(), FileInfo.end(), SortCallback); +} + +void DirList::SortList(BOOL (*SortFunc)(const DirEntry &a, const DirEntry &b)) { + if (FileInfo.size() > 1) + std::sort(FileInfo.begin(), FileInfo.end(), SortFunc); +} + +uint64_t DirList::GetFilesize(int32_t index) const { + struct stat st; + const char *path = GetFilepath(index); + + if (!path || stat(path, &st) != 0) + return 0; + + return st.st_size; +} + +int32_t DirList::GetFileIndex(const char *filename) const { + if (!filename) + return -1; + + for (uint32_t i = 0; i < FileInfo.size(); ++i) { + if (strcasecmp(GetFilename(i), filename) == 0) + return i; + } + + return -1; +} diff --git a/src/fs/DirList.h b/src/fs/DirList.h new file mode 100644 index 0000000..ed36f97 --- /dev/null +++ b/src/fs/DirList.h @@ -0,0 +1,118 @@ +/**************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * DirList Class + * for WiiXplorer 2010 + ***************************************************************************/ +#ifndef ___DIRLIST_H_ +#define ___DIRLIST_H_ + +#include +#include +#include + +typedef struct { + char *FilePath; + BOOL isDir; +} DirEntry; + +class DirList { +public: + //!Constructor + DirList(void); + + //!\param path Path from where to load the filelist of all files + //!\param filter A fileext that needs to be filtered + //!\param flags search/filter flags from the enum + DirList(const std::string &path, const char *filter = NULL, uint32_t flags = Files | Dirs, uint32_t maxDepth = 0xffffffff); + + //!Destructor + virtual ~DirList(); + + //! Load all the files from a directory + BOOL LoadPath(const std::string &path, const char *filter = NULL, uint32_t flags = Files | Dirs, uint32_t maxDepth = 0xffffffff); + + //! Get a filename of the list + //!\param list index + const char *GetFilename(int32_t index) const; + + //! Get the a filepath of the list + //!\param list index + const char *GetFilepath(int32_t index) const { + if (!valid(index)) return ""; + else return FileInfo[index].FilePath; + } + + //! Get the a filesize of the list + //!\param list index + uint64_t GetFilesize(int32_t index) const; + + //! Is index a dir or a file + //!\param list index + BOOL IsDir(int32_t index) const { + if (!valid(index)) return false; + return FileInfo[index].isDir; + }; + + //! Get the filecount of the whole list + int32_t GetFilecount() const { + return FileInfo.size(); + }; + + //! Sort list by filepath + void SortList(); + + //! Custom sort command for custom sort functions definitions + void SortList(BOOL (*SortFunc)(const DirEntry &a, const DirEntry &b)); + + //! Get the index of the specified filename + int32_t GetFileIndex(const char *filename) const; + + //! Enum for search/filter flags + enum { + Files = 0x01, + Dirs = 0x02, + CheckSubfolders = 0x08, + }; +protected: + // Internal parser + BOOL InternalLoadPath(std::string &path); + + //!Add a list entrie + void AddEntrie(const std::string &filepath, const char *filename, BOOL isDir); + + //! Clear the list + void ClearList(); + + //! Check if valid pos is requested + inline BOOL valid(uint32_t pos) const { + return (pos < FileInfo.size()); + }; + + uint32_t Flags; + uint32_t Depth; + const char *Filter; + std::vector FileInfo; +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..e701464 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,18 @@ +#include +#include +#include "modpackSelector.h" +#include + +WUPS_PLUGIN_NAME("SDCafiine"); +WUPS_PLUGIN_DESCRIPTION("Wiiload Server"); +WUPS_PLUGIN_VERSION("0.1"); +WUPS_PLUGIN_AUTHOR("Maschell"); +WUPS_PLUGIN_LICENSE("GPL"); + +WUPS_USE_WUT_DEVOPTAB() + +/* Entry point */ +ON_APPLICATION_START() { + WHBLogUdpInit(); + HandleMultiModPacks(OSGetTitleID()); +} \ No newline at end of file diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..67b89ad --- /dev/null +++ b/src/main.h @@ -0,0 +1,15 @@ +#pragma once + +/* Main */ +#ifdef __cplusplus +extern "C" { +#endif +#include +#include + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/modpackSelector.cpp b/src/modpackSelector.cpp new file mode 100644 index 0000000..7348370 --- /dev/null +++ b/src/modpackSelector.cpp @@ -0,0 +1,199 @@ +#include +#include +#include +#include +#include +#include +#include +#include "modpackSelector.h" + +#include +#include + +#include +#include +#include +#include +#include + +#define TEXT_SEL(x, text1, text2) ((x) ? (text1) : (text2)) + + +void ReplaceContent(const std::string &basePath); + +void HandleMultiModPacks(uint64_t titleID) { + char TitleIDString[17]; + snprintf(TitleIDString, 17, "%016llX", titleID); + + std::map modTitlePath; + + std::map mounting_points; + + + std::string modTitleIDPath = std::string("fs:/vol/external01/sdcafiine/") + TitleIDString; + DirList modTitleDirList(modTitleIDPath.c_str(), nullptr, DirList::Dirs); + + modTitleDirList.SortList(); + + for (int index = 0; index < modTitleDirList.GetFilecount(); index++) { + std::string curFile = modTitleDirList.GetFilename(index); + //DEBUG_FUNCTION_LINE("curFile %s \n",curFile.c_str()); + if (curFile.compare(".") == 0 || curFile.compare("..") == 0) { + continue; + } + + std::string packageName = curFile; + modTitlePath[packageName] = modTitleIDPath.append("/").append(curFile); + DEBUG_FUNCTION_LINE("found %s", packageName.c_str()); + } + + if (modTitlePath.empty()) { + return; + } + + int selected = 0; + int initScreen = 1; + int x_offset = -2; + + // Init screen and screen buffers + OSScreenInit(); + uint32_t screen_buf0_size = OSScreenGetBufferSizeEx(SCREEN_TV); + uint32_t screen_buf1_size = OSScreenGetBufferSizeEx(SCREEN_DRC); + uint8_t *screenBuffer = (uint8_t *) MEMAllocFromMappedMemoryForGX2Ex(screen_buf0_size + screen_buf1_size, 0x100 + ); + if (screenBuffer == nullptr) { + DEBUG_FUNCTION_LINE("Failed to alloc"); + return; + } + OSScreenSetBufferEx(SCREEN_TV, (void *) screenBuffer); + OSScreenSetBufferEx(SCREEN_DRC, (void *) (screenBuffer + screen_buf0_size)); + + OSScreenEnableEx(SCREEN_TV, 1); + OSScreenEnableEx(SCREEN_DRC, 1); + + // Clear screens + OSScreenClearBufferEx(SCREEN_TV, 0); + OSScreenClearBufferEx(SCREEN_DRC, 0); + + OSScreenFlipBuffersEx(SCREEN_TV); + OSScreenFlipBuffersEx(SCREEN_DRC); + + VPADStatus vpad_data; + VPADReadError error; + + int wantToExit = 0; + int page = 0; + int per_page = 13; + int max_pages = (modTitlePath.size() / per_page) + 1; + + while (true) { + + error = VPAD_READ_NO_SAMPLES; + VPADRead(VPAD_CHAN_0, &vpad_data, 1, &error); + + if (error == VPAD_READ_SUCCESS) { + if (vpad_data.trigger & VPAD_BUTTON_A) { + wantToExit = 1; + initScreen = 1; + } else if (vpad_data.trigger & VPAD_BUTTON_B) { + break; + } else if (vpad_data.trigger & VPAD_BUTTON_DOWN) { + selected++; + initScreen = 1; + } else if (vpad_data.trigger & VPAD_BUTTON_UP) { + selected--; + initScreen = 1; + } else if (vpad_data.trigger & VPAD_BUTTON_L) { + selected -= per_page; + initScreen = 1; + } else if (vpad_data.trigger & VPAD_BUTTON_R) { + selected += per_page; + initScreen = 1; + } + if (selected < 0) { selected = 0; } + if (selected >= modTitlePath.size()) { selected = modTitlePath.size() - 1; } + page = selected / per_page; + } + + if (initScreen) { + OSScreenClearBufferEx(SCREEN_TV, 0); + OSScreenClearBufferEx(SCREEN_DRC, 0); + console_print_pos(x_offset, -1, " -- SDCafiine plugin %s by Maschell --"); + console_print_pos(x_offset, 1, "Select your options and press A to launch."); + console_print_pos(x_offset, 2, "Press B to launch without mods"); + int y_offset = 4; + int cur_ = 0; + + for (auto &it : modTitlePath) { + std::string key = it.first; + std::string value = it.second; + + if (wantToExit && cur_ == selected) { + ReplaceContent(value.append("/content")); + //snprintf(gModFolder, FS_MAX_ENTNAME_SIZE, "%s", value.c_str()); + break; + } + + if (cur_ >= (page * per_page) && cur_ < ((page + 1) * per_page)) { + console_print_pos(x_offset, y_offset++, "%s %s", TEXT_SEL((selected == cur_), "--->", " "), key.c_str()); + } + cur_++; + } + + if (wantToExit) { //just in case. + break; + } + + if (max_pages > 0) { + console_print_pos(x_offset, 17, "Page %02d/%02d. Press L/R to change page.", page + 1, max_pages); + } + + // Flip buffers + OSScreenFlipBuffersEx(SCREEN_TV); + OSScreenFlipBuffersEx(SCREEN_DRC); + + initScreen = 0; + } + OSSleepTicks(OSMillisecondsToTicks(100)); + } + + OSScreenClearBufferEx(SCREEN_TV, 0); + OSScreenClearBufferEx(SCREEN_DRC, 0); + + // Flip buffers + OSScreenFlipBuffersEx(SCREEN_TV); + OSScreenFlipBuffersEx(SCREEN_DRC); + + MEMFreeToMappedMemory(screenBuffer); + + return; +} + +void ReplaceContent(const std::string &basePath) { + if (RL_RedirectContentWithFallback(basePath.c_str())) { + DEBUG_FUNCTION_LINE("redirect /vol/content to %s", basePath.c_str()); + } else { + DEBUG_FUNCTION_LINE("ERROR: Failed to redirect /vol/content to %s", basePath.c_str()); + } +} + +void console_print_pos(int x, int y, const char *format, ...) { + char *tmp = NULL; + + va_list va; + va_start(va, format); + if ((vasprintf(&tmp, format, va) >= 0) && tmp) { + if (strlen(tmp) > 79) { + tmp[79] = 0; + } + + OSScreenPutFontEx(SCREEN_TV, x, y, tmp); + OSScreenPutFontEx(SCREEN_DRC, x, y, tmp); + + } + va_end(va); + + if (tmp) { + free(tmp); + } +} diff --git a/src/modpackSelector.h b/src/modpackSelector.h new file mode 100644 index 0000000..bb4763f --- /dev/null +++ b/src/modpackSelector.h @@ -0,0 +1,15 @@ +#ifndef _MODPACK_SELECTOR_H_ +#define _MODPACK_SELECTOR_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +void HandleMultiModPacks(uint64_t titleid/*,bool showMenu = true*/); +void console_print_pos(int x, int y, const char *format, ...); + +#ifdef __cplusplus +} +#endif + +#endif //_MODPACK_SELECTOR_H_ diff --git a/src/utils/StringTools.cpp b/src/utils/StringTools.cpp new file mode 100644 index 0000000..39a5273 --- /dev/null +++ b/src/utils/StringTools.cpp @@ -0,0 +1,210 @@ +/*************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * for WiiXplorer 2010 + ***************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +BOOL StringTools::EndsWith(const std::string &a, const std::string &b) { + if (b.size() > a.size()) return false; + return std::equal(a.begin() + a.size() - b.size(), a.end(), b.begin()); +} + +const char *StringTools::byte_to_binary(int32_t x) { + static char b[9]; + b[0] = '\0'; + + int32_t z; + for (z = 128; z > 0; z >>= 1) { + strcat(b, ((x & z) == z) ? "1" : "0"); + } + + return b; +} + +std::string StringTools::removeCharFromString(std::string &input, char toBeRemoved) { + std::string output = input; + size_t position; + while (1) { + position = output.find(toBeRemoved); + if (position == std::string::npos) + break; + output.erase(position, 1); + } + return output; +} + +const char *StringTools::fmt(const char *format, ...) { + static char strChar[512]; + strChar[0] = 0; + + va_list va; + va_start(va, format); + if ((vsprintf(strChar, format, va) >= 0)) { + va_end(va); + return (const char *) strChar; + } + va_end(va); + + return NULL; +} + +const wchar_t *StringTools::wfmt(const char *format, ...) { + static char tmp[512]; + static wchar_t strWChar[512]; + strWChar[0] = 0; + tmp[0] = 0; + + if (!format) + return (const wchar_t *) strWChar; + + if (strcmp(format, "") == 0) + return (const wchar_t *) strWChar; + + va_list va; + va_start(va, format); + if ((vsprintf(tmp, format, va) >= 0)) { + int bt; + int32_t strlength = strlen(tmp); + bt = mbstowcs(strWChar, tmp, (strlength < 512) ? strlength : 512); + + if (bt > 0) { + strWChar[bt] = 0; + return (const wchar_t *) strWChar; + } + } + va_end(va); + + return NULL; +} + +int32_t StringTools::strprintf(std::string &str, const char *format, ...) { + static char tmp[512]; + tmp[0] = 0; + int32_t result = 0; + + va_list va; + va_start(va, format); + if ((vsprintf(tmp, format, va) >= 0)) { + str = tmp; + result = str.size(); + } + va_end(va); + + return result; +} + +std::string StringTools::strfmt(const char *format, ...) { + std::string str; + static char tmp[512]; + tmp[0] = 0; + + va_list va; + va_start(va, format); + if ((vsprintf(tmp, format, va) >= 0)) { + str = tmp; + } + va_end(va); + + return str; +} + +BOOL StringTools::char2wchar_t(const char *strChar, wchar_t *dest) { + if (!strChar || !dest) + return false; + + int bt; + bt = mbstowcs(dest, strChar, strlen(strChar)); + if (bt > 0) { + dest[bt] = 0; + return true; + } + + return false; +} + +int32_t StringTools::strtokcmp(const char *string, const char *compare, const char *separator) { + if (!string || !compare) + return -1; + + char TokCopy[512]; + strncpy(TokCopy, compare, sizeof(TokCopy)); + TokCopy[511] = '\0'; + + char *strTok = strtok(TokCopy, separator); + + while (strTok != NULL) { + if (strcasecmp(string, strTok) == 0) { + return 0; + } + strTok = strtok(NULL, separator); + } + + return -1; +} + +int32_t StringTools::strextcmp(const char *string, const char *extension, char seperator) { + if (!string || !extension) + return -1; + + char *ptr = strrchr(string, seperator); + if (!ptr) + return -1; + + return strcasecmp(ptr + 1, extension); +} + + +std::vector StringTools::stringSplit(const std::string &inValue, const std::string &splitter) { + std::string value = inValue; + std::vector result; + while (true) { + uint32_t index = value.find(splitter); + if (index == std::string::npos) { + result.push_back(value); + break; + } + std::string first = value.substr(0, index); + result.push_back(first); + if (index + splitter.size() == value.length()) { + result.push_back(""); + break; + } + if (index + splitter.size() > value.length()) { + break; + } + value = value.substr(index + splitter.size(), value.length()); + } + return result; +} diff --git a/src/utils/StringTools.h b/src/utils/StringTools.h new file mode 100644 index 0000000..5d9f2c4 --- /dev/null +++ b/src/utils/StringTools.h @@ -0,0 +1,88 @@ +/*************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * for WiiXplorer 2010 + ***************************************************************************/ +#ifndef __STRING_TOOLS_H +#define __STRING_TOOLS_H + +#include +#include +#include + +class StringTools { +public: + static BOOL EndsWith(const std::string &a, const std::string &b); + + static const char *byte_to_binary(int32_t x); + + static std::string removeCharFromString(std::string &input, char toBeRemoved); + + static const char *fmt(const char *format, ...); + + static const wchar_t *wfmt(const char *format, ...); + + static int32_t strprintf(std::string &str, const char *format, ...); + + static std::string strfmt(const char *format, ...); + + static BOOL char2wchar_t(const char *src, wchar_t *dest); + + static int32_t strtokcmp(const char *string, const char *compare, const char *separator); + + static int32_t strextcmp(const char *string, const char *extension, char seperator); + + static const char *FullpathToFilename(const char *path) { + if (!path) return path; + + const char *ptr = path; + const char *Filename = ptr; + + while (*ptr != '\0') { + if (ptr[0] == '/' && ptr[1] != '\0') + Filename = ptr + 1; + + ++ptr; + } + + return Filename; + } + + static void RemoveDoubleSlashs(std::string &str) { + uint32_t length = str.size(); + + //! clear path of double slashes + for (uint32_t i = 1; i < length; ++i) { + if (str[i - 1] == '/' && str[i] == '/') { + str.erase(i, 1); + i--; + length--; + } + } + } + + static std::vector stringSplit(const std::string &value, const std::string &splitter); +}; + +#endif /* __STRING_TOOLS_H */ + diff --git a/src/utils/logger.h b/src/utils/logger.h new file mode 100644 index 0000000..95f28f9 --- /dev/null +++ b/src/utils/logger.h @@ -0,0 +1,27 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define __FILENAME_X__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILENAME_X__) + +#define OSFATAL_FUNCTION_LINE(FMT, ARGS...)do { \ + OSFatal_printf("[%s]%s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \ + } while (0) + +#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); + +#ifdef __cplusplus +} +#endif