Update ConfigMenu to have a "hidden" sub menu for toggling heap tracking, preserve heap tracking option on wiiloading

This commit is contained in:
Maschell 2026-02-18 15:39:14 +01:00
parent f6dd18f001
commit 5cd5a7b89b
17 changed files with 520 additions and 188 deletions

View File

@ -43,10 +43,10 @@ PluginManagement::loadPlugins(const std::vector<PluginLoadWrapper> &pluginDataLi
DisplayErrorNotificationMessage(errMsg, 15.0f);
continue;
}
plugins.emplace_back(std::move(*metaInfo), std::move(*linkInfo), pluginDataWrapper.getPluginData());
plugins.emplace_back(std::move(*metaInfo), std::move(*linkInfo), pluginDataWrapper.getPluginData(), pluginDataWrapper.getHeapTrackingOptions());
} else {
DEBUG_FUNCTION_LINE_INFO("LOAD (INACTIVE) %s", metaInfo->getName().c_str());
plugins.emplace_back(std::move(*metaInfo), PluginLinkInformation::CreateStub(), pluginDataWrapper.getPluginData());
plugins.emplace_back(std::move(*metaInfo), PluginLinkInformation::CreateStub(), pluginDataWrapper.getPluginData(), std::nullopt);
}
} else {
auto errMsg = string_format("Failed to load plugin: %s", pluginDataWrapper.getPluginData()->getSource().c_str());

View File

@ -430,9 +430,6 @@ namespace ShellCommands {
};
} // namespace
std::unique_ptr<IOPShellModule::CommandGroup> sPluginGroup;
std::optional<IOPShellModule::Command> sPluginCmdHandle;
std::string BytesToHumanReadable(uint32_t bytes) {
char buffer[32];
snprintf(buffer, sizeof(buffer), "%.2f KiB", bytes / 1024.0f);
@ -658,14 +655,34 @@ namespace ShellCommands {
}
}
static std::unique_ptr<IOPShellModule::CommandGroup> sPluginsGroup;
void InitPluginsCommandGroup() {
// Must be in memory while commands are active
sPluginsGroup = std::make_unique<IOPShellModule::CommandGroup>("plugins", "Manage aroma plugins");
if (const auto res = sPluginsGroup->RegisterGroup(); res != IOPSHELL_MODULE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE_WARN("Failed to register \"aroma plugins\" command: %s", IOPShellModule::GetErrorString(res));
}
if (const auto res = sPluginsGroup->AddCommand("heap_usage", PrintHeapUsage, "Show current heap usage for tracked plugins"); res != IOPSHELL_MODULE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE_WARN("Failed to create \"aroma plugins heap_usage\" command: %s", IOPShellModule::GetErrorString(res));
}
if (const auto res = sPluginsGroup->AddRawCommand("list", ListPlugins, "Lists active plugins", "Usage: \"list -a\" to list all plugins"); res != IOPSHELL_MODULE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE_WARN("Failed to create \"aroma plugins list\" command: %s", IOPShellModule::GetErrorString(res));
}
if (auto res = sPluginsGroup->AddCommand("details", PluginDetails, "Shows details for plugins"); res != IOPSHELL_MODULE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE_WARN("Failed to create \"aroma plugins details\" command: %s", IOPShellModule::GetErrorString(res));
} else {
if (res = sPluginsGroup->AddAlias("details", "show"); res != IOPSHELL_MODULE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE_WARN("Failed to create \"aroma plugins details\" alias \"aroma plugins show\": %s", IOPShellModule::GetErrorString(res));
}
}
if (const auto res = sPluginsGroup->RegisterGroup(); res != IOPSHELL_MODULE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE_WARN("Failed to register \"aroma plugins\" command: %s", IOPShellModule::GetErrorString(res));
}
}
void Init() {
sPluginGroup = std::make_unique<IOPShellModule::CommandGroup>("plugins", "Manage aroma plugins");
sPluginGroup->AddCommand("heap_usage", PrintHeapUsage, "Show current heap usage for tracked plugins");
sPluginGroup->AddRawCommand("list", ListPlugins, "Lists active plugins", "Usage: \"list -a\" to list all plugins");
sPluginGroup->AddCommand("details", PluginDetails, "Shows details for plugins");
sPluginCmdHandle = sPluginGroup->Register();
InitPluginsCommandGroup();
}
} // namespace ShellCommands

View File

@ -284,8 +284,11 @@ WUMS_APPLICATION_STARTS() {
// Check which plugins are already loaded and which needs to be
for (const auto &pluginLoadWrapper : filteredLoadOnNextLaunch) {
const auto &pluginNeedsNoReloadFn = [&pluginLoadWrapper](const PluginContainer &container) {
bool willBeLinked = pluginLoadWrapper.isLoadAndLink();
return (container.getPluginDataCopy()->getHandle() == pluginLoadWrapper.getPluginData()->getHandle()) &&
(container.isLinkedAndLoaded() == pluginLoadWrapper.isLoadAndLink());
(container.isLinkedAndLoaded() == pluginLoadWrapper.isLoadAndLink()) &&
// Only check if tracking is enabled if the plugin will actually be loaded.
(!willBeLinked || (container.isUsingTrackingPluginHeapMemoryAllocator() == pluginLoadWrapper.isHeapTrackingEnabled()));
};
// Check if the plugin data is already loaded
if (const auto it = std::ranges::find_if(gLoadedPlugins, pluginNeedsNoReloadFn);

View File

@ -16,14 +16,19 @@
#include <optional>
PluginContainer::PluginContainer(PluginMetaInformation metaInformation, PluginLinkInformation pluginLinkInformation, std::shared_ptr<PluginData> pluginData)
PluginContainer::PluginContainer(PluginMetaInformation metaInformation, PluginLinkInformation pluginLinkInformation, std::shared_ptr<PluginData> pluginData, std::optional<PluginMetaInformation::HeapTrackingOptions> heapTrackingOptions)
: mMetaInformation(std::move(metaInformation)),
mPluginLinkInformation(std::move(pluginLinkInformation)),
mPluginData(std::move(pluginData)) {
// Abuse this as a stable handle that references itself and survives std::move
*mHandle = reinterpret_cast<uint32_t>(mHandle.get());
if (const bool res = useTrackingPluginHeapMemoryAllocator(mMetaInformation.getHeapTrackingOptions()); !res) {
auto trackingOptions = mMetaInformation.getHeapTrackingOptions();
if (heapTrackingOptions) {
trackingOptions = *heapTrackingOptions;
}
if (const bool res = useTrackingPluginHeapMemoryAllocator(trackingOptions); !res) {
DEBUG_FUNCTION_LINE_WARN("Failed to set heap tracking options for \"%s\"", mMetaInformation.getName().c_str());
}
}

View File

@ -33,7 +33,7 @@ class PluginData;
class PluginContainer {
public:
PluginContainer(PluginMetaInformation metaInformation, PluginLinkInformation pluginLinkInformation, std::shared_ptr<PluginData> pluginData);
PluginContainer(PluginMetaInformation metaInformation, PluginLinkInformation pluginLinkInformation, std::shared_ptr<PluginData> pluginData, std::optional<PluginMetaInformation::HeapTrackingOptions> heapTrackingOptions);
PluginContainer(const PluginContainer &) = delete;
@ -73,11 +73,6 @@ public:
[[nodiscard]] uint32_t getButtonComboManagerHandle() const;
/**
* @return Returns true if setting the value was successful
*/
bool useTrackingPluginHeapMemoryAllocator(PluginMetaInformation::HeapTrackingOptions options);
[[nodiscard]] bool isUsingTrackingPluginHeapMemoryAllocator() const;
[[nodiscard]] const IPluginHeapMemoryAllocator &getMemoryAllocator() const;
@ -87,6 +82,11 @@ public:
[[nodiscard]] size_t getMemoryFootprint() const;
private:
/**
* @return Returns true if setting the value was successful
*/
bool useTrackingPluginHeapMemoryAllocator(PluginMetaInformation::HeapTrackingOptions options);
PluginMetaInformation mMetaInformation;
PluginLinkInformation mPluginLinkInformation;
std::optional<TrackingPluginHeapMemoryAllocator> mTrackingHeapAllocatorOpt;

View File

@ -42,7 +42,11 @@ std::vector<PluginLoadWrapper> PluginDataFactory::loadDir(const std::string_view
if (inactivePluginsFilenames.contains(fileName)) {
shouldBeLoadedAndLinked = false;
}
result.emplace_back(std::move(pluginData), shouldBeLoadedAndLinked);
bool enableHeapTracking = false;
#ifdef DEBUG
enableHeapTracking = true;
#endif
result.emplace_back(std::move(pluginData), shouldBeLoadedAndLinked, enableHeapTracking);
} else {
auto errMsg = string_format("Failed to load plugin: %s", full_file_path.c_str());
DEBUG_FUNCTION_LINE_ERR("%s", errMsg.c_str());

View File

@ -1,12 +1,16 @@
#pragma once
#include "PluginMetaInformation.h"
#include <memory>
class PluginData;
class PluginLoadWrapper {
public:
PluginLoadWrapper(std::shared_ptr<PluginData> pluginData, const bool linkAndLoad) : mPluginData(std::move(pluginData)), mIsLoadAndLink(linkAndLoad) {
PluginLoadWrapper(std::shared_ptr<PluginData> pluginData, const bool linkAndLoad, const bool heapTrackingEnabled = false)
: mPluginData(std::move(pluginData)), mIsLoadAndLink(linkAndLoad), mIsHeapTrackingEnabled(heapTrackingEnabled) {
}
[[nodiscard]] const std::shared_ptr<PluginData> &getPluginData() const {
@ -17,7 +21,19 @@ public:
return mIsLoadAndLink;
}
[[nodiscard]] bool isHeapTrackingEnabled() const {
return mIsHeapTrackingEnabled;
}
[[nodiscard]] std::optional<PluginMetaInformation::HeapTrackingOptions> getHeapTrackingOptions() const {
if (mIsHeapTrackingEnabled) {
return PluginMetaInformation::HeapTrackingOptions::TRACK_HEAP_OPTIONS_TRACK_SIZE_AND_COLLECT_STACK_TRACES;
}
return std::nullopt;
}
private:
std::shared_ptr<PluginData> mPluginData;
bool mIsLoadAndLink = false;
bool mIsLoadAndLink = false;
bool mIsHeapTrackingEnabled = false;
};

View File

@ -2,16 +2,18 @@
#include "config/WUPSConfig.h"
#include <memory>
#include <cassert>
#include <memory>
ConfigDisplayItem::ConfigDisplayItem(GeneralConfigInformation &info,
std::unique_ptr<WUPSConfigAPIBackend::WUPSConfig> config,
const bool isActive) : mConfig(std::move(config)),
mInfo(std::move(info)),
mIsActivePlugin(isActive),
mInitialIsActivePlugin(isActive) {
const bool isActive,
const bool isHeapTracking) : mConfig(std::move(config)),
mInfo(std::move(info)),
mIsActivePlugin(isActive),
mInitialIsActivePlugin(isActive),
mIsHeapTrackingEnabled(isHeapTracking),
mInitialIsHeapTrackingEnabled(isHeapTracking) {
assert(mConfig);
}
@ -33,4 +35,16 @@ void ConfigDisplayItem::toggleIsActivePlugin() {
void ConfigDisplayItem::resetIsActivePlugin() {
mIsActivePlugin = mInitialIsActivePlugin;
}
bool ConfigDisplayItem::isHeapTrackingEnabled() const {
return mIsHeapTrackingEnabled;
}
void ConfigDisplayItem::toggleIsHeapTrackingEnabled() {
mIsHeapTrackingEnabled = !mIsHeapTrackingEnabled;
}
void ConfigDisplayItem::resetIsHeapTrackingEnabled() {
mIsHeapTrackingEnabled = mInitialIsHeapTrackingEnabled;
}

View File

@ -15,21 +15,25 @@ struct GeneralConfigInformation {
class ConfigDisplayItem {
public:
ConfigDisplayItem(GeneralConfigInformation &info, std::unique_ptr<WUPSConfigAPIBackend::WUPSConfig> config, bool isActive);
ConfigDisplayItem(GeneralConfigInformation &info, std::unique_ptr<WUPSConfigAPIBackend::WUPSConfig> config, bool isActive, bool isHeapTracking = false);
[[nodiscard]] const GeneralConfigInformation &getConfigInformation() const;
[[nodiscard]] const WUPSConfigAPIBackend::WUPSConfig &getConfig() const;
[[nodiscard]] bool isActivePlugin() const;
void toggleIsActivePlugin();
void resetIsActivePlugin();
[[nodiscard]] bool isHeapTrackingEnabled() const;
void toggleIsHeapTrackingEnabled();
void resetIsHeapTrackingEnabled();
private:
std::unique_ptr<WUPSConfigAPIBackend::WUPSConfig> mConfig;
GeneralConfigInformation mInfo;
bool mIsActivePlugin;
bool mInitialIsActivePlugin;
bool mIsHeapTrackingEnabled;
bool mInitialIsHeapTrackingEnabled;
};

View File

@ -1,4 +1,5 @@
#include "ConfigRenderer.h"
#include "ConfigRendererStates.h"
#include "CategoryRenderer.h"
#include "ConfigDisplayItem.h"
@ -11,15 +12,11 @@
#include "utils/logger.h"
#include <algorithm>
#include <iterator>
#include <utility>
ConfigRenderer::ConfigRenderer(std::vector<ConfigDisplayItem> &&vec) : mConfigs(std::move(vec)) {
std::ranges::copy(mConfigs,
std::back_inserter(mAllConfigs));
std::ranges::copy_if(mConfigs,
std::back_inserter(mActiveConfigs),
[&](const auto &value) {
return value.isActivePlugin();
});
SetListState(std::make_unique<DefaultListState>());
}
ConfigRenderer::~ConfigRenderer() = default;
@ -44,6 +41,10 @@ ConfigSubState ConfigRenderer::Update(Input &input, const WUPSConfigSimplePadDat
if (complexInputData.vpad.vpadError == VPAD_READ_SUCCESS && complexInputData.vpad.data.hold != 0) {
mLastInputWasOnWiimote = false;
}
// Reset transient return state
mNextSubState = SUB_STATE_RUNNING;
switch (mState) {
case STATE_MAIN:
return UpdateStateMain(input);
@ -51,9 +52,9 @@ ConfigSubState ConfigRenderer::Update(Input &input, const WUPSConfigSimplePadDat
if (mCategoryRenderer) {
auto subResult = mCategoryRenderer->Update(input, simpleInputData, complexInputData);
if (subResult != SUB_STATE_RUNNING) {
mNeedRedraw = true;
mActivePluginsDirty = false;
mState = STATE_MAIN;
mNeedRedraw = true;
mPluginListDirty = false;
mState = STATE_MAIN;
return SUB_STATE_RUNNING;
}
return SUB_STATE_RUNNING;
@ -99,90 +100,50 @@ void ConfigRenderer::ResetNeedsRedraw() {
}
}
void ConfigRenderer::RequestRedraw() {
mNeedRedraw = true;
}
ConfigSubState ConfigRenderer::UpdateStateMain(const Input &input) {
auto &configs = GetConfigList();
if (!mListState) return SUB_STATE_ERROR;
auto &configs = GetDisplayedConfigList();
const auto prevSelectedItem = mCursorPos;
auto totalElementSize = (int32_t) configs.size();
const auto &savePendingConfigFn = [&configs, this]() {
for (const auto &element : configs) {
CallOnCloseCallback(element.get().getConfigInformation(), element.get().getConfig());
}
};
// Delegate specific inputs to the State
bool inputHandled = mListState->HandleInput(*this, input);
auto totalElementSize = (int32_t) configs.size();
if (mNextSubState != SUB_STATE_RUNNING) {
return mNextSubState;
}
if (inputHandled) {
return SUB_STATE_RUNNING;
}
// Handle Navigation (Common to all states)
if (input.data.buttons_d & Input::eButtons::BUTTON_DOWN) {
mCursorPos++;
} else if (input.data.buttons_d & Input::eButtons::BUTTON_LEFT) {
// Paging up
mCursorPos -= MAX_BUTTONS_ON_SCREEN - 1;
// Don't jump past the top
if (mCursorPos < 0)
mCursorPos = 0;
if (mCursorPos < 0) mCursorPos = 0;
} else if (input.data.buttons_d & Input::eButtons::BUTTON_RIGHT) {
// Paging down
mCursorPos += MAX_BUTTONS_ON_SCREEN - 1;
// Don't jump past the bottom
if (mCursorPos >= totalElementSize)
mCursorPos = totalElementSize - 1;
if (mCursorPos >= totalElementSize) mCursorPos = totalElementSize - 1;
} else if (input.data.buttons_d & Input::eButtons::BUTTON_UP) {
mCursorPos--;
} else if (input.data.buttons_d & Input::eButtons::BUTTON_PLUS) {
if (mSetActivePluginsMode) {
mNeedRedraw = true;
mCategoryRenderer.reset();
savePendingConfigFn();
return SUB_STATE_RETURN_WITH_PLUGIN_RELOAD;
}
} else if (input.data.buttons_d & Input::eButtons::BUTTON_X) {
if (!mSetActivePluginsMode && !mAllConfigs.empty()) {
mSetActivePluginsMode = true;
mNeedRedraw = true;
return SUB_STATE_RUNNING;
}
} else if (input.data.buttons_d & Input::eButtons::BUTTON_A) {
if (mSetActivePluginsMode) {
mActivePluginsDirty = true;
mNeedRedraw = true;
configs[mCursorPos].get().toggleIsActivePlugin();
return SUB_STATE_RUNNING;
} else if (!configs.empty()) {
if (mCursorPos != mCurrentOpen) {
mCategoryRenderer.reset();
mCategoryRenderer = make_unique_nothrow<CategoryRenderer>(&(configs[mCursorPos].get().getConfigInformation()), &(configs[mCursorPos].get().getConfig()), true);
}
mNeedRedraw = true;
mCurrentOpen = mCursorPos;
mState = STATE_SUB;
return SUB_STATE_RUNNING;
}
} else if (input.data.buttons_d & (Input::eButtons::BUTTON_B | Input::eButtons::BUTTON_HOME)) {
if (mSetActivePluginsMode) {
for (auto &cur : mConfigs) {
cur.resetIsActivePlugin();
}
mActivePluginsDirty = false;
mNeedRedraw = true;
mSetActivePluginsMode = false;
return SUB_STATE_RUNNING;
} else {
mNeedRedraw = true;
mCategoryRenderer.reset();
savePendingConfigFn();
return SUB_STATE_RETURN;
}
}
if (mCursorPos < 0) {
mCursorPos = totalElementSize - 1;
} else if (mCursorPos >= totalElementSize) {
mCursorPos = 0;
}
if (mCursorPos < 0) {
if (totalElementSize > 0) {
if (mCursorPos < 0) mCursorPos = totalElementSize - 1;
else if (mCursorPos >= totalElementSize)
mCursorPos = 0;
} else {
mCursorPos = 0;
}
// Adjust the render offset when reaching the boundaries
// Adjust render offset
if (mCursorPos < mRenderOffset) {
mRenderOffset = mCursorPos;
} else if (mCursorPos >= mRenderOffset + MAX_BUTTONS_ON_SCREEN - 1) {
@ -197,71 +158,70 @@ ConfigSubState ConfigRenderer::UpdateStateMain(const Input &input) {
}
void ConfigRenderer::RenderStateMain() const {
auto &configs = GetConfigList();
auto &configs = GetDisplayedConfigList();
DrawUtils::beginDraw();
DrawUtils::clear(COLOR_BACKGROUND);
auto totalElementSize = (int32_t) configs.size();
int start = std::max(0, mRenderOffset);
int end = std::min(start + MAX_BUTTONS_ON_SCREEN, totalElementSize);
// Calculate the range of items to display
int start = std::max(0, mRenderOffset);
int end = std::min(start + MAX_BUTTONS_ON_SCREEN, totalElementSize);
if (mActiveConfigs.empty() && !mSetActivePluginsMode) {
if (configs.empty()) {
DrawUtils::setFontSize(24);
std::string noConfigText = "No active plugins";
uint32_t szNoConfig = DrawUtils::getTextWidth(noConfigText.data());
std::string noConfigText = "No plugins available";
if (!mAllConfigs.empty()) {
if (!mListState->IsMainView()) {
noConfigText = "No active plugins";
}
uint32_t szNoConfig = DrawUtils::getTextWidth(noConfigText.data());
if (mListState->IsMainView()) {
DrawUtils::print((SCREEN_WIDTH / 2) - (szNoConfig / 2), (SCREEN_HEIGHT / 2), noConfigText.data());
} else {
const auto activateHint = string_format("Press %s to activate inactive plugins", mLastInputWasOnWiimote ? "\uE048" : "\uE002");
const auto szHint = DrawUtils::getTextWidth(activateHint.c_str());
DrawUtils::print((SCREEN_WIDTH / 2) - (szNoConfig / 2), (SCREEN_HEIGHT / 2) - 16, noConfigText.data());
DrawUtils::print((SCREEN_WIDTH / 2) - (szHint / 2), (SCREEN_HEIGHT / 2) + 16, activateHint.data());
} else {
DrawUtils::print((SCREEN_WIDTH / 2) - (szNoConfig / 2), (SCREEN_HEIGHT / 2), noConfigText.data());
}
} else {
uint32_t yOffset = 8 + 24 + 8 + 4;
for (int32_t i = start; i < end; i++) {
DrawConfigEntry(yOffset, configs[i].get().getConfigInformation(), i == mCursorPos, configs[i].get().isActivePlugin());
DrawConfigEntry(yOffset, configs[i].get(), i == mCursorPos);
yOffset += 42 + 8;
}
}
DrawUtils::setFontColor(COLOR_TEXT);
// draw top bar
// Top Bar
DrawUtils::setFontSize(24);
if (mSetActivePluginsMode) {
DrawUtils::print(16, 6 + 24, "Please select the plugins that should be active");
} else {
DrawUtils::print(16, 6 + 24, "Wii U Plugin System Config Menu");
DrawUtils::print(16, 6 + 24, mListState->GetTitle().c_str());
auto countInactivePlugins = mAllConfigs.size() - mActiveConfigs.size();
if (mListState->IsMainView()) {
auto countInactivePlugins = mConfigs.size() - mFilteredConfigs.size();
if (countInactivePlugins > 0) {
DrawUtils::setFontSize(14);
const std::string plugin_unloaded = string_format("Found %d inactive plugins", countInactivePlugins);
DrawUtils::print(SCREEN_WIDTH - 16 - DrawUtils::getTextWidth(MODULE_VERSION_FULL) - 32, 8 + 24, plugin_unloaded.c_str(), true);
}
}
DrawUtils::setFontSize(18);
DrawUtils::print(SCREEN_WIDTH - 16, 8 + 24, MODULE_VERSION_FULL, true);
DrawUtils::drawRectFilled(8, 8 + 24 + 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_BLACK);
// draw bottom bar
// Bottom Bar
DrawUtils::drawRectFilled(8, SCREEN_HEIGHT - 24 - 8 - 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_BLACK);
DrawUtils::setFontSize(18);
DrawUtils::print(16, SCREEN_HEIGHT - 10, "\uE07D/\uE07E Navigate ");
if (mSetActivePluginsMode) {
DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 10, "\uE000 Toggle | \uE045 Apply", true);
} else if (totalElementSize > 0) {
const auto text = string_format("\ue000 Select | %s Manage plugins", mLastInputWasOnWiimote ? "\uE048" : "\uE002");
DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 10, text.c_str(), true);
}
// draw scroll indicator
if (totalElementSize > 0) {
DrawUtils::print(16, SCREEN_HEIGHT - 10, "\uE07D/\uE07E Navigate ");
}
DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 10, mListState->GetBottomBar(mLastInputWasOnWiimote).c_str(), true);
// Scroll Indicator
DrawUtils::setFontSize(24);
if (end < totalElementSize) {
DrawUtils::print(SCREEN_WIDTH / 2 + 12, SCREEN_HEIGHT - 32, "\ufe3e", true);
@ -270,18 +230,15 @@ void ConfigRenderer::RenderStateMain() const {
DrawUtils::print(SCREEN_WIDTH / 2 + 12, 32 + 20, "\ufe3d", true);
}
// draw home button
// Home Button
DrawUtils::setFontSize(18);
const char *exitHint = "\ue044 Exit";
if (mSetActivePluginsMode) {
exitHint = "\ue001 Abort";
}
const char *exitHint = mListState->IsMainView() ? "\ue001 Abort" : "\ue044 Exit";
DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(exitHint) / 2, SCREEN_HEIGHT - 10, exitHint, true);
DrawUtils::endDraw();
}
void ConfigRenderer::DrawConfigEntry(uint32_t yOffset, const GeneralConfigInformation &configInformation, bool isHighlighted, bool isActive) const {
void ConfigRenderer::DrawConfigEntry(uint32_t yOffset, const ConfigDisplayItem &item, bool isHighlighted) const {
DrawUtils::setFontColor(COLOR_TEXT);
if (isHighlighted) {
@ -291,18 +248,15 @@ void ConfigRenderer::DrawConfigEntry(uint32_t yOffset, const GeneralConfigInform
}
int textXOffset = 16 * 2;
if (mSetActivePluginsMode) {
DrawUtils::setFontSize(24);
if (isActive) {
DrawUtils::print(textXOffset, yOffset + 8 + 24, "\u25C9");
} else {
DrawUtils::print(textXOffset, yOffset + 8 + 24, "\u25CE");
}
// Delegate Icon drawing to state, returns true if icon was drawn
if (mListState->RenderItemIcon(item, textXOffset, yOffset + 8 + 24)) {
textXOffset += 32;
}
DrawUtils::setFontSize(24);
const auto &configInformation = item.getConfigInformation();
DrawUtils::print(textXOffset, yOffset + 8 + 24, configInformation.name.c_str());
uint32_t sz = DrawUtils::getTextWidth(configInformation.name.c_str());
DrawUtils::setFontSize(12);
@ -310,6 +264,82 @@ void ConfigRenderer::DrawConfigEntry(uint32_t yOffset, const GeneralConfigInform
DrawUtils::print(SCREEN_WIDTH - 16 * 2, yOffset + 8 + 24, configInformation.version.c_str(), true);
}
void ConfigRenderer::SetListState(std::unique_ptr<ConfigListState> state) {
mListState = std::move(state);
mNeedRedraw = true;
mCursorPos = 0;
mRenderOffset = 0;
// Fallback to "show all"
std::function<bool(const ConfigDisplayItem &)> pred = [](const auto &) { return true; };
if (mListState) {
pred = mListState->GetConfigFilter();
}
// Copy references into filteredConfigView
mFilteredConfigs.clear();
std::ranges::copy_if(mConfigs, std::back_inserter(mFilteredConfigs),
std::move(pred));
}
const std::vector<ConfigDisplayItem> &ConfigRenderer::GetConfigItems() {
return mConfigs;
}
std::vector<std::reference_wrapper<ConfigDisplayItem>> &ConfigRenderer::GetFilteredConfigItems() {
return mFilteredConfigs;
}
int32_t ConfigRenderer::GetCursorPos() const {
return mCursorPos;
}
void ConfigRenderer::EnterSelectedCategory() {
auto &items = GetDisplayedConfigList();
if (mCursorPos < 0 || static_cast<size_t>(mCursorPos) >= items.size()) return;
if (mCursorPos != mCurrentOpen) {
mCategoryRenderer.reset();
mCategoryRenderer = make_unique_nothrow<CategoryRenderer>(&(items[mCursorPos].get().getConfigInformation()), &(items[mCursorPos].get().getConfig()), true);
}
mNeedRedraw = true;
mCurrentOpen = mCursorPos;
mState = STATE_SUB;
}
void ConfigRenderer::SavePendingConfigs() {
for (const auto &element : mConfigs) {
CallOnCloseCallback(element.getConfigInformation(), element.getConfig());
}
}
void ConfigRenderer::Exit() {
mNeedRedraw = true;
mCategoryRenderer.reset();
SavePendingConfigs();
mNextSubState = SUB_STATE_RETURN;
}
void ConfigRenderer::ExitWithReload() {
mNeedRedraw = true;
mCategoryRenderer.reset();
SavePendingConfigs();
mNextSubState = SUB_STATE_RETURN_WITH_PLUGIN_RELOAD;
}
void ConfigRenderer::SetPluginsListDirty(bool dirty) {
mPluginListDirty = dirty;
}
bool ConfigRenderer::GetPluginsListIfChanged(std::vector<PluginLoadWrapper> &result) {
if (mPluginListDirty) {
result.clear();
for (const auto &cur : mConfigs) {
result.emplace_back(cur.getConfigInformation().pluginData, cur.isActivePlugin(), cur.isHeapTrackingEnabled());
}
return true;
}
return false;
}
void ConfigRenderer::CallOnCloseCallback(const GeneralConfigInformation &info, const std::vector<std::unique_ptr<WUPSConfigAPIBackend::WUPSConfigCategory>> &categories) {
for (const auto &cat : categories) {
if (!cat->getCategories().empty()) {
@ -321,18 +351,6 @@ void ConfigRenderer::CallOnCloseCallback(const GeneralConfigInformation &info, c
}
}
bool ConfigRenderer::GetActivePluginsIfChanged(std::vector<PluginLoadWrapper> &result) {
if (mActivePluginsDirty) {
std::vector<std::string> inactive_plugins;
result.clear();
for (const auto &cur : mConfigs) {
result.emplace_back(cur.getConfigInformation().pluginData, cur.isActivePlugin());
}
return true;
}
return false;
}
void ConfigRenderer::CallOnCloseCallback(const GeneralConfigInformation &info, const WUPSConfigAPIBackend::WUPSConfig &config) {
CallOnCloseCallback(info, config.getCategories());
for (const auto &item : config.getItems()) {
@ -340,9 +358,6 @@ void ConfigRenderer::CallOnCloseCallback(const GeneralConfigInformation &info, c
}
}
const std::vector<std::reference_wrapper<ConfigDisplayItem>> &ConfigRenderer::GetConfigList() const {
if (mSetActivePluginsMode) {
return mAllConfigs;
}
return mActiveConfigs;
}
const std::vector<std::reference_wrapper<ConfigDisplayItem>> &ConfigRenderer::GetDisplayedConfigList() const {
return mFilteredConfigs;
}

View File

@ -5,11 +5,11 @@
#include <wups/config.h>
#include <cstdint>
#include <functional>
#include <memory>
#include <vector>
#include <cstdint>
namespace WUPSConfigAPIBackend {
class WUPSConfig;
class WUPSConfigCategory;
@ -18,6 +18,8 @@ class PluginLoadWrapper;
class Input;
class CategoryRenderer;
class ConfigDisplayItem;
class ConfigListState;
class ConfigRenderer {
public:
@ -30,22 +32,31 @@ public:
void Render() const;
[[nodiscard]] bool NeedsRedraw() const;
void ResetNeedsRedraw();
void RequestRedraw();
bool GetActivePluginsIfChanged(std::vector<PluginLoadWrapper> &result);
bool GetPluginsListIfChanged(std::vector<PluginLoadWrapper> &result);
void SetListState(std::unique_ptr<ConfigListState> state);
const std::vector<ConfigDisplayItem> &GetConfigItems();
std::vector<std::reference_wrapper<ConfigDisplayItem>> &GetFilteredConfigItems(); // Mutable access
int32_t GetCursorPos() const;
void EnterSelectedCategory();
void Exit();
void ExitWithReload();
void SetPluginsListDirty(bool dirty);
private:
ConfigSubState UpdateStateMain(const Input &input);
void RenderStateMain() const;
void DrawConfigEntry(uint32_t yOffset, const GeneralConfigInformation &configInformation, bool isHighlighted, bool isActive) const;
void DrawConfigEntry(uint32_t yOffset, const ConfigDisplayItem &item, bool isHighlighted) const;
void CallOnCloseCallback(const GeneralConfigInformation &info, const std::vector<std::unique_ptr<WUPSConfigAPIBackend::WUPSConfigCategory>> &categories);
void CallOnCloseCallback(const GeneralConfigInformation &info, const WUPSConfigAPIBackend::WUPSConfig &config);
void SavePendingConfigs();
[[nodiscard]] const std::vector<std::reference_wrapper<ConfigDisplayItem>> &GetConfigList() const;
[[nodiscard]] const std::vector<std::reference_wrapper<ConfigDisplayItem>> &GetDisplayedConfigList() const;
enum State {
STATE_MAIN = 0,
@ -53,18 +64,20 @@ private:
};
std::vector<ConfigDisplayItem> mConfigs;
std::vector<std::reference_wrapper<ConfigDisplayItem>> mAllConfigs;
std::vector<std::reference_wrapper<ConfigDisplayItem>> mActiveConfigs;
mutable std::vector<std::reference_wrapper<ConfigDisplayItem>> mFilteredConfigs;
std::unique_ptr<ConfigListState> mListState;
std::unique_ptr<CategoryRenderer> mCategoryRenderer;
State mState = STATE_MAIN;
// Used to signal the main loop to return a specific state
ConfigSubState mNextSubState = SUB_STATE_RUNNING;
int32_t mCursorPos = 0;
int32_t mRenderOffset = 0;
int32_t mCurrentOpen = -1;
bool mNeedRedraw = true;
bool mSetActivePluginsMode = false;
bool mActivePluginsDirty = false;
bool mPluginListDirty = false;
bool mLastInputWasOnWiimote = false;
};
};

View File

@ -0,0 +1,177 @@
#include "ConfigRendererStates.h"
#include "ConfigDisplayItem.h"
#include "ConfigRenderer.h"
#include "utils/DrawUtils.h"
#include "utils/StringTools.h"
#include "utils/input/Input.h"
std::function<bool(const ConfigDisplayItem &)> DefaultListState::GetConfigFilter() const {
return [](const auto &item) { return item.isActivePlugin(); };
}
bool DefaultListState::IsMainView() const {
return true;
}
bool DefaultListState::HandleInput(ConfigRenderer &renderer, const Input &input) {
if (input.data.buttons_d & Input::eButtons::BUTTON_DOWN) {
// Hidden Combo: L + R + Down
constexpr auto COMBO_HOLD = Input::eButtons::BUTTON_L | Input::eButtons::BUTTON_R;
if ((input.data.buttons_h & COMBO_HOLD) == COMBO_HOLD) {
// Switch to Heap Tracking Mode
if (!renderer.GetConfigItems().empty()) {
renderer.SetListState(std::make_unique<HeapTrackingListState>());
return true;
}
}
return false; // Allow scrolling
}
if (input.data.buttons_d & Input::eButtons::BUTTON_A) {
if (!renderer.GetConfigItems().empty()) {
renderer.EnterSelectedCategory();
}
return true;
}
if (input.data.buttons_d & Input::eButtons::BUTTON_X) {
if (!renderer.GetConfigItems().empty()) {
renderer.SetListState(std::make_unique<ActivePluginsListState>());
}
return true;
}
if (input.data.buttons_d & (Input::eButtons::BUTTON_B | Input::eButtons::BUTTON_HOME)) {
renderer.Exit();
return true;
}
return false;
}
std::string DefaultListState::GetTitle() const {
return "Wii U Plugin System Config Menu";
}
std::string DefaultListState::GetBottomBar(bool isWiimote) const {
return string_format("\ue000 Select | %s Manage plugins", isWiimote ? "\uE048" : "\uE002");
}
bool DefaultListState::RenderItemIcon(const ConfigDisplayItem & /*item*/, int /*x*/, int /*y*/) const {
return false;
}
bool ActivePluginsListState::HandleInput(ConfigRenderer &renderer, const Input &input) {
if (input.data.buttons_d & Input::eButtons::BUTTON_A) {
auto &items = renderer.GetFilteredConfigItems();
int pos = renderer.GetCursorPos();
if (pos >= 0 && static_cast<size_t>(pos) < items.size()) {
items[pos].get().toggleIsActivePlugin();
renderer.SetPluginsListDirty(true);
renderer.RequestRedraw();
}
return true;
}
if (input.data.buttons_d & Input::eButtons::BUTTON_PLUS) {
// Apply and Reload
renderer.ExitWithReload();
return true;
}
if (input.data.buttons_d & (Input::eButtons::BUTTON_B | Input::eButtons::BUTTON_HOME)) {
// Abort / Reset
for (auto &item : renderer.GetFilteredConfigItems()) {
item.get().resetIsActivePlugin();
}
renderer.SetPluginsListDirty(false);
renderer.SetListState(std::make_unique<DefaultListState>());
return true;
}
return false;
}
std::string ActivePluginsListState::GetTitle() const {
return "Please select the plugins that should be active";
}
std::string ActivePluginsListState::GetBottomBar(bool /*isWiimote*/) const {
return "\uE000 Toggle | \uE001 Abort | \uE045 Apply";
}
bool ActivePluginsListState::RenderItemIcon(const ConfigDisplayItem &item, int x, int y) const {
DrawUtils::setFontSize(24);
if (item.isActivePlugin()) {
DrawUtils::print(x, y, "\u25C9");
} else {
DrawUtils::print(x, y, "\u25CE");
}
return true;
}
std::function<bool(const ConfigDisplayItem &)> ActivePluginsListState::GetConfigFilter() const {
return [](const auto &) { return true; };
}
bool ActivePluginsListState::IsMainView() const {
return false;
}
bool HeapTrackingListState::HandleInput(ConfigRenderer &renderer, const Input &input) {
if (input.data.buttons_d & Input::eButtons::BUTTON_A) {
auto &items = renderer.GetFilteredConfigItems();
int pos = renderer.GetCursorPos();
if (pos >= 0 && static_cast<size_t>(pos) < items.size()) {
items[pos].get().toggleIsHeapTrackingEnabled();
renderer.SetPluginsListDirty(true);
renderer.RequestRedraw();
}
return true;
}
if (input.data.buttons_d & Input::eButtons::BUTTON_PLUS) {
// Apply and Reload
renderer.ExitWithReload();
return true;
}
if (input.data.buttons_d & (Input::eButtons::BUTTON_B | Input::eButtons::BUTTON_HOME)) {
// Abort
for (auto &item : renderer.GetFilteredConfigItems()) {
item.get().resetIsHeapTrackingEnabled();
}
renderer.SetPluginsListDirty(false);
renderer.SetListState(std::make_unique<DefaultListState>());
return true;
}
return false;
}
std::string HeapTrackingListState::GetTitle() const {
return "Select plugins to enable Heap Tracking";
}
std::string HeapTrackingListState::GetBottomBar(bool /*isWiimote*/) const {
return "\uE000 Toggle | \uE001 Abort | \uE045 Apply";
}
bool HeapTrackingListState::RenderItemIcon(const ConfigDisplayItem &item, const int x, const int y) const {
DrawUtils::setFontSize(24);
if (item.isHeapTrackingEnabled()) {
DrawUtils::print(x, y, "\u25C9");
} else {
DrawUtils::print(x, y, "\u25CE");
}
return true;
}
std::function<bool(const ConfigDisplayItem &)> HeapTrackingListState::GetConfigFilter() const {
return [](const auto &item) { return item.isActivePlugin(); };
}
bool HeapTrackingListState::IsMainView() const {
return false;
}

View File

@ -0,0 +1,53 @@
#pragma once
#include <functional>
#include <memory>
#include <string>
class ConfigRenderer;
class Input;
class ConfigDisplayItem;
class ConfigListState {
public:
virtual ~ConfigListState() = default;
virtual bool HandleInput(ConfigRenderer &renderer, const Input &input) = 0;
virtual std::string GetTitle() const = 0;
virtual std::string GetBottomBar(bool isWiimote) const = 0;
virtual bool RenderItemIcon(const ConfigDisplayItem &item, int x, int y) const = 0;
virtual std::function<bool(const ConfigDisplayItem &)> GetConfigFilter() const = 0;
virtual bool IsMainView() const = 0;
};
class DefaultListState : public ConfigListState {
public:
bool HandleInput(ConfigRenderer &renderer, const Input &input) override;
std::string GetTitle() const override;
std::string GetBottomBar(bool isWiimote) const override;
bool RenderItemIcon(const ConfigDisplayItem &item, int x, int y) const override;
std::function<bool(const ConfigDisplayItem &)> GetConfigFilter() const override;
bool IsMainView() const override;
};
class ActivePluginsListState : public ConfigListState {
public:
bool HandleInput(ConfigRenderer &renderer, const Input &input) override;
std::string GetTitle() const override;
std::string GetBottomBar(bool isWiimote) const override;
bool RenderItemIcon(const ConfigDisplayItem &item, int x, int y) const override;
std::function<bool(const ConfigDisplayItem &)> GetConfigFilter() const override;
bool IsMainView() const override;
};
class HeapTrackingListState : public ConfigListState {
public:
bool HandleInput(ConfigRenderer &renderer, const Input &input) override;
std::string GetTitle() const override;
std::string GetBottomBar(bool isWiimote) const override;
bool RenderItemIcon(const ConfigDisplayItem &item, int x, int y) const override;
std::function<bool(const ConfigDisplayItem &)> GetConfigFilter() const override;
bool IsMainView() const override;
};

View File

@ -133,7 +133,7 @@ void ConfigUtils::displayMenu() {
config = make_unique_nothrow<WUPSConfigAPIBackend::WUPSConfig>(info.name);
}
configs.emplace_back(info, std::move(config), plugin.isLinkedAndLoaded());
configs.emplace_back(info, std::move(config), plugin.isLinkedAndLoaded(), plugin.isUsingTrackingPluginHeapMemoryAllocator());
}
// Sort Configs by name
@ -226,7 +226,7 @@ void ConfigUtils::displayMenu() {
std::vector<PluginLoadWrapper> newActivePluginsList;
if (subStateReturnValue == SUB_STATE_RETURN_WITH_PLUGIN_RELOAD && renderer.GetActivePluginsIfChanged(newActivePluginsList)) {
if (subStateReturnValue == SUB_STATE_RETURN_WITH_PLUGIN_RELOAD && renderer.GetPluginsListIfChanged(newActivePluginsList)) {
startTime = OSGetTime();
renderBasicScreen("Applying changes, app will now restart...");
@ -243,6 +243,7 @@ void ConfigUtils::displayMenu() {
}
}
}
gLoadOnNextLaunch = newActivePluginsList;
WUPSBackendSettings::SetInactivePluginFilenames(newInactivePluginsList);
if (!WUPSBackendSettings::SaveSettings()) {

View File

@ -38,7 +38,17 @@ extern "C" PluginBackendApiErrorType WUPSLoadAndLinkByDataHandle(const wups_back
for (const auto &pluginData : gLoadedData) {
if (pluginData->getHandle() == handle) {
gLoadOnNextLaunch.emplace_back(pluginData, true);
bool heapTrackingActive = false;
#ifdef DEBUG
heapTrackingActive = true;
#endif
for (const auto &plugin : gLoadedPlugins) {
if (plugin.getPluginDataCopy()->getHandle() == handle) {
heapTrackingActive = plugin.isUsingTrackingPluginHeapMemoryAllocator();
break;
}
}
gLoadOnNextLaunch.emplace_back(pluginData, true, heapTrackingActive);
found = true;
break;
}
@ -52,7 +62,7 @@ extern "C" PluginBackendApiErrorType WUPSLoadAndLinkByDataHandle(const wups_back
// Keep inactive plugins loaded. Duplicates will be eliminated by comparing name/author
for (const auto &plugin : gLoadedPlugins) {
if (!plugin.isLinkedAndLoaded()) {
gLoadOnNextLaunch.emplace_back(plugin.getPluginDataCopy(), false);
gLoadOnNextLaunch.emplace_back(plugin.getPluginDataCopy(), false, false);
}
}

View File

@ -253,7 +253,7 @@ std::string getModuleAndSymbolName(uint32_t addr) {
}
}
void PrintCapturedStackTrace(std::span<uint32_t> trace) {
void PrintCapturedStackTrace(const std::span<const uint32_t> trace) {
if (trace.empty()) {
DEBUG_FUNCTION_LINE_INFO("┌────────────────────── CAPTURED TRACE ──────────────────────┐");
DEBUG_FUNCTION_LINE_INFO("│ <Empty Trace>");

View File

@ -169,6 +169,6 @@ std::vector<uint32_t> CaptureStackTrace(uint32_t maxDepth);
std::string getModuleAndSymbolName(uint32_t addr);
void PrintCapturedStackTrace(std::span<uint32_t> trace);
void PrintCapturedStackTrace(std::span<const uint32_t> trace);
std::string hookNameToString(wups_loader_hook_type_t type);