Compare commits

..
60 changed files with 613 additions and 2214 deletions

View file

@ -1,22 +1,18 @@
name: Inkay-CI
permissions:
contents: read
on:
- push
- pull_request
on: push
jobs:
build-inkay:
runs-on: debian-trixie
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v2
- name: build toolchain container
run: docker build . -t builder
- uses: https://github.com/ammaraskar/gcc-problem-matcher@master
- uses: ammaraskar/gcc-problem-matcher@master
- name: build Inkay
run: docker run --rm -v ${PWD}:/app -w /app builder
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@master
with:
name: inkay
path: dist/
path: "*.wps"

7
.gitignore vendored
View file

@ -1,12 +1,7 @@
.cache/
.vscode/
.idea/
/build
/plugin/build
/dist
*.elf
*.wms
*.wps
certs/
*.lst
inkay_config.local.h
*.lst

View file

@ -1,11 +1,10 @@
FROM ghcr.io/wiiu-env/devkitppc:20260504
FROM ghcr.io/wiiu-env/devkitppc:20240505
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:20250219 /artifacts $DEVKITPRO
COPY --from=ghcr.io/wiiu-env/wiiupluginsystem:20240505 /artifacts $DEVKITPRO
WORKDIR /app
CMD make -f Makefile -j$(nproc)
CMD make -f Makefile -j$(nproc)

View file

@ -8,10 +8,10 @@ endif
TOPDIR ?= $(CURDIR)
include $(DEVKITPRO)/wums/share/wums_rules
include $(DEVKITPRO)/wups/share/wups_rules
WUMS_ROOT := $(DEVKITPRO)/wums
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
@ -19,38 +19,39 @@ WUT_ROOT := $(DEVKITPRO)/wut
# DATA is a list of directories containing data files
# INCLUDES is a list of directories containing header files
#-------------------------------------------------------------------------------
TARGET := Malamar-spfn
TARGET := Inkay-pretendo
BUILD := build
SOURCES := src src/patches src/utils src/ext/inih common
SOURCES := src src/patches src/utils src/ext/inih
DATA := data
INCLUDES := src src/ext/inih src/lang common
INCLUDES := src src/ext/inih
#DEBUG := 1
#-------------------------------------------------------------------------------
# options for code generation
#-------------------------------------------------------------------------------
OPT := -Os -fno-exceptions -fno-asynchronous-unwind-tables
CFLAGS := -Wall -ffunction-sections -fdata-sections \
$(MACHDEP) $(OPT)
CFLAGS := -g -Wall -O2 -ffunction-sections \
$(MACHDEP)
CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__
CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ -D__WUPS__
ifeq ($(DEBUG),1)
CFLAGS += -DDEBUG
endif
CXXFLAGS := $(CFLAGS) -std=c++20
ASFLAGS := -g $(ARCH)
LDFLAGS = -g $(ARCH) $(OPT) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) -Wl,-gc-sections -T$(WUMS_ROOT)/share/libkernel.ld $(WUMSSPECS)
LDFLAGS = -g $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map)
ifeq ($(DEBUG),1)
CXXFLAGS += -DDEBUG -g
CFLAGS += -DDEBUG -g
endif
LDFLAGS += -T$(WUMS_ROOT)/share/libkernel.ld $(WUPSSPECS)
LIBS := -lwums -lmocha -lkernel -lwut -lfunctionpatcher -lnotifications
LIBS := -lwups -lmocha -lkernel -lwut -lnotifications -lfunctionpatcher
#-------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level
# containing include and lib
#-------------------------------------------------------------------------------
LIBDIRS := $(PORTLIBS) $(WUT_ROOT) $(WUMS_ROOT) $(WUT_ROOT)/usr
LIBDIRS := $(PORTLIBS) $(WUMS_ROOT) $(WUPS_ROOT) $(WUT_ROOT) $(WUT_ROOT)/usr
#-------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
@ -102,20 +103,14 @@ 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 dist $(BUILD) $(TARGET).wms $(TARGET).elf
@$(MAKE) --no-print-directory -C $(CURDIR)/plugin -f $(CURDIR)/plugin/Makefile clean
@rm -fr $(BUILD) $(TARGET).wps $(TARGET).elf
#-------------------------------------------------------------------------------
else
@ -126,10 +121,10 @@ DEPENDS := $(OFILES:.o=.d)
#-------------------------------------------------------------------------------
# main targets
#-------------------------------------------------------------------------------
all : $(OUTPUT).wms
all : $(OUTPUT).wps
$(OUTPUT).wms : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
$(OUTPUT).wps : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
$(OFILES_SRC) : $(HFILES_BIN)
@ -146,11 +141,6 @@ $(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)
#-------------------------------------------------------------------------------

View file

@ -1,40 +1,16 @@
# Malamar - Aroma patches for Splatfestival Network/Spacebar
# Inkay - Aroma patches for Pretendo
Malamar is an Aroma/WUPS plugin based on Pretendo's Malamar that patches various Nintendo Network URLs on a Wii U to use Splatfestival/Spacebar Network instead. It also (for the time being) bypasses SSL verification in most cases. It redirects Nintendo Network in:
[![Pretendo network logo](https://github.com/PretendoNetwork/website/raw/master/public/assets/images/opengraph/opengraph-image.png)](https://pretendo.network)
- IOSU-side connections (Friends, SpotPass, accounts etc.)
- Account Settings
- NNCS
- Nintendo eShop
- Miiverse (in-game)
- Miiverse applet
Inkay is an Aroma/WUPS plugin that patches various Nintendo Network URLs on a Wii U to use Pretendo Network instead. It also (for the time being) bypasses SSL verification in most cases.
Malamar also includes game-specific patches to add extra features:
- Modpack-specific matchmaking for global, regional rooms (by simulating extra DLC) - **Mario Kart 8**
- P2P port override for better connection stability (if you port forward) - **Minecraft: Wii U Edition**, **Mario Kart 8**, **Splatoon**
Inkay does not currently include the game-specific patches present in [Nimble](https://github.com/PretendoNetwork/Nimble). These will be implemented soon™.
## Requirements
Malamar is only supported on the release version of Aroma configured for autoboot/coldboot. Other configurations (specifically lacking coldboot) may cause issues with SpotPass.
## Dependencies
Inkay is only supported on the release version of Aroma configured for autoboot/coldboot. For Tiramisu, see [Nimble](https://github.com/PretendoNetwork/Nimble).
## Safety
Malamar's patches are all temporary, and only applied in-memory without modifying your console. The SSL patch, while also temporary, could reduce the overall security of your console while active - this is because it no longer checks if a server is verified. However, this does not apply to the Internet Browser, where SSL still works as expected.
Inkay's patches are all temporary, and only applied in-memory without modifying your console. The SSL patch, while also temporary, could reduce the overall security of your console while active - this is because it no longer checks if a server is verified. However, this does not apply to the Internet Browser, where SSL still works as expected.
## Compiling - Docker
Malamar's dependencies and build tooling can be handled as a container, which is recommended for WUPS plugins. Using `docker` or `podman`:
```shell
docker build -t malamar .
docker run --rm -v $(pwd):/app malamar make
# you can replace "make" with other commands - e.g. make clean
```
If using `podman` on SELinux systems (like Fedora Linux), you might need to use `$(pwd):/app:Z` instead of `$(pwd):/app`.
## Compiling - System
Malamar has the following dependencies aside from devkitPPC and wut:
- [WiiUPluginSystem](https://github.com/wiiu-env/WiiUPluginSystem)
- [WiiUModuleSystem](https://github.com/wiiu-env/WiiUModuleSystem)
- [libmocha](https://github.com/wiiu-env/libmocha)
- [libkernel](https://github.com/wiiu-env/libkernel/)
- [libnotifications](https://github.com/wiiu-env/libnotifications/)
- [libfunctionpatcher](https://github.com/wiiu-env/libfunctionpatcher)
Each of these should be `make install`-able. After that, you can compile Malamar with `make`.
## TODO
See [Issues](https://github.com/PretendoNetwork/Inkay/issues).

View file

@ -1,3 +0,0 @@
#pragma once
void ShowNotification(const char* notification);

View file

@ -1,21 +0,0 @@
//
// Created by ash on 17/12/24.
//
#ifndef INKAY_INKAY_CONFIG_H
#define INKAY_INKAY_CONFIG_H
#ifdef __has_include
#if __has_include("inkay_config.local.h")
#include "inkay_config.local.h"
#define INKAY_CUSTOM 1
#endif
#endif
#ifndef NETWORK_BASEURL
#define NETWORK_BASEURL "spbr.net"
#endif
#endif //INKAY_INKAY_CONFIG_H

View file

@ -1,47 +0,0 @@
//
// Created by ash on 21/11/24.
//
#include "lang.h"
config_strings get_config_strings(inkay_language language) {
switch (language) {
case inkay_language::English:
default: return {
#include "en_US.lang"
};
case inkay_language::Spanish: return {
#include "es_ES.lang"
};
case inkay_language::French: return {
#include "fr_FR.lang"
};
case inkay_language::Italian: return {
#include "it_IT.lang"
};
case inkay_language::German: return {
#include "de_DE.lang"
};
case inkay_language::SimplifiedChinese: return {
#include "zh_CN.lang"
};
case inkay_language::Portuguese: return {
#include "pt_BR.lang"
};
case inkay_language::Japanese: return {
#include "ja_JP.lang"
};
case inkay_language::Dutch: return {
#include "nl_NL.lang"
};
case inkay_language::Russian: return {
#include "ru_RU.lang"
};
case inkay_language::Korean: return {
#include "ko_KR.lang"
};
case inkay_language::EnUwU: return {
#include "en@uwu.lang"
};
}
}

View file

@ -1,47 +0,0 @@
#pragma once
#include <string_view>
enum inkay_language {
// Wii U System Languages
Japanese = 0,
English,
French,
German,
Italian,
Spanish,
SimplifiedChinese,
Korean,
Dutch,
Portuguese,
Russian,
TraditionalChinese,
Invalid,
// Custom Languages
System,
EnUwU,
};
struct config_strings {
const char *plugin_name;
std::string_view network_category;
std::string_view connect_to_network_setting;
std::string_view show_startup_toast_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;
std::string_view setting_yes;
std::string_view setting_no;
std::string_view language_setting;
std::string_view language_name;
std::string_view system_language;
};
config_strings get_config_strings(inkay_language language);

View file

@ -1,115 +0,0 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
Copyright 2024 Ash Logan <ash@heyquark.com>
Copyright 2020-2022 V10lator <v10lator@myway.de>
Copyright 2022 Xpl0itU <DaThinkingChair@protonmail.com>
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 <http://www.gnu.org/licenses/>.
*/
#include "sysconfig.h"
#include "utils/logger.h"
#include "utils/scope_exit.h"
#include <coreinit/mcp.h>
#include <coreinit/userconfig.h>
#include <optional>
nn::swkbd::LanguageType get_system_language() {
static std::optional <nn::swkbd::LanguageType> cached_language{};
if (cached_language) return *cached_language;
UCHandle handle = UCOpen();
if (handle < 0) {
DEBUG_FUNCTION_LINE("Error opening UC: %d", handle);
return nn::swkbd::LanguageType::English;
}
scope_exit uc_c([&] { UCClose(handle); });
nn::swkbd::LanguageType language;
alignas(0x40) UCSysConfig settings = {
.name = "cafe.language",
.access = 0,
.dataType = UC_DATATYPE_UNSIGNED_INT,
.error = UC_ERROR_OK,
.dataSize = sizeof(language),
.data = &language,
};
UCError err = UCReadSysConfig(handle, 1, &settings);
if (err != UC_ERROR_OK) {
DEBUG_FUNCTION_LINE("Error reading UC: %d!", err);
return nn::swkbd::LanguageType::English;
}
DEBUG_FUNCTION_LINE_VERBOSE("System language found: %d", static_cast<int>(language));
cached_language = language;
return language;
}
static std::optional<MCPSysProdSettings> mcp_config;
static std::optional<MCPSystemVersion> mcp_os_version;
static void get_mcp_config() {
int mcp = MCP_Open();
scope_exit mcp_c([&] { MCP_Close(mcp); });
alignas(0x40) MCPSysProdSettings config {};
if (MCP_GetSysProdSettings(mcp, &config)) {
DEBUG_FUNCTION_LINE("Could not get MCP system config!");
return;
}
mcp_config = config;
//get os version
MCPSystemVersion os_version;
if (MCP_GetSystemVersion(mcp, &os_version)) {
DEBUG_FUNCTION_LINE("Could not get MCP system config!");
return;
}
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
);
}
const char * get_console_serial() {
if (!mcp_config) get_mcp_config();
return mcp_config ? mcp_config->serial_id : "123456789";
}
MCPSystemVersion get_console_os_version() {
if (!mcp_os_version) get_mcp_config();
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;
}

View file

@ -1,13 +0,0 @@
#pragma once
#include <functional>
// https://stackoverflow.com/a/61242721
template<typename F>
struct scope_exit
{
F func;
explicit scope_exit(F&& f): func(std::forward<F>(f)) {}
~scope_exit() { func(); }
};
template<typename F> scope_exit(F&& frv) -> scope_exit<F>;

View file

@ -1,149 +0,0 @@
#-------------------------------------------------------------------------------
.SUFFIXES:
#-------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/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 := Malamar-spfn
BUILD := build
SOURCES := src src/utils ../common
DATA := data
INCLUDES := src ../src/lang ../common
#DEBUG := 1
#-------------------------------------------------------------------------------
# options for code generation
#-------------------------------------------------------------------------------
OPT := -Os -fno-exceptions -fno-asynchronous-unwind-tables
CFLAGS := -Wall -ffunction-sections -fdata-sections \
$(MACHDEP) $(OPT)
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) $(OPT) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) -Wl,-gc-sections -Wl,--discard-all
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
#-------------------------------------------------------------------------------

View file

@ -1,331 +0,0 @@
/* Copyright 2022 Pretendo Network contributors <pretendo.network>
Copyright 2022 Ash Logan <ash@heyquark.com>
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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "wut_extra.h"
#include "utils/logger.h"
#include "sysconfig.h"
#include "lang.h"
#include <wups.h>
#include <wups/storage.h>
#include <wups/config_api.h>
#include <wups/config/WUPSConfigItemMultipleValues.h>
#include <wups/config/WUPSConfigItemBoolean.h>
#include <wups/config/WUPSConfigItemStub.h>
#include <coreinit/title.h>
#include <coreinit/launch.h>
#include <sysapp/title.h>
#include <sysapp/launch.h>
#include <nn/act.h>
#include <format>
static config_strings strings;
bool Config::connect_to_network = true;
bool Config::show_startup_toast = true;
bool Config::need_relaunch = false;
bool Config::unregister_task_item_pressed = false;
bool Config::is_wiiu_menu = false;
uint32_t Config::language = 13;
inkay_language Config::current_language = English;
static WUPSConfigAPICallbackStatus report_error(WUPSConfigAPIStatus err, int line) {
DEBUG_FUNCTION_LINE_VERBOSE("WUPS config error: %s (L%04d)", WUPSConfigAPI_GetStatusStr(err), line);
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 = WUPSStorageAPI_StoreBool(nullptr, "connect_to_network", Config::connect_to_network);
if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res);
}
static void language_changed(ConfigItemMultipleValues *item, uint32_t new_value) {
DEBUG_FUNCTION_LINE_VERBOSE("language changed to: %d", new_value);
if (new_value != Config::language) {
if (new_value == inkay_language::System)
Config::current_language = (inkay_language) get_system_language();
else
Config::current_language = (inkay_language) new_value;
}
Config::language = new_value;
WUPSStorageError res = WUPSStorageAPI_StoreU32(nullptr, "language", Config::language);
if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res);
}
static void show_startup_toast_changed(ConfigItemBoolean *item, bool new_value) {
DEBUG_FUNCTION_LINE_VERBOSE("show_startup_toast changed to: %d", new_value);
Config::show_startup_toast = new_value;
WUPSStorageError res = WUPSStorageAPI_StoreBool(nullptr, "show_startup_toast", Config::show_startup_toast);
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 root_cat) {
WUPSConfigAPIStatus err;
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(Config::current_language);
// Network submenu
// Connect to Pretendo Network Yes/No
// Using UDP port 5000 for multiplayer
WUPSConfigCategoryHandle network_cat;
err = WUPSConfigAPI_Category_Create({strings.network_category.data()}, &network_cat);
if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err, __LINE__);
WUPSConfigItemHandle connect_item;
err = WUPSConfigItemBoolean_CreateEx(
"connect_to_network", strings.connect_to_network_setting.data(), true, Config::connect_to_network,
&connect_to_network_changed, strings.setting_yes.data(), strings.setting_no.data(), &connect_item);
if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err, __LINE__);
err = WUPSConfigAPI_Category_AddItem(network_cat, connect_item);
if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err, __LINE__);
const uint16_t port = get_console_peertopeer_port();
char buffer[256];
snprintf(buffer, sizeof(buffer), strings.multiplayer_port_display.data(), port);
WUPSConfigItemHandle multiplayer_port_display;
err = WUPSConfigItemStub_Create(buffer, &multiplayer_port_display);
if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err, __LINE__);
err = WUPSConfigAPI_Category_AddItem(network_cat, multiplayer_port_display);
if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err, __LINE__);
// Add to root and finish
err = WUPSConfigAPI_Category_AddCategory(root_cat, network_cat);
if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err, __LINE__);
// Other category
// Reset Wara Wara Plaza Press A
// Language English/Spanish/etc..
// Show startup toast Yes/No
WUPSConfigCategoryHandle other_cat;
err = WUPSConfigAPI_Category_Create({strings.other_category.data()}, &other_cat);
if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err, __LINE__);
WUPSConfigItemHandle unregisterTasksItem;
err = WUPSConfigAPI_Item_Create({
.displayName = strings.reset_wwp_setting.data(),
.context = nullptr,
.callbacks = {
.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
},
}, &unregisterTasksItem);
if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err, __LINE__);
err = WUPSConfigAPI_Category_AddItem(other_cat, unregisterTasksItem);
if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err, __LINE__);
// TODO localise these!
constexpr std::array<ConfigItemMultipleValuesPair, 14> languages = {
{
{0, "Japanese"},
{1, "English"},
{2, "French"},
{3, "German"},
{4, "Italian"},
{5, "Spanish"},
{6, "Simplified Chinese"},
{7, "Korean"},
{8, "Dutch"},
{9, "Portuguese"},
{10, "Russian"},
{11, "Traditional Chinese"},
{13, "System"},
{14, "English (UwU)"},
}
};
constexpr auto find_lang = [languages](const uint32_t lang) -> int {
return std::ranges::find_if(languages, [lang](auto const l) { return l.value == lang; }) -
std::begin(languages);
};
constexpr int system_language = find_lang(13 /* System */);
const int current_language = find_lang(Config::language);
WUPSConfigItemHandle language_item;
err = WUPSConfigItemMultipleValues_Create("language", strings.language_setting.data(), system_language,
current_language,
const_cast<ConfigItemMultipleValuesPair *>(languages.data()), // Yikes!
languages.size(), &language_changed, &language_item);
if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err, __LINE__);
err = WUPSConfigAPI_Category_AddItem(other_cat, language_item);
if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err, __LINE__);
WUPSConfigItemHandle startup_toast_item;
err = WUPSConfigItemBoolean_CreateEx("show_startup_toast", strings.show_startup_toast_setting.data(), true,
Config::show_startup_toast, &show_startup_toast_changed,
strings.setting_yes.data(), strings.setting_no.data(), &startup_toast_item);
if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err, __LINE__);
err = WUPSConfigAPI_Category_AddItem(other_cat, startup_toast_item);
if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err, __LINE__);
// finish up
err = WUPSConfigAPI_Category_AddCategory(root_cat, other_cat);
if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err, __LINE__);
return WUPSCONFIG_API_CALLBACK_RESULT_SUCCESS;
}
static void ConfigMenuClosedCallback() {
// Save all changes
WUPSStorageError res = WUPSStorageAPI_SaveStorage(false);
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() {
// Init the config api
WUPSConfigAPIStatus cres =
WUPSConfigAPI_Init({.name = "Inkay"}, ConfigMenuOpenedCallback, ConfigMenuClosedCallback);
if (cres != WUPSCONFIG_API_RESULT_SUCCESS) return (void) report_error(cres, __LINE__);
WUPSStorageError res;
// Try to get values from storage
res = WUPSStorageAPI_GetBool(nullptr, "connect_to_network", &Config::connect_to_network);
if (res == WUPS_STORAGE_ERROR_NOT_FOUND) {
DEBUG_FUNCTION_LINE_VERBOSE("Connect to network value not found, attempting to migrate/create");
bool skipPatches = false;
if (WUPSStorageAPI_GetBool(nullptr, "skipPatches", &skipPatches) == WUPS_STORAGE_ERROR_SUCCESS) {
// Migrate old config value
Config::connect_to_network = !skipPatches;
WUPSStorageAPI_DeleteItem(nullptr, "skipPatches");
}
// Add the value to the storage if it's missing.
res = WUPSStorageAPI_StoreBool(nullptr, "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);
res = WUPSStorageAPI_GetU32(nullptr, "language", &Config::language);
if (res == WUPS_STORAGE_ERROR_NOT_FOUND) {
DEBUG_FUNCTION_LINE_VERBOSE("Language value not found, attempting to create");
// Add the value to the storage.
res = WUPSStorageAPI_StoreU32(nullptr, "language", Config::language);
if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res);
} else if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res);
res = WUPSStorageAPI_GetBool(nullptr, "show_startup_toast", &Config::show_startup_toast);
if (res == WUPS_STORAGE_ERROR_NOT_FOUND) {
DEBUG_FUNCTION_LINE_VERBOSE("Show startup toast value not found, attempting to create");
// Add the value to the storage if it's missing.
res = WUPSStorageAPI_StoreBool(nullptr, "show_startup_toast", show_startup_toast);
if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res);
} else if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res);
// Set the language that's currently used
if (Config::language == inkay_language::System)
Config::current_language = (inkay_language) get_system_language();
else
Config::current_language = (inkay_language) Config::language;
// Save storage
res = WUPSStorageAPI_SaveStorage(false);
if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res);
}

View file

@ -1,31 +0,0 @@
//
// Created by ash on 10/12/22.
//
#ifndef INKAY_CONFIG_H
#define INKAY_CONFIG_H
#include <stdint.h>
#include "lang.h"
class Config {
public:
static void Init();
// wups config items
static bool connect_to_network;
static uint32_t language;
static bool show_startup_toast;
// private stuff
static bool need_relaunch;
// private stuff
static bool is_wiiu_menu;
static inkay_language current_language;
static bool unregister_task_item_pressed;
};
#endif //INKAY_CONFIG_H

View file

@ -1,152 +0,0 @@
/* Copyright 2022 Pretendo Network contributors <pretendo.network>
Copyright 2022 Ash Logan <ash@heyquark.com>
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 <http://www.gnu.org/licenses/>.
*/
#include <wups.h>
#include <notifications/notifications.h>
#include <utils/logger.h>
#include "config.h"
#include<coreinit/title.h>
#include<coreinit/dynload.h>
#include<vector>
#include "module.h"
#define INKAY_VERSION "v3.1.0"
/**
Mandatory plugin information.
If not set correctly, the loader will refuse to use the plugin.
**/
WUPS_META(name, "Malamar (SPFN)");
WUPS_META(wups, WUPS_VERSION_STR);
WUPS_USE_WUT_MALLOC();
WUPS_USE_WUT_SOCKETS();
WUPS_USE_WUT_NEWLIB();
WUPS___INIT_WRAPPER();
WUPS___FINI_WRAPPER();
WUPS_INIT_CONFIG_FUNCTIONS();
WUPS_META(buildtimestamp, __DATE__ " " __TIME__);
extern const char wups_meta_plugin_name[] WUPS_SECTION("meta");
const char wups_meta_plugin_name[] = "Malamar (SPFN)";
extern const char wups_meta_info_dump[] WUPS_SECTION("meta");
const char wups_meta_info_dump[] = "(plugin: Malamar (SPFN);" \
"wups " WUPS_VERSION_STR ";" \
"buildtime: " __DATE__ " " __TIME__ ")";
extern const char wups_meta_info_linking_order[] WUPS_SECTION("meta");
const char wups_meta_info_linking_order[] = "Loading \"Malamar (SPFN)\" failed.\n" \
"Function \"wut_get_thread_specific\" returned unexpected value.\n" \
"Please check linking order (expected \"-lwups -lwut\")";
WUPS_PLUGIN_DESCRIPTION("SPFN Network Patcher");
WUPS_PLUGIN_VERSION(INKAY_VERSION);
WUPS_PLUGIN_AUTHOR("redbinder0526, kittentm and Pretendo contributors");
WUPS_PLUGIN_LICENSE("GPLv3");
WUPS_USE_STORAGE("inkay");
WUPS_USE_WUT_DEVOPTAB();
bool is_current_game_splatoon(){
auto current_game = OSGetTitleID();
if(
current_game == 0x0005000010162B00 ||
current_game == 0x0005000010176900 ||
current_game == 0x0005000010176A00
){
return true;
}
return false;
}
void splatoon_patcher(){
if (!Config::connect_to_network) {
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: Splatoon patches skipped");
return;
}
if(is_current_game_splatoon()){
NotificationModule_AddInfoNotification("Splatoon Detected, applying patch");
auto rpl_count = OSDynLoad_GetNumberOfRPLs();
std::vector<OSDynLoad_NotifyData> rpls(rpl_count);
if(!OSDynLoad_GetRPLInfo(0, rpl_count, rpls.data())){
NotificationModule_AddInfoNotification("failed to get rpls");
}
for(auto rpl: rpls){
if(rpl.name != 0){
if(strstr(rpl.name, "Gambit") != NULL){
NotificationModule_AddInfoNotification(rpl.name);
auto text_color = NMColor();
text_color.r = 255;
text_color.g = 255;
text_color.b = 255;
text_color.a = 255;
auto bg_color = NMColor();
bg_color.r = 0;
bg_color.g = 0;
bg_color.b = 0;
bg_color.a = 0;
NotificationModule_AddInfoNotificationEx(rpl.name, 30.0f, text_color, bg_color, nullptr, nullptr, true);
NotificationModule_AddInfoNotification("applying patches!!!!");
void* game_data_region = (void*)rpl.dataAddr;
auto str_loc = (char*)(0x000009ac + (uint32_t)game_data_region);
strcpy(str_loc, "https://splatpost.spbr.net/post");
}
}
}
}
}
INITIALIZE_PLUGIN() {
WHBLogCafeInit();
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, Config::show_startup_toast);
}
DEINITIALIZE_PLUGIN() {
Inkay_Finalize();
NotificationModule_DeInitLibrary();
WHBLogCafeDeinit();
}
ON_APPLICATION_START() {
// Tell the module the plugin is running!
splatoon_patcher();
Inkay_SetPluginRunning();
}
ON_APPLICATION_ENDS() {
}

View file

@ -1,93 +0,0 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
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 <http://www.gnu.org/licenses/>.
*/
#include "module.h"
#include "Notification.h"
#include "utils/logger.h"
#include "config.h"
#include "lang.h"
#include <coreinit/dynload.h>
static OSDynLoad_Module module;
static void (*moduleInitialize)(bool, bool, inkay_language) = nullptr;
static InkayStatus (*moduleGetStatus)() = nullptr;
static void (*moduleSetPluginRunning)() = nullptr;
static const char *get_module_not_found_message() {
return get_config_strings(Config::current_language).module_not_found.data();
}
static const char *get_module_init_not_found_message() {
return get_config_strings(Config::current_language).module_init_not_found.data();
}
void Inkay_Initialize(bool apply_patches, bool show_startup_toast) {
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<void * *>(&moduleInitialize)) != OS_DYNLOAD_OK) {
DEBUG_FUNCTION_LINE("Failed to find function %s", "Inkay_Initialize");
ShowNotification(get_module_init_not_found_message());
OSDynLoad_Release(module);
return;
}
moduleInitialize(apply_patches, show_startup_toast, Config::current_language);
}
void Inkay_Finalize() {
if (module) {
OSDynLoad_Release(module);
moduleInitialize = nullptr;
moduleGetStatus = nullptr;
moduleSetPluginRunning = nullptr;
}
}
InkayStatus Inkay_GetStatus() {
if (!module) {
return InkayStatus::Error;
}
if (!moduleGetStatus && OSDynLoad_FindExport(module, OS_DYNLOAD_EXPORT_FUNC, "Inkay_GetStatus", reinterpret_cast<void * *>(&moduleGetStatus)) != OS_DYNLOAD_OK) {
DEBUG_FUNCTION_LINE("Failed to find function %s", "Inkay_GetStatus");
return InkayStatus::Error;
}
return moduleGetStatus();
}
void Inkay_SetPluginRunning() {
if (!module) {
return;
}
if (!moduleSetPluginRunning && OSDynLoad_FindExport(module, OS_DYNLOAD_EXPORT_FUNC, "Inkay_SetPluginRunning", reinterpret_cast<void * *>(&moduleSetPluginRunning)) != OS_DYNLOAD_OK) {
DEBUG_FUNCTION_LINE("Failed to find function %s", "Inkay_SetPluginRunning");
return;
}
moduleSetPluginRunning();
}

View file

@ -1,30 +0,0 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
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 <http://www.gnu.org/licenses/>.
*/
#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, bool show_startup_toast);
void Inkay_Finalize();
InkayStatus Inkay_GetStatus();
void Inkay_SetPluginRunning();

View file

@ -1,36 +0,0 @@
#pragma once
#include <string.h>
#include <whb/log.h>
#include <whb/log_module.h>
#include <whb/log_cafe.h>
#include <whb/log_udp.h>
#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

View file

@ -1,3 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}

View file

@ -18,7 +18,7 @@
#include <notifications/notification_defines.h>
#include <notifications/notifications.h>
void ShowNotification(const char* notification) {
void ShowNotification(std::string_view notification) {
auto err1 = NotificationModule_SetDefaultValue(NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO,
NOTIFICATION_MODULE_DEFAULT_OPTION_KEEP_UNTIL_SHOWN, true);
auto err2 = NotificationModule_SetDefaultValue(NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO,
@ -27,5 +27,5 @@ void ShowNotification(const char* notification) {
if (err1 != NOTIFICATION_MODULE_RESULT_SUCCESS || err2 != NOTIFICATION_MODULE_RESULT_SUCCESS) return;
NotificationModule_AddInfoNotification(notification);
NotificationModule_AddInfoNotification(notification.data());
}

5
src/Notification.h Normal file
View file

@ -0,0 +1,5 @@
#pragma once
#include <string_view>
void ShowNotification(std::string_view notification);

View file

@ -17,9 +17,244 @@
#include "config.h"
bool Config::connect_to_network = false;
bool Config::show_startup_toast = false;
bool Config::initialized = false;
bool Config::shown_warning = false;
bool Config::plugin_is_loaded = false;
bool Config::block_initialize = false;
#include "wut_extra.h"
#include "utils/logger.h"
#include "utils/sysconfig.h"
#include <wups.h>
#include <wups/storage.h>
#include <wups/config_api.h>
#include <wups/config/WUPSConfigItemBoolean.h>
#include <coreinit/title.h>
#include <coreinit/launch.h>
#include <sysapp/title.h>
#include <sysapp/launch.h>
#include <nn/act.h>
bool Config::connect_to_network = true;
bool Config::need_relaunch = false;
bool Config::unregister_task_item_pressed = false;
bool Config::is_wiiu_menu = false;
config_strings strings;
constexpr config_strings get_config_strings(nn::swkbd::LanguageType language) {
switch (language) {
case nn::swkbd::LanguageType::English:
default:
return {
.plugin_name = "Inkay",
.network_category = "Network selection",
.connect_to_network_setting = "Connect to the Pretendo network",
.other_category = "Other settings",
.reset_wwp_setting = "Reset Wara Wara Plaza",
.press_a_action = "Press A",
.restart_to_apply_action = "Restart to apply",
.need_menu_action = "From WiiU menu only"
};
case nn::swkbd::LanguageType::Spanish:
return {
.plugin_name = "Inkay",
.network_category = "Selección de red",
.connect_to_network_setting = "Conectar a la red Pretendo",
.other_category = "Otros ajustes",
.reset_wwp_setting = "Restablecer Wara Wara Plaza",
.press_a_action = "Pulsa A",
.restart_to_apply_action = "Reinicia para confirmar",
.need_menu_action = "Sólo desde el menú de WiiU",
};
case nn::swkbd::LanguageType::French:
return {
.plugin_name = "Inkay",
.network_category = "Sélection du réseau",
.connect_to_network_setting = "Connexion à Pretendo",
.other_category = "Autres paramètres",
.reset_wwp_setting = "Réinitialiser la place WaraWara",
.press_a_action = "Appuyez sur A",
.restart_to_apply_action = "Redémarrer pour appliquer",
.need_menu_action = "Depuis le menu Wii U seulement",
};
case nn::swkbd::LanguageType::Italian:
return {
.plugin_name = "Inkay",
.network_category = "Selezione della rete",
.connect_to_network_setting = "Connettiti alla rete Pretendo",
.other_category = "Altre categorie",
.reset_wwp_setting = "Ripristina Wara Wara Plaza",
.press_a_action = "Premi A",
.restart_to_apply_action = "Riavvia per applicare",
.need_menu_action = "Solo dal menu WiiU",
};
case nn::swkbd::LanguageType::German:
return {
.plugin_name = "Inkay",
.network_category = "Netzwerkauswahl",
.connect_to_network_setting = "Verbinde zum Pretendo Network",
.other_category = "Andere Einstellungen",
.reset_wwp_setting = "Wara Wara Plaza zurücksetzen",
.press_a_action = "Drücke A",
.restart_to_apply_action = "Neustarten zum Anwenden",
.need_menu_action = "Nur vom Wii U-Menü aus",
};
}
}
static void connect_to_network_changed(ConfigItemBoolean* item, bool new_value) {
DEBUG_FUNCTION_LINE("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;
if (WUPSStorageAPI::Store<bool>("connect_to_network", Config::connect_to_network) != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE("Failed to save \"connect_to_network\" value (%d)", Config::connect_to_network);
}
}
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) {
if (!Config::is_wiiu_menu)
strncpy(out_buf, strings.need_menu_action, out_size);
else
strncpy(out_buf, Config::unregister_task_item_pressed ? strings.restart_to_apply_action : strings.press_a_action, out_size);
return 0;
}
static WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback(WUPSConfigCategoryHandle rootHandle) {
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 patching_cat = WUPSConfigCategory::Create(strings.network_category);
// config id display name default current value changed callback
patching_cat.add(WUPSConfigItemBoolean::Create("connect_to_network", strings.connect_to_network_setting, true, Config::connect_to_network, &connect_to_network_changed));
root.add(std::move(patching_cat));
auto other_cat = WUPSConfigCategory::Create(strings.other_category);
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,
.context = nullptr,
.callbacks = unregisterTasksItemCallbacks,
};
WUPSConfigItemHandle unregisterTasksItem;
WUPSConfigAPIStatus err;
if ((err = WUPSConfigAPI_Item_Create(unregisterTasksItemOptions, &unregisterTasksItem)) != WUPSCONFIG_API_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE("Creating config menu failed: %s", WUPSConfigAPI_GetStatusStr(err));
return WUPSCONFIG_API_CALLBACK_RESULT_ERROR;
}
if ((err = WUPSConfigAPI_Category_AddItem(other_cat.getHandle(), unregisterTasksItem)) != WUPSCONFIG_API_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE("Creating config menu failed: %s", WUPSConfigAPI_GetStatusStr(err));
return WUPSCONFIG_API_CALLBACK_RESULT_ERROR;
}
root.add(std::move(other_cat));
return WUPSCONFIG_API_CALLBACK_RESULT_SUCCESS;
}
static void ConfigMenuClosedCallback() {
// Save all changes
if (WUPSStorageAPI::SaveStorage() != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE("Failed to save storage");
}
if (Config::need_relaunch) {
// Need to reload the console so the patches reset
OSForceFullRelaunch();
SYSLaunchMenu();
Config::need_relaunch = false;
}
}
void Config::Init() {
// Init the config api
WUPSConfigAPIOptionsV1 configOptions = { .name = "Inkay" };
if (WUPSConfigAPI_Init(configOptions, ConfigMenuOpenedCallback, ConfigMenuClosedCallback) != WUPSCONFIG_API_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE("Failed to initialize WUPS Config API");
return;
}
WUPSStorageError storageRes;
// Try to get value from storage
if ((storageRes = WUPSStorageAPI::Get<bool>("connect_to_network", Config::connect_to_network)) == WUPS_STORAGE_ERROR_NOT_FOUND) {
DEBUG_FUNCTION_LINE("Connect to network value not found, attempting to migrate/create");
bool skipPatches = false;
if (WUPSStorageAPI::Get<bool>("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.
if (WUPSStorageAPI::Store<bool>("connect_to_network", connect_to_network) != 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)", WUPSStorageAPI_GetStatusStr(storageRes), storageRes);
}
// Save storage
if (WUPSStorageAPI::SaveStorage() != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE("Failed to save storage");
}
}

View file

@ -7,18 +7,29 @@
class Config {
public:
static void Init();
// wups config items
static bool connect_to_network;
static bool show_startup_toast;
// private stuff
static bool need_relaunch;
static bool initialized;
// private stuff
static bool is_wiiu_menu;
static bool shown_warning;
static bool unregister_task_item_pressed;
};
static bool plugin_is_loaded;
static bool block_initialize;
struct config_strings {
const char *plugin_name;
const char *network_category;
const char *connect_to_network_setting;
const char *other_category;
const char *reset_wwp_setting;
const char *press_a_action;
const char *restart_to_apply_action;
const char *need_menu_action;
};
#endif //INKAY_CONFIG_H

View file

@ -1,25 +0,0 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
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 <http://www.gnu.org/licenses/>.
*/
#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
};

View file

@ -8,35 +8,35 @@ typedef struct URL_Patch {
static const URL_Patch url_patches[] = {
//nim-boss .rodata
{0xE2282550, "http://pushmorewupshop.spbr.net/pushmore/r/%s"},
{0xE229A0A0, "http://npns-devcapp.spbr.net/bst.dat"},
{0xE229A0D0, "http://npns-devcapp.spbr.net/bst2.dat"},
{0xE2281964, "https://tagayawupshop.spbr.net/tagaya/versionlist/%s/%s/%s"},
{0xE22819B4, "https://tagayawupshop.spbr.net/tagaya/versionlist/%s/%s/latest_version"},
{0xE2282584, "http://pushmowupshop.spbr.net/pushmo/d/%s/%u"},
{0xE22825B8, "http://pushmowupshop.spbr.net/pushmo/c/%u/%u"},
{0xE2282DB4, "https://ecswupshop.spbr.net/ecs/services/ECommerceSOAP"},
{0xE22830A0, "https://ecswupshop.spbr.net/ecs/services/ECommerceSOAP"},
{0xE22830E0, "https://nuswupshop.spbr.net/nus/services/NetUpdateSOAP"},
{0xE2299990, "policylist.spbr.net"},
{0xE229A600, "https://plswupshop.spbr.net/pls/upload"},
{0xE229A6AC, "https://npvk-devapp.spbr.net/reports"},
{0xE229A6D8, "https://npvkapp.spbr.net/reports"},
{0xE229B1F4, "https://tasksheets.spbr.net/p01/tasksheet/%s/%s/%s/%s?c=%s&l=%s"},
{0xE229B238, "https://tasksheets.spbr.net/p01/tasksheet/%s/%s/%s?c=%s&l=%s"},
{0xE22AB2D8, "https://idbe-wupcdn.spbr.net/icondata/%02X/%016llX.idbe"},
{0xE22AB318, "https://idbe-ctrcdn.spbr.net/icondata/%02X/%016llX.idbe"},
{0xE22AB358, "https://idbe-wupcdn.spbr.net/icondata/%02X/%016llX-%d.idbe"},
{0xE22AB398, "https://idbe-ctrcdn.spbr.net/icondata/%02X/%016llX-%d.idbe"},
{0xE22B3EF8, "https://ecscshop.spbr.net"},
{0xE22B3F30, "https://ecscshop.spbr.net/ecs/services/ECommerceSOAP"},
{0xE22B3F70, "https://iascshop.spbr.net/ias/services/IdentityAuthenticationSOAP"},
{0xE22B3FBC, "https://cascshop.spbr.net/cas/services/CatalogingSOAP"},
{0xE22B3FFC, "https://nuscshop.spbr.net/nus/services/NetUpdateSOAP"},
{0xE229DE0C, "n.app.spbr.net"},
{0xE2282550, "http://pushmore.wup.shop.pretendo.cc/pushmore/r/%s"},
{0xE229A0A0, "http://npns-dev.c.app.pretendo.cc/bst.dat"},
{0xE229A0D0, "http://npns-dev.c.app.pretendo.cc/bst2.dat"},
{0xE2281964, "https://tagaya.wup.shop.pretendo.cc/tagaya/versionlist/%s/%s/%s"},
{0xE22819B4, "https://tagaya.wup.shop.pretendo.cc/tagaya/versionlist/%s/%s/latest_version"},
{0xE2282584, "http://pushmo.wup.shop.pretendo.cc/pushmo/d/%s/%u"},
{0xE22825B8, "http://pushmo.wup.shop.pretendo.cc/pushmo/c/%u/%u"},
{0xE2282DB4, "https://ecs.wup.shop.pretendo.cc/ecs/services/ECommerceSOAP"},
{0xE22830A0, "https://ecs.wup.shop.pretendo.cc/ecs/services/ECommerceSOAP"},
{0xE22830E0, "https://nus.wup.shop.pretendo.cc/nus/services/NetUpdateSOAP"},
{0xE2299990, "nppl.app.pretendo.cc"},
{0xE229A600, "https://pls.wup.shop.pretendo.cc/pls/upload"},
{0xE229A6AC, "https://npvk-dev.app.pretendo.cc/reports"},
{0xE229A6D8, "https://npvk.app.pretendo.cc/reports"},
{0xE229B1F4, "https://npts.app.pretendo.cc/p01/tasksheet/%s/%s/%s/%s?c=%s&l=%s"},
{0xE229B238, "https://npts.app.pretendo.cc/p01/tasksheet/%s/%s/%s?c=%s&l=%s"},
{0xE22AB2D8, "https://idbe-wup.cdn.pretendo.cc/icondata/%02X/%016llX.idbe"},
{0xE22AB318, "https://idbe-ctr.cdn.pretendo.cc/icondata/%02X/%016llX.idbe"},
{0xE22AB358, "https://idbe-wup.cdn.pretendo.cc/icondata/%02X/%016llX-%d.idbe"},
{0xE22AB398, "https://idbe-ctr.cdn.pretendo.cc/icondata/%02X/%016llX-%d.idbe"},
{0xE22B3EF8, "https://ecs.c.shop.pretendo.cc"},
{0xE22B3F30, "https://ecs.c.shop.pretendo.cc/ecs/services/ECommerceSOAP"},
{0xE22B3F70, "https://ias.c.shop.pretendo.cc/ias/services/IdentityAuthenticationSOAP"},
{0xE22B3FBC, "https://cas.c.shop.pretendo.cc/cas/services/CatalogingSOAP"},
{0xE22B3FFC, "https://nus.c.shop.pretendo.cc/nus/services/NetUpdateSOAP"},
{0xE229DE0C, "n.app.pretendo.cc"},
//nim-boss .bss
{0xE24B8A24, "https://policylist.spbr.net/p01/policylist/1/1/UNK"},
{0xE31930D4, "https://%s%saccount.spbr.net/v%u/api/"}
{0xE24B8A24, "https://nppl.app.pretendo.cc/p01/policylist/1/1/UNK"},
{0xE31930D4, "https://%s%saccount.pretendo.cc/v%u/api/"}
};

View file

@ -1,18 +0,0 @@
.plugin_name="Inkay"
,.network_category="Netzwerkauswahl"
,.connect_to_network_setting="Verbinde zum SPFN"
,.other_category="Andere Einstellungen"
,.reset_wwp_setting="Wara Wara Plaza zurücksetzen"
,.press_a_action="Drücke A"
,.restart_to_apply_action="Neustarten zum Anwenden"
,.need_menu_action="Nur vom Wii U-Menü aus"
,.using_nintendo_network="Nutze Nintendo Network"
,.using_pretendo_network="Nutze SPFN"
,.multiplayer_port_display="Nutze UDP Port %hu für Mehrspieler"
,.module_not_found="SPFN Patch fehlgeschlagen - nutze Aroma Updater zum Reparieren (686-1001 Module missing)"
,.module_init_not_found="SPFN Patch fehlgeschlagen - nutze Aroma Updater zum Reparieren (686-1002 Module init)"
,.setting_yes=""
,.setting_no=""
,.language_setting=""
,.language_name="Deutsch"
,.system_language=""

View file

@ -1,19 +0,0 @@
.plugin_name="Inky ^w^"
,.network_category="Newowk sewectiown! :3"
,.connect_to_network_setting="use dah pretender!!!!"
,.show_startup_toast_setting="show da deets :3"
,.other_category="oth3r stuffzz"
,.reset_wwp_setting="warah wara go BYEBYE x3c"
,.press_a_action="Press A plz?"
,.restart_to_apply_action="ima restart ur consoel >:3c"
,.need_menu_action="no menu?? only menu -w-"
,.using_nintendo_network="meanie network!!!"
,.using_pretendo_network="pretender netw3rk >:D"
,.multiplayer_port_display="mah secret word is %hu... ur modem will know"
,.module_not_found="oh noez! pls get aroma updater help :( (686-1001 Module missing)"
,.module_init_not_found="oh noez! pls get aroma updater help :( (686-1002 Module init)"
,.setting_yes="yaah!"
,.setting_no="nu >w<"
,.language_setting="spek..."
,.language_name="kitteh speak!!"
,.system_language="System"

View file

@ -1,19 +0,0 @@
.plugin_name="Malamar"
,.network_category="Network selection"
,.connect_to_network_setting="Connect to SPFN"
,.show_startup_toast_setting="Show startup toast"
,.other_category="Other settings"
,.reset_wwp_setting="Reset Wara Wara Plaza"
,.press_a_action="Press A"
,.restart_to_apply_action="Restart to apply"
,.need_menu_action="From WiiU menu only"
,.using_nintendo_network="Using Nintendo Network"
,.using_pretendo_network="Using SPFN"
,.multiplayer_port_display="Using UDP port %hu for multiplayer"
,.module_not_found="SPFN patch failed - use Aroma Updater to repair (686-1001 Module missing)"
,.module_init_not_found="SPFN patch failed - use Aroma Updater to repair (686-1002 Module init)"
,.setting_yes="Yes"
,.setting_no="No"
,.language_setting="Language"
,.language_name="English"
,.system_language="System"

View file

@ -1,19 +0,0 @@
.plugin_name="Inkay"
,.network_category="Seleccionar red"
,.connect_to_network_setting="Conectar a SPFN"
,.show_startup_toast_setting=""
,.other_category="Otros ajustes"
,.reset_wwp_setting="Restablecer Plaza Wara Wara"
,.press_a_action="Pulsa A"
,.restart_to_apply_action="Reiniciar para aplicar"
,.need_menu_action="Solo desde el Menú de Wii U"
,.using_nintendo_network="Usando Nintendo Network"
,.using_pretendo_network="Usando SPFN"
,.multiplayer_port_display="Usando puerto UDP %hu para multijugador"
,.module_not_found="Ha fallado el parche SPFN - usa Aroma Updater para repararlo (686-1001 Falta un módulo)"
,.module_init_not_found="Ha fallado el parche SPFN - usa Aroma Updater para repararlo (686-1002 Inicialización del módulo)"
,.setting_yes=""
,.setting_no=""
,.language_setting=""
,.language_name="Español"
,.system_language=""

View file

@ -1,18 +0,0 @@
.plugin_name="Inkay"
,.network_category="Sélection du réseau"
,.connect_to_network_setting="Connexion au SPFN"
,.other_category="Autres paramètres"
,.reset_wwp_setting="Réinitialiser la place WaraWara"
,.press_a_action="Appuyez sur A"
,.restart_to_apply_action="Redémarrer pour appliquer"
,.need_menu_action="(retournez d'abord au menu Wii U)"
,.using_nintendo_network="Réseau sélectionné : Nintendo Network"
,.using_pretendo_network="Réseau sélectionné : SPFN"
,.multiplayer_port_display="Le port UDP %hu sera utilisé pour le multijoueur"
,.module_not_found="Le patch SPFN a rencontré un problème, veuillez le réparer avec Aroma Updater (686-1001 Module missing)"
,.module_init_not_found="Le patch SPFN a rencontré un problème, veuillez le réparer avec Aroma Updater (686-1002 Module init)"
,.setting_yes=""
,.setting_no=""
,.language_setting=""
,.language_name="Français"
,.system_language=""

View file

@ -1,19 +0,0 @@
.plugin_name="Inkay"
,.network_category="Seleziona la rete"
,.connect_to_network_setting="Connettiti a SPFN"
,.show_startup_toast_setting=""
,.other_category="Altre impostazioni"
,.reset_wwp_setting="Ripristina Wara Wara Plaza"
,.press_a_action="Premi A"
,.restart_to_apply_action="Riavvia per applicare"
,.need_menu_action="Solo dal menu WiiU"
,.using_nintendo_network="In uso Nintendo Network"
,.using_pretendo_network="In uso SPFN"
,.multiplayer_port_display="È in uso la porta UDP %hu per il multigiocatore"
,.module_not_found="Patch di SPFN fallita - usa Aroma Updater per correggere (686-1001 Modulo mancante)"
,.module_init_not_found="Patch di SPFN fallita - usa Aroma Updater per correggere (686-1002 Module init)"
,.setting_yes=""
,.setting_no=""
,.language_setting=""
,.language_name="Italiano"
,.system_language=""

View file

@ -1,19 +0,0 @@
.plugin_name="Inkay"
,.network_category="ネットワークの選択"
,.connect_to_network_setting="SPFNネットワークに接続する"
,.show_startup_toast_setting=""
,.other_category="その他の設定"
,.reset_wwp_setting="わらわら広場をリセット"
,.press_a_action="Aボタンを押そう"
,.restart_to_apply_action="再起動後に反映されます"
,.need_menu_action="Wii Uメニューからのみ実行可能"
,.using_nintendo_network="選択済み:ニンテンドーネットワーク"
,.using_pretendo_network="選択済みSPFN ネットワーク"
,.multiplayer_port_display="マルチプレイに UDPポート %hu を使用します"
,.module_not_found="SPFN パッチに失敗しました。Aroma Updaterを使用して解決してみてください。(686-1001 Module missing)"
,.module_init_not_found="SPFN パッチに失敗しました。Aroma Updaterを使用して解決してみてください。(686-1002 Module init)"
,.setting_yes=""
,.setting_no=""
,.language_setting=""
,.language_name="日本語"
,.system_language=""

View file

@ -1,19 +0,0 @@
.plugin_name="Inkay"
,.network_category="네트워크 선택"
,.connect_to_network_setting="SPFN 사용"
,.show_startup_toast_setting=""
,.other_category="기타 설정"
,.reset_wwp_setting="Wara Wara Plaza 초기화"
,.press_a_action="A 버튼을 누르세요"
,.restart_to_apply_action="재시작 후 적용"
,.need_menu_action="Wii U 메뉴에서만"
,.using_nintendo_network="Nintendo Network 사용 중"
,.using_pretendo_network="SPFN 사용 중"
,.multiplayer_port_display="멀티플레이어에 UDP 포트 %hu을(를) 사용합니다"
,.module_not_found="패치를 적용하는 데 실패했습니다. Aroma Updater로 복구해 주세요. (686-1001 모듈 없음)"
,.module_init_not_found="패치를 적용하는 데 실패했습니다. Aroma Updater로 복구해 주세요. (686-1002 모듈 초기화)"
,.setting_yes=""
,.setting_no=""
,.language_setting=""
,.language_name="한국어"
,.system_language=""

View file

@ -1,19 +0,0 @@
.plugin_name="Inkay"
,.network_category="Netwerkselectie"
,.connect_to_network_setting="Verbind met het SPFN-netwerk"
,.show_startup_toast_setting=""
,.other_category="Overige instellingen"
,.reset_wwp_setting="Reset het Wara Wara Plaza"
,.press_a_action="Druk A"
,.restart_to_apply_action="Herstart om toe te passen"
,.need_menu_action="Alleen vanuit het WiiU-menu"
,.using_nintendo_network="Nintendo Network wordt gebruikt"
,.using_pretendo_network="SPFN wordt gebruikt"
,.multiplayer_port_display="Gebruikt UDP port %hu voor multiplayer"
,.module_not_found="SPFN patch mislukt - gebruik Aroma Updater om het te herstellen (686-1001 Module ontbreekt)"
,.module_init_not_found="SPFN patch mislukt - gebruik Aroma Updater om het te herstellen (686-1002 Module init)"
,.setting_yes=""
,.setting_no=""
,.language_setting=""
,.language_name="Nederlands"
,.system_language=""

View file

@ -1,19 +0,0 @@
.plugin_name="Inkay"
,.network_category="Selecionar rede"
,.connect_to_network_setting="Conecta-se à SPFN"
,.show_startup_toast_setting=""
,.other_category="Outras configurações"
,.reset_wwp_setting="Resetar Wara Wara Plaza"
,.press_a_action="Aperte A"
,.restart_to_apply_action="Reinicie para aplicar"
,.need_menu_action="Apenas no menu do Wii U"
,.using_nintendo_network="Usando Nintendo Network"
,.using_pretendo_network="Usando SPFN"
,.multiplayer_port_display="Usando UDP port %hu para a jogatina multiplayer"
,.module_not_found="Atualização do SPFN falhou - use o Aroma Updater para corrigir (686-1001 Módulo faltando)"
,.module_init_not_found="Atualização do SPFN falhou - use o Aroma Updater para corrigir (686-1002 Módulo de início)"
,.setting_yes=""
,.setting_no=""
,.language_setting=""
,.language_name="Português"
,.system_language=""

View file

@ -1,19 +0,0 @@
.plugin_name="Inkay"
,.network_category="Выбор сети"
,.connect_to_network_setting="Подключиться к SPFN"
,.show_startup_toast_setting=""
,.other_category="Другие настройки"
,.reset_wwp_setting="Сбросить Wara Wara Plaza"
,.press_a_action="Нажмите A"
,.restart_to_apply_action="Перезагрузите для применения изменений"
,.need_menu_action="Только из меню Wii U"
,.using_nintendo_network="Используется Nintendo Network"
,.using_pretendo_network="Используется SPFN"
,.multiplayer_port_display="Используем UDP-порт %hu для сетевой игры"
,.module_not_found="Ошибка установки патча SPFN - для восстановления, запустите Aroma Updater (686-1001 Модуль отсутствует)"
,.module_init_not_found="Ошибка установки патча SPFN - для восстановления, запустите Aroma Updater (686-1002 Инициализация модуля)"
,.setting_yes=""
,.setting_no=""
,.language_setting=""
,.language_name="Русский язык"
,.system_language=""

View file

@ -1,18 +0,0 @@
.plugin_name="Inkay"
,.network_category="选择网络"
,.connect_to_network_setting="连接到SPFN"
,.other_category="其他设置"
,.reset_wwp_setting="重置Wara Wara Plaza"
,.press_a_action="请按 A"
,.restart_to_apply_action="重启以应用设置"
,.need_menu_action="仅来自WiiU Menu"
,.using_nintendo_network="使用 Nintendo Network"
,.using_pretendo_network="使用 SPFN"
,.multiplayer_port_display="多人游戏使用 UDP 端口 %hu"
,.module_not_found="SPFN 补丁失败 - 使用 Aroma Updater 修复686-1001 模块丢失)"
,.module_init_not_found="SPFN 补丁失败 - 使用 Aroma Updater 修复686-1002 模块初始化)"
,.setting_yes=""
,.setting_no=""
,.language_setting=""
,.language_name="中文(简体)"
,.system_language=""

View file

@ -1,20 +0,0 @@
.plugin_name="Inkay"
,.network_category="選擇網路"
,.connect_to_network_setting="連接到SPFN"
,.show_startup_toast_setting=""
,.other_category="其他設定"
,.reset_wwp_setting="重置Wara Wara Plaza"
,.press_a_action="請按 A"
,.restart_to_apply_action="重啓以套用設定"
,.need_menu_action="僅來自WiiU Menu"
,.using_nintendo_network="使用 Nintendo Network"
,.using_pretendo_network="使用 SPFN"
,.multiplayer_port_display="Using UDP port %hu for multiplayer"
,.module_not_found="SPFN patch failed - use Aroma Updater to repair (686-1001 Module missing)"
,.module_init_not_found="SPFN patch failed - use Aroma Updater to repair (686-1002 Module init)"
,.multiplayer_port_display="使用 UDP 端口 %hu 進行多人遊戲"
,.setting_yes=""
,.setting_no=""
,.language_setting=""
,.language_name="中文(繁體)"
,.system_language=""

View file

@ -16,74 +16,60 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "export.h"
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <wups.h>
#include <optional>
#include <nsysnet/nssl.h>
#include <sysapp/title.h>
#include <coreinit/cache.h>
#include <coreinit/dynload.h>
#include <coreinit/mcp.h>
#include <coreinit/memory.h>
#include <coreinit/memorymap.h>
#include <coreinit/memexpheap.h>
#include <coreinit/title.h>
#include <notifications/notifications.h>
#include <utils/logger.h>
#include "iosu_url_patches.h"
#include "config.h"
#include "Notification.h"
#include "patches/olv_urls.h"
#include "patches/game_matchmaking.h"
#include <wums.h>
#include <coreinit/dynload.h>
#include <coreinit/mcp.h>
#include <notifications/notifications.h>
#include <utils/logger.h>
#include <string>
#include <optional>
#include <coreinit/filesystem.h>
#include <cstring>
#include <cstdint>
#include <string>
#include <nn/erreula/erreula_cpp.h>
#include <nn/act/client_cpp.h>
#include <curl/curl.h>
#include "ca_pem.h"
#define INKAY_VERSION "v3.1.0"
#include <gx2/surface.h>
#define INKAY_VERSION "v2.5.0"
/**
Mandatory plugin information.
If not set correctly, the loader will refuse to use the plugin.
**/
WUMS_META(export_name, "inkay");
WUMS_META(wums, WUMS_VERSION);
WUMS_USE_WUT_MALLOC();
WUMS_USE_WUT_SOCKETS();
WUMS_USE_WUT_NEWLIB();
WUMS___INIT_WRAPPER();
WUMS___FINI_WRAPPER();
WUMS_META(buildtimestamp, __DATE__ " " __TIME__);
extern const char wums_meta_module_name[] WUMS_SECTION("meta");
const char wums_meta_module_name[] = "inkay";
extern const char wums_meta_info_dump[] WUMS_SECTION("meta");
const char wums_meta_info_dump[] = "(module: inkay;" \
"wums " WUMS_VERSION ";" \
"buildtime: " __DATE__ " " __TIME__ ")";
extern const char wums_meta_info_linking_order[] WUMS_SECTION("meta");
const char wums_meta_info_linking_order[] = "Loading module \"inkay\" failed.\n" \
"Function \"wut_get_thread_specific\" returned unexpected value.\n" \
"Please check linking order (expected \"-lwums -lwut\")";
WUPS_PLUGIN_NAME("Inkay");
WUPS_PLUGIN_DESCRIPTION("Pretendo Network Patcher");
WUPS_PLUGIN_VERSION(INKAY_VERSION);
WUPS_PLUGIN_AUTHOR("Pretendo contributors");
WUPS_PLUGIN_LICENSE("ISC");
WUMS_MODULE_DESCRIPTION("SPFN Network Patcher");
WUMS_MODULE_VERSION(INKAY_VERSION);
WUMS_MODULE_AUTHOR("Pretendo contributors and RusticMaple");
WUMS_MODULE_LICENSE("GPLv3");
WUPS_USE_STORAGE("inkay");
WUMS_DEPENDS_ON(homebrew_functionpatcher);
WUMS_DEPENDS_ON(homebrew_kernel);
WUMS_DEPENDS_ON(homebrew_notifications);
WUMS_USE_WUT_DEVOPTAB();
WUPS_USE_WUT_DEVOPTAB();
#include <kernel/kernel.h>
#include <mocha/mocha.h>
#include <function_patcher/function_patching.h>
#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 "sysconfig.h"
#include "lang.h"
#include "utils/sysconfig.h"
//thanks @Gary#4139 :p
static void write_string(uint32_t addr, const char *str) {
@ -111,51 +97,78 @@ static bool is555(MCPSystemVersion version) {
return (version.major == 5) && (version.minor == 5) && (version.patch >= 5);
}
static const char *get_nintendo_network_message(inkay_language language) {
static const char *get_nintendo_network_message() {
// TL note: "Nintendo Network" is a proper noun - "Network" is part of the name
// TL note: "Using" instead of "Connected" is deliberate - we don't know if a successful connection exists, we are
// only specifying what we'll *attempt* to connect to
return get_config_strings(language).using_nintendo_network.data();
switch (get_system_language()) {
case nn::swkbd::LanguageType::English:
default:
return "Using Nintendo Network";
case nn::swkbd::LanguageType::Spanish:
return "Usando Nintendo Network";
case nn::swkbd::LanguageType::French:
return "Sur Nintendo Network";
case nn::swkbd::LanguageType::Italian:
return "Usando Nintendo Network";
case nn::swkbd::LanguageType::German:
return "Nutze Nintendo Network";
}
}
static const char *get_pretendo_message(inkay_language language) {
static const char *get_pretendo_message() {
// TL note: "Pretendo Network" is also a proper noun - though "Pretendo" alone can refer to us as a project
// TL note: "Using" instead of "Connected" is deliberate - we don't know if a successful connection exists, we are
// only specifying what we'll *attempt* to connect to
return get_config_strings(language).using_pretendo_network.data();
}
static void Inkay_SetPluginRunning() {
Config::plugin_is_loaded = true;
}
static InkayStatus Inkay_GetStatus() {
if (!Config::initialized)
return InkayStatus::Uninitialized;
if (Config::connect_to_network) {
return InkayStatus::Pretendo;
} else {
return InkayStatus::Nintendo;
switch (get_system_language()) {
case nn::swkbd::LanguageType::English:
default:
return "Using Pretendo Network";
case nn::swkbd::LanguageType::Spanish:
return "Usando Pretendo Network";
case nn::swkbd::LanguageType::French:
return "Sur Pretendo Network";
case nn::swkbd::LanguageType::Italian:
return "Usando Pretendo Network";
case nn::swkbd::LanguageType::German:
return "Nutze Pretendo Network";
}
}
static void Inkay_Initialize(bool apply_patches, bool show_startup_toast, inkay_language language) {
if (Config::initialized)
return;
INITIALIZE_PLUGIN() {
WHBLogCafeInit();
WHBLogUdpInit();
Config::show_startup_toast = show_startup_toast;
Config::Init();
if (Config::block_initialize) {
ShowNotification("Cannot load Inkay while the system is running. Please restart the console");
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");
}
//get os version
MCPSystemVersion os_version;
int mcp = MCP_Open();
int ret = MCP_GetSystemVersion(mcp, &os_version);
if (ret < 0) {
DEBUG_FUNCTION_LINE("getting system version failed (%d/%d)!", mcp, ret);
os_version = (MCPSystemVersion) {
.major = 5, .minor = 5, .patch = 5, .region = 'E'
};
}
DEBUG_FUNCTION_LINE_VERBOSE("Running on %d.%d.%d%c",
os_version.major, os_version.minor, os_version.patch, os_version.region
);
// if using pretendo then (try to) apply the ssl patches
if (apply_patches) {
Config::connect_to_network = true;
if (is555(get_console_os_version())) {
if (Config::connect_to_network) {
if (is555(os_version)) {
Mocha_IOSUKernelWrite32(0xE1019F78, 0xE3A00001); // mov r0, #1
} else {
Mocha_IOSUKernelWrite32(0xE1019E84, 0xE3A00001); // mov r0, #1
@ -164,56 +177,23 @@ static void Inkay_Initialize(bool apply_patches, bool show_startup_toast, inkay_
for (const auto &patch: url_patches) {
write_string(patch.address, patch.url);
}
// IOS-NIM-BOSS GlobalPolicyList->state: poking this forces a refresh after we changed the url
Mocha_IOSUKernelWrite32(0xE24B3D90, 4);
DEBUG_FUNCTION_LINE_VERBOSE("Pretendo URL and NoSSL patches applied successfully.");
if (Config::show_startup_toast) {
ShowNotification(get_pretendo_message(language));
}
Config::initialized = true;
ShowNotification(get_pretendo_message());
} else {
DEBUG_FUNCTION_LINE_VERBOSE("Pretendo URL and NoSSL patches skipped.");
if(Config::show_startup_toast) {
ShowNotification(get_nintendo_network_message(language));
}
Config::initialized = true;
return;
ShowNotification(get_nintendo_network_message());
}
MCP_Close(mcp);
if (FunctionPatcher_InitLibrary() == FUNCTION_PATCHER_RESULT_SUCCESS) {
patchDNS();
patchEshop();
patchOlvApplet();
patchAccountSettings();
install_matchmaking_patches();
} else {
DEBUG_FUNCTION_LINE("FunctionPatcher_InitLibrary failed");
}
}
WUMS_INITIALIZE() {
WHBLogCafeInit();
WHBLogUdpInit();
if (const auto res = Mocha_InitLibrary(); 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();
DEINITIALIZE_PLUGIN() {
remove_matchmaking_patches();
Mocha_DeInitLibrary();
@ -224,39 +204,14 @@ WUMS_DEINITIALIZE() {
WHBLogUdpDeinit();
}
WUMS_APPLICATION_STARTS() {
ON_APPLICATION_START() {
DEBUG_FUNCTION_LINE_VERBOSE("Inkay " INKAY_VERSION " starting up...\n");
// Reset plugin loaded flag
Config::plugin_is_loaded = false;
}
WUMS_ALL_APPLICATION_STARTS_DONE() {
// we need to do the patches here because otherwise the Config::connect_to_network flag might be set yet
setup_olv_libs();
peertopeer_patch();
matchmaking_notify_titleswitch();
hotpatchAccountSettings();
if (Config::initialized && !Config::plugin_is_loaded) {
DEBUG_FUNCTION_LINE("Inkay is running but the plugin got unloaded");
if (!Config::block_initialize) {
ShowNotification("Inkay module is still running. Please restart the console");
}
Config::shown_warning = true;
} else if (!Config::initialized && !Config::shown_warning) {
DEBUG_FUNCTION_LINE("Inkay module not initialized");
ShowNotification("Inkay module was not initialized. Ensure you have the Inkay plugin loaded");
Config::shown_warning = true;
}
if (!Config::initialized) {
Config::block_initialize = true;
}
patchAccountSettings();
}
WUMS_APPLICATION_ENDS() {
}
ON_APPLICATION_ENDS() {
WUMS_EXPORT_FUNCTION(Inkay_Initialize);
WUMS_EXPORT_FUNCTION(Inkay_GetStatus);
WUMS_EXPORT_FUNCTION(Inkay_SetPluginRunning);
}

View file

@ -21,15 +21,13 @@
#include "olv_urls.h"
#include "utils/logger.h"
#include "utils/replace_mem.h"
#include "inkay_config.h"
#include <function_patcher/function_patching.h>
#include <wups.h>
#include <optional>
#include <coreinit/debug.h>
#include <coreinit/filesystem.h>
#include <coreinit/title.h>
#include <vector>
#include <optional>
#include <nsysnet/nssl.h>
#include "ca_pem.h" // generated at buildtime
@ -37,40 +35,25 @@
#define ACCOUNT_SETTINGS_TID_U 0x000500101004B100
#define ACCOUNT_SETTINGS_TID_E 0x000500101004B200
struct account_settings_allowlist {
char scheme[16];
char domain[128];
char path[128]; // unverified
uint32_t flags;
const char whitelist_original[] = {
0x68, 0x74, 0x74, 0x70, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x61, 0x63, 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x2E,
0x6E, 0x69, 0x6E, 0x74, 0x65, 0x6E, 0x64, 0x6F, 0x2E, 0x6E, 0x65, 0x74
};
constexpr struct account_settings_allowlist original_entry = {
.scheme = "https",
.domain = "account.nintendo.net",
.path = "",
.flags = 0x01010101,
const char whitelist_new[] = {
0x68, 0x74, 0x74, 0x70, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x61, 0x63, 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x2E,
0x70, 0x72, 0x65, 0x74, 0x65, 0x6E, 0x64, 0x6F, 0x2E, 0x63, 0x63, 0x00
};
constexpr struct account_settings_allowlist new_entry = {
.scheme = "https",
.domain = "account." NETWORK_BASEURL,
.path = "",
.flags = 0x01010101,
};
constexpr char wave_original[] = "saccount.nintendo.net";
const char wave_original[] = "saccount.nintendo.net";
constexpr char wave_new[] = "saccount." NETWORK_BASEURL;
const char wave_new[] = "saccount.pretendo.cc";
static bool isAccountSettingsTitle() {
return (OSGetTitleID() != 0 && (
OSGetTitleID() == ACCOUNT_SETTINGS_TID_J ||
OSGetTitleID() == ACCOUNT_SETTINGS_TID_U ||
OSGetTitleID() == ACCOUNT_SETTINGS_TID_E
));
}
bool isAccountSettingsTitle();
static std::optional<FSFileHandle> rootca_pem_handle{};
std::vector<PatchedFunctionHandle> account_patches;
DECL_FUNCTION(int, FSOpenFile_accSettings, FSClient *client, FSCmdBlock *block, char *path, const char *mode, uint32_t *handle,
int error) {
@ -79,7 +62,7 @@ DECL_FUNCTION(int, FSOpenFile_accSettings, FSClient *client, FSCmdBlock *block,
}
if (!Config::connect_to_network) {
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: account settings patches skipped.");
DEBUG_FUNCTION_LINE("Inkay: account settings patches skipped.");
return real_FSOpenFile_accSettings(client, block, path, mode, handle, error);
}
@ -87,7 +70,7 @@ DECL_FUNCTION(int, FSOpenFile_accSettings, FSClient *client, FSCmdBlock *block,
if (strcmp("vol/content/browser/rootca.pem", path) == 0) {
int ret = real_FSOpenFile_accSettings(client, block, path, mode, handle, error);
rootca_pem_handle = *handle;
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: Found account settings CA, replacing...");
DEBUG_FUNCTION_LINE("Inkay: Found account settings CA, replacing...");
return ret;
}
return real_FSOpenFile_accSettings(client, block, path, mode, handle, error);
@ -119,52 +102,39 @@ DECL_FUNCTION(FSStatus, FSCloseFile_accSettings, FSClient *client, FSCmdBlock *b
return real_FSCloseFile_accSettings(client, block, handle, errorMask);
}
bool patchAccountSettings() {
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, FSCloseFile, FP_TARGET_PROCESS_GAME), "FSCloseFile_accSettings");
return true;
bool isAccountSettingsTitle() {
return (OSGetTitleID() != 0 && (
OSGetTitleID() == ACCOUNT_SETTINGS_TID_J ||
OSGetTitleID() == ACCOUNT_SETTINGS_TID_U ||
OSGetTitleID() == ACCOUNT_SETTINGS_TID_E
));
}
bool hotpatchAccountSettings() {
bool patchAccountSettings() {
if(!isAccountSettingsTitle()) {
return false;
}
if (!Config::connect_to_network) {
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: account settings patches skipped.");
DEBUG_FUNCTION_LINE("Inkay: account settings patches skipped.");
return false;
}
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: hewwo account settings!\n");
DEBUG_FUNCTION_LINE("Inkay: hewwo account settings!\n");
if (!replace(0x10000000, 0x10000000, wave_original, sizeof(wave_original), wave_new, sizeof(wave_new))) {
DEBUG_FUNCTION_LINE("Inkay: We didn't find the url /)>~<(\\");
return false;
}
if (!replace(0x10000000, 0x10000000, (const char *)&original_entry, sizeof(original_entry), (const char *)&new_entry, sizeof(new_entry))) {
if (!replace(0x10000000, 0x10000000, whitelist_original, sizeof(whitelist_original), whitelist_new, sizeof(whitelist_new))) {
DEBUG_FUNCTION_LINE("Inkay: We didn't find the whitelist /)>~<(\\");
return false;
}
return true;
}
void unpatchAccountSettings() {
for (const auto handle: account_patches) {
FunctionPatcher_RemoveFunctionPatch(handle);
}
account_patches.clear();
}
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);

View file

@ -15,6 +15,6 @@
#pragma once
bool patchAccountSettings();
bool hotpatchAccountSettings();
void unpatchAccountSettings();
bool isAccountSettingsTitle();
bool patchAccountSettings();

View file

@ -15,21 +15,17 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <wups.h>
#include <netdb.h>
#include "config.h"
#include "utils/logger.h"
#include "inkay_config.h"
#include "config.h"
#include <array>
#include <vector>
#include <function_patcher/function_patching.h>
std::vector<PatchedFunctionHandle> dns_patches;
constexpr std::pair<const char *, const char *> dns_replacements[] = {
const std::pair<const char *, const char *> dns_replacements[] = {
// NNCS servers
{ "nncs1.app.nintendowifi.net", "nncs1.app." NETWORK_BASEURL },
{ "nncs2.app.nintendowifi.net", "nncs2.app." NETWORK_BASEURL },
{ "nncs1.app.nintendowifi.net", "nncs1.app.pretendo.cc" },
{ "nncs2.app.nintendowifi.net", "nncs2.app.pretendo.cc" },
};
static const char * replace_dns_name(const char *dns_name) {
@ -46,31 +42,10 @@ 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);
}
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();
}
WUPS_MUST_REPLACE(getaddrinfo, WUPS_LOADER_LIBRARY_NSYSNET, getaddrinfo);

View file

@ -1,20 +0,0 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
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 <http://www.gnu.org/licenses/>.
*/
#pragma once
void patchDNS();
void unpatchDNS();

View file

@ -20,10 +20,8 @@
#include "olv_urls.h"
#include "utils/logger.h"
#include "utils/replace_mem.h"
#include "inkay_config.h"
#include <vector>
#include <function_patcher/function_patching.h>
#include <wups.h>
#include <optional>
#include <coreinit/debug.h>
#include <coreinit/filesystem.h>
@ -31,39 +29,31 @@
#include "ca_pem.h" // generated at buildtime
constexpr char wave_original[] = "https://ninja.wup.shop.nintendo.net/ninja/wood_index.html?";
constexpr char wave_new[] = "http://samurai.wup.shop." NETWORK_BASEURL "/ninja/wood_index.html?";
const char wave_original[] = "https://ninja.wup.shop.nintendo.net/ninja/wood_index.html?";
const char wave_new[] = "http://samurai.wup.shop.pretendo.cc/ninja/wood_index.html?";
struct eshop_allowlist {
char scheme[16];
char domain[128];
char path[128]; // unverified
unsigned char flags[5];
const char whitelist_original[] = {
0x68, 0x74, 0x74, 0x70, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x73, 0x61, 0x6D, 0x75, 0x72, 0x61, 0x69, 0x2E,
0x77, 0x75, 0x70, 0x2E, 0x73, 0x68, 0x6F, 0x70, 0x2E, 0x6E, 0x69, 0x6E,
0x74, 0x65, 0x6E, 0x64, 0x6F, 0x2E, 0x6E, 0x65, 0x74
};
constexpr struct eshop_allowlist original_entry = {
.scheme = "https",
.domain = "samurai.wup.shop.nintendo.net",
.path = "",
.flags = {1, 1, 1, 1, 0},
};
constexpr struct eshop_allowlist new_entry = {
.scheme = "http",
.domain = "samurai.wup.shop." NETWORK_BASEURL,
.path = "",
.flags = {1, 1, 1, 1, 0},
const char whitelist_new[] = {
0x68, 0x74, 0x74, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x73, 0x61, 0x6D, 0x75, 0x72, 0x61, 0x69, 0x2E,
0x77, 0x75, 0x70, 0x2E, 0x73, 0x68, 0x6F, 0x70, 0x2E, 0x70, 0x72, 0x65,
0x74, 0x65, 0x6E, 0x64, 0x6F, 0x2E, 0x63, 0x63, 0x00
};
static std::optional<FSFileHandle> rootca_pem_handle{};
std::vector<PatchedFunctionHandle> eshop_patches;
DECL_FUNCTION(int, FSOpenFile_eShop, FSClient *client, FSCmdBlock *block, char *path, const char *mode, uint32_t *handle,
int error) {
const char *initialOma = "vol/content/initial.oma";
if (!Config::connect_to_network) {
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: eShop patches skipped.");
DEBUG_FUNCTION_LINE("Inkay: eShop patches skipped.");
return real_FSOpenFile_eShop(client, block, path, mode, handle, error);
}
@ -71,19 +61,19 @@ DECL_FUNCTION(int, FSOpenFile_eShop, FSClient *client, FSCmdBlock *block, char *
//below is a hacky (yet functional!) way to get Inkay to redirect URLs from the Miiverse applet
//we do it when loading this file since it should only load once, preventing massive lag spikes as it searches all of MEM2 xD
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: hewwo eShop!\n");
DEBUG_FUNCTION_LINE("Inkay: hewwo eShop!\n");
if (!replace(0x10000000, 0x10000000, wave_original, sizeof(wave_original), wave_new, sizeof(wave_new)))
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: We didn't find the url /)>~<(\\");
DEBUG_FUNCTION_LINE("Inkay: We didn't find the url /)>~<(\\");
if (!replace(0x10000000, 0x10000000, (const char *)&original_entry, sizeof(original_entry), (const char *)&new_entry, sizeof(new_entry)))
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: We didn't find the whitelist /)>~<(\\");
if (!replace(0x10000000, 0x10000000, whitelist_original, sizeof(whitelist_original), whitelist_new, sizeof(whitelist_new)))
DEBUG_FUNCTION_LINE("Inkay: We didn't find the whitelist /)>~<(\\");
// Check for root CA file and take note of its handle
} else if (strcmp("vol/content/browser/rootca.pem", path) == 0) {
int ret = real_FSOpenFile_eShop(client, block, path, mode, handle, error);
rootca_pem_handle = *handle;
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: Found eShop CA, replacing...");
DEBUG_FUNCTION_LINE("Inkay: Found eShop CA, replacing...");
return ret;
}
@ -112,25 +102,6 @@ DECL_FUNCTION(FSStatus, FSCloseFile_eShop, FSClient *client, FSCmdBlock *block,
return real_FSCloseFile_eShop(client, block, handle, errorMask);
}
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();
}
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);

View file

@ -1,20 +0,0 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
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 <http://www.gnu.org/licenses/>.
*/
#pragma once
void patchEshop();
void unpatchEshop();

View file

@ -16,8 +16,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "game_matchmaking.h"
#include "config.h"
#include "utils/logger.h"
#include "ini.h"
@ -58,7 +58,7 @@ static int handler(void *user, const char *section, const char *name, const char
static void check_modpack() {
modpack mod;
if (ini_parse("fs:/vol/content/pretendo.ini", handler, &mod)) {
DEBUG_FUNCTION_LINE_VERBOSE("Inkay/MK8: Doesn't look like a modpack");
DEBUG_FUNCTION_LINE("Inkay/MK8: Doesn't look like a modpack");
}
DEBUG_FUNCTION_LINE("Inkay/MK8: Playing %s (%08x)", mod.name.c_str(), mod.dlc_id);
@ -72,7 +72,7 @@ DECL_FUNCTION(void, mk8_MatchmakeSessionSearchCriteria_SetAttribute, void *_this
const int dlc_id = dlc_modpack->dlc_id;
if (dlc_id != -1) {
DEBUG_FUNCTION_LINE_VERBOSE("Inkay/MK8: Searching for %s session (%08x)", dlc_modpack->name.c_str(), dlc_id);
DEBUG_FUNCTION_LINE("Inkay/MK8: Searching for %s session (%08x)", dlc_modpack->name.c_str(), dlc_id);
attributeValue = dlc_id;
}
}
@ -86,7 +86,7 @@ DECL_FUNCTION(void, mk8_MatchmakeSession_SetAttribute, void *_this, uint32_t att
const int dlc_id = dlc_modpack->dlc_id;
if (dlc_id != -1) {
DEBUG_FUNCTION_LINE_VERBOSE("Inkay/MK8: Creating %s session (%08x)", dlc_modpack->name.c_str(), dlc_id);
DEBUG_FUNCTION_LINE("Inkay/MK8: Creating %s session (%08x)", dlc_modpack->name.c_str(), dlc_id);
attributeValue = dlc_id;
}
}

View file

@ -1,131 +0,0 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
Copyright 2024 Ash Logan <ash@heyquark.com>
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include <coreinit/title.h>
#include <coreinit/dynload.h>
#include "game_peertopeer.h"
#include "config.h"
#include "sysconfig.h"
#include "utils/logger.h"
#include "utils/rpl_info.h"
#include "utils/replace_mem.h"
#include <optional>
#include <algorithm>
#include <string_view>
using namespace std::string_view_literals;
static struct {
std::array<uint64_t, 3> tid;
uint16_t version;
uint32_t min_port_addr;
uint32_t max_port_addr;
std::string_view rpx;
} generic_patch_games[] = {
{
// MARIO KART 8
{0x00050000'1010ec00, 0x00050000'1010ed00, 0x00050000'1010eb00},
81,
0x101a9a52,
0x101a9a54,
"Turbo.rpx"sv,
},
{
// Splatoon
{0x00050000'10176900, 0x00050000'10176a00, 0x00050000'10162b00},
288,
0x101e8952,
0x101e8954,
"Gambit.rpx"sv,
},
};
static void generic_peertopeer_patch() {
uint64_t tid = OSGetTitleID();
uint16_t title_version = 0;
if (const auto version_opt = get_current_title_version(); !version_opt) {
DEBUG_FUNCTION_LINE("Failed to detect current title version");
return;
} else {
title_version = *version_opt;
DEBUG_FUNCTION_LINE("Title version detected: %d", title_version);
}
for (const auto &patch: generic_patch_games) {
if (std::ranges::find(patch.tid, tid) == patch.tid.end()) continue;
std::optional<OSDynLoad_NotifyData> game = search_for_rpl(patch.rpx);
if (!game) {
DEBUG_FUNCTION_LINE("Couldn't find game rpx! (%s)", patch.rpx.data());
return;
}
if (title_version != patch.version) {
DEBUG_FUNCTION_LINE("Unexpected title version. Expected %d but got %d (%s)", patch.version, title_version,
patch.rpx.data());
continue;
}
auto port = get_console_peertopeer_port();
DEBUG_FUNCTION_LINE_VERBOSE("Will use port %d. %08x", port, game->textAddr);
auto target = (uint16_t *)rpl_addr(*game, patch.min_port_addr);
replace_unsigned<uint16_t>(target, 0xc000, port);
target = (uint16_t *)rpl_addr(*game, patch.max_port_addr);
replace_unsigned<uint16_t>(target, 0xffff, port);
break;
}
}
static void minecraft_peertopeer_patch() {
std::optional<OSDynLoad_NotifyData> minecraft = search_for_rpl("Minecraft.Client.rpx"sv);
if (!minecraft) {
DEBUG_FUNCTION_LINE("Couldn't find minecraft rpx!");
return;
}
if (const auto version_opt = get_current_title_version(); !version_opt || *version_opt != 688) {
DEBUG_FUNCTION_LINE("Wrong mincecraft version detected");
return;
}
auto port = get_console_peertopeer_port();
DEBUG_FUNCTION_LINE_VERBOSE("Will use port %d. %08x", port, minecraft->textAddr);
auto target_func = (uint32_t *)rpl_addr(*minecraft, 0x03579530);
replace_instruction(&target_func[0], 0x3c600001, 0x3c600000); // li r3, 0
replace_instruction(&target_func[1], 0x3863c000, 0x60630000 | port); // ori r3, r3, port
// blr
target_func = (uint32_t *)rpl_addr(*minecraft, 0x0357953c);
replace_instruction(&target_func[0], 0x3c600001, 0x3c600000); // li r3, 0
replace_instruction(&target_func[1], 0x3863ffff, 0x60630000 | port); // ori r3, r3, port
// blr
}
void peertopeer_patch() {
if (!Config::connect_to_network) {
return;
}
uint64_t tid = OSGetTitleID();
if (tid == 0x00050000'101D7500 || // EUR
tid == 0x00050000'101D9D00 || // USA
tid == 0x00050000'101DBE00) { // JPN
minecraft_peertopeer_patch();
} else {
generic_peertopeer_patch();
}
}

View file

@ -1,16 +0,0 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
Copyright 2024 Ash Logan <ash@heyquark.com>
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
void peertopeer_patch();

View file

@ -21,36 +21,24 @@
#include "utils/logger.h"
#include "utils/replace_mem.h"
#include <vector>
#include <wups.h>
#include <optional>
#include <coreinit/debug.h>
#include <coreinit/filesystem.h>
#include <nsysnet/nssl.h>
#include <function_patcher/function_patching.h>
#include "ca_pem.h" // generated at buildtime
struct olv_allowlist {
char scheme[16];
char domain[128];
char path[128]; // unverified
unsigned char flags[5];
const char wave_original[] = {
0x68, 0x74, 0x74, 0x70, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x2E, 0x6E, 0x69, 0x6E, 0x74, 0x65, 0x6E, 0x64,
0x6F, 0x2E, 0x6E, 0x65, 0x74
};
constexpr struct olv_allowlist original_entry = {
.scheme = "https",
.domain = ".nintendo.net",
.path = "",
.flags = {1, 1, 1, 1, 1},
const char wave_new[] = {
0x68, 0x74, 0x74, 0x70, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x2E, 0x70, 0x72, 0x65, 0x74, 0x65, 0x6E, 0x64,
0x6F, 0x2E, 0x63, 0x63, 0x00
};
constexpr struct olv_allowlist new_entry = {
.scheme = "https",
.domain = ".spbr.net",
.path = "",
.flags = {1, 1, 1, 1, 1},
};
const unsigned char miiverse_green_highlight[] = {
0x82, 0xff, 0x05, 0xff, 0x82, 0xff, 0x05, 0xff, 0x1d, 0xff, 0x04, 0xff, 0x1d, 0xff, 0x04, 0xff
};
@ -77,15 +65,13 @@ const replacement replacements[] = {
};
static std::optional<FSFileHandle> rootca_pem_handle{};
static std::optional<FSFileHandle> valid_tld_handle{};
std::vector<PatchedFunctionHandle> olv_patches;
DECL_FUNCTION(int, FSOpenFile, FSClient *client, FSCmdBlock *block, char *path, const char *mode, uint32_t *handle,
int error) {
const char *initialOma = "vol/content/initial.oma";
if (!Config::connect_to_network) {
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: Miiverse patches skipped.");
DEBUG_FUNCTION_LINE("Inkay: Miiverse patches skipped.");
return real_FSOpenFile(client, block, path, mode, handle, error);
}
@ -94,25 +80,17 @@ DECL_FUNCTION(int, FSOpenFile, FSClient *client, FSCmdBlock *block, char *path,
//we do it when loading this file since it should only load once, preventing massive lag spikes as it searches all of MEM2 xD
//WHBLogUdpInit();
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: hewwo!\n");
DEBUG_FUNCTION_LINE("Inkay: hewwo!\n");
auto olv_ok = setup_olv_libs();
// Patch applet binary too
if (olv_ok)
replace(0x10000000, 0x10000000, (const char *)&original_entry, sizeof(original_entry), (const char *)&new_entry, sizeof(new_entry));
replace(0x10000000, 0x10000000, wave_original, sizeof(wave_original), wave_new, sizeof(wave_new));
// Check for root CA file and take note of its handle
} else if (strcmp("vol/content/browser/rootca.pem", path) == 0) {
int ret = real_FSOpenFile(client, block, path, mode, handle, error);
rootca_pem_handle = *handle;
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: Found Miiverse CA, replacing...");
return ret;
}
else if (strcmp("vol/content/browser/effective_tld_names.dat", path) == 0)
{
// we patch the TLD because .cafe isnt on the list
int ret = real_FSOpenFile(client, block, path, mode, handle, error);
valid_tld_handle = *handle;
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: Found Miiverse TLD List, patching...");
DEBUG_FUNCTION_LINE("Inkay: Found Miiverse CA, replacing...");
return ret;
}
@ -120,83 +98,52 @@ DECL_FUNCTION(int, FSOpenFile, FSClient *client, FSCmdBlock *block, char *path,
}
DECL_FUNCTION(FSStatus, FSReadFile, FSClient *client, FSCmdBlock *block, uint8_t *buffer, uint32_t size, uint32_t count,
FSFileHandle handle, uint32_t unk1, uint32_t flags)
{
if (size != 1)
{
DEBUG_FUNCTION_LINE("Inkay: Miiverse CA/LTD replacement failed!");
FSFileHandle handle, uint32_t unk1, uint32_t flags) {
if (size != 1) {
DEBUG_FUNCTION_LINE("Inkay: Miiverse CA replacement failed!");
}
if (rootca_pem_handle && *rootca_pem_handle == handle)
{
strlcpy((char *)buffer, (const char *)ca_pem, size * count);
if (rootca_pem_handle && *rootca_pem_handle == handle) {
strlcpy((char *) buffer, (const char *) ca_pem, size * count);
// this can't be done above (in the FSOpenFile hook) since it's not loaded yet.
// the hardcoded offsets suck but they really are at Random Places In The Heap
return (FSStatus)count;
}
else if (valid_tld_handle && *valid_tld_handle == handle)
{
// we patch the tld "aero"...
FSStatus ret = real_FSReadFile(
client, block, buffer, size, count, handle, unk1, flags);
if (ret > 0)
{
uint32_t totalSize = size * count;
char *buf = (char *)buffer;
for (uint32_t i = 0; i + 4 <= totalSize; i++)
{
if (memcmp(&buf[i], "aero", 4) == 0)
{
memcpy(&buf[i], "cafe", 4);
DEBUG_FUNCTION_LINE_VERBOSE(
"Inkay: patched TLD aero -> cafe");
}
}
}
return ret;
//this can't be done above (in the FSOpenFile hook) since it's not loaded yet.
//the hardcoded offsets suck but they really are at Random Places In The Heap
replaceBulk(0x11000000, 0x02000000, replacements);
return (FSStatus) count;
}
return real_FSReadFile(client, block, buffer, size, count, handle, unk1, flags);
}
DECL_FUNCTION(FSStatus, FSCloseFile, FSClient *client, FSCmdBlock *block, FSFileHandle handle, FSErrorFlag errorMask) {
if (handle == rootca_pem_handle) {
rootca_pem_handle.reset();
}
if (handle == valid_tld_handle)
{
valid_tld_handle.reset();
}
return real_FSCloseFile(client, block, handle, errorMask);
}
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);
DECL_FUNCTION(uint32_t, NSSLExportInternalServerCertificate, NSSLServerCertId cert, int unk, void *unk2, void *unk3) {
if (cert == NSSL_SERVER_CERT_THAWTE_PREMIUM_SERVER_CA) { // Martini patches
OSFatal("[598-0069] Please uninstall Martini patches to continue.\n" \
"See pretendo.network/docs/search for more info.\n\n" \
"Hold the POWER button for 4 seconds to shut down.\n\n"
" .\n"
".---------.'---.\n"
"'. : .'\n"
" '. .::: .'\n"
" '.'::'.'\n"
" '||'\n"
" ||\n"
" ||\n"
"mrz ||\n"
" ---====---");
}
olv_patches.clear();
return real_NSSLExportInternalServerCertificate(cert, unk, unk2, unk3);
}
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);
WUPS_MUST_REPLACE_FOR_PROCESS(NSSLExportInternalServerCertificate, WUPS_LOADER_LIBRARY_NSYSNET,
NSSLExportInternalServerCertificate, WUPS_FP_TARGET_PROCESS_MIIVERSE);

View file

@ -1,20 +0,0 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
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 <http://www.gnu.org/licenses/>.
*/
#pragma once
void patchOlvApplet();
void unpatchOlvApplet();

View file

@ -16,8 +16,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "olv_urls.h"
#include "config.h"
#include "utils/logger.h"
#include "utils/replace_mem.h"
@ -51,20 +51,20 @@ bool path_is_olv(const char* path) {
void new_rpl_loaded(OSDynLoad_Module module, void* ctx, OSDynLoad_NotifyReason reason, OSDynLoad_NotifyData* rpl) {
if (!Config::connect_to_network) {
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: Miiverse patches skipped.");
DEBUG_FUNCTION_LINE("Inkay: Miiverse patches skipped.");
return;
}
// Loaded olv?
if (reason != OS_DYNLOAD_NOTIFY_LOADED) return;
if (!rpl->name || !path_is_olv(rpl->name)) return;
if (!path_is_olv(rpl->name)) return;
replace(rpl->dataAddr, rpl->dataSize, original_url, sizeof(original_url), new_url, sizeof(new_url));
}
bool setup_olv_libs() {
if (!Config::connect_to_network) {
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: Miiverse patches skipped.");
DEBUG_FUNCTION_LINE("Inkay: Miiverse patches skipped.");
return false;
}
@ -72,7 +72,7 @@ bool setup_olv_libs() {
auto olvLoaded = check_olv_libs();
if (!olvLoaded) {
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: no olv, quitting for now\n");
DEBUG_FUNCTION_LINE("Inkay: no olv, quitting for now\n");
return false;
}

View file

@ -15,12 +15,11 @@
#pragma once
#include <cstdlib>
#include "inkay_config.h"
constexpr char original_url[] = "discovery.olv.nintendo.net/v1/endpoint";
constexpr char new_url[] = "discovery.olv.spbr.net/v1/endpoint";
const char original_url[] = "discovery.olv.nintendo.net/v1/endpoint";
const char new_url[] = "discovery.olv.pretendo.cc/v1/endpoint";
_Static_assert(sizeof(original_url) >= sizeof(new_url),
_Static_assert(sizeof(original_url) > sizeof(new_url),
"new_url too long! Must be less than 38chars.");
bool setup_olv_libs();

View file

@ -1,28 +1,28 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <string.h>
#include <whb/log.h>
#include <whb/log_module.h>
#include <whb/log_cafe.h>
#include <whb/log_udp.h>
#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("[(M) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \
OSFatal_printf("[(P) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \
} while (0)
#define DEBUG_FUNCTION_LINE(FMT, ARGS...)do { \
WHBLogPrintf("[(M) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \
WHBLogPrintf("[(P) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \
} while (0);
#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...)do { \
WHBLogWritef("[(M) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \
WHBLogWritef("[(P) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \
} while (0);
#ifdef DEBUG

View file

@ -22,7 +22,6 @@
#include <kernel/kernel.h>
#include <coreinit/memorymap.h>
#include <algorithm>
#include <coreinit/cache.h>
bool replace(uint32_t start, uint32_t size, const char *original_val, size_t original_val_sz, const char *new_val,
size_t new_val_sz) {
@ -66,36 +65,7 @@ void replaceBulk(uint32_t start, uint32_t size, std::span<const replacement> rep
}
}
}
#ifdef DEBUG
for (auto c: counts) {
DEBUG_FUNCTION_LINE("replaced %d times", c);
}
#endif
}
template <typename U>
requires std::integral<U>
bool replace_unsigned(U *addr, U original_value, U new_value) {
if (*addr != original_value) return false;
KernelCopyData(
OSEffectiveToPhysical((uint32_t) addr),
OSEffectiveToPhysical((uint32_t) &new_value),
sizeof(new_value)
);
DCFlushRange(addr, sizeof(new_value));
return *addr == new_value;
}
template bool replace_unsigned<uint64_t>(uint64_t *, uint64_t, uint64_t);
template bool replace_unsigned<uint32_t>(uint32_t *, uint32_t, uint32_t);
template bool replace_unsigned<uint16_t>(uint16_t *, uint16_t, uint16_t);
template bool replace_unsigned<uint8_t>(uint8_t *, uint8_t, uint8_t);
bool replace_instruction(uint32_t *inst, uint32_t original_value, uint32_t new_value) {
bool res = replace_unsigned<uint32_t>(inst, original_value, new_value);
if (!res) return res;
ICInvalidateRange(inst, sizeof(new_value));
return true;
}

View file

@ -27,9 +27,3 @@ struct replacement {
};
void replaceBulk(uint32_t start, uint32_t size, std::span<const replacement> replacements);
template <typename U>
requires std::integral<U>
bool replace_unsigned(U *addr, U original_value, U new_value);
bool replace_instruction(uint32_t *inst, uint32_t original_value, uint32_t new_value);

View file

@ -1,64 +0,0 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
Copyright 2024 Ash Logan <ash@heyquark.com>
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "rpl_info.h"
#include <vector>
#include <algorithm>
#include <string_view>
#include <coreinit/mcp.h>
#include <coreinit/title.h>
#include "logger.h"
// if we get more than like.. two callsites for this, it should really be refactored out rather than doing a fresh
// search every time
std::optional<OSDynLoad_NotifyData> search_for_rpl(std::string_view name) {
int num_rpls = OSDynLoad_GetNumberOfRPLs();
if (!num_rpls)
return std::nullopt;
// allocates but we free it at return
std::vector<OSDynLoad_NotifyData> rpls(num_rpls);
if (!OSDynLoad_GetRPLInfo(0, num_rpls, rpls.data()))
return std::nullopt;
auto rpl = std::find_if(rpls.begin(), rpls.end(), [=](const OSDynLoad_NotifyData &rpl) {
return std::string_view(rpl.name).ends_with(name);
});
if (rpl == rpls.end())
return std::nullopt;
return *rpl;
}
std::optional<uint16_t> get_current_title_version() {
const auto mcpHandle = MCP_Open();
MCPTitleListType titleInfo;
int32_t res = -1;
const uint64_t curTitleId = OSGetTitleID();
if ((curTitleId & 0x0000000F00000000) == 0) {
res = MCP_GetTitleInfo(mcpHandle, curTitleId | 0x0000000E00000000, &titleInfo);
}
if (res != 0) {
res = MCP_GetTitleInfo(mcpHandle, curTitleId, &titleInfo);
}
MCP_Close(mcpHandle);
if (res != 0) {
DEBUG_FUNCTION_LINE("Failed to get title version of %016llX.", curTitleId);
return {};
}
MCP_Close(mcpHandle);
const auto tmp_result = titleInfo.titleVersion; // make the compiler happy because we access a packed struct
return tmp_result;
}

View file

@ -1,30 +0,0 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
Copyright 2024 Ash Logan <ash@heyquark.com>
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <optional>
#include <string_view>
#include <coreinit/dynload.h>
std::optional<OSDynLoad_NotifyData> search_for_rpl(std::string_view name);
constexpr void *rpl_addr(OSDynLoad_NotifyData rpl, uint32_t cemu_addr) {
if (cemu_addr < 0x1000'0000) {
return (void *)(rpl.textAddr + cemu_addr - 0x0200'0000);
} else {
return (void *)(rpl.dataAddr + cemu_addr - 0x1000'0000);
}
}
std::optional<uint16_t> get_current_title_version();

57
src/utils/sysconfig.cpp Normal file
View file

@ -0,0 +1,57 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
Copyright 2024 Ash Logan <ash@heyquark.com>
Copyright 2020-2022 V10lator <v10lator@myway.de>
Copyright 2022 Xpl0itU <DaThinkingChair@protonmail.com>
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 <http://www.gnu.org/licenses/>.
*/
#include "sysconfig.h"
#include "utils/logger.h"
#include <coreinit/userconfig.h>
#include <optional>
nn::swkbd::LanguageType get_system_language() {
static std::optional <nn::swkbd::LanguageType> cached_language{};
if (cached_language) return *cached_language;
UCHandle handle = UCOpen();
if (handle >= 0) {
nn::swkbd::LanguageType language;
UCSysConfig settings __attribute__((__aligned__(0x40))) = {
.name = "cafe.language",
.access = 0,
.dataType = UC_DATATYPE_UNSIGNED_INT,
.error = UC_ERROR_OK,
.dataSize = sizeof(language),
.data = &language,
};
UCError err = UCReadSysConfig(handle, 1, &settings);
UCClose(handle);
if (err != UC_ERROR_OK) {
DEBUG_FUNCTION_LINE("Error reading UC: %d!", err);
return nn::swkbd::LanguageType::English;
} else {
DEBUG_FUNCTION_LINE_VERBOSE("System language found: %d", language);
cached_language = language;
return language;
}
} else {
DEBUG_FUNCTION_LINE("Error opening UC: %d", handle);
return nn::swkbd::LanguageType::English;
}
}

View file

@ -6,11 +6,7 @@
#define INKAY_SYSCONFIG_H
#include <nn/swkbd.h>
#include <coreinit/mcp.h>
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