diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index b12eb1c..57de4c8 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -30,6 +30,7 @@ jobs: - name: clang-format run: | docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./plugins/example_plugin/src + docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./plugins/example_plugin_cpp/src docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./plugins/storage_test_plugin/src --exclude ./plugins/storage_test_plugin/src/catch2 build-examples: runs-on: ubuntu-22.04 @@ -40,8 +41,10 @@ jobs: run: | docker build . -f Dockerfile.buildexamples -t builder cd ./plugins/example_plugin - docker run --rm -v ${PWD}:/project builder make - cd ../storage_test_plugin + docker run --rm -v ${PWD}:/project builder make + cd ../example_plugin_cpp + docker run --rm -v ${PWD}:/project builder make + cd ../storage_test_plugin docker run --rm -v ${PWD}:/project builder make - uses: actions/upload-artifact@master with: diff --git a/.github/workflows/push_image.yml b/.github/workflows/push_image.yml index f72a249..b46eea4 100644 --- a/.github/workflows/push_image.yml +++ b/.github/workflows/push_image.yml @@ -33,6 +33,8 @@ jobs: - name: clang-format run: | docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./plugins/storage_test_plugin/src --exclude ./plugins/storage_test_plugin/src/catch2 + docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./plugins/example_plugin/src + docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./plugins/example_plugin_cpp/src build-examples: runs-on: ubuntu-22.04 needs: clang-format-examples @@ -42,8 +44,10 @@ jobs: run: | docker build . -f Dockerfile.buildexamples -t builder cd ./plugins/example_plugin - docker run --rm -v ${PWD}:/project builder make - cd ../storage_test_plugin + docker run --rm -v ${PWD}:/project builder make + cd ../example_plugin_cpp + docker run --rm -v ${PWD}:/project builder make + cd ../storage_test_plugin docker run --rm -v ${PWD}:/project builder make build-and-push-image: runs-on: ubuntu-latest diff --git a/README.MD b/README.MD index 171ba08..69f9b5c 100644 --- a/README.MD +++ b/README.MD @@ -28,4 +28,4 @@ It's highly recommended to pin the version to the **latest date** instead of usi ## Format the code via docker -`docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./include ./libraries ./plugins/example_plugin/src ./plugins/storage_test_plugin/src --exclude ./plugins/storage_test_plugin/src/catch2 -i` +`docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./include ./libraries ./plugins/example_plugin/src ./plugins/example_plugin_cpp/src ./plugins/storage_test_plugin/src --exclude ./plugins/storage_test_plugin/src/catch2 -i` diff --git a/plugins/example_plugin/src/main.c b/plugins/example_plugin/src/main.c index 2a62259..766d0b3 100644 --- a/plugins/example_plugin/src/main.c +++ b/plugins/example_plugin/src/main.c @@ -1,10 +1,12 @@ #include "utils/logger.h" #include #include +#include #include #include #include #include +#include /** Mandatory plugin information. @@ -56,7 +58,7 @@ WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback(WUPSConfigCategoryHandle ro } // Add a new item to this settings category - if (!WUPSConfigItemBoolean_AddToCategory(settingsCategory, LOG_FS_OPEN_CONFIG_ID, "Log FSOpen calls", logFSOpen, &logFSOpenChanged)) { + if (WUPSConfigItemBoolean_AddToCategory(settingsCategory, LOG_FS_OPEN_CONFIG_ID, "Log FSOpen calls", true, logFSOpen, &logFSOpenChanged) != WUPSCONFIG_API_RESULT_SUCCESS) { DEBUG_FUNCTION_LINE_ERR("Failed to add item to category"); return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; } @@ -81,7 +83,7 @@ WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback(WUPSConfigCategoryHandle ro DEBUG_FUNCTION_LINE_ERR("Failed to create categoryLevel1"); return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; } - if (!WUPSConfigItemBoolean_AddToCategory(categoryLevel2, "stubInsideCategory", "This is stub item inside a nested category", false, NULL)) { + if (WUPSConfigItemBoolean_AddToCategory(categoryLevel2, "stubInsideCategory", "This is stub item inside a nested category", false, false, NULL) != WUPSCONFIG_API_RESULT_SUCCESS) { DEBUG_FUNCTION_LINE_ERR("Failed to add stub item to root category"); return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; } @@ -98,10 +100,9 @@ WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback(WUPSConfigCategoryHandle ro return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; } } - { // We can also directly add items to the root category - if (!WUPSConfigItemBoolean_AddToCategory(root, "stub0", "This is stub item without category", false, NULL)) { + if (WUPSConfigItemStub_AddToCategory(root, "This is stub item without category") != WUPSCONFIG_API_RESULT_SUCCESS) { DEBUG_FUNCTION_LINE_ERR("Failed to add stub item to root category"); return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; } @@ -117,11 +118,11 @@ WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback(WUPSConfigCategoryHandle ro values[i].value = i; values[i].valueName = str; } - bool multValuesRes = WUPSConfigItemMultipleValues_AddToCategory(root, "multival", "Multiple values", 0, values, numOfElements, NULL); + WUPSConfigAPIStatus multValuesRes = WUPSConfigItemMultipleValues_AddToCategory(root, "multival", "Multiple values", 0, 0, values, numOfElements, NULL); for (int i = 0; i < sizeof(values) / sizeof(values[0]); i++) { - free(values[i].valueName); + free((void *) values[i].valueName); } - if (!multValuesRes) { + if (multValuesRes != WUPSCONFIG_API_RESULT_SUCCESS) { return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; } } diff --git a/plugins/example_plugin_cpp/.clang-format b/plugins/example_plugin_cpp/.clang-format new file mode 100644 index 0000000..56cc685 --- /dev/null +++ b/plugins/example_plugin_cpp/.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/plugins/example_plugin_cpp/Dockerfile b/plugins/example_plugin_cpp/Dockerfile new file mode 100644 index 0000000..b9bbafb --- /dev/null +++ b/plugins/example_plugin_cpp/Dockerfile @@ -0,0 +1,5 @@ +FROM ghcr.io/wiiu-env/devkitppc:20230218 + +COPY --from=ghcr.io/wiiu-env/wiiupluginsystem:20230215 /artifacts $DEVKITPRO + +WORKDIR project \ No newline at end of file diff --git a/plugins/example_plugin_cpp/Makefile b/plugins/example_plugin_cpp/Makefile new file mode 100644 index 0000000..1860d5c --- /dev/null +++ b/plugins/example_plugin_cpp/Makefile @@ -0,0 +1,135 @@ +#------------------------------------------------------------------------------- +.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 +#------------------------------------------------------------------------------- +# 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 := ExamplePluginCPP +BUILD := build +SOURCES := src 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=c++20 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -g $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) $(WUPSSPECS) + +LIBS := -lwups -lwut + +#------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level +# containing include and lib +#------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(WUPS_ROOT) $(WUT_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): + @$(shell [ ! -d $(BUILD) ] && mkdir -p $(BUILD)) + @$(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/plugins/example_plugin_cpp/README.md b/plugins/example_plugin_cpp/README.md new file mode 100644 index 0000000..b68d161 --- /dev/null +++ b/plugins/example_plugin_cpp/README.md @@ -0,0 +1,57 @@ +# Example plugin + +This is just a simple example plugin which can be used as a template. +The plugin logs the FSOpenFile calls via UDP (**Only when build via `make DEBUG=1`**). + +The logging can be enabled/disabled via the WUPS Config menu (press L, DPAD Down and Minus on the GamePad, Pro Controller or Classic Controller). + +## Installation + +(`[ENVIRONMENT]` is a placeholder for the actual environment name.) + +1. Copy the file `ExamplePlugin.wps` into `sd:/wiiu/environments/[ENVIRONMENT]/plugins`. +2. Requires the [WiiUPluginLoaderBackend](https://github.com/wiiu-env/WiiUPluginLoaderBackend) in `sd:/wiiu/environments/[ENVIRONMENT]/modules`. + +Start the environment (e.g Aroma) and the backend should load the plugin. + +## Building + +For building you need: + +- [wups](https://github.com/Maschell/WiiUPluginSystem) +- [wut](https://github.com/devkitpro/wut) + +Install them (in this order) according to their README's. Don't forget the dependencies of the libs itself. + +Then you should be able to compile via `make` (with no logging) or `make DEBUG=1` (with logging). + +## Buildflags + +### Logging + +Building via `make` only logs errors (via OSReport). To enable logging via the [LoggingModule](https://github.com/wiiu-env/LoggingModule) set `DEBUG` to `1` or `VERBOSE`. + +`make` Logs errors only (via OSReport). +`make DEBUG=1` Enables information and error logging via [LoggingModule](https://github.com/wiiu-env/LoggingModule). +`make DEBUG=VERBOSE` Enables verbose information and error logging via [LoggingModule](https://github.com/wiiu-env/LoggingModule). + +If the [LoggingModule](https://github.com/wiiu-env/LoggingModule) is not present, it'll fallback to UDP (Port 4405) and [CafeOS](https://github.com/wiiu-env/USBSerialLoggingModule) logging. + +## 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 example-plugin-builder + +# make +docker run -it --rm -v ${PWD}:/project example-plugin-builder make DEBUG=1 + +# make clean +docker run -it --rm -v ${PWD}:/project example-plugin-builder make clean +``` + +## Format the code via docker + +`docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./src -i` \ No newline at end of file diff --git a/plugins/example_plugin_cpp/src/main.cpp b/plugins/example_plugin_cpp/src/main.cpp new file mode 100644 index 0000000..af62e94 --- /dev/null +++ b/plugins/example_plugin_cpp/src/main.cpp @@ -0,0 +1,306 @@ +#include "utils/logger.h" +#include +#include +#include +#include +#include +#include +#include +#include + +/** + Mandatory plugin information. + If not set correctly, the loader will refuse to use the plugin. +**/ +WUPS_PLUGIN_NAME("Example plugin C++"); +WUPS_PLUGIN_DESCRIPTION("This is just an example plugin written in C++"); +WUPS_PLUGIN_VERSION("v1.0"); +WUPS_PLUGIN_AUTHOR("Maschell"); +WUPS_PLUGIN_LICENSE("BSD"); + +#define LOG_FS_OPEN_CONFIG_ID "logFSOpen" +#define OTHER_EXAMPLE_BOOL_CONFIG_ID "otherBoolItem" +#define OTHER_EXAMPLE2_BOOL_CONFIG_ID "other2BoolItem" +#define INTEGER_RANGE_EXAMPLE_CONFIG_ID "intRangeExample" +#define MULTIPLE_VALUES_EXAMPLE_CONFIG_ID "multValueExample" + +/** + All of this defines can be used in ANY file. + It's possible to split it up into multiple files. + +**/ + +WUPS_USE_WUT_DEVOPTAB(); // Use the wut devoptabs +WUPS_USE_STORAGE("example_plugin_cpp"); // Unique id for the storage api + +enum ExampleOptions { + EXAMPLE_OPTION_1 = 0, + EXAMPLE_OPTION_2 = 1, + EXAMPLE_OPTION_3 = 2, +}; + +#define LOF_FS_OPEN_DEFAULT_VALUE true +#define INTEGER_RANGE_DEFAULT_VALUE 10 +#define MULTIPLE_VALUES_DEFAULT_VALUE EXAMPLE_OPTION_2 + +bool sLogFSOpen = LOF_FS_OPEN_DEFAULT_VALUE; +int sIntegerRangeValue = INTEGER_RANGE_DEFAULT_VALUE; +ExampleOptions sExampleOptionValue = MULTIPLE_VALUES_DEFAULT_VALUE; + +/** + * Callback that will be called if the config has been changed + */ +void boolItemChanged(ConfigItemBoolean *item, bool newValue) { + DEBUG_FUNCTION_LINE_INFO("New value in boolItemChanged: %d", newValue); + if (std::string_view(LOG_FS_OPEN_CONFIG_ID) == item->identifier) { + sLogFSOpen = newValue; + // If the value has changed, we store it in the storage. + WUPS_StoreInt(nullptr, item->identifier, newValue); + } else if (std::string_view(OTHER_EXAMPLE_BOOL_CONFIG_ID) == item->identifier) { + DEBUG_FUNCTION_LINE_ERR("Other bool value has changed to %d", newValue); + } else if (std::string_view(OTHER_EXAMPLE2_BOOL_CONFIG_ID) == item->identifier) { + DEBUG_FUNCTION_LINE_ERR("Other2 bool value has changed to %d", newValue); + } +} + +void integerRangeItemChanged(ConfigItemIntegerRange *item, int newValue) { + DEBUG_FUNCTION_LINE_INFO("New value in integerRangeItemChanged: %d", newValue); + // If the value has changed, we store it in the storage. + if (std::string_view(LOG_FS_OPEN_CONFIG_ID) == item->identifier) { + sIntegerRangeValue = newValue; + // If the value has changed, we store it in the storage. + WUPS_StoreInt(nullptr, item->identifier, newValue); + } +} + +void multipleValueItemChanged(ConfigItemIntegerRange *item, uint32_t newValue) { + DEBUG_FUNCTION_LINE_INFO("New value in multipleValueItemChanged: %d", newValue); + // If the value has changed, we store it in the storage. + if (std::string_view(MULTIPLE_VALUES_EXAMPLE_CONFIG_ID) == item->identifier) { + sExampleOptionValue = (ExampleOptions) newValue; + // If the value has changed, we store it in the storage. + WUPS_StoreInt(nullptr, item->identifier, sExampleOptionValue); + } +} + +WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback(WUPSConfigCategoryHandle rootHandle) { + // We open the storage, so we can persist the configuration the user did. + if (WUPS_OpenStorage() != WUPS_STORAGE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE("Failed to open storage"); + return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; + } + // To use the C++ API, we create new WUPSConfigCategory from the root handle! + WUPSConfigCategory root = WUPSConfigCategory(rootHandle); + + // The functions of the Config API come in two variants: One that throws an exception, and another one which doesn't + // To use the Config API without exception see the example below this try/catch block. + try { + // Then we can simply create a new category + auto functionPatchesCat = WUPSConfigCategory::Create("function patches"); + + // Add a boolean item to this newly created category + functionPatchesCat.add(WUPSConfigItemBoolean::Create(LOG_FS_OPEN_CONFIG_ID, "Log FSOpen calls", + LOF_FS_OPEN_DEFAULT_VALUE, sLogFSOpen, + boolItemChanged)); + + // And finally move that category to the root category. + // Note: "functionPatchesCat" can NOT be changed after adding it to root. + root.add(std::move(functionPatchesCat)); + + // We can also add items directly to root! + root.add(WUPSConfigItemBoolean::Create(OTHER_EXAMPLE_BOOL_CONFIG_ID, "Just another bool item", + false, false, + boolItemChanged)); + + // You can also add an item which just displays any text. + root.add(WUPSConfigItemStub::Create("This item is just displaying some text")); + + // It's also possible to create and item to select an integer from a range. + root.add(WUPSConfigItemIntegerRange::Create(INTEGER_RANGE_EXAMPLE_CONFIG_ID, "Item for selecting an integer between 0 and 50", + INTEGER_RANGE_DEFAULT_VALUE, sIntegerRangeValue, + 0, 50, + &integerRangeItemChanged)); + + + // To select value from an enum WUPSConfigItemMultipleValues fits the best. + constexpr WUPSConfigItemMultipleValues::ValuePair possibleValues[] = { + {EXAMPLE_OPTION_1, "Option 1"}, + {EXAMPLE_OPTION_2, "Option 2"}, + {EXAMPLE_OPTION_3, "Option 3"}, + }; + + // It comes in two variants. + // - "WUPSConfigItemMultipleValues::CreateFromValue" will take a default and current **value** + // - "WUPSConfigItemMultipleValues::CreateFromIndex" will take a default and current **index** + root.add(WUPSConfigItemMultipleValues::CreateFromValue(MULTIPLE_VALUES_EXAMPLE_CONFIG_ID, "Select an option!", + MULTIPLE_VALUES_DEFAULT_VALUE, sExampleOptionValue, + possibleValues, + nullptr)); + + // It's also possible to have nested categories + auto nc1 = WUPSConfigCategory::Create("Category inside root"); + auto nc2 = WUPSConfigCategory::Create("Category inside subcategory 1"); + auto nc3 = WUPSConfigCategory::Create("Category inside subcategory 2"); + + nc3.add(WUPSConfigItemStub::Create("Item inside subcategory 3")); + nc2.add(WUPSConfigItemStub::Create("Item inside subcategory 2")); + nc1.add(WUPSConfigItemStub::Create("Item inside subcategory 1")); + + nc2.add(std::move(nc3)); + nc1.add(std::move(nc2)); + root.add(std::move(nc1)); + } catch (std::exception &e) { + DEBUG_FUNCTION_LINE_ERR("Creating config menu failed: %s", e.what()); + return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; + } + + // In case we don't like exception, we can use the API as well. + // If we add a "WUPSConfigAPIStatus" reference to the API calls, the function won't throw an exception. + // Instead it will return std::optionals and write the result into the WUPSConfigAPIStatus. + WUPSConfigAPIStatus err; + auto categoryOpt = WUPSConfigCategory::Create("Just another Category", err); + if (!categoryOpt) { + DEBUG_FUNCTION_LINE_ERR("Failed to create category: %s", WUPSConfigAPI_GetStatusStr(err)); + return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; + } + + auto boolItemOpt = WUPSConfigItemBoolean::Create(OTHER_EXAMPLE2_BOOL_CONFIG_ID, "Just another bool item", + false, false, + boolItemChanged, + err); + if (!boolItemOpt) { + DEBUG_FUNCTION_LINE_ERR("Failed to create bool item: %s", WUPSConfigAPI_GetStatusStr(err)); + return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; + } + + // Add bool item to category + if (!categoryOpt->add(std::move(*boolItemOpt), err)) { + DEBUG_FUNCTION_LINE_ERR("Failed to add bool item to category: %s", WUPSConfigAPI_GetStatusStr(err)); + return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; + } + + // Add category to root. + if (!root.add(std::move(*categoryOpt), err)) { + DEBUG_FUNCTION_LINE_ERR("Failed to add category to root: %s", WUPSConfigAPI_GetStatusStr(err)); + return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; + } + + return WUPSCONFIG_API_CALLBACK_RESULT_SUCCESS; +} + +void ConfigMenuClosedCallback() { + // Save all changes + if (WUPS_CloseStorage() != WUPS_STORAGE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_ERR("Failed to close storage"); + } +} + +/** + Gets called ONCE when the plugin was loaded. +**/ +INITIALIZE_PLUGIN() { + // Logging only works when compiled with `make DEBUG=1`. See the README for more information. + initLogging(); + DEBUG_FUNCTION_LINE("INITIALIZE_PLUGIN of example_plugin!"); + + WUPSConfigAPIOptionsV1 configOptions = {.name = "example_plugin"}; + if (WUPSConfigAPI_Init(configOptions, ConfigMenuOpenedCallback, ConfigMenuClosedCallback) != WUPSCONFIG_API_RESULT_SUCCESS) { + DEBUG_FUNCTION_LINE_ERR("Failed to init config api"); + } + + // Open storage to read values + WUPSStorageError storageRes = WUPS_OpenStorage(); + if (storageRes != WUPS_STORAGE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE("Failed to open storage %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes); + } else { + // Try to get value from storage + if ((storageRes = WUPS_GetBool(nullptr, LOG_FS_OPEN_CONFIG_ID, &sLogFSOpen)) == WUPS_STORAGE_ERROR_NOT_FOUND) { + // Add the value to the storage if it's missing. + if (WUPS_StoreBool(nullptr, LOG_FS_OPEN_CONFIG_ID, sLogFSOpen) != WUPS_STORAGE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE("Failed to store bool"); + } + } else if (storageRes != WUPS_STORAGE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE("Failed to get bool %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes); + } + + // Close storage + if (WUPS_CloseStorage() != WUPS_STORAGE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE("Failed to close storage"); + } + } + deinitLogging(); +} + +/** + Gets called when the plugin will be unloaded. +**/ +DEINITIALIZE_PLUGIN() { + DEBUG_FUNCTION_LINE("DEINITIALIZE_PLUGIN of example_plugin!"); +} + +/** + Gets called when an application starts. +**/ +ON_APPLICATION_START() { + initLogging(); + + DEBUG_FUNCTION_LINE("ON_APPLICATION_START of example_plugin!"); +} + +/** + * Gets called when an application actually ends + */ +ON_APPLICATION_ENDS() { + deinitLogging(); +} + +/** + Gets called when an application request to exit. +**/ +ON_APPLICATION_REQUESTS_EXIT() { + DEBUG_FUNCTION_LINE_INFO("ON_APPLICATION_REQUESTS_EXIT of example_plugin!"); +} + +/** + This defines a function replacement. + It allows to replace the system function with an own function. + So whenever a game / application calls an overridden function, your function gets called instead. + + Currently it's only possible to override functions that are loaded from .rpl files of OSv10 (00050010-1000400A). + + Signature of this macro: + DECL_FUNCTION( RETURN_TYPE, ARBITRARY_NAME_OF_FUNCTION , ARGS_SEPERATED_BY_COMMA){ + //Your code goes here. + } + + Within this macro, two more function get declare you can use. + my_ARBITRARY_NAME_OF_FUNCTION and real_ARBITRARY_NAME_OF_FUNCTION + + RETURN_TYPE my_ARBITRARY_NAME_OF_FUNCTION(ARGS_SEPERATED_BY_COMMA): + is just name of the function that gets declared in this macro. + It has the same effect as calling the overridden function directly. + + RETURN_TYPE real_ARBITRARY_NAME_OF_FUNCTION(ARGS_SEPERATED_BY_COMMA): + is the name of the function, that leads to function that was overridden. + Use this to call the original function that will be overridden. + CAUTION: Other plugins may already have manipulated the return value or arguments. + + Use this macro for each function you want to override +**/ +DECL_FUNCTION(int, FSOpenFile, FSClient *pClient, FSCmdBlock *pCmd, const char *path, const char *mode, int *handle, int error) { + int result = real_FSOpenFile(pClient, pCmd, path, mode, handle, error); + if (sLogFSOpen) { + DEBUG_FUNCTION_LINE_INFO("FSOpenFile called for folder %s! Result %d", path, result); + } + return result; +} + +/** +This tells the loader which functions from which library (.rpl) should be replaced with which function from this file. +The list of possible libraries can be found in the wiki. (In general it's WUPS_LOADER_LIBRARY_ + the name of the RPL in caps lock) + +WUPS_MUST_REPLACE(FUNCTION_NAME_IN_THIS_FILE, NAME_OF_LIB_WHICH_CONTAINS_THIS_FUNCTION, NAME_OF_FUNCTION_TO_OVERRIDE) + +Define this for each function you want to override. +**/ +WUPS_MUST_REPLACE(FSOpenFile, WUPS_LOADER_LIBRARY_COREINIT, FSOpenFile); \ No newline at end of file diff --git a/plugins/example_plugin_cpp/src/utils/logger.c b/plugins/example_plugin_cpp/src/utils/logger.c new file mode 100644 index 0000000..f700806 --- /dev/null +++ b/plugins/example_plugin_cpp/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/plugins/example_plugin_cpp/src/utils/logger.h b/plugins/example_plugin_cpp/src/utils/logger.h new file mode 100644 index 0000000..5f195ba --- /dev/null +++ b/plugins/example_plugin_cpp/src/utils/logger.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define LOG_APP_TYPE "P" +#define LOG_APP_NAME "ExamplePlugin" + +#define __FILENAME__ ({ \ + const char *__filename = __FILE__; \ + const char *__pos = strrchr(__filename, '/'); \ + if (!__pos) __pos = strrchr(__filename, '\\'); \ + __pos ? __pos + 1 : __filename; \ +}) + +#define LOG(LOG_FUNC, FMT, ARGS...) LOG_EX_DEFAULT(LOG_FUNC, "", "", FMT, ##ARGS) + +#define LOG_EX_DEFAULT(LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ARGS...) LOG_EX(__FILENAME__, __FUNCTION__, __LINE__, LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ##ARGS) + +#define LOG_EX(FILENAME, FUNCTION, LINE, LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ARGS...) \ + do { \ + LOG_FUNC("[(%s)%18s][%23s]%30s@L%04d: " LOG_LEVEL "" FMT "" LINE_END, LOG_APP_TYPE, LOG_APP_NAME, FILENAME, FUNCTION, LINE, ##ARGS); \ + } while (0) + +#ifdef DEBUG + +#ifdef VERBOSE_DEBUG +#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) LOG(WHBLogPrintf, FMT, ##ARGS) +#define DEBUG_FUNCTION_LINE_VERBOSE_EX(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, WHBLogPrintf, "", "", FMT, ##ARGS); +#else +#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0) +#define DEBUG_FUNCTION_LINE_VERBOSE_EX(FMT, ARGS...) while (0) +#endif + +#define DEBUG_FUNCTION_LINE(FMT, ARGS...) LOG(WHBLogPrintf, FMT, ##ARGS) + +#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) LOG(WHBLogWritef, FMT, ##ARGS) + +#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, "##ERROR## ", "", FMT, ##ARGS) +#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, "##WARN ## ", "", FMT, ##ARGS) +#define DEBUG_FUNCTION_LINE_INFO(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, "##INFO ## ", "", FMT, ##ARGS) + +#define DEBUG_FUNCTION_LINE_ERR_LAMBDA(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, WHBLogPrintf, "##ERROR## ", "", FMT, ##ARGS); + +#else + +#define DEBUG_FUNCTION_LINE_VERBOSE_EX(FMT, ARGS...) while (0) + +#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) + +#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, "##ERROR## ", "\n", FMT, ##ARGS) +#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, "##WARN ## ", "\n", FMT, ##ARGS) +#define DEBUG_FUNCTION_LINE_INFO(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, "##INFO ## ", "\n", FMT, ##ARGS) + +#define DEBUG_FUNCTION_LINE_ERR_LAMBDA(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, OSReport, "##ERROR## ", "\n", FMT, ##ARGS); + +#endif + +void initLogging(); + +void deinitLogging(); + +#ifdef __cplusplus +} +#endif