diff --git a/.gitmodules b/.gitmodules index fbec754c6..ef8c708d7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -43,4 +43,4 @@ url = https://github.com/google/googletest.git [submodule "core/deps/asio"] path = core/deps/asio - url = https://github.com/chriskohlhoff/asio.git + url = https://github.com/flyinghead/asio.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 8dd949e61..40713fb28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -146,6 +146,13 @@ if(NINTENDO_SWITCH) if(USE_GLES) target_compile_definitions(${PROJECT_NAME} PRIVATE GLES) endif() + # asio + target_compile_definitions(${PROJECT_NAME} PRIVATE + ASIO_DISABLE_LOCAL_SOCKETS + ASIO_DISABLE_SERIAL_PORT + ESHUTDOWN=110 + SA_RESTART=0 + SA_NOCLDWAIT=0) elseif(LIBRETRO) add_library(${PROJECT_NAME} SHARED core/emulator.cpp) diff --git a/core/cfg/option.cpp b/core/cfg/option.cpp index eb06c2509..658e18c90 100644 --- a/core/cfg/option.cpp +++ b/core/cfg/option.cpp @@ -135,6 +135,7 @@ Option DiscordPresence("DiscordPresence", true); #if defined(__ANDROID__) && !defined(LIBRETRO) Option UseSafFilePicker("UseSafFilePicker", true); #endif +OptionString LogServer("LogServer", "", "log"); // Profiler Option ProfilerEnabled("Profiler.Enabled"); diff --git a/core/cfg/option.h b/core/cfg/option.h index c3ba52786..e0d94a63d 100644 --- a/core/cfg/option.h +++ b/core/cfg/option.h @@ -495,6 +495,7 @@ extern Option DiscordPresence; #if defined(__ANDROID__) && !defined(LIBRETRO) extern Option UseSafFilePicker; #endif +extern OptionString LogServer; // Profiling extern Option ProfilerEnabled; diff --git a/core/deps/asio b/core/deps/asio index 03ae834ed..d3402006e 160000 --- a/core/deps/asio +++ b/core/deps/asio @@ -1 +1 @@ -Subproject commit 03ae834edbace31a96157b89bf50e5ee464e5ef9 +Subproject commit d3402006e84efb6114ff93e4f2b8508412ed80d5 diff --git a/core/log/LogManager.cpp b/core/log/LogManager.cpp index dacaac706..881c9724a 100644 --- a/core/log/LogManager.cpp +++ b/core/log/LogManager.cpp @@ -15,6 +15,7 @@ #include "ConsoleListener.h" #include "InMemoryListener.h" +#include "NetworkListener.h" #include "Log.h" #include "StringUtil.h" #include "cfg/cfg.h" @@ -118,8 +119,6 @@ LogManager::LogManager() m_log[LogTypes::SAVESTATE] = {"SAVESTATE", "Save States"}; m_log[LogTypes::SH4] = {"SH4", "SH4 Modules"}; - RegisterListener(LogListener::CONSOLE_LISTENER, new ConsoleListener()); - // Set up log listeners int verbosity = cfgLoadInt("log", "Verbosity", LogTypes::LDEBUG); @@ -128,47 +127,56 @@ LogManager::LogManager() verbosity = 1; if (verbosity > MAX_LOGLEVEL) verbosity = MAX_LOGLEVEL; - SetLogLevel(static_cast(verbosity)); - if (cfgLoadBool("log", "LogToFile", false)) - { -#if defined(__ANDROID__) || defined(__APPLE__) || defined(TARGET_UWP) - std::string logPath = get_writable_data_path("flycast.log"); -#else - std::string logPath = "flycast.log"; -#endif - FileLogListener *listener = new FileLogListener(logPath); - if (!listener->IsValid()) - { - const char *home = nowide::getenv("HOME"); - if (home != nullptr) - { - delete listener; - listener = new FileLogListener(home + ("/" + logPath)); - } - } - RegisterListener(LogListener::FILE_LISTENER, listener); - EnableListener(LogListener::FILE_LISTENER, true); - } + + RegisterListener(LogListener::CONSOLE_LISTENER, new ConsoleListener()); EnableListener(LogListener::CONSOLE_LISTENER, cfgLoadBool("log", "LogToConsole", true)); - // EnableListener(LogListener::LOG_WINDOW_LISTENER, Config::Get(LOGGER_WRITE_TO_WINDOW)); RegisterListener(LogListener::IN_MEMORY_LISTENER, new InMemoryListener()); EnableListener(LogListener::IN_MEMORY_LISTENER, true); for (LogContainer& container : m_log) - { container.m_enable = cfgLoadBool("log", container.m_short_name, true); - } m_path_cutoff_point = DeterminePathCutOffPoint(); + + UpdateConfig(); } -LogManager::~LogManager() +void LogManager::UpdateConfig() { - // The log window listener pointer is owned by the GUI code. - delete m_listeners[LogListener::CONSOLE_LISTENER]; - delete m_listeners[LogListener::FILE_LISTENER]; - delete m_listeners[LogListener::IN_MEMORY_LISTENER]; + bool logToFile = cfgLoadBool("log", "LogToFile", false); + if (logToFile != IsListenerEnabled(LogListener::FILE_LISTENER)) + { + if (!logToFile) { + m_listeners[LogListener::FILE_LISTENER].reset(); + } + else { +#if defined(__ANDROID__) || defined(__APPLE__) || defined(TARGET_UWP) + std::string logPath = get_writable_data_path("flycast.log"); +#else + std::string logPath = "flycast.log"; +#endif + FileLogListener *listener = new FileLogListener(logPath); + if (!listener->IsValid()) + { + const char *home = nowide::getenv("HOME"); + if (home != nullptr) + { + delete listener; + listener = new FileLogListener(home + ("/" + logPath)); + } + } + RegisterListener(LogListener::FILE_LISTENER, listener); + } + EnableListener(LogListener::FILE_LISTENER, logToFile); + } + std::string newLogServer = cfgLoadStr("log", "LogServer", ""); + if (logServer != newLogServer) + { + logServer = newLogServer; + RegisterListener(LogListener::NETWORK_LISTENER, new NetworkListener(logServer)); + EnableListener(LogListener::NETWORK_LISTENER, !logServer.empty()); + } } // Return the current time formatted as Minutes:Seconds:Milliseconds @@ -241,7 +249,7 @@ const char* LogManager::GetFullName(LogTypes::LOG_TYPE type) const void LogManager::RegisterListener(LogListener::LISTENER id, LogListener* listener) { - m_listeners[id] = listener; + m_listeners[id] = std::unique_ptr(listener); } void LogManager::EnableListener(LogListener::LISTENER id, bool enable) diff --git a/core/log/LogManager.h b/core/log/LogManager.h index 0cbd92255..5cafd3628 100644 --- a/core/log/LogManager.h +++ b/core/log/LogManager.h @@ -6,6 +6,7 @@ #include #include +#include #include "BitSet.h" #include "Log.h" @@ -23,6 +24,7 @@ public: CONSOLE_LISTENER, LOG_WINDOW_LISTENER, IN_MEMORY_LISTENER, + NETWORK_LISTENER, NUMBER_OF_LISTENERS // Must be last }; @@ -52,6 +54,7 @@ public: void RegisterListener(LogListener::LISTENER id, LogListener* listener); void EnableListener(LogListener::LISTENER id, bool enable); bool IsListenerEnabled(LogListener::LISTENER id) const; + void UpdateConfig(); private: struct LogContainer @@ -66,7 +69,6 @@ private: }; LogManager(); - ~LogManager(); LogManager(const LogManager&) = delete; LogManager& operator=(const LogManager&) = delete; @@ -75,7 +77,8 @@ private: LogTypes::LOG_LEVELS m_level; std::array m_log{}; - std::array m_listeners{}; + std::array, LogListener::NUMBER_OF_LISTENERS> m_listeners{}; BitSet32 m_listener_ids; size_t m_path_cutoff_point = 0; + std::string logServer; }; diff --git a/core/log/NetworkListener.h b/core/log/NetworkListener.h new file mode 100644 index 000000000..069adcde1 --- /dev/null +++ b/core/log/NetworkListener.h @@ -0,0 +1,88 @@ +/* + Copyright 2025 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . + */ +#pragma once +#include +#include +#include "LogManager.h" + +class NetworkListener : public LogListener +{ +public: + NetworkListener(const std::string& dest) + { + if (dest.empty()) + return; + std::string host; + std::string port("31667"); + auto colon = dest.find(':'); + if (colon != std::string::npos) { + port = dest.substr(colon + 1); + host = dest.substr(0, colon); + } + else { + host = dest; + } + asio::ip::udp::resolver resolver(io_context); + asio::error_code ec; + auto it = resolver.resolve(host, port, ec); + if (ec || it.empty()) { + fprintf(stderr, "Unknown hostname %s: %s\n", host.c_str(), ec.message().c_str()); + } + else + { + asio::ip::udp::endpoint endpoint = *it.begin(); + socket.connect(endpoint, ec); + if (ec) + fprintf(stderr, "Connect to log server failed: %s\n", ec.message().c_str()); + } + } + + void Log(LogTypes::LOG_LEVELS level, const char* msg) override + { + if (!socket.is_open()) + return; + const char *reset_attr = "\x1b[0m"; + std::string color_attr; + + switch (level) + { + case LogTypes::LOG_LEVELS::LNOTICE: + // light green + color_attr = "\x1b[92m"; + break; + case LogTypes::LOG_LEVELS::LERROR: + // light red + color_attr = "\x1b[91m"; + break; + case LogTypes::LOG_LEVELS::LWARNING: + // light yellow + color_attr = "\x1b[93m"; + break; + default: + break; + } + std::string str = color_attr + msg + reset_attr; + asio::error_code ec; + socket.send(asio::buffer(str), 0, ec); + } + +private: + asio::io_context io_context; + asio::ip::udp::socket socket { io_context }; +}; diff --git a/core/nullDC.cpp b/core/nullDC.cpp index 8e60388a3..b86fbd7b1 100644 --- a/core/nullDC.cpp +++ b/core/nullDC.cpp @@ -110,6 +110,7 @@ void SaveSettings() void SaveAndroidSettings(); SaveAndroidSettings(); #endif + LogManager::GetInstance()->UpdateConfig(); } void flycast_term() diff --git a/core/ui/gui.cpp b/core/ui/gui.cpp index 9f68259f2..2279f4138 100644 --- a/core/ui/gui.cpp +++ b/core/ui/gui.cpp @@ -1609,6 +1609,8 @@ static void contentpath_warning_popup() } } +#if !defined(NDEBUG) || defined(DEBUGFAST) || FC_PROFILER + static void gui_debug_tab() { header("Logging"); @@ -1640,6 +1642,9 @@ static void gui_debug_tab() } ImGui::EndCombo(); } + ImGui::InputText("Log Server", &config::LogServer.get(), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); + ImGui::SameLine(); + ShowHelpMarker("Log to this hostname[:port] with UDP. Default port is 31667."); } #if FC_PROFILER ImGui::Spacing(); @@ -1663,6 +1668,7 @@ static void gui_debug_tab() } #endif } +#endif static void addContentPathCallback(const std::string& path) { @@ -2839,14 +2845,8 @@ static void gui_settings_advanced() "Dump all textures into data/texdump/"); bool logToFile = cfgLoadBool("log", "LogToFile", false); - bool newLogToFile = logToFile; - ImGui::Checkbox("Log to File", &newLogToFile); - if (logToFile != newLogToFile) - { - cfgSaveBool("log", "LogToFile", newLogToFile); - LogManager::Shutdown(); - LogManager::Init(); - } + if (ImGui::Checkbox("Log to File", &logToFile)) + cfgSaveBool("log", "LogToFile", logToFile); ImGui::SameLine(); ShowHelpMarker("Log debug information to flycast.log"); #ifdef SENTRY_UPLOAD diff --git a/shell/switch/stubs.c b/shell/switch/stubs.c index 7b096ecf5..b4d28f5fd 100644 --- a/shell/switch/stubs.c +++ b/shell/switch/stubs.c @@ -1,5 +1,8 @@ #include #include +#include +#include +#include // Seems to be missing in newlib, dumb stub (file permissions is not a thing on fat32 anyways) mode_t umask(mode_t mask) @@ -7,3 +10,32 @@ mode_t umask(mode_t mask) return mask; } +int pause() +{ + sleep(0xffffffff); + return -1; +} + +// FIXME always failing stub +int pthread_sigmask(int how, const sigset_t *set, sigset_t *oset) +{ + switch (how) + { + case SIG_BLOCK: + case SIG_UNBLOCK: + case SIG_SETMASK: + break; + default: + errno = EINVAL; + return -1; + } + errno = ENOSYS; + return -1; +} + +// Map an interface index into its name. +char *if_indextoname(unsigned ifindex, char *ifname) +{ + errno = ENXIO; + return NULL; +} diff --git a/shell/switch/sys/uio.h b/shell/switch/sys/uio.h new file mode 100644 index 000000000..e8b7c9604 --- /dev/null +++ b/shell/switch/sys/uio.h @@ -0,0 +1,28 @@ +#ifndef SYS_UIO_H_ +#define SYS_UIO_H_ +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Read data from file descriptor FD, and put the result in the + buffers described by IOVEC, which is a vector of COUNT 'struct iovec's. + The buffers are filled in the order specified. + Operates just like 'read' (see ) except that data are + put in IOVEC instead of a contiguous buffer. */ +extern ssize_t readv (int __fd, const struct iovec *__iovec, int __count); + +/* Write data pointed by the buffers described by IOVEC, which + is a vector of COUNT 'struct iovec's, to file descriptor FD. + The data is written in the order specified. + Operates just like 'write' (see ) except that the data + are taken from IOVEC instead of a contiguous buffer. */ +extern ssize_t writev (int __fd, const struct iovec *__iovec, int __count); + +#ifdef __cplusplus +} +#endif + +#endif /* SYS_UIO_H_ */ diff --git a/shell/switch/sys/un.h b/shell/switch/sys/un.h new file mode 100644 index 000000000..b86217d40 --- /dev/null +++ b/shell/switch/sys/un.h @@ -0,0 +1,50 @@ +/* Copyright (C) 1991-2020 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _SYS_UN_H +#define _SYS_UN_H 1 + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Structure describing the address of an AF_LOCAL (aka AF_UNIX) socket. */ +struct sockaddr_un +{ + sa_family_t sun_family; + char sun_path[108]; /* Path name. */ +}; + +/* Should be defined in sockets.h */ +struct ipv6_mreq +{ + struct in6_addr ipv6mr_multiaddr; + unsigned int ipv6mr_interface; +}; + +/* Should be declared in net/if.h */ +char* if_indextoname(unsigned int, char*); +unsigned int if_nametoindex(const char*); + +#ifdef __cplusplus +} +#endif + +#endif /* sys/un.h */