mirror of
https://github.com/geode-sdk/geode.git
synced 2025-03-25 04:11:42 -04:00
setting change events + remove platform console input queue + simplify
platform console API + add show-platform-console event listener
This commit is contained in:
parent
5af74e9ab7
commit
1b8289a420
12 changed files with 209 additions and 87 deletions
loader
|
@ -7,3 +7,4 @@
|
|||
#include "loader/Loader.hpp"
|
||||
#include "loader/Interface.hpp"
|
||||
#include "loader/Setting.hpp"
|
||||
#include "loader/SettingEvent.hpp"
|
||||
|
|
|
@ -4,33 +4,40 @@
|
|||
#include <type_traits>
|
||||
#include "Mod.hpp"
|
||||
#include "Interface.hpp"
|
||||
#include <unordered_set>
|
||||
|
||||
namespace geode {
|
||||
class Mod;
|
||||
class Event;
|
||||
|
||||
enum class PassThrough : bool {
|
||||
Propagate,
|
||||
Stop,
|
||||
};
|
||||
|
||||
struct GEODE_DLL BasicEventHandler {
|
||||
virtual bool onEvent(Event*) = 0;
|
||||
virtual PassThrough passThrough(Event*) = 0;
|
||||
|
||||
void listen();
|
||||
void unlisten();
|
||||
};
|
||||
|
||||
class GEODE_DLL Event {
|
||||
static std::vector<BasicEventHandler*> handlers;
|
||||
static std::unordered_set<BasicEventHandler*> s_handlers;
|
||||
|
||||
friend BasicEventHandler;
|
||||
|
||||
Mod* m_sender;
|
||||
|
||||
public:
|
||||
static std::vector<BasicEventHandler*> const& getHandlers();
|
||||
static std::unordered_set<BasicEventHandler*> const& getHandlers();
|
||||
|
||||
void postFrom(Mod* sender);
|
||||
inline void post() {
|
||||
postFrom(Mod::get());
|
||||
}
|
||||
|
||||
Mod* sender();
|
||||
Mod* getSender();
|
||||
|
||||
virtual ~Event();
|
||||
};
|
||||
|
@ -38,12 +45,12 @@ namespace geode {
|
|||
template <typename T>
|
||||
class EventHandler : public BasicEventHandler {
|
||||
public:
|
||||
virtual bool handle(T*) = 0;
|
||||
bool onEvent(Event* ev) override {
|
||||
virtual PassThrough handle(T*) = 0;
|
||||
PassThrough passThrough(Event* ev) override {
|
||||
if (auto myev = dynamic_cast<T*>(ev)) {
|
||||
return handle(myev);
|
||||
}
|
||||
return true;
|
||||
return PassThrough::Propagate;
|
||||
}
|
||||
|
||||
EventHandler() {
|
||||
|
|
|
@ -17,12 +17,15 @@
|
|||
namespace geode {
|
||||
using ModJson = nlohmann::ordered_json;
|
||||
|
||||
class Setting;
|
||||
class SettingNode;
|
||||
class BoolSetting;
|
||||
class IntSetting;
|
||||
class FloatSetting;
|
||||
class StringSetting;
|
||||
|
||||
struct ModInfo;
|
||||
|
||||
enum class SettingType {
|
||||
Bool,
|
||||
Int,
|
||||
|
@ -34,9 +37,21 @@ namespace geode {
|
|||
User,
|
||||
};
|
||||
|
||||
class GEODE_DLL Setting {
|
||||
/**
|
||||
* Base class for all settings in Geode mods. Note that for most purposes
|
||||
* you should use the built-in setting types. If you need a custom setting
|
||||
* type however, inherit from this class. Do note that you are responsible
|
||||
* for things like storing the default value, broadcasting value change
|
||||
* events, making the setting node etc.
|
||||
*/
|
||||
class GEODE_DLL Setting :
|
||||
public std::enable_shared_from_this<Setting>
|
||||
{
|
||||
protected:
|
||||
std::string m_key;
|
||||
std::string m_modID;
|
||||
|
||||
friend struct ModInfo;
|
||||
|
||||
static Result<std::shared_ptr<Setting>> parse(
|
||||
std::string const& type,
|
||||
|
@ -59,6 +74,8 @@ namespace geode {
|
|||
|
||||
virtual SettingNode* createNode(float width) = 0;
|
||||
|
||||
void valueChanged();
|
||||
|
||||
std::string getKey() const;
|
||||
virtual SettingType getType() const = 0;
|
||||
};
|
||||
|
@ -161,6 +178,7 @@ namespace geode {
|
|||
if constexpr (std::is_base_of_v<IMatch<Class, ValueType>, Class>) {
|
||||
static_cast<Class*>(this)->constrainMatch(m_value);
|
||||
}
|
||||
this->valueChanged();
|
||||
}
|
||||
|
||||
Result<> isValidValue(ValueType value) {
|
||||
|
@ -392,8 +410,7 @@ namespace geode {
|
|||
}
|
||||
|
||||
class GEODE_DLL BoolSetting :
|
||||
public GeodeSetting<BoolSetting, bool, SettingType::Bool>,
|
||||
public std::enable_shared_from_this<BoolSetting>
|
||||
public GeodeSetting<BoolSetting, bool, SettingType::Bool>
|
||||
{
|
||||
public:
|
||||
SettingNode* createNode(float width) override;
|
||||
|
@ -403,7 +420,6 @@ namespace geode {
|
|||
public GeodeSetting<IntSetting, int64_t, SettingType::Int>,
|
||||
public IOneOf<IntSetting, int64_t>,
|
||||
public IMinMax<int64_t>,
|
||||
public std::enable_shared_from_this<IntSetting>,
|
||||
public ICArrows, public ICSlider<int64_t>, public ICInput
|
||||
{
|
||||
public:
|
||||
|
@ -414,7 +430,6 @@ namespace geode {
|
|||
public GeodeSetting<FloatSetting, double, SettingType::Float>,
|
||||
public IOneOf<FloatSetting, double>,
|
||||
public IMinMax<double>,
|
||||
public std::enable_shared_from_this<FloatSetting>,
|
||||
public ICArrows, public ICSlider<double>, public ICInput
|
||||
{
|
||||
public:
|
||||
|
@ -424,8 +439,7 @@ namespace geode {
|
|||
class GEODE_DLL StringSetting :
|
||||
public GeodeSetting<StringSetting, std::string, SettingType::String>,
|
||||
public IOneOf<StringSetting, std::string>,
|
||||
public IMatch<StringSetting, std::string>,
|
||||
public std::enable_shared_from_this<StringSetting>
|
||||
public IMatch<StringSetting, std::string>
|
||||
{
|
||||
public:
|
||||
SettingNode* createNode(float width) override;
|
||||
|
@ -433,24 +447,21 @@ namespace geode {
|
|||
|
||||
class GEODE_DLL FileSetting :
|
||||
public GeodeSetting<FileSetting, ghc::filesystem::path, SettingType::File>,
|
||||
public ICFileFilters,
|
||||
public std::enable_shared_from_this<FileSetting>
|
||||
public ICFileFilters
|
||||
{
|
||||
public:
|
||||
SettingNode* createNode(float width) override;
|
||||
};
|
||||
|
||||
class GEODE_DLL ColorSetting :
|
||||
public GeodeSetting<ColorSetting, cocos2d::ccColor3B, SettingType::Color>,
|
||||
public std::enable_shared_from_this<ColorSetting>
|
||||
public GeodeSetting<ColorSetting, cocos2d::ccColor3B, SettingType::Color>
|
||||
{
|
||||
public:
|
||||
SettingNode* createNode(float width) override;
|
||||
};
|
||||
|
||||
class GEODE_DLL ColorAlphaSetting :
|
||||
public GeodeSetting<ColorAlphaSetting, cocos2d::ccColor4B, SettingType::ColorAlpha>,
|
||||
public std::enable_shared_from_this<ColorAlphaSetting>
|
||||
public GeodeSetting<ColorAlphaSetting, cocos2d::ccColor4B, SettingType::ColorAlpha>
|
||||
{
|
||||
public:
|
||||
SettingNode* createNode(float width) override;
|
||||
|
|
71
loader/include/Geode/loader/SettingEvent.hpp
Normal file
71
loader/include/Geode/loader/SettingEvent.hpp
Normal file
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
|
||||
#include "Event.hpp"
|
||||
#include <optional>
|
||||
#include "Setting.hpp"
|
||||
|
||||
namespace geode {
|
||||
class GEODE_DLL SettingChangedEvent : public Event {
|
||||
protected:
|
||||
std::string m_modID;
|
||||
std::shared_ptr<Setting> m_setting;
|
||||
|
||||
public:
|
||||
SettingChangedEvent(
|
||||
std::string const& modID,
|
||||
std::shared_ptr<Setting> setting
|
||||
);
|
||||
std::string getModID() const;
|
||||
std::shared_ptr<Setting> getSetting() const;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
class SettingChangedEventHandler : public EventHandler<SettingChangedEvent> {
|
||||
public:
|
||||
using Consumer = void(*)(std::shared_ptr<T>);
|
||||
|
||||
static_assert(
|
||||
std::is_base_of_v<Setting, T>,
|
||||
"Setting must inherit from the Setting class"
|
||||
);
|
||||
|
||||
protected:
|
||||
Consumer m_consumer;
|
||||
std::string m_modID;
|
||||
std::optional<std::string> m_targetKey;
|
||||
|
||||
public:
|
||||
PassThrough handle(SettingChangedEvent* event) override {
|
||||
if (
|
||||
m_modID == event->getModID() && (
|
||||
!m_targetKey ||
|
||||
m_targetKey.value() == event->getSetting()->getKey()
|
||||
)) {
|
||||
m_consumer(std::static_pointer_cast<T>(event->getSetting()));
|
||||
}
|
||||
return PassThrough::Propagate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to changes on a specific setting
|
||||
*/
|
||||
SettingChangedEventHandler(
|
||||
std::string const& modID,
|
||||
std::string const& settingID,
|
||||
Consumer handler
|
||||
) : m_modID(modID),
|
||||
m_targetKey(settingID),
|
||||
m_consumer(handler) {}
|
||||
|
||||
/**
|
||||
* Listen to changes on all of a mods' settings
|
||||
*/
|
||||
SettingChangedEventHandler(
|
||||
std::string const& modID,
|
||||
Consumer handler
|
||||
) : m_modID(modID),
|
||||
m_targetKey(std::nullopt),
|
||||
m_consumer(handler) {}
|
||||
};
|
||||
}
|
||||
|
|
@ -56,12 +56,14 @@ void InternalLoader::executeGDThreadQueue() {
|
|||
m_gdThreadMutex.unlock();
|
||||
}
|
||||
|
||||
void InternalLoader::queueConsoleMessage(LogPtr* msg) {
|
||||
this->m_logQueue.push_back(msg);
|
||||
void InternalLoader::logConsoleMessage(LogPtr* msg) {
|
||||
if (m_platformConsoleOpen) {
|
||||
std::cout << msg->toString(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool InternalLoader::platformConsoleReady() const {
|
||||
return m_platformConsoleReady;
|
||||
bool InternalLoader::platformConsoleOpen() const {
|
||||
return m_platformConsoleOpen;
|
||||
}
|
||||
|
||||
bool InternalLoader::shownInfoAlert(std::string const& key) {
|
||||
|
@ -85,42 +87,28 @@ void InternalLoader::platformMessageBox(const char* title, std::string const& in
|
|||
MessageBoxA(nullptr, info.c_str(), title, MB_ICONERROR);
|
||||
}
|
||||
|
||||
void InternalLoader::setupPlatformConsole() {
|
||||
if (m_platformConsoleReady) return;
|
||||
void InternalLoader::openPlatformConsole() {
|
||||
if (m_platformConsoleOpen) return;
|
||||
if (AllocConsole() == 0) return;
|
||||
// redirect console output
|
||||
freopen_s(reinterpret_cast<FILE**>(stdout), "CONOUT$", "w", stdout);
|
||||
freopen_s(reinterpret_cast<FILE**>(stdin), "CONIN$", "r", stdin);
|
||||
|
||||
m_platformConsoleReady = true;
|
||||
}
|
||||
m_platformConsoleOpen = true;
|
||||
|
||||
void InternalLoader::awaitPlatformConsole() {
|
||||
if (!m_platformConsoleReady) return;
|
||||
|
||||
for (auto const& log : m_logQueue) {
|
||||
for (auto const& log : Loader::get()->getLogs()) {
|
||||
std::cout << log->toString(true) << "\n";
|
||||
m_logQueue.clear();
|
||||
}
|
||||
|
||||
std::string inp;
|
||||
getline(std::cin, inp);
|
||||
std::string inpa;
|
||||
std::stringstream ss(inp);
|
||||
std::vector<std::string> args;
|
||||
|
||||
while (ss >> inpa) args.push_back(inpa);
|
||||
ss.clear();
|
||||
|
||||
this->awaitPlatformConsole();
|
||||
}
|
||||
|
||||
void InternalLoader::closePlatformConsole() {
|
||||
if (!m_platformConsoleReady) return;
|
||||
if (!m_platformConsoleOpen) return;
|
||||
|
||||
fclose(stdin);
|
||||
fclose(stdout);
|
||||
FreeConsole();
|
||||
|
||||
m_platformConsoleOpen = false;
|
||||
}
|
||||
|
||||
#elif defined(GEODE_IS_MACOS)
|
||||
|
@ -130,8 +118,8 @@ void InternalLoader::platformMessageBox(const char* title, std::string const& in
|
|||
std::cout << title << ": " << info << std::endl;
|
||||
}
|
||||
|
||||
void InternalLoader::setupPlatformConsole() {
|
||||
m_platformConsoleReady = true;
|
||||
void InternalLoader::openPlatformConsole() {
|
||||
m_platformConsoleOpen = true;
|
||||
}
|
||||
|
||||
void InternalLoader::awaitPlatformConsole() {
|
||||
|
@ -150,13 +138,13 @@ void InternalLoader::platformMessageBox(const char* title, std::string const& in
|
|||
std::cout << title << ": " << info << std::endl;
|
||||
}
|
||||
|
||||
void InternalLoader::setupPlatformConsole() {
|
||||
void InternalLoader::openPlatformConsole() {
|
||||
ghc::filesystem::path(getpwuid(getuid())->pw_dir);
|
||||
freopen(ghc::filesystem::path(
|
||||
utils::file::geodeRoot() / "geode_log.txt"
|
||||
).string().c_str(),"w",stdout);
|
||||
InternalLoader::
|
||||
m_platformConsoleReady = true;
|
||||
m_platformConsoleOpen = true;
|
||||
}
|
||||
|
||||
void InternalLoader::awaitPlatformConsole() {
|
||||
|
|
|
@ -17,10 +17,9 @@ USE_GEODE_NAMESPACE();
|
|||
*/
|
||||
class InternalLoader : public Loader {
|
||||
protected:
|
||||
std::vector<LogPtr*> m_logQueue;
|
||||
std::vector<std::function<void(void)>> m_gdThreadQueue;
|
||||
mutable std::mutex m_gdThreadMutex;
|
||||
bool m_platformConsoleReady = false;
|
||||
bool m_platformConsoleOpen = false;
|
||||
std::unordered_set<std::string> m_shownInfoAlerts;
|
||||
|
||||
void saveInfoAlerts(nlohmann::json& json);
|
||||
|
@ -48,10 +47,9 @@ public:
|
|||
void queueInGDThread(std::function<void GEODE_CALL(void)> func);
|
||||
void executeGDThreadQueue();
|
||||
|
||||
void queueConsoleMessage(LogPtr*);
|
||||
bool platformConsoleReady() const;
|
||||
void setupPlatformConsole();
|
||||
void awaitPlatformConsole();
|
||||
void logConsoleMessage(LogPtr*);
|
||||
bool platformConsoleOpen() const;
|
||||
void openPlatformConsole();
|
||||
void closePlatformConsole();
|
||||
static void platformMessageBox(const char* title, std::string const& info);
|
||||
|
||||
|
|
|
@ -3,15 +3,14 @@
|
|||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
std::vector<BasicEventHandler*> Event::handlers = {};
|
||||
std::unordered_set<BasicEventHandler*> Event::s_handlers = {};
|
||||
|
||||
void BasicEventHandler::listen() {
|
||||
if (!utils::vector::contains(Event::handlers, this))
|
||||
Event::handlers.push_back(this);
|
||||
Event::s_handlers.insert(this);
|
||||
}
|
||||
|
||||
void BasicEventHandler::unlisten() {
|
||||
utils::vector::erase(Event::handlers, this);
|
||||
Event::s_handlers.erase(this);
|
||||
}
|
||||
|
||||
Event::~Event() {}
|
||||
|
@ -20,12 +19,17 @@ void Event::postFrom(Mod* m) {
|
|||
if (m)
|
||||
m_sender = m;
|
||||
|
||||
for (auto h : Event::handlers) {
|
||||
if (!h->onEvent(this))
|
||||
for (auto h : Event::s_handlers) {
|
||||
if (h->passThrough(this) == PassThrough::Stop) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<BasicEventHandler*> const& Event::getHandlers() {
|
||||
return Event::handlers;
|
||||
Mod* Event::getSender() {
|
||||
return m_sender;
|
||||
}
|
||||
|
||||
std::unordered_set<BasicEventHandler*> const& Event::getHandlers() {
|
||||
return Event::s_handlers;
|
||||
}
|
||||
|
|
|
@ -376,11 +376,7 @@ Loader::~Loader() {
|
|||
void Loader::pushLog(LogPtr* logptr) {
|
||||
m_logs.push_back(logptr);
|
||||
|
||||
if (InternalLoader::get()->platformConsoleReady()) {
|
||||
std::cout << logptr->toString(true);
|
||||
} else {
|
||||
InternalLoader::get()->queueConsoleMessage(logptr);
|
||||
}
|
||||
InternalLoader::get()->logConsoleMessage(logptr);
|
||||
|
||||
m_logStream << logptr->toString(true) << std::endl;
|
||||
}
|
||||
|
@ -458,12 +454,7 @@ bool Loader::supportedModVersion(VersionInfo const& version) {
|
|||
}
|
||||
|
||||
void Loader::openPlatformConsole() {
|
||||
if (!InternalLoader::get()->platformConsoleReady()) {
|
||||
InternalLoader::get()->setupPlatformConsole();
|
||||
std::thread([]() {
|
||||
InternalLoader::get()->awaitPlatformConsole();
|
||||
}).detach();
|
||||
}
|
||||
InternalLoader::get()->openPlatformConsole();
|
||||
}
|
||||
|
||||
void Loader::closePlatfromConsole() {
|
||||
|
|
|
@ -136,7 +136,7 @@ void Log::flush() {
|
|||
|
||||
Log::~Log() {
|
||||
this->flush();
|
||||
if (InternalLoader::get()->platformConsoleReady()) {
|
||||
if (InternalLoader::get()->platformConsoleOpen()) {
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,9 +64,11 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
|
|||
}
|
||||
|
||||
for (auto& [key, value] : root.has("settings").items()) {
|
||||
auto sett = Setting::parse(key, value.json());
|
||||
PROPAGATE(sett);
|
||||
info.m_settings.push_back({ key, sett.value() });
|
||||
auto settRes = Setting::parse(key, value.json());
|
||||
PROPAGATE(settRes);
|
||||
auto sett = settRes.value();
|
||||
sett->m_modID = info.m_id;
|
||||
info.m_settings.push_back({ key, sett });
|
||||
}
|
||||
|
||||
if (auto resources = root.has("resources").obj()) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include <Geode/loader/Setting.hpp>
|
||||
#include <Geode/loader/SettingEvent.hpp>
|
||||
#include <Geode/utils/general.hpp>
|
||||
#include <Geode/loader/SettingNode.hpp>
|
||||
#include "../ui/internal/settings/GeodeSettingNode.hpp"
|
||||
|
@ -54,30 +55,68 @@ Result<std::shared_ptr<Setting>> Setting::parse(
|
|||
return Err("Setting value is not an object");
|
||||
}
|
||||
|
||||
void Setting::valueChanged() {
|
||||
SettingChangedEvent(m_modID, shared_from_this()).post();
|
||||
}
|
||||
|
||||
SettingNode* BoolSetting::createNode(float width) {
|
||||
return BoolSettingNode::create(shared_from_this(), width);
|
||||
return BoolSettingNode::create(
|
||||
std::static_pointer_cast<BoolSetting>(shared_from_this()),
|
||||
width
|
||||
);
|
||||
}
|
||||
|
||||
SettingNode* IntSetting::createNode(float width) {
|
||||
return IntSettingNode::create(shared_from_this(), width);
|
||||
return IntSettingNode::create(
|
||||
std::static_pointer_cast<IntSetting>(shared_from_this()),
|
||||
width
|
||||
);
|
||||
}
|
||||
|
||||
SettingNode* FloatSetting::createNode(float width) {
|
||||
return FloatSettingNode::create(shared_from_this(), width);
|
||||
return FloatSettingNode::create(
|
||||
std::static_pointer_cast<FloatSetting>(shared_from_this()),
|
||||
width
|
||||
);
|
||||
}
|
||||
|
||||
SettingNode* StringSetting::createNode(float width) {
|
||||
return StringSettingNode::create(shared_from_this(), width);
|
||||
return StringSettingNode::create(
|
||||
std::static_pointer_cast<StringSetting>(shared_from_this()),
|
||||
width
|
||||
);
|
||||
}
|
||||
|
||||
SettingNode* FileSetting::createNode(float width) {
|
||||
return FileSettingNode::create(shared_from_this(), width);
|
||||
return FileSettingNode::create(
|
||||
std::static_pointer_cast<FileSetting>(shared_from_this()),
|
||||
width
|
||||
);
|
||||
}
|
||||
|
||||
SettingNode* ColorSetting::createNode(float width) {
|
||||
return ColorSettingNode::create(shared_from_this(), width);
|
||||
return ColorSettingNode::create(
|
||||
std::static_pointer_cast<ColorSetting>(shared_from_this()),
|
||||
width
|
||||
);
|
||||
}
|
||||
|
||||
SettingNode* ColorAlphaSetting::createNode(float width) {
|
||||
return ColorAlphaSettingNode::create(shared_from_this(), width);
|
||||
return ColorAlphaSettingNode::create(
|
||||
std::static_pointer_cast<ColorAlphaSetting>(shared_from_this()),
|
||||
width
|
||||
);
|
||||
}
|
||||
|
||||
SettingChangedEvent::SettingChangedEvent(
|
||||
std::string const& modID,
|
||||
std::shared_ptr<Setting> setting
|
||||
) : m_modID(modID), m_setting(setting) {}
|
||||
|
||||
std::string SettingChangedEvent::getModID() const {
|
||||
return m_modID;
|
||||
}
|
||||
|
||||
std::shared_ptr<Setting> SettingChangedEvent::getSetting() const {
|
||||
return m_setting;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include <Geode/loader/Mod.hpp>
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/loader/SettingEvent.hpp>
|
||||
#include <InternalLoader.hpp>
|
||||
#include <InternalMod.hpp>
|
||||
#include <Geode/loader/Log.hpp>
|
||||
|
@ -50,6 +51,16 @@ BOOL WINAPI DllMain(HINSTANCE lib, DWORD reason, LPVOID) {
|
|||
}
|
||||
#endif
|
||||
|
||||
static SettingChangedEventHandler<BoolSetting> _(
|
||||
"geode.loader", "show-platform-console",
|
||||
[](std::shared_ptr<BoolSetting> setting) {
|
||||
if (setting->getValue()) {
|
||||
Loader::get()->openPlatformConsole();
|
||||
} else {
|
||||
Loader::get()->closePlatfromConsole();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
int geodeEntry(void* platformData) {
|
||||
// setup internals
|
||||
|
@ -108,8 +119,7 @@ int geodeEntry(void* platformData) {
|
|||
<< "Set up loader";
|
||||
|
||||
if (InternalMod::get()->getSettingValue<bool>("show-platform-console")) {
|
||||
InternalLoader::get()->setupPlatformConsole();
|
||||
InternalLoader::get()->awaitPlatformConsole();
|
||||
Loader::get()->openPlatformConsole();
|
||||
}
|
||||
|
||||
InternalMod::get()->log()
|
||||
|
|
Loading…
Add table
Reference in a new issue