Skip to content
encounterPublic

About

Borealis provides cross-platform modules for Aurora-based ports.

Resources

Stars

4 stars

Watchers

0 watching

Forks

Repository files navigation

Borealis

Modules for Aurora-based ports. Borealis provides cross-platform logging, updates, crash reporting, an HTTP client, Discord rich presence, data directory handling, and more.

Supported platforms: Windows, Linux, Android, macOS, iOS and tvOS.

Modules

Target Contents
borealis::cli Standard options with cxxopts
borealis::config ConfigVar system with JSON storage, observers & overrides
borealis::core Shared utilities
borealis::crash In-process crash handler with backtrace unwinding & logging
borealis::data Data directory resolution, portable mode, data migration
borealis::disc Disc inspection and hash verification
borealis::discord Discord rich presence IPC client
borealis::file_select Cross-platform file/folder selection
borealis::http Asynchronous HTTPS client (HTTP/2, TLS 1.2+)
borealis::io File I/O + paths, bookmarks (iOS), and document URIs (Android)
borealis::log fmt-based logging + sinks (console, rotating file, logcat, ring buffer)
borealis::net TCP, UDP, and asynchronous DNS
borealis::presentation Android frame-rate configuration
borealis::sentry Optional sentry-native/crashpad integration and consent state
borealis::task Shared async task pool with cancellation and progress
borealis::ui RmlUi UI framework, document system, and shared components
borealis::update Update checks via GitHub releases
borealis::ws WebSocket client over HTTPS

Borealis also provides an Android platform layer that integrates SDL, Aurora and provides Java-side support for Borealis modules.

Usage

Add borealis as a submodule alongside aurora and link the targets you use:

add_subdirectory(extern/borealis EXCLUDE_FROM_ALL)
target_link_libraries(mygame PRIVATE borealis::log)

Logging

#include <borealis/log.hpp>

namespace {
constexpr borealis::Log Log{"mygame::data"};
}

Log.info("Loaded {}", path);
Log.fatal("Unrecoverable: {}", err);  // flushes all sinks, then Options::onFatal

Initialize once at startup, before aurora_initialize:

borealis::log::init({
    .level = borealis::LogLevel::Info,
    .fileDirectory = cachePath / "logs",
    .filePrefix = "mygame",
});
config.logCallback = borealis::log::aurora_callback();

C code (e.g. OSReport shims) may use the printf-style bridge in <borealis/log_c.h>.

Application identity

Define borealis::AppInfo for modules that need application identity. Version and build strings come from the generated <borealis/version.h>.

inline constexpr borealis::AppInfo AppInfo{
    .orgName = "Twilit Realm",
    .appName = "Dusklight",
    .githubOwner = "TwilitRealm",
    .githubRepo = "dusklight",
    .discordApplicationId = "1495632471994405035",
};

HTTP and update checks

borealis::http provides pollable asynchronous HTTPS requests using WinHTTP on Windows, NSURLSession on Apple, libcurl on Linux or OkHttp on Android. The shared worker pool starts lazily, grows on demand, and releases idle threads automatically. Call borealis::shutdown() during application shutdown to cancel and drain outstanding work.

Asynchronous operations return a Task<T>. Poll with ready() or try_take(), request cancellation with cancel() and use map() to transform results.

auto check = borealis::update::start_latest_github_release_check(AppInfo);
// Poll from the main loop.
if (auto result = check.try_take();
    result && result->status == borealis::update::Status::UpdateAvailable) {
    show_update_prompt(result->latest.tagName, result->latest.htmlUrl);
}

Status::Disabled indicates the build was compiled without an available HTTP backend.

TCP, UDP, and DNS

borealis::net provides non-blocking TCP clients and listeners, UDP sockets, and asynchronous DNS. A Context manages its sockets and event queue, which can be polled from any thread.

#include <borealis/net.hpp>

borealis::net::Context network;
const auto stream = network.connect("tcp://127.0.0.1:34197");

borealis::net::Event event;
while (network.poll(event)) {
    if (event.id == stream && event.kind == borealis::net::Event::Kind::StreamData) {
        consume(event.id, event.data);
    }
}

Endpoints use tcp://host:port or udp://host:port.

WebSocket connections

borealis::ws provides asynchronous WebSocket client connections. Poll each connection for Open, Message, and Closed events.

#include <borealis/ws.hpp>

auto connection = borealis::ws::connect({
    .url = "wss://example.com/events",
    .protocols = {"events.v1"},
});

borealis::ws::Event event;
while (connection.poll(event)) {
    if (event.kind == borealis::ws::Event::Kind::Message) {
        consume(event.messageKind, event.data);
    }
}

Only wss:// is accepted by default.

Data directories

Create a borealis::data::Manager with the application identity, portable path, legacy identities, and files eligible for migration:

borealis::data::Manager dataManager{AppInfo, {
    .portableRelativePath = std::filesystem::path{"data"},
    .legacyApps = {
        {
            .orgName = "OldOrgName",
            .appName = "OldAppName",
        },
    },
    .migration = {
        .directories = {"saves", "texture_replacements"},
        .files = {"config.json"},
        .extensions = {".gci"},
    },
}};

const auto status = dataManager.initialize(userDirectoryOverride);
const auto& paths = dataManager.paths();

Configuration

borealis::config stores typed settings as flat keys in a JSON file. Declare Var<T> members in a settings struct using <borealis/config.hpp>, and define their keys and defaults in a source file that includes <borealis/config_codec.hpp>.

struct Settings {
    struct {
        Var<bool> fullscreen;
        Var<Resampler> resampler;  // enums need a constexpr config_enum_values(E) table next to them
    } video;
    struct {
        Var<int> scale;
    } ui;
};

Settings settings{
    .video = {
        .fullscreen{"video.fullscreen", false},
        .resampler{"video.resampler", Resampler::Bilinear},
    },
    .ui = {
        .scale{"ui.scale", 100, {.min = 50, .max = 200}},
    },
};

Load once at startup, call update() every frame to autosave changes after a short delay, and flush() on shutdown and when entering background (for mobile). --cvar KEY=VALUE (from borealis::cli) sets session-only overrides:

borealis::config::apply_overrides(standardOptions.configOverrides);
borealis::config::load({.path = paths.userPath / "config.json", .version = 1});
borealis::config::update();
borealis::config::flush();

Read vars directly (if (settings.video.fullscreen), *settings.ui.scale) and write them with set() and reset(). Only user values are saved, and unknown keys are preserved. Subsystems apply settings with observe(), which runs immediately and on every change. Overlay temporarily overrides values, e.g. for a game mode that forces some settings. <borealis/ui/config.hpp> provides binding helpers for controls.

auto binding = settings.video.resampler.observe(apply_resampler);  // disconnects when destroyed, or use .release()

borealis::config::Overlay recording{"recording"};
recording.set(settings.video.fullscreen, true);  // recording.clear() restores

pane.add_child<BoolButton>(bind(settings.video.fullscreen, {.key = "Fullscreen"}));

Disc inspection and verification

borealis::disc inspects and verifies GameCube and Wii disc images.

constexpr std::array AcceptedDiscs{
    borealis::disc::AcceptedDisc{
        .gameId = "GZ2E01",
        .expectedHash = borealis::disc::parse_xxh3_128("14e886f08e548a000afde98a3195e788"),
    },
};
constexpr std::array<std::string_view, 1> RecognizedGameIds{"RZDE01"};

borealis::disc::Progress progress;
const borealis::disc::Result result = borealis::disc::verify(path,
    {.acceptedDiscs = AcceptedDiscs, .recognizedGameIds = RecognizedGameIds}, &progress);

Game ID, disc number, and revision must match an accepted record. IDs in another record or recognizedGameIds return UnsupportedVersion; other IDs return UnknownGame.

Verification uses XXH3-128. Progress can be polled or canceled from another thread.

Crash unwinding and logging

Install the crash handler once, after logging is initialized:

#include <borealis/crash.hpp>

borealis::log::init(logOptions);
borealis::crash::install();

Reports are written to stderr and the active log file. They include build identity, fault address, module build IDs, and relative virtual addresses for symbolication.

Sentry crash reporting

Set BOREALIS_ENABLE_SENTRY=ON to include sentry-native/crashpad. The DSN and environment are build inputs: BOREALIS_SENTRY_DSN and BOREALIS_SENTRY_ENVIRONMENT. At runtime, BOREALIS_SENTRY_ENABLED, BOREALIS_SENTRY_DSN, and BOREALIS_SENTRY_DEBUG can be used as overrides.

borealis::sentry::Options options{
    .release = std::string(AppInfo.appName) + "@" + BOREALIS_APP_DESCRIBE,
    .databaseDirectory = cachePath / "sentry",
};
if (const char* logPath = borealis::log::file_path()) {
    options.attachments.emplace_back(logPath);
}
borealis::sentry::initialize(options);

Reports require user consent through get_consent() and set_consent(). Call shutdown() before shutting down logging.

Discord Rich Presence

borealis::discord uses Discord's local IPC protocol. Set the application ID in AppInfo:

borealis::discord::initialize(AppInfo, handlers);
borealis::discord::update_presence({
    .details = "Ordon Village",
    .largeImageKey = "icon",
    .largeImageText = std::string(AppInfo.appName),
});

Call run_callbacks() from the main loop and shutdown() during application teardown.

License

Borealis is licensed under the MIT License.

About

Borealis provides cross-platform modules for Aurora-based ports.

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages