diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index adfbbeb..60c8bf2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,13 +6,13 @@ jobs: build-inkay: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: build toolchain container run: docker build . -t builder - uses: ammaraskar/gcc-problem-matcher@master - name: build Inkay run: docker run --rm -v ${PWD}:/app -w /app builder - - uses: actions/upload-artifact@master + - uses: actions/upload-artifact@v4 with: name: inkay - path: "*.wps" + path: dist/ diff --git a/.gitignore b/.gitignore index 982b963..3f9ac48 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,11 @@ +.cache/ .vscode/ .idea/ /build +/plugin/build +/dist *.elf +*.wms *.wps certs/ -*.lst \ No newline at end of file +*.lst diff --git a/Dockerfile b/Dockerfile index 4147038..0f51ffa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,8 @@ COPY --from=ghcr.io/wiiu-env/libnotifications:20240426 /artifacts $DEVKITPRO COPY --from=ghcr.io/wiiu-env/libfunctionpatcher:20230621 /artifacts $DEVKITPRO COPY --from=ghcr.io/wiiu-env/libkernel:20230621 /artifacts $DEVKITPRO COPY --from=ghcr.io/wiiu-env/libmocha:20231127 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/wiiumodulesystem:20240424 /artifacts $DEVKITPRO COPY --from=ghcr.io/wiiu-env/wiiupluginsystem:20240505 /artifacts $DEVKITPRO WORKDIR /app -CMD make -f Makefile -j$(nproc) \ No newline at end of file +CMD make -f Makefile -j$(nproc) diff --git a/Makefile b/Makefile index 2d63adc..72a3a66 100644 --- a/Makefile +++ b/Makefile @@ -8,10 +8,10 @@ endif TOPDIR ?= $(CURDIR) -include $(DEVKITPRO)/wups/share/wups_rules +include $(DEVKITPRO)/wums/share/wums_rules -WUT_ROOT := $(DEVKITPRO)/wut 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 @@ -21,37 +21,35 @@ WUMS_ROOT := $(DEVKITPRO)/wums #------------------------------------------------------------------------------- TARGET := Inkay-pretendo BUILD := build -SOURCES := src src/patches src/utils src/ext/inih +SOURCES := src src/patches src/utils src/ext/inih common DATA := data -INCLUDES := src src/ext/inih -#DEBUG := 1 +INCLUDES := src src/ext/inih src/lang common #------------------------------------------------------------------------------- # options for code generation #------------------------------------------------------------------------------- -CFLAGS := -Wall -O2 -ffunction-sections -fdata-sections \ +CFLAGS := -Wall -O2 -ffunction-sections\ $(MACHDEP) -CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ -D__WUPS__ - -ifeq ($(DEBUG),1) - CFLAGS += -DDEBUG -g -endif +CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ CXXFLAGS := $(CFLAGS) -std=c++20 ASFLAGS := -g $(ARCH) -LDFLAGS = -g $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) -Wl,-gc-sections +LDFLAGS = -g $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) -Wl,-gc-sections -T$(WUMS_ROOT)/share/libkernel.ld $(WUMSSPECS) -LDFLAGS += -T$(WUMS_ROOT)/share/libkernel.ld $(WUPSSPECS) +ifeq ($(DEBUG),1) +CXXFLAGS += -DDEBUG -g +CFLAGS += -DDEBUG -g +endif -LIBS := -lwups -lmocha -lkernel -lwut -lnotifications -lfunctionpatcher +LIBS := -lwums -lmocha -lkernel -lwut -lfunctionpatcher -lnotifications #------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level # containing include and lib #------------------------------------------------------------------------------- -LIBDIRS := $(PORTLIBS) $(WUMS_ROOT) $(WUPS_ROOT) $(WUT_ROOT) $(WUT_ROOT)/usr +LIBDIRS := $(PORTLIBS) $(WUT_ROOT) $(WUMS_ROOT) $(WUT_ROOT)/usr #------------------------------------------------------------------------------- # no real need to edit anything past this point unless you need to add additional @@ -103,14 +101,20 @@ export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) #------------------------------------------------------------------------------- all: $(BUILD) + $(BUILD): @$(shell [ ! -d $(BUILD) ] && mkdir -p $(BUILD)) @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + @$(MAKE) --no-print-directory -C $(CURDIR)/plugin -f $(CURDIR)/plugin/Makefile + mkdir -p dist/ + cp *.wms dist/ + cp plugin/*.wps dist/ #------------------------------------------------------------------------------- clean: @echo clean ... - @rm -fr $(BUILD) $(TARGET).wps $(TARGET).elf + @rm -fr dist $(BUILD) $(TARGET).wms $(TARGET).elf + @$(MAKE) --no-print-directory -C $(CURDIR)/plugin -f $(CURDIR)/plugin/Makefile clean #------------------------------------------------------------------------------- else @@ -121,10 +125,10 @@ DEPENDS := $(OFILES:.o=.d) #------------------------------------------------------------------------------- # main targets #------------------------------------------------------------------------------- -all : $(OUTPUT).wps +all : $(OUTPUT).wms -$(OUTPUT).wps : $(OUTPUT).elf -$(OUTPUT).elf : $(OFILES) +$(OUTPUT).wms : $(OUTPUT).elf +$(OUTPUT).elf : $(OFILES) $(OFILES_SRC) : $(HFILES_BIN) @@ -141,6 +145,11 @@ $(OFILES_SRC) : $(HFILES_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) #------------------------------------------------------------------------------- diff --git a/src/Notification.cpp b/common/Notification.cpp similarity index 100% rename from src/Notification.cpp rename to common/Notification.cpp diff --git a/src/Notification.h b/common/Notification.h similarity index 100% rename from src/Notification.h rename to common/Notification.h diff --git a/common/lang.cpp b/common/lang.cpp new file mode 100644 index 0000000..3f32b17 --- /dev/null +++ b/common/lang.cpp @@ -0,0 +1,44 @@ +// +// Created by ash on 21/11/24. +// + +#include "lang.h" + +config_strings get_config_strings(nn::swkbd::LanguageType language) { + switch (language) { + case nn::swkbd::LanguageType::English: + default: return { +#include "en_US.lang" + }; + case nn::swkbd::LanguageType::Spanish: return { +#include "es_ES.lang" + }; + case nn::swkbd::LanguageType::French: return { +#include "fr_FR.lang" + }; + case nn::swkbd::LanguageType::Italian: return { +#include "it_IT.lang" + }; + case nn::swkbd::LanguageType::German: return { +#include "de_DE.lang" + }; + case nn::swkbd::LanguageType::SimplifiedChinese: return { +#include "zh_CN.lang" + }; + case nn::swkbd::LanguageType::TraditionalChinese: return { +#include "zh_Hant.lang" + }; + case nn::swkbd::LanguageType::Portuguese: return { +#include "pt_BR.lang" + }; + case nn::swkbd::LanguageType::Japanese: return { +#include "ja_JP.lang" + }; + case nn::swkbd::LanguageType::Dutch: return { +#include "nl_NL.lang" + }; + case nn::swkbd::LanguageType::Russian: return { +#include "ru_RU.lang" + }; + } +} diff --git a/common/lang.h b/common/lang.h new file mode 100644 index 0000000..6e557fe --- /dev/null +++ b/common/lang.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +struct config_strings { + const char *plugin_name; + std::string_view network_category; + std::string_view connect_to_network_setting; + std::string_view other_category; + std::string_view reset_wwp_setting; + std::string_view press_a_action; + std::string_view restart_to_apply_action; + std::string_view need_menu_action; + std::string_view using_nintendo_network; + std::string_view using_pretendo_network; + std::string_view multiplayer_port_display; + std::string_view module_not_found; + std::string_view module_init_not_found; +}; + +config_strings get_config_strings(nn::swkbd::LanguageType language); diff --git a/src/utils/sysconfig.cpp b/common/sysconfig.cpp similarity index 83% rename from src/utils/sysconfig.cpp rename to common/sysconfig.cpp index 97ae4a3..ec56614 100644 --- a/src/utils/sysconfig.cpp +++ b/common/sysconfig.cpp @@ -1,6 +1,5 @@ /* Copyright 2024 Pretendo Network contributors Copyright 2024 Ash Logan - Copyright 2023 Maschell Copyright 2020-2022 V10lator Copyright 2022 Xpl0itU @@ -81,8 +80,8 @@ static void get_mcp_config() { mcp_os_version = os_version; DEBUG_FUNCTION_LINE_VERBOSE("Running on %d.%d.%d%c; %s%s", - os_version.major, os_version.minor, os_version.patch, os_version.region - config.code_id, config.serial_id + os_version.major, os_version.minor, os_version.patch, os_version.region + config.code_id, config.serial_id ); } @@ -97,3 +96,20 @@ MCPSystemVersion get_console_os_version() { return mcp_os_version.value_or((MCPSystemVersion) { .major = 5, .minor = 5, .patch = 5, .region = 'E' }); } + +static inline int digit(char a) { + if (a < '0' || a > '9') return 0; + return a - '0'; +} + +uint16_t get_console_peertopeer_port() { + const char * serial = get_console_serial(); + + uint16_t port = 50000 + + (digit(serial[4]) * 1000) + + (digit(serial[5]) * 100 ) + + (digit(serial[6]) * 10 ) + + (digit(serial[7]) * 1 ); + + return port; +} diff --git a/src/utils/sysconfig.h b/common/sysconfig.h similarity index 86% rename from src/utils/sysconfig.h rename to common/sysconfig.h index 3b3e075..d517fc7 100644 --- a/src/utils/sysconfig.h +++ b/common/sysconfig.h @@ -11,5 +11,6 @@ nn::swkbd::LanguageType get_system_language(); const char * get_console_serial(); MCPSystemVersion get_console_os_version(); +unsigned short get_console_peertopeer_port(); #endif //INKAY_SYSCONFIG_H diff --git a/src/utils/scope_exit.h b/common/utils/scope_exit.h similarity index 100% rename from src/utils/scope_exit.h rename to common/utils/scope_exit.h diff --git a/plugin/Makefile b/plugin/Makefile new file mode 100644 index 0000000..fee7a0b --- /dev/null +++ b/plugin/Makefile @@ -0,0 +1,148 @@ +#------------------------------------------------------------------------------- +.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 := Inkay-pretendo +BUILD := build +SOURCES := src src/utils ../common +DATA := data +INCLUDES := src ../src/lang ../common +#DEBUG := 1 + +#------------------------------------------------------------------------------- +# options for code generation +#------------------------------------------------------------------------------- +CFLAGS := -Wall -O2 -ffunction-sections -fdata-sections \ + $(MACHDEP) + +CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ -D__WUPS__ + +ifeq ($(DEBUG),1) + CFLAGS += -DDEBUG -g +endif + +CXXFLAGS := $(CFLAGS) -std=c++20 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -g $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) -Wl,-gc-sections + +LDFLAGS += $(WUPSSPECS) + +LIBS := -lwups -lwut -lnotifications + +#------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level +# containing include and lib +#------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(WUMS_ROOT) $(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) + +%.pem.o %_pem.h : %.pem +#------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#------------------------------------------------------------------------------- +endif +#------------------------------------------------------------------------------- diff --git a/plugin/src/config.cpp b/plugin/src/config.cpp new file mode 100644 index 0000000..1ca4be4 --- /dev/null +++ b/plugin/src/config.cpp @@ -0,0 +1,229 @@ +/* Copyright 2022 Pretendo Network contributors + Copyright 2022 Ash Logan + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "config.h" + +#include "wut_extra.h" +#include "utils/logger.h" +#include "sysconfig.h" +#include "lang.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static config_strings strings; + +bool Config::connect_to_network = true; +bool Config::need_relaunch = false; +bool Config::unregister_task_item_pressed = false; +bool Config::is_wiiu_menu = false; + +static WUPSConfigAPICallbackStatus report_error(WUPSConfigAPIStatus err) { + DEBUG_FUNCTION_LINE_VERBOSE("WUPS config error: %s", WUPSConfigAPI_GetStatusStr(err)); + return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; +} + +static void report_storage_error(WUPSStorageError err) { + DEBUG_FUNCTION_LINE_VERBOSE("WUPS storage error: %s", WUPSStorageAPI_GetStatusStr(err)); +} + +static void connect_to_network_changed(ConfigItemBoolean* item, bool new_value) { + DEBUG_FUNCTION_LINE_VERBOSE("connect_to_network changed to: %d", new_value); + if (new_value != Config::connect_to_network) { + Config::need_relaunch = true; + } + Config::connect_to_network = new_value; + + WUPSStorageError res; + res = WUPSStorageAPI::Store("connect_to_network", Config::connect_to_network); + if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res); +} + +static void unregister_task_item_on_input_cb(void *context, WUPSConfigSimplePadData input) { + if (!Config::unregister_task_item_pressed && Config::is_wiiu_menu && ((input.buttons_d & WUPS_CONFIG_BUTTON_A) == WUPS_CONFIG_BUTTON_A)) { + + nn::act::Initialize(); + Initialize__Q2_2nn4bossFv(); + + for (uint8_t i = 1; i <= nn::act::GetNumOfAccounts(); i++) + { + if (nn::act::IsSlotOccupied(i) && nn::act::IsNetworkAccountEx(i)) + { + nn::boss::Task task{}; + nn::act::PersistentId persistentId = nn::act::GetPersistentIdEx(i); + + __ct__Q3_2nn4boss4TaskFv(&task); + Initialize__Q3_2nn4boss4TaskFPCcUi(&task, "oltopic", persistentId); + + // bypasses compiler warning about unused variable + #ifdef DEBUG + uint32_t res = Unregister__Q3_2nn4boss4TaskFv(&task); + DEBUG_FUNCTION_LINE_VERBOSE("Unregistered oltopic for: SlotNo %d | Persistent ID %08x -> 0x%08x", i, persistentId, res); + #else + Unregister__Q3_2nn4boss4TaskFv(&task); + #endif + } + } + + Finalize__Q2_2nn4bossFv(); + nn::act::Finalize(); + + Config::unregister_task_item_pressed = !Config::unregister_task_item_pressed; + Config::need_relaunch = true; + } +} + +static int32_t unregister_task_item_get_display_value(void *context, char *out_buf, int32_t out_size) { + auto string = strings.need_menu_action; + if (Config::is_wiiu_menu) { + if (Config::unregister_task_item_pressed) { + string = strings.restart_to_apply_action; + } else { + string = strings.press_a_action; + } + } + + if ((int)string.length() > out_size - 1) return -1; + string.copy(out_buf, string.length()); + out_buf[string.length()] = '\0'; + + return 0; +} + +static WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback(WUPSConfigCategoryHandle rootHandle) { + WUPSConfigAPIStatus err; + bool res; + + uint64_t current_title_id = OSGetTitleID(); + uint64_t wiiu_menu_tid = _SYSGetSystemApplicationTitleId(SYSTEM_APP_ID_WII_U_MENU); + Config::is_wiiu_menu = (current_title_id == wiiu_menu_tid); + + // get translation strings + strings = get_config_strings(get_system_language()); + + // create root config category + WUPSConfigCategory root = WUPSConfigCategory(rootHandle); + + auto network_cat = WUPSConfigCategory::Create(strings.network_category, err); + if (!network_cat) return report_error(err); + + // config id display name default current value changed callback + auto connect_item = WUPSConfigItemBoolean::Create("connect_to_network", strings.connect_to_network_setting, true, Config::connect_to_network, &connect_to_network_changed, err); + if (!connect_item) return report_error(err); + + res = network_cat->add(std::move(*connect_item), err); + if (!res) return report_error(err); + + { + uint16_t port = get_console_peertopeer_port(); + std::string multiplayer_port_text = std::vformat(strings.multiplayer_port_display, std::make_format_args(port)); + res = network_cat->add(WUPSConfigItemStub::Create(multiplayer_port_text), err); + if (!res) return report_error(err); + } + + res = root.add(std::move(*network_cat), err); + if (!res) return report_error(err); + + auto other_cat = WUPSConfigCategory::Create(strings.other_category, err); + if (!other_cat) return report_error(err); + + WUPSConfigAPIItemCallbacksV2 unregisterTasksItemCallbacks = { + .getCurrentValueDisplay = unregister_task_item_get_display_value, + .getCurrentValueSelectedDisplay = unregister_task_item_get_display_value, + .onSelected = nullptr, + .restoreDefault = nullptr, + .isMovementAllowed = nullptr, + .onCloseCallback = nullptr, + .onInput = unregister_task_item_on_input_cb, + .onInputEx = nullptr, + .onDelete = nullptr + }; + + WUPSConfigAPIItemOptionsV2 unregisterTasksItemOptions = { + .displayName = strings.reset_wwp_setting.data(), + .context = nullptr, + .callbacks = unregisterTasksItemCallbacks, + }; + + WUPSConfigItemHandle unregisterTasksItem; + err = WUPSConfigAPI_Item_Create(unregisterTasksItemOptions, &unregisterTasksItem); + if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err); + + err = WUPSConfigAPI_Category_AddItem(other_cat->getHandle(), unregisterTasksItem); + if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err); + + res = root.add(std::move(*other_cat), err); + if (!res) return report_error(err); + + return WUPSCONFIG_API_CALLBACK_RESULT_SUCCESS; +} + +static void ConfigMenuClosedCallback() { + // Save all changes + WUPSStorageError res; + res = WUPSStorageAPI::SaveStorage(); + if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res); + + if (Config::need_relaunch) { + // Need to reload the console so the patches reset + OSForceFullRelaunch(); + SYSLaunchMenu(); + Config::need_relaunch = false; + } +} + +void Config::Init() { + WUPSConfigAPIStatus cres; + + // Init the config api + WUPSConfigAPIOptionsV1 configOptions = { .name = "Inkay" }; + cres = WUPSConfigAPI_Init(configOptions, ConfigMenuOpenedCallback, ConfigMenuClosedCallback); + if (cres != WUPSCONFIG_API_RESULT_SUCCESS) return (void)report_error(cres); + + WUPSStorageError res; + // Try to get value from storage + res = WUPSStorageAPI::Get("connect_to_network", Config::connect_to_network); + if (res == WUPS_STORAGE_ERROR_NOT_FOUND) { + DEBUG_FUNCTION_LINE("Connect to network value not found, attempting to migrate/create"); + + bool skipPatches = false; + if (WUPSStorageAPI::Get("skipPatches", skipPatches) == WUPS_STORAGE_ERROR_SUCCESS) { + // Migrate old config value + Config::connect_to_network = !skipPatches; + WUPSStorageAPI::DeleteItem("skipPatches"); + } + + // Add the value to the storage if it's missing. + res = WUPSStorageAPI::Store("connect_to_network", connect_to_network); + if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res); + } + else if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res); + + // Save storage + res = WUPSStorageAPI::SaveStorage(); + if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res); +} diff --git a/plugin/src/config.h b/plugin/src/config.h new file mode 100644 index 0000000..0928d5e --- /dev/null +++ b/plugin/src/config.h @@ -0,0 +1,24 @@ +// +// Created by ash on 10/12/22. +// + +#ifndef INKAY_CONFIG_H +#define INKAY_CONFIG_H + +class Config { +public: + static void Init(); + + // wups config items + static bool connect_to_network; + + // private stuff + static bool need_relaunch; + + // private stuff + static bool is_wiiu_menu; + + static bool unregister_task_item_pressed; +}; + +#endif //INKAY_CONFIG_H diff --git a/plugin/src/main.cpp b/plugin/src/main.cpp new file mode 100644 index 0000000..4845a38 --- /dev/null +++ b/plugin/src/main.cpp @@ -0,0 +1,70 @@ +/* Copyright 2022 Pretendo Network contributors + Copyright 2022 Ash Logan + Copyright 2019 Maschell + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include +#include +#include "config.h" +#include "module.h" + +#define INKAY_VERSION "v2.6.0" + +/** + Mandatory plugin information. + If not set correctly, the loader will refuse to use the plugin. +**/ +WUPS_PLUGIN_NAME("Inkay"); +WUPS_PLUGIN_DESCRIPTION("Pretendo Network Patcher"); +WUPS_PLUGIN_VERSION(INKAY_VERSION); +WUPS_PLUGIN_AUTHOR("Pretendo contributors"); +WUPS_PLUGIN_LICENSE("GPLv3"); + +WUPS_USE_STORAGE("inkay"); + +WUPS_USE_WUT_DEVOPTAB(); + +INITIALIZE_PLUGIN() { + WHBLogCafeInit(); + WHBLogUdpInit(); + + Config::Init(); + + if (NotificationModule_InitLibrary() != NOTIFICATION_MODULE_RESULT_SUCCESS) { + DEBUG_FUNCTION_LINE("NotificationModule_InitLibrary failed"); + } + + // if using pretendo then (try to) apply the ssl patches + Inkay_Initialize(Config::connect_to_network); +} + +DEINITIALIZE_PLUGIN() { + Inkay_Finalize(); + NotificationModule_DeInitLibrary(); + + WHBLogCafeDeinit(); + WHBLogUdpDeinit(); +} + +ON_APPLICATION_START() { + +} + +ON_APPLICATION_ENDS() { + +} diff --git a/plugin/src/module.cpp b/plugin/src/module.cpp new file mode 100644 index 0000000..5fd9c2a --- /dev/null +++ b/plugin/src/module.cpp @@ -0,0 +1,78 @@ +/* Copyright 2024 Pretendo Network contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "module.h" +#include + +#include "config.h" +#include "Notification.h" +#include "utils/logger.h" +#include "sysconfig.h" +#include "lang.h" + +static OSDynLoad_Module module; +static void (*moduleInitialize)(bool) = nullptr; +static InkayStatus (*moduleGetStatus)() = nullptr; + +static const char *get_module_not_found_message() { + return get_config_strings(get_system_language()).module_not_found.data(); +} + +static const char *get_module_init_not_found_message() { + return get_config_strings(get_system_language()).module_init_not_found.data(); +} + +void Inkay_Initialize(bool apply_patches) { + if (module) { + return; + } + + if (OSDynLoad_Acquire("inkay", &module) != OS_DYNLOAD_OK) { + DEBUG_FUNCTION_LINE("Failed to acquire module"); + ShowNotification(get_module_not_found_message()); + return; + } + + if (OSDynLoad_FindExport(module, OS_DYNLOAD_EXPORT_FUNC, "Inkay_Initialize", reinterpret_cast(&moduleInitialize)) != OS_DYNLOAD_OK) { + DEBUG_FUNCTION_LINE("Failed to find initialization function"); + ShowNotification(get_module_init_not_found_message()); + OSDynLoad_Release(module); + return; + } + + moduleInitialize(apply_patches); +} + +void Inkay_Finalize() { + if (module) { + OSDynLoad_Release(module); + moduleInitialize = nullptr; + moduleGetStatus = nullptr; + } +} + +InkayStatus Inkay_GetStatus() { + if (!module) { + return InkayStatus::Error; + } + + if (!moduleGetStatus && OSDynLoad_FindExport(module, OS_DYNLOAD_EXPORT_FUNC, "Inkay_GetStatus", reinterpret_cast(&moduleGetStatus)) != OS_DYNLOAD_OK) { + DEBUG_FUNCTION_LINE("Failed to find status function"); + return InkayStatus::Error; + } + + return moduleGetStatus(); +} diff --git a/plugin/src/module.h b/plugin/src/module.h new file mode 100644 index 0000000..b6247a4 --- /dev/null +++ b/plugin/src/module.h @@ -0,0 +1,29 @@ +/* Copyright 2024 Pretendo Network contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +enum class InkayStatus { + Uninitialized, ///< The module isn't initialized + Nintendo, ///< The module is initialized but hasn't applied any patches + Pretendo, ///< The module is initialized and has applied the Pretendo patches + + Error = -1 ///< Failed to retrieve the module status +}; + +void Inkay_Initialize(bool apply_patches); +void Inkay_Finalize(); +InkayStatus Inkay_GetStatus(); diff --git a/plugin/src/utils/logger.h b/plugin/src/utils/logger.h new file mode 100644 index 0000000..f7278b6 --- /dev/null +++ b/plugin/src/utils/logger.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#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 OSFATAL_FUNCTION_LINE(FMT, ARGS...)do { \ + OSFatal_printf("[(P) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \ + } while (0) + +#define DEBUG_FUNCTION_LINE(FMT, ARGS...)do { \ + WHBLogPrintf("[(P) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \ + } while (0); + +#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...)do { \ + WHBLogWritef("[(P) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \ + } while (0); + +#ifdef DEBUG +#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) DEBUG_FUNCTION_LINE(FMT, ##ARGS) +#else +#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/src/wut_extra.h b/plugin/src/wut_extra.h similarity index 100% rename from src/wut_extra.h rename to plugin/src/wut_extra.h diff --git a/src/config.cpp b/src/config.cpp index d7e6ffc..56c8740 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -17,252 +17,6 @@ #include "config.h" -#include "wut_extra.h" -#include "utils/logger.h" -#include "utils/sysconfig.h" -#include "patches/game_peertopeer.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -bool Config::connect_to_network = true; -bool Config::need_relaunch = false; -bool Config::unregister_task_item_pressed = false; -bool Config::is_wiiu_menu = false; - -static config_strings strings; - -config_strings get_config_strings(nn::swkbd::LanguageType language) { - switch (language) { - case nn::swkbd::LanguageType::English: - default: return { -#include "lang/en_US.lang" - }; - case nn::swkbd::LanguageType::Spanish: return { -#include "lang/es_ES.lang" - }; - case nn::swkbd::LanguageType::French: return { -#include "lang/fr_FR.lang" - }; - case nn::swkbd::LanguageType::Italian: return { -#include "lang/it_IT.lang" - }; - case nn::swkbd::LanguageType::German: return { -#include "lang/de_DE.lang" - }; - case nn::swkbd::LanguageType::SimplifiedChinese: return { -#include "lang/zh_CN.lang" - }; - case nn::swkbd::LanguageType::TraditionalChinese: return { -#include "lang/zh_Hant.lang" - }; - case nn::swkbd::LanguageType::Portuguese: return { -#include "lang/pt_BR.lang" - }; - case nn::swkbd::LanguageType::Japanese: return { -#include "lang/ja_JP.lang" - }; - case nn::swkbd::LanguageType::Dutch: return { -#include "lang/nl_NL.lang" - }; - case nn::swkbd::LanguageType::Russian: return { -#include "lang/ru_RU.lang" - }; - } -} - -static WUPSConfigAPICallbackStatus report_error(WUPSConfigAPIStatus err) { - DEBUG_FUNCTION_LINE_VERBOSE("WUPS config error: %s", WUPSConfigAPI_GetStatusStr(err)); - return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; -} - -static void report_storage_error(WUPSStorageError err) { - DEBUG_FUNCTION_LINE_VERBOSE("WUPS storage error: %s", WUPSStorageAPI_GetStatusStr(err)); -} - -static void connect_to_network_changed(ConfigItemBoolean* item, bool new_value) { - DEBUG_FUNCTION_LINE_VERBOSE("connect_to_network changed to: %d", new_value); - if (new_value != Config::connect_to_network) { - Config::need_relaunch = true; - } - Config::connect_to_network = new_value; - - WUPSStorageError res; - res = WUPSStorageAPI::Store("connect_to_network", Config::connect_to_network); - if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res); -} - -static void unregister_task_item_on_input_cb(void *context, WUPSConfigSimplePadData input) { - if (!Config::unregister_task_item_pressed && Config::is_wiiu_menu && ((input.buttons_d & WUPS_CONFIG_BUTTON_A) == WUPS_CONFIG_BUTTON_A)) { - - nn::act::Initialize(); - Initialize__Q2_2nn4bossFv(); - - for (uint8_t i = 1; i <= nn::act::GetNumOfAccounts(); i++) - { - if (nn::act::IsSlotOccupied(i) && nn::act::IsNetworkAccountEx(i)) - { - nn::boss::Task task{}; - nn::act::PersistentId persistentId = nn::act::GetPersistentIdEx(i); - - __ct__Q3_2nn4boss4TaskFv(&task); - Initialize__Q3_2nn4boss4TaskFPCcUi(&task, "oltopic", persistentId); - - // bypasses compiler warning about unused variable - #ifdef DEBUG - uint32_t res = Unregister__Q3_2nn4boss4TaskFv(&task); - DEBUG_FUNCTION_LINE_VERBOSE("Unregistered oltopic for: SlotNo %d | Persistent ID %08x -> 0x%08x", i, persistentId, res); - #else - Unregister__Q3_2nn4boss4TaskFv(&task); - #endif - } - } - - Finalize__Q2_2nn4bossFv(); - nn::act::Finalize(); - - Config::unregister_task_item_pressed = !Config::unregister_task_item_pressed; - Config::need_relaunch = true; - } -} - -static int32_t unregister_task_item_get_display_value(void *context, char *out_buf, int32_t out_size) { - auto string = strings.need_menu_action; - if (Config::is_wiiu_menu) { - if (Config::unregister_task_item_pressed) { - string = strings.restart_to_apply_action; - } else { - string = strings.press_a_action; - } - } - - if ((int)string.length() > out_size - 1) return -1; - string.copy(out_buf, string.length()); - out_buf[string.length()] = '\0'; - - return 0; -} - -static WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback(WUPSConfigCategoryHandle rootHandle) { - WUPSConfigAPIStatus err; - bool res; - - uint64_t current_title_id = OSGetTitleID(); - uint64_t wiiu_menu_tid = _SYSGetSystemApplicationTitleId(SYSTEM_APP_ID_WII_U_MENU); - Config::is_wiiu_menu = (current_title_id == wiiu_menu_tid); - - // get translation strings - strings = get_config_strings(get_system_language()); - - // create root config category - WUPSConfigCategory root = WUPSConfigCategory(rootHandle); - - auto network_cat = WUPSConfigCategory::Create(strings.network_category, err); - if (!network_cat) return report_error(err); - - // config id display name default current value changed callback - auto connect_item = WUPSConfigItemBoolean::Create("connect_to_network", strings.connect_to_network_setting, true, Config::connect_to_network, &connect_to_network_changed, err); - if (!connect_item) return report_error(err); - - res = network_cat->add(std::move(*connect_item), err); - if (!res) return report_error(err); - - { - std::string multiplayer_port_text = std::vformat(strings.multiplayer_port_display, std::make_format_args(peertopeer_port())); - res = network_cat->add(WUPSConfigItemStub::Create(multiplayer_port_text), err); - if (!res) return report_error(err); - } - - res = root.add(std::move(*network_cat), err); - if (!res) return report_error(err); - - auto other_cat = WUPSConfigCategory::Create(strings.other_category, err); - if (!other_cat) return report_error(err); - - WUPSConfigAPIItemCallbacksV2 unregisterTasksItemCallbacks = { - .getCurrentValueDisplay = unregister_task_item_get_display_value, - .getCurrentValueSelectedDisplay = unregister_task_item_get_display_value, - .onSelected = nullptr, - .restoreDefault = nullptr, - .isMovementAllowed = nullptr, - .onCloseCallback = nullptr, - .onInput = unregister_task_item_on_input_cb, - .onInputEx = nullptr, - .onDelete = nullptr - }; - - WUPSConfigAPIItemOptionsV2 unregisterTasksItemOptions = { - .displayName = strings.reset_wwp_setting.data(), - .context = nullptr, - .callbacks = unregisterTasksItemCallbacks, - }; - - WUPSConfigItemHandle unregisterTasksItem; - err = WUPSConfigAPI_Item_Create(unregisterTasksItemOptions, &unregisterTasksItem); - if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err); - - err = WUPSConfigAPI_Category_AddItem(other_cat->getHandle(), unregisterTasksItem); - if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err); - - res = root.add(std::move(*other_cat), err); - if (!res) return report_error(err); - - return WUPSCONFIG_API_CALLBACK_RESULT_SUCCESS; -} - -static void ConfigMenuClosedCallback() { - // Save all changes - WUPSStorageError res; - res = WUPSStorageAPI::SaveStorage(); - if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res); - - if (Config::need_relaunch) { - // Need to reload the console so the patches reset - OSForceFullRelaunch(); - SYSLaunchMenu(); - Config::need_relaunch = false; - } -} - -void Config::Init() { - WUPSConfigAPIStatus cres; - - // Init the config api - WUPSConfigAPIOptionsV1 configOptions = { .name = "Inkay" }; - cres = WUPSConfigAPI_Init(configOptions, ConfigMenuOpenedCallback, ConfigMenuClosedCallback); - if (cres != WUPSCONFIG_API_RESULT_SUCCESS) return (void)report_error(cres); - - WUPSStorageError res; - // Try to get value from storage - res = WUPSStorageAPI::Get("connect_to_network", Config::connect_to_network); - if (res == WUPS_STORAGE_ERROR_NOT_FOUND) { - DEBUG_FUNCTION_LINE("Connect to network value not found, attempting to migrate/create"); - - bool skipPatches = false; - if (WUPSStorageAPI::Get("skipPatches", skipPatches) == WUPS_STORAGE_ERROR_SUCCESS) { - // Migrate old config value - Config::connect_to_network = !skipPatches; - WUPSStorageAPI::DeleteItem("skipPatches"); - } - - // Add the value to the storage if it's missing. - res = WUPSStorageAPI::Store("connect_to_network", connect_to_network); - if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res); - } - else if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res); - - // Save storage - res = WUPSStorageAPI::SaveStorage(); - if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res); -} +bool Config::connect_to_network = false; +bool Config::initialized = false; +bool Config::shown_uninitialized_warning = false; diff --git a/src/config.h b/src/config.h index 51c888b..96f6775 100644 --- a/src/config.h +++ b/src/config.h @@ -10,34 +10,11 @@ class Config { public: - static void Init(); - - // wups config items static bool connect_to_network; - // private stuff - static bool need_relaunch; - - // private stuff - static bool is_wiiu_menu; + static bool initialized; - static bool unregister_task_item_pressed; + static bool shown_uninitialized_warning; }; -struct config_strings { - const char *plugin_name; - std::string_view network_category; - std::string_view connect_to_network_setting; - std::string_view other_category; - std::string_view reset_wwp_setting; - std::string_view press_a_action; - std::string_view restart_to_apply_action; - std::string_view need_menu_action; - std::string_view using_nintendo_network; - std::string_view using_pretendo_network; - std::string_view multiplayer_port_display; -}; - -config_strings get_config_strings(nn::swkbd::LanguageType language); - #endif //INKAY_CONFIG_H diff --git a/src/export.h b/src/export.h new file mode 100644 index 0000000..ecc7d99 --- /dev/null +++ b/src/export.h @@ -0,0 +1,25 @@ +/* Copyright 2024 Pretendo Network contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +enum class InkayStatus { + Uninitialized, ///< The module isn't initialized + Nintendo, ///< The module is initialized but hasn't applied any patches + Pretendo, ///< The module is initialized and has applied the Pretendo patches + + Error = -1 ///< Failed to retrieve the module status +}; diff --git a/src/lang/en_US.lang b/src/lang/en_US.lang index ed0e423..29fb991 100644 --- a/src/lang/en_US.lang +++ b/src/lang/en_US.lang @@ -9,3 +9,5 @@ ,.using_nintendo_network = "Using Nintendo Network" ,.using_pretendo_network = "Using Pretendo Network" ,.multiplayer_port_display = "Using UDP port {} for multiplayer" +,.module_not_found = "Pretendo patch failed - use Aroma Updater to repair (686-1001 Module missing)" +,.module_init_not_found = "Pretendo patch failed - use Aroma Updater to repair (686-1002 Module init)" diff --git a/src/main.cpp b/src/main.cpp index 560ec42..12ba0e8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,7 +19,7 @@ #include #include #include -#include +#include #include #include #include @@ -32,6 +32,7 @@ #include #include #include +#include "export.h" #include "iosu_url_patches.h" #include "config.h" #include "Notification.h" @@ -54,22 +55,28 @@ Mandatory plugin information. If not set correctly, the loader will refuse to use the plugin. **/ -WUPS_PLUGIN_NAME("Inkay"); -WUPS_PLUGIN_DESCRIPTION("Pretendo Network Patcher"); -WUPS_PLUGIN_VERSION(INKAY_VERSION); -WUPS_PLUGIN_AUTHOR("Pretendo contributors"); -WUPS_PLUGIN_LICENSE("GPLv3"); +WUMS_MODULE_EXPORT_NAME("inkay"); +WUMS_MODULE_DESCRIPTION("Pretendo Network Patcher"); +WUMS_MODULE_VERSION(INKAY_VERSION); +WUMS_MODULE_AUTHOR("Pretendo contributors"); +WUMS_MODULE_LICENSE("GPLv3"); -WUPS_USE_STORAGE("inkay"); +WUMS_DEPENDS_ON(homebrew_functionpatcher); +WUMS_DEPENDS_ON(homebrew_kernel); +WUMS_DEPENDS_ON(homebrew_notifications); -WUPS_USE_WUT_DEVOPTAB(); +WUMS_USE_WUT_DEVOPTAB(); #include #include #include #include "patches/account_settings.h" +#include "patches/dns_hooks.h" +#include "patches/eshop_applet.h" +#include "patches/olv_applet.h" #include "patches/game_peertopeer.h" -#include "utils/sysconfig.h" +#include "sysconfig.h" +#include "lang.h" //thanks @Gary#4139 :p static void write_string(uint32_t addr, const char *str) { @@ -111,25 +118,25 @@ static const char *get_pretendo_message() { return get_config_strings(get_system_language()).using_pretendo_network.data(); } -INITIALIZE_PLUGIN() { - WHBLogCafeInit(); - WHBLogUdpInit(); - - Config::Init(); - - auto res = Mocha_InitLibrary(); +static InkayStatus Inkay_GetStatus() { + if (!Config::initialized) + return InkayStatus::Uninitialized; - if (res != MOCHA_RESULT_SUCCESS) { - DEBUG_FUNCTION_LINE("Mocha init failed with code %d!", res); - return; + if (Config::connect_to_network) { + return InkayStatus::Pretendo; + } else { + return InkayStatus::Nintendo; } +} - if (NotificationModule_InitLibrary() != NOTIFICATION_MODULE_RESULT_SUCCESS) { - DEBUG_FUNCTION_LINE("NotificationModule_InitLibrary failed"); - } +static void Inkay_Initialize(bool apply_patches) { + if (Config::initialized) + return; // if using pretendo then (try to) apply the ssl patches - if (Config::connect_to_network) { + if (apply_patches) { + Config::connect_to_network = true; + if (is555(get_console_os_version())) { Mocha_IOSUKernelWrite32(0xE1019F78, 0xE3A00001); // mov r0, #1 } else { @@ -146,18 +153,46 @@ INITIALIZE_PLUGIN() { DEBUG_FUNCTION_LINE_VERBOSE("Pretendo URL and NoSSL patches applied successfully."); ShowNotification(get_pretendo_message()); + Config::initialized = true; } else { DEBUG_FUNCTION_LINE_VERBOSE("Pretendo URL and NoSSL patches skipped."); ShowNotification(get_nintendo_network_message()); + Config::initialized = true; + return; } if (FunctionPatcher_InitLibrary() == FUNCTION_PATCHER_RESULT_SUCCESS) { + patchDNS(); + patchEshop(); + patchOlvApplet(); install_matchmaking_patches(); + } else { + DEBUG_FUNCTION_LINE("FunctionPatcher_InitLibrary failed"); } } -DEINITIALIZE_PLUGIN() { +WUMS_INITIALIZE() { + WHBLogCafeInit(); + WHBLogUdpInit(); + + auto res = Mocha_InitLibrary(); + + if (res != MOCHA_RESULT_SUCCESS) { + DEBUG_FUNCTION_LINE("Mocha init failed with code %d!", res); + return; + } + + if (NotificationModule_InitLibrary() != NOTIFICATION_MODULE_RESULT_SUCCESS) { + DEBUG_FUNCTION_LINE("NotificationModule_InitLibrary failed"); + } +} + +WUMS_DEINITIALIZE() { + unpatchDNS(); + unpatchEshop(); + unpatchOlvApplet(); + unpatchAccountSettings(); remove_matchmaking_patches(); Mocha_DeInitLibrary(); @@ -168,15 +203,30 @@ DEINITIALIZE_PLUGIN() { WHBLogUdpDeinit(); } -ON_APPLICATION_START() { +WUMS_APPLICATION_STARTS() { DEBUG_FUNCTION_LINE_VERBOSE("Inkay " INKAY_VERSION " starting up...\n"); + // TODO - Add a way to reliably check this. We can't do it here since this path gets triggered before + // the plugin gets initialized. + // + // if (!Config::initialized && !Config::shown_uninitialized_warning) { + // DEBUG_FUNCTION_LINE("Inkay module not initialized"); + // ShowNotification("Inkay module was not initialized. Ensure you have the Inkay plugin loaded"); + // Config::shown_uninitialized_warning = true; + // } + setup_olv_libs(); peertopeer_patch(); matchmaking_notify_titleswitch(); - patchAccountSettings(); + + if (isAccountSettingsTitle()) { + patchAccountSettings(); + } } -ON_APPLICATION_ENDS() { +WUMS_APPLICATION_ENDS() { } + +WUMS_EXPORT_FUNCTION(Inkay_Initialize); +WUMS_EXPORT_FUNCTION(Inkay_GetStatus); diff --git a/src/patches/account_settings.cpp b/src/patches/account_settings.cpp index efea324..57af0b9 100644 --- a/src/patches/account_settings.cpp +++ b/src/patches/account_settings.cpp @@ -22,7 +22,8 @@ #include "utils/logger.h" #include "utils/replace_mem.h" -#include +#include +#include #include #include #include @@ -54,6 +55,7 @@ const char wave_new[] = "saccount.pretendo.cc"; bool isAccountSettingsTitle(); static std::optional rootca_pem_handle{}; +std::vector account_patches; DECL_FUNCTION(int, FSOpenFile_accSettings, FSClient *client, FSCmdBlock *block, char *path, const char *mode, uint32_t *handle, int error) { @@ -131,10 +133,27 @@ bool patchAccountSettings() { DEBUG_FUNCTION_LINE_VERBOSE("Inkay: We didn't find the whitelist /)>~<(\\"); return false; } + + account_patches.reserve(3); + + auto add_patch = [](function_replacement_data_t repl, const char *name) { + PatchedFunctionHandle handle = 0; + if (FunctionPatcher_AddFunctionPatch(&repl, &handle, nullptr) != FUNCTION_PATCHER_RESULT_SUCCESS) { + DEBUG_FUNCTION_LINE("Inkay/Account: Failed to patch %s!", name); + } + account_patches.push_back(handle); + }; + + add_patch(REPLACE_FUNCTION_FOR_PROCESS(FSOpenFile_accSettings, LIBRARY_COREINIT, FSOpenFile, FP_TARGET_PROCESS_GAME), "FSOpenFile_accSettings"); + add_patch(REPLACE_FUNCTION_FOR_PROCESS(FSReadFile_accSettings, LIBRARY_COREINIT, FSReadFile, FP_TARGET_PROCESS_GAME), "FSReadFile_accSettings"); + add_patch(REPLACE_FUNCTION_FOR_PROCESS(FSCloseFile_accSettings, LIBRARY_COREINIT, FSReadFile, FP_TARGET_PROCESS_GAME), "FSCloseFile_accSettings"); return true; } -WUPS_MUST_REPLACE_FOR_PROCESS(FSOpenFile_accSettings, WUPS_LOADER_LIBRARY_COREINIT, FSOpenFile, WUPS_FP_TARGET_PROCESS_GAME); -WUPS_MUST_REPLACE_FOR_PROCESS(FSReadFile_accSettings, WUPS_LOADER_LIBRARY_COREINIT, FSReadFile, WUPS_FP_TARGET_PROCESS_GAME); -WUPS_MUST_REPLACE_FOR_PROCESS(FSCloseFile_accSettings, WUPS_LOADER_LIBRARY_COREINIT, FSCloseFile, WUPS_FP_TARGET_PROCESS_GAME); +void unpatchAccountSettings() { + for (auto handle: account_patches) { + FunctionPatcher_RemoveFunctionPatch(handle); + } + account_patches.clear(); +} diff --git a/src/patches/account_settings.h b/src/patches/account_settings.h index 6d09e68..4cc77be 100644 --- a/src/patches/account_settings.h +++ b/src/patches/account_settings.h @@ -17,4 +17,5 @@ bool isAccountSettingsTitle(); -bool patchAccountSettings(); \ No newline at end of file +bool patchAccountSettings(); +void unpatchAccountSettings(); diff --git a/src/patches/dns_hooks.cpp b/src/patches/dns_hooks.cpp index d832a57..d51520b 100644 --- a/src/patches/dns_hooks.cpp +++ b/src/patches/dns_hooks.cpp @@ -15,12 +15,16 @@ along with this program. If not, see . */ -#include #include -#include "utils/logger.h" + #include "config.h" +#include "utils/logger.h" #include +#include +#include + +std::vector dns_patches; const std::pair dns_replacements[] = { // NNCS servers @@ -42,10 +46,31 @@ static const char * replace_dns_name(const char *dns_name) { DECL_FUNCTION(struct hostent *, gethostbyname, const char *dns_name) { return real_gethostbyname(replace_dns_name(dns_name)); } -// might need a WUPS_MUST_REPLACE_FOR_PROCESS for Friends -WUPS_MUST_REPLACE(gethostbyname, WUPS_LOADER_LIBRARY_NSYSNET, gethostbyname); DECL_FUNCTION(int, getaddrinfo, const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) { return real_getaddrinfo(replace_dns_name(node), service, hints, res); } -WUPS_MUST_REPLACE(getaddrinfo, WUPS_LOADER_LIBRARY_NSYSNET, getaddrinfo); \ No newline at end of file + +void patchDNS() { + dns_patches.reserve(2); + + auto add_patch = [](function_replacement_data_t repl, const char *name) { + PatchedFunctionHandle handle = 0; + if (FunctionPatcher_AddFunctionPatch(&repl, &handle, nullptr) != FUNCTION_PATCHER_RESULT_SUCCESS) { + DEBUG_FUNCTION_LINE("Inkay/DNS: Failed to patch %s!", name); + } + dns_patches.push_back(handle); + }; + + // might need a REPLACE_FUNCTION_FOR_PROCESS for Friends + add_patch(REPLACE_FUNCTION(gethostbyname, LIBRARY_NSYSNET, gethostbyname), "gethostbyname"); + + add_patch(REPLACE_FUNCTION(getaddrinfo, LIBRARY_NSYSNET, getaddrinfo), "getaddrinfo"); +} + +void unpatchDNS() { + for (auto handle: dns_patches) { + FunctionPatcher_RemoveFunctionPatch(handle); + } + dns_patches.clear(); +} diff --git a/src/patches/dns_hooks.h b/src/patches/dns_hooks.h new file mode 100644 index 0000000..9bbc216 --- /dev/null +++ b/src/patches/dns_hooks.h @@ -0,0 +1,20 @@ +/* Copyright 2024 Pretendo Network contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +void patchDNS(); +void unpatchDNS(); diff --git a/src/patches/eshop_applet.cpp b/src/patches/eshop_applet.cpp index d3ee43c..33467ac 100644 --- a/src/patches/eshop_applet.cpp +++ b/src/patches/eshop_applet.cpp @@ -21,7 +21,8 @@ #include "utils/logger.h" #include "utils/replace_mem.h" -#include +#include +#include #include #include #include @@ -47,6 +48,7 @@ const char whitelist_new[] = { }; static std::optional rootca_pem_handle{}; +std::vector eshop_patches; DECL_FUNCTION(int, FSOpenFile_eShop, FSClient *client, FSCmdBlock *block, char *path, const char *mode, uint32_t *handle, int error) { @@ -102,6 +104,25 @@ DECL_FUNCTION(FSStatus, FSCloseFile_eShop, FSClient *client, FSCmdBlock *block, return real_FSCloseFile_eShop(client, block, handle, errorMask); } -WUPS_MUST_REPLACE_FOR_PROCESS(FSOpenFile_eShop, WUPS_LOADER_LIBRARY_COREINIT, FSOpenFile, WUPS_FP_TARGET_PROCESS_ESHOP); -WUPS_MUST_REPLACE_FOR_PROCESS(FSReadFile_eShop, WUPS_LOADER_LIBRARY_COREINIT, FSReadFile, WUPS_FP_TARGET_PROCESS_ESHOP); -WUPS_MUST_REPLACE_FOR_PROCESS(FSCloseFile_eShop, WUPS_LOADER_LIBRARY_COREINIT, FSCloseFile, WUPS_FP_TARGET_PROCESS_ESHOP); +void patchEshop() { + eshop_patches.reserve(3); + + auto add_patch = [](function_replacement_data_t repl, const char *name) { + PatchedFunctionHandle handle = 0; + if (FunctionPatcher_AddFunctionPatch(&repl, &handle, nullptr) != FUNCTION_PATCHER_RESULT_SUCCESS) { + DEBUG_FUNCTION_LINE("Inkay/eShop: Failed to patch %s!", name); + } + eshop_patches.push_back(handle); + }; + + add_patch(REPLACE_FUNCTION_FOR_PROCESS(FSOpenFile_eShop, LIBRARY_COREINIT, FSOpenFile, FP_TARGET_PROCESS_ESHOP), "FSOpenFile_eShop"); + add_patch(REPLACE_FUNCTION_FOR_PROCESS(FSReadFile_eShop, LIBRARY_COREINIT, FSReadFile, FP_TARGET_PROCESS_ESHOP), "FSReadFile_eShop"); + add_patch(REPLACE_FUNCTION_FOR_PROCESS(FSCloseFile_eShop, LIBRARY_COREINIT, FSCloseFile, FP_TARGET_PROCESS_ESHOP), "FSCloseFile_eShop"); +} + +void unpatchEshop() { + for (auto handle: eshop_patches) { + FunctionPatcher_RemoveFunctionPatch(handle); + } + eshop_patches.clear(); +} diff --git a/src/patches/eshop_applet.h b/src/patches/eshop_applet.h new file mode 100644 index 0000000..6f978ea --- /dev/null +++ b/src/patches/eshop_applet.h @@ -0,0 +1,20 @@ +/* Copyright 2024 Pretendo Network contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +void patchEshop(); +void unpatchEshop(); diff --git a/src/patches/game_matchmaking.cpp b/src/patches/game_matchmaking.cpp index c78d56c..fe26eed 100644 --- a/src/patches/game_matchmaking.cpp +++ b/src/patches/game_matchmaking.cpp @@ -16,8 +16,8 @@ along with this program. If not, see . */ -#include "game_matchmaking.h" #include "config.h" +#include "game_matchmaking.h" #include "utils/logger.h" #include "ini.h" diff --git a/src/patches/game_peertopeer.cpp b/src/patches/game_peertopeer.cpp index 01a6fc6..e0e0fa3 100644 --- a/src/patches/game_peertopeer.cpp +++ b/src/patches/game_peertopeer.cpp @@ -15,30 +15,14 @@ #include #include "game_peertopeer.h" -#include "utils/sysconfig.h" +#include "config.h" +#include "sysconfig.h" #include "utils/logger.h" #include "utils/rpl_info.h" #include "utils/replace_mem.h" #include -static inline int digit(char a) { - if (a < '0' || a > '9') return 0; - return a - '0'; -} - -unsigned short peertopeer_port() { - const char * serial = get_console_serial(); - - unsigned short port = 50000 + - (digit(serial[4]) * 1000) + - (digit(serial[5]) * 100 ) + - (digit(serial[6]) * 10 ) + - (digit(serial[7]) * 1 ); - - return port; -} - static void minecraft_peertopeer_patch() { std::optional minecraft = search_for_rpl("Minecraft.Client.rpx"); if (!minecraft) { @@ -46,7 +30,7 @@ static void minecraft_peertopeer_patch() { return; } - auto port = peertopeer_port(); + auto port = get_console_peertopeer_port(); DEBUG_FUNCTION_LINE_VERBOSE("Will use port %d. %08x", port, minecraft->textAddr); uint32_t *target_func = rpl_addr(*minecraft, 0x03579530); @@ -61,6 +45,10 @@ static void minecraft_peertopeer_patch() { } void peertopeer_patch() { + if (!Config::connect_to_network) { + return; + } + uint64_t tid = OSGetTitleID(); if (tid == 0x00050000'101D7500 || // EUR tid == 0x00050000'101D9D00 || // USA diff --git a/src/patches/game_peertopeer.h b/src/patches/game_peertopeer.h index e26862b..8211c00 100644 --- a/src/patches/game_peertopeer.h +++ b/src/patches/game_peertopeer.h @@ -14,4 +14,3 @@ #pragma once void peertopeer_patch(); -unsigned short peertopeer_port(); diff --git a/src/patches/olv_applet.cpp b/src/patches/olv_applet.cpp index 9bafd44..256b3fd 100644 --- a/src/patches/olv_applet.cpp +++ b/src/patches/olv_applet.cpp @@ -21,11 +21,12 @@ #include "utils/logger.h" #include "utils/replace_mem.h" -#include +#include #include #include #include #include +#include #include "ca_pem.h" // generated at buildtime @@ -65,6 +66,7 @@ const replacement replacements[] = { }; static std::optional rootca_pem_handle{}; +std::vector olv_patches; DECL_FUNCTION(int, FSOpenFile, FSClient *client, FSCmdBlock *block, char *path, const char *mode, uint32_t *handle, int error) { @@ -123,6 +125,25 @@ DECL_FUNCTION(FSStatus, FSCloseFile, FSClient *client, FSCmdBlock *block, FSFile return real_FSCloseFile(client, block, handle, errorMask); } -WUPS_MUST_REPLACE_FOR_PROCESS(FSOpenFile, WUPS_LOADER_LIBRARY_COREINIT, FSOpenFile, WUPS_FP_TARGET_PROCESS_MIIVERSE); -WUPS_MUST_REPLACE_FOR_PROCESS(FSReadFile, WUPS_LOADER_LIBRARY_COREINIT, FSReadFile, WUPS_FP_TARGET_PROCESS_MIIVERSE); -WUPS_MUST_REPLACE_FOR_PROCESS(FSCloseFile, WUPS_LOADER_LIBRARY_COREINIT, FSCloseFile, WUPS_FP_TARGET_PROCESS_MIIVERSE); +void patchOlvApplet() { + olv_patches.reserve(3); + + auto add_patch = [](function_replacement_data_t repl, const char *name) { + PatchedFunctionHandle handle = 0; + if (FunctionPatcher_AddFunctionPatch(&repl, &handle, nullptr) != FUNCTION_PATCHER_RESULT_SUCCESS) { + DEBUG_FUNCTION_LINE("Inkay/OLV: Failed to patch %s!", name); + } + olv_patches.push_back(handle); + }; + + add_patch(REPLACE_FUNCTION_FOR_PROCESS(FSOpenFile, LIBRARY_COREINIT, FSOpenFile, FP_TARGET_PROCESS_MIIVERSE), "FSOpenFile"); + add_patch(REPLACE_FUNCTION_FOR_PROCESS(FSReadFile, LIBRARY_COREINIT, FSReadFile, FP_TARGET_PROCESS_MIIVERSE), "FSReadFile"); + add_patch(REPLACE_FUNCTION_FOR_PROCESS(FSCloseFile, LIBRARY_COREINIT, FSCloseFile, FP_TARGET_PROCESS_MIIVERSE), "FSCloseFile"); +} + +void unpatchOlvApplet() { + for (auto handle: olv_patches) { + FunctionPatcher_RemoveFunctionPatch(handle); + } + olv_patches.clear(); +} diff --git a/src/patches/olv_applet.h b/src/patches/olv_applet.h new file mode 100644 index 0000000..4f307c7 --- /dev/null +++ b/src/patches/olv_applet.h @@ -0,0 +1,20 @@ +/* Copyright 2024 Pretendo Network contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +void patchOlvApplet(); +void unpatchOlvApplet(); diff --git a/src/patches/olv_urls.cpp b/src/patches/olv_urls.cpp index 862453d..849d29b 100644 --- a/src/patches/olv_urls.cpp +++ b/src/patches/olv_urls.cpp @@ -16,8 +16,8 @@ along with this program. If not, see . */ -#include "olv_urls.h" #include "config.h" +#include "olv_urls.h" #include "utils/logger.h" #include "utils/replace_mem.h" diff --git a/src/utils/logger.h b/src/utils/logger.h index f7278b6..e26a487 100644 --- a/src/utils/logger.h +++ b/src/utils/logger.h @@ -14,15 +14,15 @@ extern "C" { #define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILENAME_X__) #define OSFATAL_FUNCTION_LINE(FMT, ARGS...)do { \ - OSFatal_printf("[(P) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \ + OSFatal_printf("[(M) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \ } while (0) #define DEBUG_FUNCTION_LINE(FMT, ARGS...)do { \ - WHBLogPrintf("[(P) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \ + WHBLogPrintf("[(M) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \ } while (0); #define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...)do { \ - WHBLogWritef("[(P) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \ + WHBLogWritef("[(M) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \ } while (0); #ifdef DEBUG