Merge branch 'main' of https://github.com/geode-sdk/geode into main

This commit is contained in:
HJfod 2024-02-03 19:00:27 +02:00
commit b0e5588419
9 changed files with 246 additions and 162 deletions

View file

@ -109,7 +109,8 @@ namespace geode {
private:
class Impl;
std::shared_ptr<Nest::Impl> m_impl;
friend class Logger;
friend GEODE_DLL std::shared_ptr<Nest> saveNest();
friend GEODE_DLL void loadNest(std::shared_ptr<Nest> const& nest);
public:
explicit Nest(std::shared_ptr<Nest::Impl> impl);
};

View file

@ -19,15 +19,6 @@ using namespace geode::prelude;
#include "load.hpp"
$execute {
listenForSettingChanges("show-platform-console", +[](bool value) {
if (value) {
console::open();
}
else {
console::close();
}
});
ipc::listen("ipc-test", [](ipc::IPCEvent* event) -> matjson::Value {
return "Hello from Geode!";
});
@ -95,7 +86,12 @@ void tryShowForwardCompat() {
int geodeEntry(void* platformData) {
thread::setName("Main");
log::Logger::get()->setup();
console::setup();
if (LoaderImpl::get()->isForwardCompatMode()) {
console::openIfClosed();
}
std::string forwardCompatSuffix;
if (LoaderImpl::get()->isForwardCompatMode())
@ -132,10 +128,9 @@ int geodeEntry(void* platformData) {
tryShowForwardCompat();
// open console
if (LoaderImpl::get()->isForwardCompatMode() ||
if (!LoaderImpl::get()->isForwardCompatMode() &&
Mod::get()->getSettingValue<bool>("show-platform-console")) {
log::debug("Opening console");
console::open();
console::openIfClosed();
}
// set up loader, load mods, etc.

View file

@ -712,7 +712,6 @@ std::vector<LoadProblem> Loader::Impl::getProblems() const {
}
void Loader::Impl::forceReset() {
console::close();
for (auto& [_, mod] : m_mods) {
delete mod;
}

View file

@ -91,23 +91,32 @@ std::string cocos2d::format_as(cocos2d::ccColor4B const& col) {
// Log
inline static thread_local int32_t s_nestLevel = 0;
inline static thread_local int32_t s_nestCountOffset = 0;
void log::vlogImpl(Severity sev, Mod* mod, fmt::string_view format, fmt::format_args args) {
Logger::get()->push(
sev,
mod,
fmt::vformat(format, args)
);
if (!mod->isLoggingEnabled()) return;
auto nestCount = s_nestLevel * 2;
if (nestCount != 0) {
nestCount += s_nestCountOffset;
}
Logger::get()->push(sev, thread::getName(), mod->getName(), nestCount,
fmt::vformat(format, args));
}
Log::Log(Severity sev, std::string&& thread, Mod* mod, std::string&& content) :
m_sender(mod),
Log::Log(Severity sev, std::string&& thread, std::string&& source, int32_t nestCount,
std::string&& content) :
m_time(log_clock::now()),
m_severity(sev),
m_thread(thread),
m_source(source),
m_nestCount(nestCount),
m_content(content) {}
Log::~Log() {}
Log::~Log() = default;
auto convertTime(auto timePoint) {
// std::chrono::current_zone() isnt available on clang (android),
@ -117,12 +126,8 @@ auto convertTime(auto timePoint) {
return fmt::localtime(timeEpoch);
}
std::string Log::toString(bool logTime, int32_t nestCount) const {
std::string res;
if (logTime) {
res += fmt::format("{:%H:%M:%S}", convertTime(m_time));
}
std::string Log::toString() const {
std::string res = fmt::format("{:%H:%M:%S}", convertTime(m_time));
switch (m_severity.m_value) {
case Severity::Debug:
@ -142,33 +147,49 @@ std::string Log::toString(bool logTime, int32_t nestCount) const {
break;
}
auto senderName = m_sender ? m_sender->getName() : "Geode?";
auto threadName = m_thread;
auto nestCount = m_nestCount;
auto source = m_source;
auto thread = m_thread;
if (nestCount != 0) {
nestCount -= static_cast<int32_t>(senderName.size() + threadName.size());
nestCount -= static_cast<int32_t>(source.size() + thread.size());
}
if (nestCount < 0) {
auto initSenderLength = static_cast<int32_t>(senderName.size());
auto initThreadLength = static_cast<int32_t>(threadName.size());
auto initSourceLength = static_cast<int32_t>(source.size());
auto initThreadLength = static_cast<int32_t>(thread.size());
auto needsCollapse = -nestCount;
auto senderCollapse = needsCollapse / 2;
auto senderLength = std::max(initSenderLength - senderCollapse, 2);
senderCollapse = initSenderLength - senderLength;
if (initThreadLength == 0) {
auto sourceCollapse = needsCollapse;
auto sourceLength = std::max(initSourceLength - sourceCollapse, 2);
if (sourceLength < source.size())
source = fmt::format("{}>", source.substr(0, sourceLength - 1));
}
else {
auto sourceCollapse = needsCollapse / 2;
auto sourceLength = std::max(initSourceLength - sourceCollapse, 2);
sourceCollapse = initSourceLength - sourceLength;
auto threadCollapse = needsCollapse - senderCollapse;
auto threadLength = std::max(initThreadLength - threadCollapse, 2);
auto threadCollapse = needsCollapse - sourceCollapse;
auto threadLength = std::max(initThreadLength - threadCollapse, 2);
threadCollapse = initThreadLength - threadLength;
if (senderLength < senderName.size())
senderName = fmt::format("{}>", senderName.substr(0, senderLength - 1));
if (threadLength < threadName.size())
threadName = fmt::format("{}>", threadName.substr(0, threadLength - 1));
sourceCollapse = needsCollapse - threadCollapse;
sourceLength = std::max(initSourceLength - sourceCollapse, 2);
if (sourceLength < source.size())
source = fmt::format("{}>", source.substr(0, sourceLength - 1));
if (threadLength < thread.size())
thread = fmt::format("{}>", thread.substr(0, threadLength - 1));
}
}
res += fmt::format(" [{}] [{}]: ", threadName, senderName);
if (thread.empty())
res += fmt::format(" [{}]: ", source);
else
res += fmt::format(" [{}] [{}]: ", thread, source);
for (int32_t i = 0; i < nestCount; i++) {
res += " ";
@ -179,22 +200,10 @@ std::string Log::toString(bool logTime, int32_t nestCount) const {
return res;
}
log_clock::time_point Log::getTime() const {
return m_time;
}
Mod* Log::getSender() const {
return m_sender;
}
Severity Log::getSeverity() const {
return m_severity;
}
std::string_view Log::getContent() const {
return m_content;
}
// Logger
Logger* Logger::get() {
@ -207,49 +216,26 @@ void Logger::setup() {
}
std::mutex g_logMutex;
void Logger::push(Severity sev, Mod* mod, std::string&& content) {
if (!mod->isLoggingEnabled()) return;
Log* log = nullptr;
void Logger::push(Severity sev, std::string&& thread, std::string&& source, int32_t nestCount,
std::string&& content) {
Log* log;
{
std::lock_guard g(g_logMutex);
log = &m_logs.emplace_back(sev, thread::getName(), mod, std::move(content));
log = &m_logs.emplace_back(sev, std::move(thread), std::move(source), nestCount,
std::move(content));
}
auto nestCount = s_nestLevel * 2;
if (nestCount != 0) {
nestCount += s_nestCountOffset;
auto const logStr = log->toString();
{
std::lock_guard g(g_logMutex);
console::log(logStr, log->getSeverity());
m_logStream << logStr << std::endl;
}
auto const logStr = log->toString(true, nestCount);
console::log(logStr, log->getSeverity());
m_logStream << logStr << std::endl;
}
void Logger::pushNest() {
if (s_nestLevel == 0)
s_nestCountOffset = static_cast<int32_t>(Mod::get()->getName().size() + thread::getName().size());
s_nestLevel++;
}
void Logger::popNest() {
s_nestLevel--;
}
Nest::Nest(std::shared_ptr<Nest::Impl> impl) : m_impl(std::move(impl)) { }
Nest::Impl::Impl(int32_t nestLevel, int32_t nestCountOffset) :
m_nestLevel(nestLevel), m_nestCountOffset(nestCountOffset) { }
std::shared_ptr<Nest> Logger::saveNest() {
return std::make_shared<Nest>(std::make_shared<Nest::Impl>(s_nestLevel, s_nestCountOffset));
}
void Logger::loadNest(std::shared_ptr<Nest> const& nest) {
s_nestLevel = nest->m_impl->m_nestLevel;
s_nestCountOffset = nest->m_impl->m_nestCountOffset;
}
std::vector<Log> const& Logger::list() {
return m_logs;
}
@ -266,17 +252,20 @@ std::string geode::log::generateLogName() {
}
void log::pushNest() {
Logger::pushNest();
if (s_nestLevel == 0)
s_nestCountOffset = static_cast<int32_t>(Mod::get()->getName().size() + thread::getName().size());
s_nestLevel++;
}
void log::popNest() {
Logger::popNest();
s_nestLevel--;
}
std::shared_ptr<Nest> log::saveNest() {
return Logger::saveNest();
return std::make_shared<Nest>(std::make_shared<Nest::Impl>(s_nestLevel, s_nestCountOffset));
}
void log::loadNest(std::shared_ptr<Nest> const& nest) {
Logger::loadNest(nest);
s_nestLevel = nest->m_impl->m_nestLevel;
s_nestCountOffset = nest->m_impl->m_nestCountOffset;
}

View file

@ -9,46 +9,36 @@
namespace geode::log {
class Log final {
Mod* m_sender;
log_clock::time_point m_time;
Severity m_severity;
std::string m_thread;
std::string m_source;
int32_t m_nestCount;
std::string m_content;
public:
~Log();
Log(Severity sev, std::string&& thread, Mod* mod, std::string&& content);
Log(Severity sev, std::string&& thread, std::string&& source, int32_t nestCount,
std::string&& content);
std::string toString(bool logTime = true, int32_t nestCount = 0) const;
[[nodiscard]] std::string toString() const;
std::string_view getContent() const;
log_clock::time_point getTime() const;
Mod* getSender() const;
Severity getSeverity() const;
[[nodiscard]] Severity getSeverity() const;
};
class Logger {
private:
std::vector<Log> m_logs;
std::ofstream m_logStream;
inline static thread_local int32_t s_nestLevel;
inline static thread_local int32_t s_nestCountOffset;
Logger() {}
Logger() = default;
public:
static Logger* get();
void setup();
void push(Severity sev, Mod* mod, std::string&& content);
// why would you need this lol
// void pop(Log* log);
static void pushNest();
static void popNest();
[[nodiscard]] static std::shared_ptr<Nest> saveNest();
static void loadNest(std::shared_ptr<Nest> const& nest);
void push(Severity sev, std::string&& thread, std::string&& source, int32_t nestCount,
std::string&& content);
std::vector<Log> const& list();
void clear();

View file

@ -3,8 +3,15 @@
#include <string>
namespace geode::console {
void open();
void close();
// intended for setting up an already attached console
// for example, if the game was launched with a debugger, it'd already have a console attached
// so we can setup that console regardless of the setting
void setup();
// if the setting is on, we call tryOpenIfClosed, and if there's no console attached yet
// (e.g. from a debugger, see above), this function should create a new console
// and attach it (perhaps, by calling setup again, see windows impl for an example)
void openIfClosed();
void log(std::string const& msg, Severity severity);
void messageBox(char const* title, std::string const& info, Severity severity = Severity::Error);
}

View file

@ -17,13 +17,8 @@ namespace {
}
}
void console::open() {
return;
}
void console::close() {
return;
}
void console::setup() { }
void console::openIfClosed() { }
void console::log(std::string const& msg, Severity severity) {
__android_log_print(

View file

@ -46,7 +46,8 @@ void console::log(std::string const& msg, Severity severity) {
}
void console::open() {
void console::setup() { }
void console::openIfClosed() {
if (s_isOpen) return;
std::string outFile = "/tmp/command_output_XXXXXX";
@ -87,20 +88,10 @@ void console::open() {
s_isOpen = true;
for (auto const& log : log::Logger::get()->list()) {
console::log(log.toString(true), log.getSeverity());
console::log(log.toString(), log.getSeverity());
}
}
void console::close() {
if (s_isOpen) {
::close(s_platformData.logFd);
unlink(s_platformData.logFile.c_str());
unlink(s_platformData.scriptFile.c_str());
}
s_isOpen = false;
}
CFDataRef msgPortCallback(CFMessagePortRef port, SInt32 messageID, CFDataRef data, void* info) {
if (!CFDataGetLength(data)) return NULL;

View file

@ -1,54 +1,171 @@
#include <loader/console.hpp>
#include <loader/LogImpl.hpp>
#include <iostream>
#include <io.h>
using namespace geode::prelude;
bool s_isOpen = false;
bool s_hasAnsiColorSupport = false;
HANDLE s_outHandle = nullptr;
bool s_useEscapeCodes = false;
void console::open() {
if (s_isOpen) return;
if (AllocConsole() == 0) return;
void setupConsole(bool forceUseEscapeCodes = false) {
SetConsoleCP(CP_UTF8);
// redirect console output
freopen_s(reinterpret_cast<FILE**>(stdout), "CONOUT$", "w", stdout);
freopen_s(reinterpret_cast<FILE**>(stdin), "CONIN$", "r", stdin);
// Set output mode to handle ansi color sequences
auto handleStdout = GetStdHandle(STD_OUTPUT_HANDLE);
// set output mode to handle ansi color sequences
DWORD consoleMode = 0;
if (GetConsoleMode(handleStdout, &consoleMode)) {
consoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
if (SetConsoleMode(handleStdout, consoleMode)) {
s_hasAnsiColorSupport = true;
s_useEscapeCodes = forceUseEscapeCodes || GetConsoleMode(s_outHandle, &consoleMode) &&
SetConsoleMode(s_outHandle, consoleMode | ENABLE_PROCESSED_OUTPUT |
ENABLE_VIRTUAL_TERMINAL_PROCESSING);
if (s_useEscapeCodes && !forceUseEscapeCodes) {
// test if the console *actually* supports escape codes (thanks wine)
s_useEscapeCodes = false;
DWORD written;
CONSOLE_SCREEN_BUFFER_INFO preInfo;
CONSOLE_SCREEN_BUFFER_INFO postInfo;
if (GetConsoleScreenBufferInfo(s_outHandle, &preInfo) &&
WriteConsoleA(s_outHandle, "\x1b[0m", 4, &written, nullptr) &&
GetConsoleScreenBufferInfo(s_outHandle, &postInfo)) {
s_useEscapeCodes = preInfo.dwCursorPosition.X == postInfo.dwCursorPosition.X &&
preInfo.dwCursorPosition.Y == postInfo.dwCursorPosition.Y;
SetConsoleCursorPosition(s_outHandle, preInfo.dwCursorPosition);
}
}
s_isOpen = true;
for (auto const& log : log::Logger::get()->list()) {
console::log(log.toString(true), log.getSeverity());
console::log(log.toString(), log.getSeverity());
}
}
void console::close() {
if (!s_isOpen) return;
struct stdData {
OVERLAPPED m_overlap{};
std::string const& m_name;
const Severity m_sev;
std::string& m_cur;
char* m_buf;
stdData(std::string const& name, const Severity sev, std::string& cur, char* buf) :
m_name(name), m_sev(sev), m_cur(cur), m_buf(buf) { }
};
void WINAPI CompletedReadRoutine(DWORD error, DWORD read, LPOVERLAPPED overlap) {
auto* o = reinterpret_cast<stdData*>(overlap);
for (auto i = 0; i < read && !error; i++) {
if (o->m_buf[i] != '\n') {
if (o->m_buf[i] != '\r')
o->m_cur += o->m_buf[i];
continue;
}
log::Logger::get()->push(o->m_sev, "", std::string(o->m_name), 0, std::string(o->m_cur));
o->m_cur.clear();
}
delete o;
}
fclose(stdin);
fclose(stdout);
FreeConsole();
bool redirectStd(FILE* which, std::string const& name, const Severity sev) {
auto pipeName = fmt::format(R"(\\.\pipe\geode-{}-{})", name, GetCurrentProcessId());
auto pipe = CreateNamedPipeA(
pipeName.c_str(),
PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
PIPE_REJECT_REMOTE_CLIENTS,
1, 0, 1024, 0, nullptr
);
if (!pipe) {
log::warn("Failed to create pipe, {} will be unavailable", name);
return false;
}
std::thread([pipe, name, sev]() {
thread::setName(fmt::format("{} Read Thread", name));
auto event = CreateEventA(nullptr, false, false, nullptr);
std::string cur;
while (true) {
char buf[1024];
auto* data = new stdData(name, sev, cur, buf);
data->m_overlap.hEvent = event;
ReadFileEx(pipe, buf, 1024, &data->m_overlap, &CompletedReadRoutine);
auto res = WaitForSingleObjectEx(event, INFINITE, true);
if (!res)
continue;
}
}).detach();
FILE* yum;
if (freopen_s(&yum, pipeName.c_str(), "w", which)) {
log::warn("Failed to reopen file, {} will be unavailable", name);
return false;
}
return true;
}
s_isOpen = false;
void console::setup() {
// if the game launched from a console or with a console already attached,
// this is where we find that out and save its handle
s_outHandle = GetStdHandle(STD_OUTPUT_HANDLE);
if (!s_outHandle && AttachConsole(ATTACH_PARENT_PROCESS)) {
s_outHandle = GetStdHandle(STD_OUTPUT_HANDLE);
}
if (s_outHandle) {
std::string path;
DWORD dummy;
// use GetConsoleMode to check if the handle is a console
if (!GetConsoleMode(s_outHandle, &dummy)) {
// explicitly ignore some stupid handles
char buf[MAX_PATH + 1];
auto count = GetFinalPathNameByHandleA(s_outHandle, buf, MAX_PATH + 1,
FILE_NAME_OPENED | VOLUME_NAME_NT);
if (count != 0) {
path = std::string(buf, count - 1);
}
// count == 0 => not a console and not a file, assume it's closed
// wine does something weird with /dev/null? not sure tbh but it's definitely up to no good
if (count == 0 || path.ends_with("\\dev\\null")) {
s_outHandle = nullptr;
CloseHandle(GetStdHandle(STD_OUTPUT_HANDLE));
CloseHandle(GetStdHandle(STD_INPUT_HANDLE));
CloseHandle(GetStdHandle(STD_ERROR_HANDLE));
FreeConsole();
SetStdHandle(STD_OUTPUT_HANDLE, nullptr);
SetStdHandle(STD_INPUT_HANDLE, nullptr);
SetStdHandle(STD_ERROR_HANDLE, nullptr);
}
}
// clion console supports escape codes but we can't query that because it's a named pipe
setupConsole(string::contains(path, "cidr-"));
}
auto oldStdout = _dup(_fileno(stdout));
redirectStd(stdout, "stdout", Severity::Info);
redirectStd(stderr, "stderr", Severity::Debug);
// re-open the file from the handle we just stole..
if (oldStdout >= 0) {
_fdopen(oldStdout, "w");
s_outHandle = reinterpret_cast<HANDLE>(_get_osfhandle(oldStdout));
}
}
void console::openIfClosed() {
if (s_outHandle)
return;
AllocConsole();
s_outHandle = GetStdHandle(STD_OUTPUT_HANDLE);
// reopen conin$/conout$ if they're closed
if (!s_outHandle) {
s_outHandle = CreateFileA("CONOUT$", GENERIC_WRITE, 0, nullptr, 0, 0, nullptr);
SetStdHandle(STD_OUTPUT_HANDLE, s_outHandle);
SetStdHandle(STD_INPUT_HANDLE, CreateFileA("CONIN$", GENERIC_READ, 0, nullptr, 0, 0, nullptr));
SetStdHandle(STD_ERROR_HANDLE, s_outHandle);
}
setupConsole();
}
void console::log(std::string const& msg, Severity severity) {
if (!s_isOpen)
if (!s_outHandle)
return;
DWORD written;
if (!s_hasAnsiColorSupport) {
std::cout << msg << "\n" << std::flush;
if (!s_useEscapeCodes || msg.size() <= 14) {
WriteFile(s_outHandle, (msg + "\n").c_str(), msg.size() + 1, &written, nullptr);
return;
}
@ -78,11 +195,11 @@ void console::log(std::string const& msg, Severity severity) {
auto const colorStr = fmt::format("\x1b[38;5;{}m", color);
auto const color2Str = color2 == -1 ? "\x1b[0m" : fmt::format("\x1b[38;5;{}m", color2);
auto const newMsg = fmt::format(
"{}{}{}{}\x1b[0m",
"{}{}{}{}\x1b[0m\n",
colorStr, msg.substr(0, 14), color2Str, msg.substr(14)
);
std::cout << newMsg << "\n" << std::flush;
WriteFile(s_outHandle, newMsg.c_str(), newMsg.size(), &written, nullptr);
}
void console::messageBox(char const* title, std::string const& info, Severity severity) {