diff --git a/Dockerfile b/Dockerfile index 9e53cca..4147038 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,10 @@ -FROM ghcr.io/wiiu-env/devkitppc:20231112 +FROM ghcr.io/wiiu-env/devkitppc:20240505 -COPY --from=ghcr.io/wiiu-env/libnotifications:20230621 /artifacts $DEVKITPRO +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/wiiupluginsystem:20230719 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/wiiupluginsystem:20240505 /artifacts $DEVKITPRO WORKDIR /app CMD make -f Makefile -j$(nproc) \ No newline at end of file diff --git a/Makefile b/Makefile index 817e6ca..b41ffa0 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,7 @@ BUILD := build SOURCES := src src/patches src/utils src/ext/inih DATA := data INCLUDES := src src/ext/inih +#DEBUG := 1 #------------------------------------------------------------------------------- # options for code generation @@ -33,6 +34,10 @@ CFLAGS := -g -Wall -O2 -ffunction-sections \ CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ -D__WUPS__ +ifeq ($(DEBUG),1) + CFLAGS += -DDEBUG +endif + CXXFLAGS := $(CFLAGS) -std=c++20 ASFLAGS := -g $(ARCH) diff --git a/src/Notification.cpp b/src/Notification.cpp index a7ca991..6fb1a99 100644 --- a/src/Notification.cpp +++ b/src/Notification.cpp @@ -15,41 +15,17 @@ */ #include "Notification.h" -#include -#include -#include #include #include -#include -static std::unique_ptr sShowHintThread; -static bool sShutdownHintThread = false; +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, + NOTIFICATION_MODULE_DEFAULT_OPTION_DURATION_BEFORE_FADE_OUT, + 15.0f); + + if (err1 != NOTIFICATION_MODULE_RESULT_SUCCESS || err2 != NOTIFICATION_MODULE_RESULT_SUCCESS) return; -void ShowNotification(const char * notification) { - // Wait for notification module to be ready - bool isOverlayReady = false; - while (!sShutdownHintThread && NotificationModule_IsOverlayReady(&isOverlayReady) == NOTIFICATION_MODULE_RESULT_SUCCESS && !isOverlayReady) - OSSleepTicks(OSMillisecondsToTicks(16)); - if (sShutdownHintThread || !isOverlayReady) return; - NotificationModuleStatus err = NotificationModule_SetDefaultValue(NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, NOTIFICATION_MODULE_DEFAULT_OPTION_DURATION_BEFORE_FADE_OUT, 15.0f); - if(err != NOTIFICATION_MODULE_RESULT_SUCCESS) return; - - NotificationModule_AddInfoNotification(notification); -} - -void StartNotificationThread(const char * value) { - uint64_t titleID = OSGetTitleID(); - if (titleID == 0x0005001010040000L || titleID == 0x0005001010040100L || titleID == 0x0005001010040200L) { - sShutdownHintThread = false; - sShowHintThread = std::make_unique(ShowNotification, value); - } -} - -void StopNotificationThread() { - if (sShowHintThread != nullptr) { - sShutdownHintThread = true; - OSMemoryBarrier(); - sShowHintThread->join(); - sShowHintThread.reset(); - } + NotificationModule_AddInfoNotification(notification.data()); } diff --git a/src/Notification.h b/src/Notification.h index b25ed3b..879f729 100644 --- a/src/Notification.h +++ b/src/Notification.h @@ -1,7 +1,5 @@ #pragma once -void ShowNotification(const char * notification); +#include -void StartNotificationThread(const char * value); - -void StopNotificationThread(); +void ShowNotification(std::string_view notification); \ No newline at end of file diff --git a/src/config.cpp b/src/config.cpp index 5471e28..56c2a1d 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -20,9 +20,10 @@ #include "wut_extra.h" #include "utils/logger.h" #include "utils/sysconfig.h" + #include #include -#include +#include #include #include @@ -36,94 +37,6 @@ bool Config::need_relaunch = false; bool Config::unregister_task_item_pressed = false; bool Config::is_wiiu_menu = false; -void Config::Init() { - WUPSStorageError storageRes = WUPS_OpenStorage(); - if (storageRes != WUPS_STORAGE_ERROR_SUCCESS) { - DEBUG_FUNCTION_LINE("Failed to open storage %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes); - } - else { - // Try to get value from storage - if ((storageRes = WUPS_GetBool(nullptr, "connect_to_network", &connect_to_network)) == WUPS_STORAGE_ERROR_NOT_FOUND) { - bool skipPatches = false; - if ((storageRes = WUPS_GetBool(nullptr, "skipPatches", &skipPatches)) != WUPS_STORAGE_ERROR_NOT_FOUND) { - // Migrate old config value - connect_to_network = !skipPatches; - } - // Add the value to the storage if it's missing. - if (WUPS_StoreBool(nullptr, "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)", WUPS_GetStorageStatusStr(storageRes), storageRes); - } - - // Close storage - if (WUPS_CloseStorage() != WUPS_STORAGE_ERROR_SUCCESS) { - DEBUG_FUNCTION_LINE("Failed to close storage"); - } - } - - 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); -} - -static void connect_to_network_changed(ConfigItemBoolean* item, bool new_value) { - DEBUG_FUNCTION_LINE("New value in skipPatchesChanged: %d", new_value); - if (new_value != Config::connect_to_network) { - Config::need_relaunch = true; - } - Config::connect_to_network = new_value; - WUPS_StoreInt(nullptr, "connect_to_network", Config::connect_to_network); -} - -static void unregister_task_item_pressed_cb(void *context, WUPSConfigButtons button) { - if (!Config::unregister_task_item_pressed && Config::is_wiiu_menu && button == 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) == true) - { - if (nn::act::IsNetworkAccountEx(i) == true) - { - nn::boss::Task task; - nn::act::PersistentId persistentId = nn::act::GetPersistentIdEx(i); - - __ct__Q3_2nn4boss4TaskFv(&task); - Initialize__Q3_2nn4boss4TaskFPCcUi(&task, "oltopic", persistentId); - - uint32_t res = Unregister__Q3_2nn4boss4TaskFv(&task); - WHBLogPrintf("Unregistered oltopic for: SlotNo %d | Persistent ID %08x -> 0x%08x", i, persistentId, res); - } - } - } - - Finalize__Q2_2nn4bossFv(); - nn::act::Finalize(); - - Config::unregister_task_item_pressed = !Config::unregister_task_item_pressed; - Config::need_relaunch = true; - } -} - -static bool unregister_task_item_is_movement_allowed(void *context) { - return true; -} - -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; -}; config_strings strings; constexpr config_strings get_config_strings(nn::swkbd::LanguageType language) { @@ -140,6 +53,7 @@ constexpr config_strings get_config_strings(nn::swkbd::LanguageType language) { .restart_to_apply_action = "Restart to apply", .need_menu_action = "From WiiU menu only" }; + case nn::swkbd::LanguageType::Spanish: return { .plugin_name = "Inkay", @@ -151,6 +65,7 @@ constexpr config_strings get_config_strings(nn::swkbd::LanguageType language) { .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", @@ -173,70 +88,136 @@ constexpr config_strings get_config_strings(nn::swkbd::LanguageType language) { .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", + .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("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) + 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; } -WUPS_GET_CONFIG() { - // We open the storage so we can persist the configuration the user did. - if (WUPS_OpenStorage() != WUPS_STORAGE_ERROR_SUCCESS) { - DEBUG_FUNCTION_LINE("Failed to open storage"); - return 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()); - WUPSConfigHandle config; - WUPSConfig_CreateHandled(&config, strings.plugin_name); + // create root config category + WUPSConfigCategory root = WUPSConfigCategory(rootHandle); - WUPSConfigCategoryHandle patching_cat; - WUPSConfig_AddCategoryByNameHandled(config, strings.network_category, &patching_cat); + try { + 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)); - WUPSConfigItemBoolean_AddToCategoryHandled(config, patching_cat, "connect_to_network", strings.connect_to_network_setting, Config::connect_to_network, &connect_to_network_changed); - - WUPSConfigCategoryHandle boss_cat; - WUPSConfig_AddCategoryByNameHandled(config, strings.other_category, &boss_cat); + auto other_cat = WUPSConfigCategory::Create(strings.other_category); - WUPSConfigCallbacks_t unregisterTasksItemCallbacks = { - .getCurrentValueDisplay = unregister_task_item_get_display_value, - .getCurrentValueSelectedDisplay = unregister_task_item_get_display_value, - .onSelected = nullptr, - .restoreDefault = nullptr, - .isMovementAllowed = unregister_task_item_is_movement_allowed, - .callCallback = nullptr, - .onButtonPressed = unregister_task_item_pressed_cb, - .onDelete = nullptr - }; + 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 + }; - WUPSConfigItemHandle unregisterTasksItem; - WUPSConfigItem_Create(&unregisterTasksItem, "unregister_task_item", strings.reset_wwp_setting, unregisterTasksItemCallbacks, &Config::unregister_task_item_pressed); - WUPSConfigCategory_AddItem(boss_cat, unregisterTasksItem); + WUPSConfigAPIItemOptionsV2 unregisterTasksItemOptions = { + .displayName = strings.reset_wwp_setting, + .context = nullptr, + .callbacks = unregisterTasksItemCallbacks, + }; - return config; + WUPSConfigItemHandle unregisterTasksItem; + WUPSConfigAPIStatus err; + if ((err = WUPSConfigAPI_Item_Create(unregisterTasksItemOptions, &unregisterTasksItem)) != WUPSCONFIG_API_RESULT_SUCCESS) { + throw std::runtime_error(std::string("Failed to create config item: ").append(WUPSConfigAPI_GetStatusStr(err))); + } + if ((err = WUPSConfigAPI_Category_AddItem(other_cat.getHandle(), unregisterTasksItem)) != WUPSCONFIG_API_RESULT_SUCCESS) { + throw std::runtime_error(std::string("Failed to add config item: ").append(WUPSConfigAPI_GetStatusStr(err))); + } + + root.add(std::move(other_cat)); + } + catch (std::exception &e) { + DEBUG_FUNCTION_LINE("Creating config menu failed: %s", e.what()); + return WUPSCONFIG_API_CALLBACK_RESULT_ERROR; + } + + return WUPSCONFIG_API_CALLBACK_RESULT_SUCCESS; } -WUPS_CONFIG_CLOSED() { - // Save all changes - if (WUPS_CloseStorage() != WUPS_STORAGE_ERROR_SUCCESS) { - DEBUG_FUNCTION_LINE("Failed to close storage"); +static void ConfigMenuClosedCallback() { + // Save all changes + if (WUPSStorageAPI::SaveStorage() != WUPS_STORAGE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE("Failed to save storage"); } if (Config::need_relaunch) { @@ -246,3 +227,38 @@ WUPS_CONFIG_CLOSED() { 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("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("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("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"); + } +} \ No newline at end of file diff --git a/src/config.h b/src/config.h index 61c3dd7..f9401ad 100644 --- a/src/config.h +++ b/src/config.h @@ -21,4 +21,15 @@ public: static bool unregister_task_item_pressed; }; +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 diff --git a/src/iosu_url_patches.h b/src/iosu_url_patches.h index f897260..2a726a0 100644 --- a/src/iosu_url_patches.h +++ b/src/iosu_url_patches.h @@ -36,6 +36,7 @@ static const URL_Patch url_patches[] = { {0xE22B3FFC, "https://nus.c.shop.pretendo.cc/nus/services/NetUpdateSOAP"}, {0xE229DE0C, "n.app.pretendo.cc"}, //nim-boss .bss + {0xE24B8A24, "https://nppl.app.pretendo.cc/p01/policylist/1/1/UNK"}, {0xE31930D4, "https://%s%saccount.pretendo.cc/v%u/api/"} }; diff --git a/src/main.cpp b/src/main.cpp index 564c3c1..61582dd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,12 +22,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include "iosu_url_patches.h" @@ -47,7 +49,7 @@ #include -#define INKAY_VERSION "v2.5" +#define INKAY_VERSION "v3.0" /** Mandatory plugin information. @@ -133,8 +135,8 @@ static const char * get_pretendo_message() { } INITIALIZE_PLUGIN() { + WHBLogCafeInit(); WHBLogUdpInit(); - WHBLogCafeInit(); Config::Init(); @@ -163,6 +165,7 @@ INITIALIZE_PLUGIN() { os_version.major, os_version.minor, os_version.patch, os_version.region ); + // if using pretendo then (try to) apply the ssl patches if (Config::connect_to_network) { if (is555(os_version)) { Mocha_IOSUKernelWrite32(0xE1019F78, 0xE3A00001); // mov r0, #1 @@ -175,11 +178,13 @@ INITIALIZE_PLUGIN() { write_string(patch.address, patch.url); } DEBUG_FUNCTION_LINE_VERBOSE("Pretendo URL and NoSSL patches applied successfully."); - StartNotificationThread(get_pretendo_message()); + + ShowNotification(get_pretendo_message()); } else { DEBUG_FUNCTION_LINE_VERBOSE("Pretendo URL and NoSSL patches skipped."); - StartNotificationThread(get_nintendo_network_message()); + + ShowNotification(get_nintendo_network_message()); } MCP_Close(mcp); @@ -188,20 +193,20 @@ INITIALIZE_PLUGIN() { install_matchmaking_patches(); } } + DEINITIALIZE_PLUGIN() { remove_matchmaking_patches(); - WHBLogUdpDeinit(); Mocha_DeInitLibrary(); NotificationModule_DeInitLibrary(); FunctionPatcher_DeInitLibrary(); + + WHBLogCafeDeinit(); + WHBLogUdpDeinit(); } ON_APPLICATION_START() { - WHBLogUdpInit(); - WHBLogCafeInit(); - - DEBUG_FUNCTION_LINE_VERBOSE("Inkay " INKAY_VERSION " starting up\n"); + DEBUG_FUNCTION_LINE_VERBOSE("Inkay " INKAY_VERSION " starting up...\n"); setup_olv_libs(); matchmaking_notify_titleswitch(); @@ -209,6 +214,6 @@ ON_APPLICATION_START() { } ON_APPLICATION_ENDS() { - DEBUG_FUNCTION_LINE_VERBOSE("Unloading Inkay...\n"); - StopNotificationThread(); + // commented because it doesnt really unload inkay + //DEBUG_FUNCTION_LINE_VERBOSE("Unloading Inkay...\n"); } diff --git a/src/utils/logger.h b/src/utils/logger.h index d6e59de..ce6b35e 100644 --- a/src/utils/logger.h +++ b/src/utils/logger.h @@ -6,8 +6,9 @@ extern "C" { #include #include -#include +#include #include +#include #define __FILENAME_X__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) #define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILENAME_X__) @@ -25,9 +26,9 @@ extern "C" { } while (0); #ifdef DEBUG -#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) DEBUG_FUNCTION_LINE(FMT, ARGS...) +#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) DEBUG_FUNCTION_LINE(FMT, ##ARGS) #else -#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while(0) +#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0); #endif #ifdef __cplusplus