Skip to content

wiiu-env/libiopshell

Repository files navigation

Publish Docker Image

libiopshell

A client library for the IOPShellModule. This library allows Wii U homebrew applications to register custom commands that can be executed via the IOPShell.

Commands registered via this library are added to the aroma shell. For example, if you register a command named set_color with arguments int, int, int, you will execute it in the shell like this:

  aroma set_color 255 0 0

It provides both a raw C API and a modern, type-safe C++ Wrapper that handles argument parsing automatically.

Requires the IOPShellModule to be running via WUMSLoader. Requires wut for building. Install via make install.

Usage

Makefile

Make sure to define WUMS_ROOT (if not already defined by your environment) and add the library to your compiler flags:

  WUMS_ROOT := $(DEVKITPRO)/wums

Add -liopshell to LIBS and $(WUMS_ROOT) to LIBDIRS.

C++ API (Recommended)

The C++ wrapper (IOPShellModule::CommandRegistry) allows you to register standard C++ functions or lambdas. The library automatically parses shell arguments into the types defined in your function signature.

Supported Types

  • Primitives: int, long, uint32_t, uint64_t, int8_t, int16_t, float, double, bool, etc.
  • Strings: std::string, std::string_view, const char*, char*.
  • Containers:
    • std::vector<T>: Variable length, comma-separated lists (e.g., input: 10,20,30).
    • std::array<T, N>: Fixed length lists (e.g., input: 255,255,0).
  • Enums: Custom enums (requires registration via macro).
  • Optionals: std::optional<T>: Arguments that can be omitted by the user.

Automatic Help Generation

If you do not provide a custom description string, libiopshell automatically generates a usage string based on your function signature:

  • Required arguments appear in angle brackets: <int>
  • Optional arguments appear in square brackets: [int]
  • Enums display their valid options: <enum(red|green|blue)> (or [enum(...)] if optional).

Command Management (RAII)

The CommandRegistry::Add function returns a std::optional<IOPShellModule::Command>.

  • Success: Contains a Command object.
  • Failure: Returns std::nullopt.

Important: The Command object follows RAII principles. When it is destroyed (e.g., goes out of scope), the command is automatically unregistered from the shell. You must store these objects (e.g., in a std::vector<Command>) to keep the commands active.

Enums

To use a C++ enum as a command argument, you must register it using IOPSHELL_REGISTER_ENUM (usually in the global scope). This enables automatic string-to-enum conversion (case-insensitive).

enum class ImageSource { TV, DRC, Both };

// Maps enum values to string inputs
IOPSHELL_REGISTER_ENUM(ImageSource,
    {ImageSource::TV, "tv"},
    {ImageSource::DRC, "drc"},
    {ImageSource::Both, "both"}
);

Optional Arguments

Use std::optional<T> for arguments that are not mandatory. Note: Once an optional argument appears in the function signature, **all subsequent arguments must also be optional ** to ensure valid parsing logic.

// Valid:
void Cmd(int a, std::optional<int> b);

// Invalid (compile-time error via static_assert):
void Cmd(std::optional<int> a, int b);

Extended Examples

Here are various ways to use the library, ranging from simple to complex.

1. Basic & Enum Usage

Registers a simple command to take a screenshot.

#include <iopshell/api.h>
#include <vector>

// Define and Register Enum
enum class ImageFormat { BMP, PNG, JPG };
IOPSHELL_REGISTER_ENUM(ImageFormat, 
    {ImageFormat::BMP, "bmp"}, 
    {ImageFormat::PNG, "png"}, 
    {ImageFormat::JPG, "jpg"}
);

// keep commands in memory to keep them registered.
std::vector<IOPShellModule::Command> sCommands;

void Init() {
    if(IOPShellModule::Init() != IOPSHELL_MODULE_ERROR_SUCCESS) return;

    // Usage: aroma screenshot <enum(bmp|png|jpg)>
    auto maybe_cmd = IOPShellModule::CommandRegistry::Add("screenshot", [](ImageFormat fmt) {
        if (fmt == ImageFormat::PNG) {
            // Take PNG...
        }
    });
    
    if (maybe_cmd) sCommands.push_back(std::move(*maybe_cmd));
}

void DeInitShell() {
    sCommands.clear();
    IOPShellModule::DeInit();
}

2. Vector (Variable Length Arguments)

Use std::vector to accept comma-separated lists of values.

// Usage: aroma sum 10,20,30,40
// Shell Input: sum 10,5,5
// Output: Sum is 20
void SumNumbers(std::vector<int> numbers) {
    int sum = 0;
    for (int n : numbers) sum += n;
    OSReport("Sum is %d\n", sum);
}

void Init() {
    [...]
    auto maybe_cmd = IOPShellModule::CommandRegistry::Add("sum", SumNumbers);
    if (maybe_cmd) sCommands.push_back(std::move(*maybe_cmd));
}

3. Fixed Array (Strict Length)

Use std::array to enforce a specific number of elements in a comma-separated list.

// Usage: aroma set_bg <list<uint8>[3]>
// Shell Input: set_bg 255,0,0
void SetBackground(std::array<uint8_t, 3> rgb) {
    OSReport("Setting BG Color: R=%d G=%d B=%d\n", rgb[0], rgb[1], rgb[2]);
}

void Init() {
    [...]
    auto maybe_cmd = IOPShellModule::CommandRegistry::Add("set_bg", SetBackground);
    if (maybe_cmd) sCommands.push_back(std::move(*maybe_cmd));
}

4. Complex Function (Mixed Types & Optionals)

A complex command combining strings, primitives, optional arguments, and enums.

enum class EntityType { Orc, Elf, Human };
IOPSHELL_REGISTER_ENUM(EntityType, 
    {EntityType::Orc, "orc"}, 
    {EntityType::Elf, "elf"}, 
    {EntityType::Human, "human"}
);

// Usage: aroma spawn <string> <enum(orc|elf|human)> <int> <int> [int] [bool]
//
// Examples:
//   aroma spawn Boss1 orc 100 200
//   aroma spawn Player1 elf 0 0 100 true
void SpawnEntity(std::string name, 
                 EntityType type, 
                 int x, 
                 int y, 
                 std::optional<int> health, 
                 std::optional<bool> isHostile) {
                 
    int hp = health.value_or(100); // Default 100 HP
    bool hostile = isHostile.value_or(true); // Default Hostile
    
    OSReport("Spawning %s (%s) at %d,%d. HP: %d, Hostile: %s\n", 
             name.c_str(), 
             (type == EntityType::Orc ? "Orc" : "Other"), 
             x, y, hp, 
             hostile ? "Yes" : "No");
}

void Init() {
    [...]
    auto maybe_cmd = IOPShellModule::CommandRegistry::Add("spawn", SpawnEntity);
    if (maybe_cmd) sCommands.push_back(std::move(*maybe_cmd));
}

5. Command Groups (Sub-commands)

You can organize related commands under a single parent command using CommandGroup. This creates a structure similar to CLI tools (e.g., git commit, git push).

Note: The CommandGroup object stores the sub-command handlers internally. It must remain alive (e.g., static or heap-allocated) for as long as the command is registered.

// 1. Define the Group (Must be persistent)
static IOPShellModule::CommandGroup sPlugins("plugins", "Plugin Manager");

void Init() {
    if(IOPShellModule::Init() != IOPSHELL_MODULE_ERROR_SUCCESS) return;

    // 2. Add Sub-commands
    // Usage: aroma plugins list
    sPlugins.AddCommand("list", []() {
        OSReport("Listing plugins...\n");
    }, "Lists all installed plugins");

    // Usage: aroma plugins install <string>
    sPlugins.AddCommand("install", [](std::string path) {
        OSReport("Installing plugin from %s\n", path.c_str());
    }, "Installs a plugin");

    // 3. Register the Main Command
    // Returns an optional<Command> that must be stored to keep the registration active.
    auto maybe_cmd = sPlugins.Register();
    if (maybe_cmd) sCommands.push_back(std::move(*maybe_cmd));
}

C API

If you prefer pure C or need to interface from a C-only project:

#include <iopshell/api.h>
#include <coreinit/debug.h>

void MyCommandCallback(int argc, char **argv) {
    OSReport("Hello! Arguments received: %d\n", argc);
    for(int i = 0; i < argc; i++) {
        OSReport("Arg %d: %s\n", i, argv[i]);
    }
}

void InitShell() {
    IOPShellModule_Error status = IOPShellModule_InitLibrary();
    if (status != IOPSHELL_MODULE_ERROR_SUCCESS) return;

    // Usage: aroma mycmd arg1 arg2
    IOPShellModule_Error res = IOPShellModule_AddCommand("mycmd", "Prints args", MyCommandCallback);
    if (res != IOPSHELL_MODULE_ERROR_SUCCESS) {
        OSReport("Failed to add command\n");
    }
}

void DeinitShell() {
    if (IOPShellModule_RemoveCommand("mycmd") != IOPSHELL_MODULE_ERROR_SUCCESS) {
        OSReport("Failed to remove command\n");
    }
    IOPShellModule_DeInitLibrary();
}

API Reference

C++ (namespace IOPShellModule)

  • Init(): Initializes the library.
  • DeInit(): Closes the library.
  • IOPSHELL_REGISTER_ENUM(Type, ...): Macro to register Enum-to-String mappings.

CommandRegistry

  • CommandRegistry::Add<Func>(name, description, [outError]): Registers a C++ function. Argument parsing is automated.
  • CommandRegistry::Add(name, lambda, description, [outError]): Registers a lambda with automatic signature deduction.
  • CommandRegistry::Add<Signature>(name, lambda, description, [outError]): Registers a lambda with an explicit signature.
  • CommandRegistry::Add(name, description, callback, [outError]): Registers a raw argc/argv callback.
  • CommandRegistry::AddRaw(name, handler, description, [outError]): Registers a raw command handler ( argc/argv). Supports capturing lambdas.
  • CommandRegistry::Remove(name): Unregisters a command.
  • CommandRegistry::List(): Returns a std::vector of all registered commands.

CommandGroup

Class for managing sub-commands (e.g., cmd subcmd).

  • CommandGroup::CommandGroup(name, description): Creates a group.
  • CommandGroup::AddCommand(name, lambda, description): Registers a sub-command with automatic type deduction.
  • CommandGroup::AddRawCommand(name, handler, description): Registers a raw sub-command.
  • CommandGroup::Register(): Registers the main command with the shell. Returns std::optional<Command>.

C API

  • IOPShellModule_InitLibrary(): Initializes the connection to the backend module.
  • IOPShellModule_AddCommand(name, description, callback): Registers a new command.
  • IOPShellModule_RemoveCommand(name): Unregisters a command.
  • IOPShellModule_ListCommands(...): Retrieves a list of commands (requires manual buffer management).

Use this lib in Dockerfiles

A prebuilt version of this lib can be found on dockerhub. To use it for your projects, add this to your Dockerfile:

[...]
COPY --from=ghcr.io/wiiu-env/libiopshell:[tag] /artifacts $DEVKITPRO
[...]

Replace [tag] with the tag you want to use. A list of tags can be found here. It is highly recommended to pin the version to the latest date instead of using latest to ensure reproducible builds.

Format the code via docker

docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./source ./include -i

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors