From 93a30cd8d9784365c96dcd1b7bf09a64b5030808 Mon Sep 17 00:00:00 2001 From: Carsten Teibes Date: Sun, 31 Dec 2023 01:31:55 +0100 Subject: [PATCH 1/4] Wii: use custom audio backend Wii: support log messages in dolphin audio_generic: fix source permissions --- CMakeLists.txt | 4 +- src/audio_decoder_midi.cpp | 0 src/audio_generic.cpp | 0 src/audio_generic.h | 0 src/platform/sdl/sdl_audio.cpp | 5 +- src/platform/sdl/sdl_ui.cpp | 15 ++-- src/platform/wii/audio.cpp | 122 +++++++++++++++++++++++++++++++++ src/platform/wii/audio.h | 32 +++++++++ src/platform/wii/main.cpp | 90 ++++++++++-------------- src/platform/wii/main.h | 6 -- 10 files changed, 201 insertions(+), 73 deletions(-) mode change 100755 => 100644 src/audio_decoder_midi.cpp mode change 100755 => 100644 src/audio_generic.cpp mode change 100755 => 100644 src/audio_generic.h create mode 100644 src/platform/wii/audio.cpp create mode 100644 src/platform/wii/audio.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 89c2404311..b76ba04ad7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -671,11 +671,11 @@ elseif(${PLAYER_TARGET_PLATFORM} STREQUAL "wii") target_compile_definitions(${PROJECT_NAME} PUBLIC USE_SDL=1 PLAYER_NINTENDO) target_include_directories(${PROJECT_NAME} PUBLIC ${SDL_INCLUDE_DIR}) target_sources(${PROJECT_NAME} PRIVATE + src/platform/wii/audio.cpp + src/platform/wii/audio.h src/platform/wii/clock.cpp src/platform/wii/clock.h src/platform/wii/input_buttons.cpp - src/platform/sdl/sdl_audio.cpp - src/platform/sdl/sdl_audio.h src/platform/sdl/axis.h src/platform/sdl/sdl_ui.cpp src/platform/sdl/sdl_ui.h) diff --git a/src/audio_decoder_midi.cpp b/src/audio_decoder_midi.cpp old mode 100755 new mode 100644 diff --git a/src/audio_generic.cpp b/src/audio_generic.cpp old mode 100755 new mode 100644 diff --git a/src/audio_generic.h b/src/audio_generic.h old mode 100755 new mode 100644 diff --git a/src/platform/sdl/sdl_audio.cpp b/src/platform/sdl/sdl_audio.cpp index adf56dfb46..063fcfc498 100644 --- a/src/platform/sdl/sdl_audio.cpp +++ b/src/platform/sdl/sdl_audio.cpp @@ -78,10 +78,7 @@ SdlAudio::SdlAudio(const Game_ConfigAudio& cfg) : return; } -#ifdef __wii__ - // Wii's DSP works at 32kHz natively - const int frequency = 32000; -#elif defined(EMSCRIPTEN) +#ifdef EMSCRIPTEN // Get preferred sample rate from Browser (-> OS) const int frequency = EM_ASM_INT_V({ var context; diff --git a/src/platform/sdl/sdl_ui.cpp b/src/platform/sdl/sdl_ui.cpp index 8f3ec03d91..e11d02292d 100644 --- a/src/platform/sdl/sdl_ui.cpp +++ b/src/platform/sdl/sdl_ui.cpp @@ -33,7 +33,11 @@ #endif #ifdef SUPPORT_AUDIO -# include "sdl_audio.h" +# ifdef __wii__ +# include "platform/wii/audio.h" +# else +# include "sdl_audio.h" +# endif AudioInterface& SdlUi::GetAudio() { return *audio_; @@ -115,11 +119,6 @@ SdlUi::SdlUi(long width, long height, const Game_Config& cfg) : BaseUi(cfg) format.b.mask, format.a.mask); -#ifdef __wii__ - // Eliminate debug spew in on-screen console - Wii::SetConsole(); -#endif - SetTitle(GAME_TITLE); #if (defined(USE_JOYSTICK) && defined(SUPPORT_JOYSTICK)) || (defined(USE_JOYSTICK_AXIS) && defined(SUPPORT_JOYSTICK_AXIS)) @@ -172,7 +171,11 @@ SdlUi::SdlUi(long width, long height, const Game_Config& cfg) : BaseUi(cfg) #ifdef SUPPORT_AUDIO if (!Player::no_audio_flag) { +# ifdef __wii__ + audio_ = std::make_unique(cfg.audio); +# else audio_ = std::make_unique(cfg.audio); +# endif return; } #endif diff --git a/src/platform/wii/audio.cpp b/src/platform/wii/audio.cpp new file mode 100644 index 0000000000..0c38c13653 --- /dev/null +++ b/src/platform/wii/audio.cpp @@ -0,0 +1,122 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player 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 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player 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 EasyRPG Player. If not, see . + */ + +#include "system.h" + +#ifdef SUPPORT_AUDIO + +#include +#include +#include "audio.h" +#include "output.h" + +namespace { + constexpr int NUMBUFS = 2; + constexpr int SNDBUFFERSIZE = 5760; + constexpr int THREADPRIORITY = 80; + AESNDPB *voice = nullptr; + uint8_t buffer[NUMBUFS][SNDBUFFERSIZE]; + int cur_buf = 0; + constexpr int AUDIOSTACK = 16384*2; + lwpq_t audioqueue; + lwp_t athread = LWP_THREAD_NULL; + uint8_t astack[AUDIOSTACK]; + bool stopaudio = false; + mutex_t audio_mutex; + WiiAudio* instance = nullptr; +} + +static void VoiceStreamCallback(AESNDPB *pb, u32 state) { + if (stopaudio || state != VOICE_STATE_STREAM) return; + + // play current buffer, switch buffers, then let thread decode more + instance->LockMutex(); + AESND_SetVoiceBuffer(pb, buffer[cur_buf], SNDBUFFERSIZE); + cur_buf = (cur_buf + 1) % NUMBUFS; + LWP_ThreadSignal(audioqueue); + instance->UnlockMutex(); +} + +static void *AudioThread (void *) { + while (!stopaudio) { + // clear old data + memset(buffer[cur_buf], 0, SNDBUFFERSIZE); + + instance->LockMutex(); + instance->Decode(buffer[cur_buf], SNDBUFFERSIZE); + instance->UnlockMutex(); + + // make sure data is in main memory + DCFlushRange(buffer[cur_buf], SNDBUFFERSIZE); + + LWP_ThreadSleep(audioqueue); + } + return nullptr; +} + +WiiAudio::WiiAudio(const Game_ConfigAudio& cfg) : + GenericAudio(cfg) +{ + instance = this; + + // setup DSP + AESND_Init(); + voice = AESND_AllocateVoice(VoiceStreamCallback); + if (voice == nullptr) { + Output::Warning("Couldn't open audio! Failed to allocate voice!"); + return; + } + + int freq = 44100; + SetFormat(freq, AudioDecoder::Format::S16, 2); + AESND_SetVoiceFormat(voice, VOICE_STEREO16); + AESND_SetVoiceFrequency(voice, freq); + AESND_SetVoiceStream(voice, true); + AESND_SetVoiceStop(voice, false); + + // setup thread and queue + LWP_MutexInit(&audio_mutex, 0); + LWP_InitQueue(&audioqueue); + LWP_CreateThread(&athread, AudioThread, nullptr, astack, AUDIOSTACK, THREADPRIORITY); + stopaudio = false; +} + +WiiAudio::~WiiAudio() { + // stop DSP + AESND_SetVoiceStop(voice, true); + AESND_FreeVoice(voice); + voice = nullptr; + AESND_Reset(); + + // stop and clear queue/thread + stopaudio = true; + LWP_ThreadSignal(audioqueue); + LWP_JoinThread(athread, nullptr); + LWP_CloseQueue(audioqueue); + athread = LWP_THREAD_NULL; + LWP_MutexDestroy(audio_mutex); +} + +void WiiAudio::LockMutex() const { + LWP_MutexLock(audio_mutex); +} + +void WiiAudio::UnlockMutex() const { + LWP_MutexUnlock(audio_mutex); +} + +#endif diff --git a/src/platform/wii/audio.h b/src/platform/wii/audio.h new file mode 100644 index 0000000000..55934fe960 --- /dev/null +++ b/src/platform/wii/audio.h @@ -0,0 +1,32 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player 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 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player 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 EasyRPG Player. If not, see . + */ + +#ifndef EP_PLATFORM_WII_AUDIO_H +#define EP_PLATFORM_WII_AUDIO_H + +#include "audio_generic.h" + +class WiiAudio : public GenericAudio { +public: + WiiAudio(const Game_ConfigAudio& cfg); + ~WiiAudio(); + + void LockMutex() const override; + void UnlockMutex() const override; +}; + +#endif diff --git a/src/platform/wii/main.cpp b/src/platform/wii/main.cpp index 0c200c3c44..19192f3871 100644 --- a/src/platform/wii/main.cpp +++ b/src/platform/wii/main.cpp @@ -32,9 +32,12 @@ # define main SDL_main #endif -// USBGecko Debugging -bool usbgecko = false; -mutex_t usbgecko_mutex = 0; +namespace { + // USBGecko Debugging + bool usbgecko = false; + + bool is_emu = false; +} // in sdl-wii extern "C" void OGC_ChangeSquare(int xscale, int yscale, int xshift, int yshift); @@ -44,10 +47,15 @@ static void GekkoResetCallback(u32 /* irq */ , void* /* ctx */) { } extern "C" int main(int argc, char* argv[]) { + // Enable USBGecko output + CON_EnableGecko(CARD_SLOTB, true); + usbgecko = usb_isgeckoalive(CARD_SLOTB); + + // cmdline std::vector args(argv, argv + argc); // dolphin - bool is_emu = argc == 0; + is_emu = argc == 0; if(is_emu) { // set arbitrary application path args.push_back("/easyrpg-player"); @@ -58,15 +66,20 @@ extern "C" int main(int argc, char* argv[]) { // Eliminate overscan / add 5% borders OGC_ChangeSquare(304, 228, 0, 0); - // Check if a game directory was provided - if (std::none_of(args.cbegin(), args.cend(), - [](const std::string& a) { return a == "--project-path"; })) { - - // Working directory not correctly handled, provide it manually - char working_dir[256]; - getcwd(working_dir, 255); - args.push_back("--project-path"); - args.push_back(working_dir); + if (is_emu || strchr(argv[0], '/') == 0) { + Output::Debug("USBGecko/Dolphin mode, changing dir to default."); + chdir("/apps/easyrpg"); + } else { + // Check if a game directory was provided + if (std::none_of(args.cbegin(), args.cend(), + [](const std::string& a) { return a == "--project-path"; })) { + + // Working directory not correctly handled, provide it manually + char working_dir[256]; + getcwd(working_dir, 255); + args.push_back("--project-path"); + args.push_back(working_dir); + } } // Run Player @@ -76,50 +89,17 @@ extern "C" int main(int argc, char* argv[]) { return EXIT_SUCCESS; } -static ssize_t __usbgecko_write(struct _reent * /* r */, void* /* fd */, const char *ptr, size_t len) { - uint32_t level; - - if (!ptr || !len || !usbgecko) - return 0; - - LWP_MutexLock(usbgecko_mutex); - level = IRQ_Disable(); - usb_sendbuffer(1, ptr, len); - IRQ_Restore(level); - LWP_MutexUnlock(usbgecko_mutex); - - return len; -} - -const devoptab_t dotab_geckoout = { - "stdout", 0, NULL, NULL, __usbgecko_write, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL -}; - -extern const devoptab_t dotab_stdnull; - -void Wii::SetConsole() { - LWP_MutexInit(&usbgecko_mutex, false); - usbgecko = usb_isgeckoalive(1); - - if (usbgecko) { - devoptab_list[STD_OUT] = &dotab_geckoout; - devoptab_list[STD_ERR] = &dotab_geckoout; - } else { - devoptab_list[STD_OUT] = &dotab_stdnull; - devoptab_list[STD_ERR] = &dotab_stdnull; - } -} - bool Wii::LogMessage(const std::string &message) { - if (usbgecko) return false; + if (is_emu) { + std::string m = std::string("[" GAME_TITLE "] ") + message + "\n"; - std::string m = std::string("[" GAME_TITLE "] ") + message + "\n"; + // Write to OSReport uart in dolphin emulator + SYS_Report("%s", m.c_str()); - // HLE in dolphin emulator - printf("%s", m.c_str()); + // additional usbgecko output not needed + return true; + } - // additional usbgecko output not needed - return true; + // let Output class write to USBGecko or eat message if not present + return !usbgecko; } diff --git a/src/platform/wii/main.h b/src/platform/wii/main.h index dd29e244a1..f40929b73d 100644 --- a/src/platform/wii/main.h +++ b/src/platform/wii/main.h @@ -19,12 +19,6 @@ #define EP_PLATFORM_WII_MAIN_H namespace Wii { - /** - * Helper function to disable the console on Wii - * and redirect to USB Gekko, if present. - */ - void SetConsole(); - bool LogMessage(const std::string &message); }; From 232cfd12069e1c7ea95906a9be1b2c83a000ac6f Mon Sep 17 00:00:00 2001 From: Carsten Teibes Date: Sun, 31 Dec 2023 03:43:05 +0100 Subject: [PATCH 2/4] Rewrite custom logging (again) Use a callback function to output log strings on certain platforms. Clean up output.cpp More verbose initial startup logging on Homebrew targets. Remove rang dependency, coloring is now done by fmt. --- CMakeLists.txt | 5 +- Makefile.am | 4 +- README.md | 2 - src/baseui.h | 8 - src/external/rang.hpp | 503 ------------------------------- src/output.cpp | 135 +++------ src/output.h | 17 +- src/platform/3ds/main.cpp | 56 +++- src/platform/3ds/ui.cpp | 14 - src/platform/3ds/ui.h | 1 - src/platform/emscripten/main.cpp | 27 ++ src/platform/psvita/main.cpp | 14 + src/platform/psvita/ui.cpp | 9 - src/platform/psvita/ui.h | 1 - src/platform/sdl/main.cpp | 32 ++ src/platform/sdl/sdl2_ui.cpp | 11 - src/platform/sdl/sdl2_ui.h | 1 - src/platform/sdl/sdl_ui.cpp | 13 - src/platform/sdl/sdl_ui.h | 1 - src/platform/switch/main.cpp | 29 +- src/platform/switch/ui.cpp | 13 - src/platform/switch/ui.h | 1 - src/platform/wii/main.cpp | 91 +++--- src/platform/wii/main.h | 25 -- 24 files changed, 268 insertions(+), 745 deletions(-) delete mode 100644 src/external/rang.hpp delete mode 100644 src/platform/wii/main.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b76ba04ad7..2bc09c406f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -458,7 +458,6 @@ add_library(${PROJECT_NAME} OBJECT src/window_teleport.h src/window_varlist.cpp src/window_varlist.h - src/external/rang.hpp ) # These are actually unused when building in CMake @@ -1212,9 +1211,7 @@ elseif(${PLAYER_TARGET_PLATFORM} MATCHES "^(psvita|3ds|switch|wii)$" OR NINTENDO install(FILES ${CMAKE_CURRENT_BINARY_DIR}/easyrpg-player.nro DESTINATION easyrpg-player COMPONENT switch) elseif(NINTENDO_WII) - add_executable(easyrpg-player - src/platform/wii/main.h - src/platform/wii/main.cpp) + add_executable(easyrpg-player src/platform/wii/main.cpp) target_link_libraries(easyrpg-player ${PROJECT_NAME} ${SDL_LIBRARIES} diff --git a/Makefile.am b/Makefile.am index 153f235633..c05af3518a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -437,8 +437,7 @@ libeasyrpg_player_a_SOURCES = \ src/window_teleport.cpp \ src/window_teleport.h \ src/window_varlist.cpp \ - src/window_varlist.h \ - src/external/rang.hpp + src/window_varlist.h SOURCEFILES_SDL2 = \ src/platform/sdl/sdl2_ui.cpp \ @@ -567,7 +566,6 @@ EXTRA_DIST += \ src/platform/wii/clock.h \ src/platform/wii/input_buttons.cpp \ src/platform/wii/main.cpp \ - src/platform/wii/main.h \ src/platform/windows/midiout_device_win32.cpp \ src/platform/windows/midiout_device_win32.h \ src/platform/windows/utils.cpp \ diff --git a/README.md b/README.md index 6526058a50..66be085240 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,6 @@ EasyRPG Player makes use of the following 3rd party software: under public domain or MIT-0 * [PicoJSON] JSON parser/serializer - Copyright (c) 2009-2010 Cybozu Labs, Inc. Copyright (c) 2011-2015 Kazuho Oku, provided under the (2-clause) BSD license -* [rang] terminal color library - by Abhinav Gauniyal, provided under Unlicense ### 3rd party resources @@ -123,7 +122,6 @@ EasyRPG Player makes use of the following 3rd party software: [FMMidi]: http://unhaut.epizy.com/fmmidi [dr_wav]: https://github.com/mackron/dr_libs [PicoJSON]: https://github.com/kazuho/picojson -[rang]: https://github.com/agauniyal/rang [baekmuk]: https://kldp.net/baekmuk [Shinonome]: http://openlab.ring.gr.jp/efont/shinonome [ttyp0]: https://people.mpi-inf.mpg.de/~uwe/misc/uw-ttyp0 diff --git a/src/baseui.h b/src/baseui.h index 8e7f8577c5..266c3ee4ad 100644 --- a/src/baseui.h +++ b/src/baseui.h @@ -103,14 +103,6 @@ class BaseUi { */ virtual bool ShowCursor(bool /* flag */) { return true; }; - /** - * Outputs a debug message over custom logger. Useful for emulators. - * - * @param message message string. - * @return wether message has been logged - */ - virtual bool LogMessage(const std::string & /* message */) { return false; } - /** * Gets if fullscreen mode is active. * diff --git a/src/external/rang.hpp b/src/external/rang.hpp deleted file mode 100644 index 01342e0f70..0000000000 --- a/src/external/rang.hpp +++ /dev/null @@ -1,503 +0,0 @@ -// Taken from https://github.com/agauniyal/rang -// Provided under Unlicense -// Modified by carstene1ns for EasyRPG inclusion - -#ifndef RANG_DOT_HPP -#define RANG_DOT_HPP - -#if defined(__unix__) || defined(__unix) || defined(__linux__) -#define OS_LINUX -#elif defined(WIN32) || defined(_WIN32) || defined(_WIN64) -#define OS_WIN -#elif defined(__APPLE__) || defined(__MACH__) -#define OS_MAC -#endif - -#if defined(OS_LINUX) || defined(OS_MAC) -#include - -#elif defined(OS_WIN) - -#if defined(_WIN32_WINNT) && (_WIN32_WINNT < 0x0600) -#error \ - "Please include rang.hpp before any windows system headers or set _WIN32_WINNT at least to _WIN32_WINNT_VISTA" -#elif !defined(_WIN32_WINNT) -#define _WIN32_WINNT _WIN32_WINNT_VISTA -#endif - -#include -#include -#include - -// Only defined in windows 10 onwards, redefining in lower windows since it -// doesn't gets used in lower versions -// https://docs.microsoft.com/en-us/windows/console/getconsolemode -#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING -#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 -#endif - -#endif - -#include -#include -#include -#include -#include - -namespace rang { - -/* For better compability with most of terminals do not use any style settings - * except of reset, bold and reversed. - * Note that on Windows terminals bold style is same as fgB color. - */ -enum class style { - reset = 0, - bold = 1, - dim = 2, - italic = 3, - underline = 4, - blink = 5, - rblink = 6, - reversed = 7, - conceal = 8, - crossed = 9 -}; - -enum class fg { - black = 30, - red = 31, - green = 32, - yellow = 33, - blue = 34, - magenta = 35, - cyan = 36, - gray = 37, - reset = 39 -}; - -enum class bg { - black = 40, - red = 41, - green = 42, - yellow = 43, - blue = 44, - magenta = 45, - cyan = 46, - gray = 47, - reset = 49 -}; - -enum class fgB { - black = 90, - red = 91, - green = 92, - yellow = 93, - blue = 94, - magenta = 95, - cyan = 96, - gray = 97 -}; - -enum class bgB { - black = 100, - red = 101, - green = 102, - yellow = 103, - blue = 104, - magenta = 105, - cyan = 106, - gray = 107 -}; - -enum class control { // Behaviour of rang function calls - Off = 0, // toggle off rang style/color calls - Auto = 1, // (Default) autodect terminal and colorize if needed - Force = 2 // force ansi color output to non terminal streams -}; -// Use rang::setControlMode to set rang control mode - -enum class winTerm { // Windows Terminal Mode - Auto = 0, // (Default) automatically detects wheter Ansi or Native API - Ansi = 1, // Force use Ansi API - Native = 2 // Force use Native API -}; -// Use rang::setWinTermMode to explicitly set terminal API for Windows -// Calling rang::setWinTermMode have no effect on other OS - -namespace rang_implementation { - - inline std::atomic &controlMode() noexcept - { - static std::atomic value(control::Auto); - return value; - } - - inline std::atomic &winTermMode() noexcept - { - static std::atomic termMode(winTerm::Auto); - return termMode; - } - - inline bool supportsColor() noexcept - { -#if defined(OS_LINUX) || defined(OS_MAC) - static const bool result = [] { - const char *Terms[] - = { "ansi", "color", "console", "cygwin", "gnome", - "konsole", "kterm", "linux", "msys", "putty", - "rxvt", "screen", "vt100", "xterm" }; - - const char *env_p = std::getenv("TERM"); - if (env_p == nullptr) { - return false; - } - return std::any_of(std::begin(Terms), std::end(Terms), - [&](const char *term) { - return std::strstr(env_p, term) != nullptr; - }); - }(); -#elif defined(OS_WIN) - // All windows versions support colors through native console methods - static constexpr bool result = true; -#else - // No color for unknown platforms - static constexpr bool result = false; - -#endif - return result; - } - -#ifdef OS_WIN - inline bool isMsysPty(int fd) noexcept - { - // Dynamic load for binary compability with old Windows - const auto ptrGetFileInformationByHandleEx - = reinterpret_cast( - GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), - "GetFileInformationByHandleEx")); - if (!ptrGetFileInformationByHandleEx) { - return false; - } - - HANDLE h = reinterpret_cast(_get_osfhandle(fd)); - if (h == INVALID_HANDLE_VALUE) { - return false; - } - - // Check that it's a pipe: - if (GetFileType(h) != FILE_TYPE_PIPE) { - return false; - } - - // POD type is binary compatible with FILE_NAME_INFO from WinBase.h - // It have the same alignment and used to avoid UB in caller code - struct MY_FILE_NAME_INFO { - DWORD FileNameLength; - WCHAR FileName[MAX_PATH]; - }; - - auto pNameInfo = std::unique_ptr( - new (std::nothrow) MY_FILE_NAME_INFO()); - if (!pNameInfo) { - return false; - } - - // Check pipe name is template of - // {"cygwin-","msys-"}XXXXXXXXXXXXXXX-ptyX-XX - if (!ptrGetFileInformationByHandleEx(h, FileNameInfo, pNameInfo.get(), - sizeof(MY_FILE_NAME_INFO))) { - return false; - } - std::wstring name(pNameInfo->FileName, pNameInfo->FileNameLength / sizeof(WCHAR)); - if ((name.find(L"msys-") == std::wstring::npos - && name.find(L"cygwin-") == std::wstring::npos) - || name.find(L"-pty") == std::wstring::npos) { - return false; - } - - return true; - } - -#endif - - inline bool isTerminal(const std::streambuf *osbuf) noexcept - { - using std::cerr; - using std::clog; - using std::cout; -#if defined(OS_LINUX) || defined(OS_MAC) - if (osbuf == cout.rdbuf()) { - static const bool cout_term = isatty(fileno(stdout)) != 0; - return cout_term; - } else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) { - static const bool cerr_term = isatty(fileno(stderr)) != 0; - return cerr_term; - } -#elif defined(OS_WIN) - if (osbuf == cout.rdbuf()) { - static const bool cout_term - = (_isatty(_fileno(stdout)) || isMsysPty(_fileno(stdout))); - return cout_term; - } else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) { - static const bool cerr_term - = (_isatty(_fileno(stderr)) || isMsysPty(_fileno(stderr))); - return cerr_term; - } -#endif - return false; - } - - template - using enableStd = typename std::enable_if< - std::is_same::value || std::is_same::value - || std::is_same::value || std::is_same::value - || std::is_same::value, - std::ostream &>::type; - - -#ifdef OS_WIN - struct SGR { // Select Graphic Rendition parameters for Windows console - BYTE fgColor; // foreground color (0-15) lower 3 rgb bits + intense bit - BYTE bgColor; // background color (0-15) lower 3 rgb bits + intense bit - BYTE bold; // emulated as FOREGROUND_INTENSITY bit - BYTE underline; // emulated as BACKGROUND_INTENSITY bit - BOOLEAN inverse; // swap foreground/bold & background/underline - BOOLEAN conceal; // set foreground/bold to background/underline - }; - - enum class AttrColor : BYTE { // Color attributes for console screen buffer - black = 0, - red = 4, - green = 2, - yellow = 6, - blue = 1, - magenta = 5, - cyan = 3, - gray = 7 - }; - - inline HANDLE getConsoleHandle(const std::streambuf *osbuf) noexcept - { - if (osbuf == std::cout.rdbuf()) { - static const HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); - return hStdout; - } else if (osbuf == std::cerr.rdbuf() || osbuf == std::clog.rdbuf()) { - static const HANDLE hStderr = GetStdHandle(STD_ERROR_HANDLE); - return hStderr; - } - return INVALID_HANDLE_VALUE; - } - - inline bool setWinTermAnsiColors(const std::streambuf *osbuf) noexcept - { - HANDLE h = getConsoleHandle(osbuf); - if (h == INVALID_HANDLE_VALUE) { - return false; - } - DWORD dwMode = 0; - if (!GetConsoleMode(h, &dwMode)) { - return false; - } - dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; - if (!SetConsoleMode(h, dwMode)) { - return false; - } - return true; - } - - inline bool supportsAnsi(const std::streambuf *osbuf) noexcept - { - using std::cerr; - using std::clog; - using std::cout; - if (osbuf == cout.rdbuf()) { - static const bool cout_ansi - = (isMsysPty(_fileno(stdout)) || setWinTermAnsiColors(osbuf)); - return cout_ansi; - } else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) { - static const bool cerr_ansi - = (isMsysPty(_fileno(stderr)) || setWinTermAnsiColors(osbuf)); - return cerr_ansi; - } - return false; - } - - inline const SGR &defaultState() noexcept - { - static const SGR defaultSgr = []() -> SGR { - CONSOLE_SCREEN_BUFFER_INFO info; - WORD attrib = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; - if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), - &info) - || GetConsoleScreenBufferInfo(GetStdHandle(STD_ERROR_HANDLE), - &info)) { - attrib = info.wAttributes; - } - SGR sgr = { 0, 0, 0, 0, FALSE, FALSE }; - sgr.fgColor = attrib & 0x0F; - sgr.bgColor = (attrib & 0xF0) >> 4; - return sgr; - }(); - return defaultSgr; - } - - inline BYTE ansi2attr(BYTE rgb) noexcept - { - static const AttrColor rev[8] - = { AttrColor::black, AttrColor::red, AttrColor::green, - AttrColor::yellow, AttrColor::blue, AttrColor::magenta, - AttrColor::cyan, AttrColor::gray }; - return static_cast(rev[rgb]); - } - - inline void setWinSGR(rang::bg col, SGR &state) noexcept - { - if (col != rang::bg::reset) { - state.bgColor = ansi2attr(static_cast(col) - 40); - } else { - state.bgColor = defaultState().bgColor; - } - } - - inline void setWinSGR(rang::fg col, SGR &state) noexcept - { - if (col != rang::fg::reset) { - state.fgColor = ansi2attr(static_cast(col) - 30); - } else { - state.fgColor = defaultState().fgColor; - } - } - - inline void setWinSGR(rang::bgB col, SGR &state) noexcept - { - state.bgColor = (BACKGROUND_INTENSITY >> 4) - | ansi2attr(static_cast(col) - 100); - } - - inline void setWinSGR(rang::fgB col, SGR &state) noexcept - { - state.fgColor - = FOREGROUND_INTENSITY | ansi2attr(static_cast(col) - 90); - } - - inline void setWinSGR(rang::style style, SGR &state) noexcept - { - switch (style) { - case rang::style::reset: state = defaultState(); break; - case rang::style::bold: state.bold = FOREGROUND_INTENSITY; break; - case rang::style::underline: - case rang::style::blink: - state.underline = BACKGROUND_INTENSITY; - break; - case rang::style::reversed: state.inverse = TRUE; break; - case rang::style::conceal: state.conceal = TRUE; break; - default: break; - } - } - - inline SGR ¤t_state() noexcept - { - static SGR state = defaultState(); - return state; - } - - inline WORD SGR2Attr(const SGR &state) noexcept - { - WORD attrib = 0; - if (state.conceal) { - if (state.inverse) { - attrib = (state.fgColor << 4) | state.fgColor; - if (state.bold) - attrib |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; - } else { - attrib = (state.bgColor << 4) | state.bgColor; - if (state.underline) - attrib |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; - } - } else if (state.inverse) { - attrib = (state.fgColor << 4) | state.bgColor; - if (state.bold) attrib |= BACKGROUND_INTENSITY; - if (state.underline) attrib |= FOREGROUND_INTENSITY; - } else { - attrib = state.fgColor | (state.bgColor << 4) | state.bold - | state.underline; - } - return attrib; - } - - template - inline void setWinColorAnsi(std::ostream &os, T const value) - { - os << "\033[" << static_cast(value) << "m"; - } - - template - inline void setWinColorNative(std::ostream &os, T const value) - { - const HANDLE h = getConsoleHandle(os.rdbuf()); - if (h != INVALID_HANDLE_VALUE) { - setWinSGR(value, current_state()); - // Out all buffered text to console with previous settings: - os.flush(); - SetConsoleTextAttribute(h, SGR2Attr(current_state())); - } - } - - template - inline enableStd setColor(std::ostream &os, T const value) - { - if (winTermMode() == winTerm::Auto) { - if (supportsAnsi(os.rdbuf())) { - setWinColorAnsi(os, value); - } else { - setWinColorNative(os, value); - } - } else if (winTermMode() == winTerm::Ansi) { - setWinColorAnsi(os, value); - } else { - setWinColorNative(os, value); - } - return os; - } -#else - template - inline enableStd setColor(std::ostream &os, T const value) - { - return os << "\033[" << static_cast(value) << "m"; - } -#endif -} // namespace rang_implementation - -template -inline rang_implementation::enableStd operator<<(std::ostream &os, - const T value) -{ - const control option = rang_implementation::controlMode(); - switch (option) { - case control::Auto: - return rang_implementation::supportsColor() - && rang_implementation::isTerminal(os.rdbuf()) - ? rang_implementation::setColor(os, value) - : os; - case control::Force: return rang_implementation::setColor(os, value); - default: return os; - } -} - -inline void setWinTermMode(const rang::winTerm value) noexcept -{ - rang_implementation::winTermMode() = value; -} - -inline void setControlMode(const control value) noexcept -{ - rang_implementation::controlMode() = value; -} - -} // namespace rang - -#undef OS_LINUX -#undef OS_WIN -#undef OS_MAC - -#endif /* ifndef RANG_DOT_HPP */ diff --git a/src/output.cpp b/src/output.cpp index 62606ede50..3012e48b8e 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -24,22 +24,14 @@ #include #include #include -#ifdef __ANDROID__ -# include -#elif defined(EMSCRIPTEN) -# include +#include +#include +#ifdef EMSCRIPTEN # include "platform/emscripten/interface.h" #elif defined(__vita__) # include #endif -// Use system rang header if available -#if __has_include() -# include -#else -# include "external/rang.hpp" -#endif - #include "output.h" #include "graphics.h" #include "filefinder.h" @@ -54,29 +46,11 @@ using namespace std::chrono_literals; namespace { - constexpr const char* const log_prefix[4] = { - "Error: ", - "Warning: ", - "Info: ", - "Debug: " + constexpr const char* const log_prefix[] = { + "Error", "Warning", "Info", "Debug" }; LogLevel log_level = LogLevel::Debug; - const char* GetLogPrefix(LogLevel lvl) { - return log_prefix[static_cast(lvl)]; - } - - // Inject colors - std::ostream& operator<<(std::ostream& os, const LogLevel lvl) - { - if (lvl == LogLevel::Error) return os << rang::fg::red; - if (lvl == LogLevel::Warning) return os << rang::fg::yellow; - if (lvl == LogLevel::Debug) return os << rang::fg::gray; - - // Default (info) - return os; - } - Filesystem_Stream::OutputStream LOG_FILE; bool output_recurse = false; bool init = false; @@ -91,6 +65,7 @@ namespace { } bool ignore_pause = false; + bool colored_log = true; std::vector log_buffer; // pair of repeat count + message @@ -99,54 +74,59 @@ namespace { std::string msg; LogLevel lvl = {}; } last_message; + + void LogCallback(LogLevel lvl, std::string const& msg, LogCallbackUserData /* userdata */) { + // terminal output + std::string prefix = Output::LogLevelToString(lvl) + ":"; + + if (colored_log) { + fmt::detail::color_type log_color = + (lvl == LogLevel::Error) ? fmt::terminal_color::red : + (lvl == LogLevel::Warning) ? fmt::terminal_color::yellow : + (lvl == LogLevel::Debug) ? fmt::terminal_color::white : + fmt::terminal_color::bright_white; + + fmt::print(std::cerr, "{} {}\n", + fmt::styled(prefix, fmt::fg(log_color) | fmt::emphasis::bold), + fmt::styled(msg, fmt::fg(log_color))); + } else { + std::cerr << prefix << " " << msg << std::endl; + } + } + + LogCallbackFn log_cb = LogCallback; + LogCallbackUserData log_cb_udata = nullptr; +} + +std::string Output::LogLevelToString(LogLevel lvl) { + return log_prefix[static_cast(lvl)]; } LogLevel Output::GetLogLevel() { return log_level; } -void Output::SetLogLevel(LogLevel ll) { - log_level = ll; +void Output::SetLogLevel(LogLevel lvl) { + log_level = lvl; } void Output::SetTermColor(bool colored) { - rang::setControlMode(colored ? rang::control::Auto : rang::control::Off); + colored_log = colored; } void Output::IgnorePause(bool const val) { ignore_pause = val; } -static void WriteLog(LogLevel lvl, std::string const& msg, Color const& c = Color()) { -#ifdef EMSCRIPTEN - -// Allow pretty log output and filtering in browser console -EM_ASM({ - lvl = $0; - msg = UTF8ToString($1); - - switch (lvl) { - case 0: - console.error(msg); - break; - case 1: - console.warn(msg); - break; - case 2: - console.info(msg); - break; - case 3: - console.debug(msg); - break; - default: - console.log(msg); - break; - } -}, static_cast(lvl), msg.c_str()); - -#else +void Output::SetLogCallback(LogCallbackFn fn, LogCallbackUserData userdata) { + log_cb = fn; + log_cb_udata = userdata; +} - const char* prefix = GetLogPrefix(lvl); +static void WriteLog(LogLevel lvl, std::string const& msg, Color const& c = Color()) { +// skip writing log file +#ifndef EMSCRIPTEN + std::string prefix = Output::LogLevelToString(lvl) + ": "; bool add_to_buffer = true; // Prevent recursion when the Save filesystem writes to the logfile on startup before it is ready @@ -172,11 +152,10 @@ EM_ASM({ last_message.repeat++; } else { if (last_message.repeat > 0) { - output_time() << GetLogPrefix(last_message.lvl) << last_message.msg << " [" << last_message.repeat + 1 << "x]" << std::endl; - output_time() << prefix << msg << '\n'; - } else { - output_time() << prefix << msg << '\n'; + output_time() << Output::LogLevelToString(last_message.lvl) << ": " << last_message.msg << " [" << last_message.repeat + 1 << "x]" << std::endl; } + output_time() << prefix << msg << '\n'; + last_message.repeat = 0; last_message.msg = msg; last_message.lvl = lvl; @@ -189,26 +168,12 @@ EM_ASM({ // buffer log messages until file system is ready log_buffer.push_back(prefix + msg); } - -# ifdef __ANDROID__ - __android_log_print(lvl == LogLevel::Error ? ANDROID_LOG_ERROR : ANDROID_LOG_INFO, GAME_TITLE, "%s", msg.c_str()); -# else - bool message_eaten = false; - // try custom logger - if (DisplayUi) { - std::string m = prefix + msg; - message_eaten = DisplayUi->LogMessage(m); - } - - // terminal output - if (!message_eaten) { - std::cerr << rang::style::bold << lvl << prefix << rang::style::reset - << lvl << msg << rang::fg::reset << std::endl; - } -# endif - #endif + // output to custom logger or terminal + log_cb(lvl, msg, log_cb_udata); + + // output to overlay if (lvl != LogLevel::Debug && lvl != LogLevel::Error) { Graphics::GetMessageOverlay().AddMessage(msg, c); } diff --git a/src/output.h b/src/output.h index fe69144346..780cecbd8f 100644 --- a/src/output.h +++ b/src/output.h @@ -31,6 +31,10 @@ enum class LogLevel { Debug }; +using LogCallbackUserData = void*; +using LogCallbackFn = void (*)(LogLevel lvl, std::string const& message, + LogCallbackUserData userdata); + /** * Output Namespace. */ @@ -43,7 +47,7 @@ namespace Output { * * @param ll the new log level */ - void SetLogLevel(LogLevel ll); + void SetLogLevel(LogLevel lvl); /** * Sets terminal log colors @@ -92,6 +96,17 @@ namespace Output { */ void IgnorePause(bool val); + /** + * Outputs debug messages over custom logger. Useful for emulators. + * + * @param fn custom callback function + * @param userdata passed as is to callback + */ + void SetLogCallback(LogCallbackFn fn, LogCallbackUserData userdata = nullptr); + + /** @return the Loglevel as string */ + std::string LogLevelToString(LogLevel lvl); + /** * Displays an info string with formatted string. * diff --git a/src/platform/3ds/main.cpp b/src/platform/3ds/main.cpp index a63f2cab5d..2c321fc83f 100644 --- a/src/platform/3ds/main.cpp +++ b/src/platform/3ds/main.cpp @@ -16,6 +16,7 @@ */ #include <3ds.h> +#include <3dslink.h> #include #include "player.h" @@ -24,22 +25,42 @@ #include #include #include +#include "output.h" /* 8 MB required for booting and need extra linear memory for the sound * effect cache and frame buffers */ u32 __ctru_linear_heap_size = 12*1024*1024; -static u32 old_time_limit; -int main(int argc, char* argv[]) { - std::vector args(argv, argv + argc); +namespace { + u32 old_time_limit; + int n3dslinkSocket = -1; + bool is_emu = false; +} - // cia/citra - if(!envIsHomebrew()) { - // set arbitrary application path - args.push_back("none:/easyrpg-player"); +static void LogCallback(LogLevel lvl, std::string const& msg, LogCallbackUserData /* userdata */) { + std::string prefix = Output::LogLevelToString(lvl); + + if (is_emu) { + std::string m = std::string("[" GAME_TITLE "] ") + prefix + ": " + msg + "\n"; + + // HLE in citra emulator + svcOutputDebugString(m.c_str(), m.length()); } + // additionally to 3dslink server or bottom console + bool want_log = n3dslinkSocket >= 0; +#ifdef _DEBUG + want_log = true; +#endif + if (want_log) { + printf("%s: %s\n", prefix.c_str(), message.c_str()); + } +} + +int main(int argc, char* argv[]) { + std::vector args(argv, argv + argc); + APT_GetAppCpuTimeLimit(&old_time_limit); APT_SetAppCpuTimeLimit(30); @@ -51,17 +72,29 @@ int main(int argc, char* argv[]) { } gfxInitDefault(); + n3dslinkSocket = link3dsStdio(); #ifdef _DEBUG consoleInit(GFX_BOTTOM, nullptr); #endif - romfsInit(); + Output::SetCustomMsgOut(LogMessage); + // cia/citra + is_emu = !envIsHomebrew(); bool is_cia = argc == 0; + if(is_emu) { + Output::Debug("Running inside emulator or as CIA."); + + // set arbitrary application path + args.push_back("none:/easyrpg-player"); + } + romfsInit(); + char tmp_path[64] = "sdmc:/3ds/easyrpg-player"; std::string ctr_dir; // Check if romfs has some files inside or not if(::access("romfs:/RPG_RT.lmt", F_OK) == 0) { + Output::Debug("Running packaged game from RomFS."); ctr_dir = "romfs:/"; if (is_cia) { @@ -92,6 +125,13 @@ int main(int argc, char* argv[]) { Player::Run(); romfsExit(); + + // Close debug log + if (n3dslinkSocket >= 0) { + close(n3dslinkSocket); + n3dslinkSocket = -1; + } + gfxExit(); if (old_time_limit != UINT32_MAX) { diff --git a/src/platform/3ds/ui.cpp b/src/platform/3ds/ui.cpp index 0c944cf9dd..4a22cab554 100644 --- a/src/platform/3ds/ui.cpp +++ b/src/platform/3ds/ui.cpp @@ -405,20 +405,6 @@ void CtrUi::UpdateDisplay() { C3D_FrameEnd(0); } -bool CtrUi::LogMessage(const std::string &message) { - std::string m = std::string("[" GAME_TITLE "] ") + message + "\n"; - - // HLE in citra emulator - svcOutputDebugString(m.c_str(), m.length()); - -#ifdef _DEBUG - // log additionally to bottom console - return false; -#else - return true; -#endif -} - void CtrUi::ToggleStretch() { vcfg.stretch.Toggle(); diff --git a/src/platform/3ds/ui.h b/src/platform/3ds/ui.h index a325c3c3c7..cb1ca5062f 100644 --- a/src/platform/3ds/ui.h +++ b/src/platform/3ds/ui.h @@ -50,7 +50,6 @@ class CtrUi final : public BaseUi { */ /** @{ */ void UpdateDisplay() override; - bool LogMessage(const std::string &message) override; void ProcessEvents() override; void ToggleStretch() override; void ToggleTouchUi() override; diff --git a/src/platform/emscripten/main.cpp b/src/platform/emscripten/main.cpp index 2792061078..50f3413839 100644 --- a/src/platform/emscripten/main.cpp +++ b/src/platform/emscripten/main.cpp @@ -28,6 +28,32 @@ namespace { int counter = 0; } +static void LogCallback(LogLevel lvl, std::string const& msg, LogCallbackUserData /* userdata */) { +// Allow pretty log output and filtering in browser console +EM_ASM({ + lvl = $0; + msg = UTF8ToString($1); + + switch (lvl) { + case 0: + console.error(msg); + break; + case 1: + console.warn(msg); + break; + case 2: + console.info(msg); + break; + case 3: + console.debug(msg); + break; + default: + console.log(msg); + break; + } +}, static_cast(lvl), msg.c_str()); +} + void main_loop() { if (counter < 5) { ++counter; @@ -57,6 +83,7 @@ extern "C" int main(int argc, char* argv[]) { args.assign(argv, argv + argc); Output::IgnorePause(true); + Output::SetLogCallback(LogCallback); emscripten_set_main_loop(main_loop, 0, 0); diff --git a/src/platform/psvita/main.cpp b/src/platform/psvita/main.cpp index 7f8d596024..0c797eae6c 100644 --- a/src/platform/psvita/main.cpp +++ b/src/platform/psvita/main.cpp @@ -20,14 +20,25 @@ #include #include #include +#include #include #include "player.h" +#include "output.h" int _newlib_heap_size_user = 330 * 1024 * 1024; +static void LogCallback(LogLevel lvl, std::string const& msg, LogCallbackUserData /* userdata */) { + std::string prefix = Output::LogLevelToString(lvl); + + // HLE in psp2shell + sceClibPrintf("[%s] %s: %s\n", GAME_TITLE, prefix.c_str(), msg.c_str()); +} + int main(int argc, char* argv[]) { std::vector args(argv, argv + argc); + Output::SetLogCallback(LogCallback); + // Check if app is invoked with an externalized game path char boot_params[1024] = {}; sceAppMgrGetAppParam(boot_params); @@ -41,6 +52,8 @@ int main(int argc, char* argv[]) { bp.assign(bp, start); if (!bp.empty()) { + Output::Debug("Running with boot params."); + // based on Utils::Tokenize() std::string cur = "--"; for (auto &c : bp) { @@ -68,6 +81,7 @@ int main(int argc, char* argv[]) { // Check if app0 filesystem contains the title id reference file FILE* f = fopen("app0:/titleid.txt", "r"); if (f != NULL) { + Output::Debug("Running packaged game from app0."); char title_id[10]; std::string save_dir = "ux0:/data/"; psp2_dir = "app0:/"; diff --git a/src/platform/psvita/ui.cpp b/src/platform/psvita/ui.cpp index 7a5477e2d7..7fad14ba72 100644 --- a/src/platform/psvita/ui.cpp +++ b/src/platform/psvita/ui.cpp @@ -35,7 +35,6 @@ #include #include #include -#include #include #include #include @@ -272,14 +271,6 @@ void Psp2Ui::UpdateDisplay() { sceKernelSignalSema(GPU_Mutex, 1); } -bool Psp2Ui::LogMessage(const std::string &message) { - // HLE in psp2shell - sceClibPrintf("[%s] %s\n", GAME_TITLE, message.c_str()); - - // skip useless stderr output - return true; -} - void Psp2Ui::SetScalingMode(ScalingMode mode) { vcfg.scaling_mode.Set(mode); } diff --git a/src/platform/psvita/ui.h b/src/platform/psvita/ui.h index 3f2fc8a2fe..9174a10656 100644 --- a/src/platform/psvita/ui.h +++ b/src/platform/psvita/ui.h @@ -49,7 +49,6 @@ class Psp2Ui final : public BaseUi { */ /** @{ */ void UpdateDisplay() override; - bool LogMessage(const std::string &message) override; void ProcessEvents() override; void SetScalingMode(ScalingMode) override; void ToggleStretch() override; diff --git a/src/platform/sdl/main.cpp b/src/platform/sdl/main.cpp index 3286d79797..f9effd3e49 100644 --- a/src/platform/sdl/main.cpp +++ b/src/platform/sdl/main.cpp @@ -20,6 +20,7 @@ #include #include "player.h" #include "utils.h" +#include "output.h" #ifdef USE_SDL // This is needed on Windows, SDL wraps main() # include @@ -27,6 +28,33 @@ #ifdef _WIN32 # include # include +#elif defined(__ANDROID__) +# include +#elif defined(__WIIU__) +# include +#endif + +#if defined(__ANDROID__) || defined(__WIIU__) +static void LogCallback(LogLevel lvl, std::string const& msg, LogCallbackUserData /* userdata */) { +# if defined(__ANDROID__) +# ifdef NDEBUG + // docs say debugging logs should be disabled for release builds + if (lvl == LogLevel::Debug || lvl == LogLevel::Info) return; +# endif + + int prio = (lvl == LogLevel::Error) ? ANDROID_LOG_ERROR : + (lvl == LogLevel::Warning) ? ANDROID_LOG_WARN : + (lvl == LogLevel::Debug) ? ANDROID_LOG_DEBUG : + ANDROID_LOG_INFO; + + __android_log_write(prio, GAME_TITLE, msg.c_str()); +# elif defined(__WIIU__) + std::string m = std::string("[" GAME_TITLE "] ") + + Output::LogLevelToString(lvl) + ": " + msg; + + OSReport("%s\n", m.c_str()); +# endif +} #endif /** @@ -51,6 +79,10 @@ extern "C" int main(int argc, char* argv[]) { args.assign(argv, argv + argc); #endif +#if defined(__WIIU__) || defined(__ANDROID__) + Output::SetLogCallback(LogCallback); +#endif + Player::Init(std::move(args)); Player::Run(); diff --git a/src/platform/sdl/sdl2_ui.cpp b/src/platform/sdl/sdl2_ui.cpp index 7e2c688ea8..653e9f4de3 100644 --- a/src/platform/sdl/sdl2_ui.cpp +++ b/src/platform/sdl/sdl2_ui.cpp @@ -32,7 +32,6 @@ # include #elif defined(__WIIU__) # include -# include #endif #include "icon.h" @@ -502,16 +501,6 @@ void Sdl2Ui::ToggleZoom() { #endif } -bool Sdl2Ui::LogMessage(const std::string &message) { -#ifdef __WIIU__ - OSReport("%s\n", message.c_str()); - return true; -#else - // not logged - return false; -#endif -} - void Sdl2Ui::ProcessEvents() { SDL_Event evnt; diff --git a/src/platform/sdl/sdl2_ui.h b/src/platform/sdl/sdl2_ui.h index e9c385fce2..c9543f2aa9 100644 --- a/src/platform/sdl/sdl2_ui.h +++ b/src/platform/sdl/sdl2_ui.h @@ -65,7 +65,6 @@ class Sdl2Ui final : public BaseUi { void UpdateDisplay() override; void SetTitle(const std::string &title) override; bool ShowCursor(bool flag) override; - bool LogMessage(const std::string &message) override; void ProcessEvents() override; void SetScalingMode(ScalingMode) override; void ToggleStretch() override; diff --git a/src/platform/sdl/sdl_ui.cpp b/src/platform/sdl/sdl_ui.cpp index e11d02292d..63ed428c2a 100644 --- a/src/platform/sdl/sdl_ui.cpp +++ b/src/platform/sdl/sdl_ui.cpp @@ -28,10 +28,6 @@ #include "player.h" #include "bitmap.h" -#ifdef __wii__ -# include "platform/wii/main.h" -#endif - #ifdef SUPPORT_AUDIO # ifdef __wii__ # include "platform/wii/audio.h" @@ -445,15 +441,6 @@ bool SdlUi::ShowCursor(bool flag) { return temp_flag; } -bool SdlUi::LogMessage(const std::string &message) { -#ifdef __wii__ - return Wii::LogMessage(message); -#else - // not logged - return false; -#endif -} - void SdlUi::ProcessEvent(SDL_Event &evnt) { switch (evnt.type) { case SDL_ACTIVEEVENT: diff --git a/src/platform/sdl/sdl_ui.h b/src/platform/sdl/sdl_ui.h index c396a88645..775957ffa3 100644 --- a/src/platform/sdl/sdl_ui.h +++ b/src/platform/sdl/sdl_ui.h @@ -63,7 +63,6 @@ class SdlUi final : public BaseUi { void UpdateDisplay() override; void SetTitle(const std::string &title) override; bool ShowCursor(bool flag) override; - bool LogMessage(const std::string &message) override; void ProcessEvents() override; void vGetConfig(Game_ConfigVideo& cfg) const override; diff --git a/src/platform/switch/main.cpp b/src/platform/switch/main.cpp index a8f8210c27..e4d32c0c73 100644 --- a/src/platform/switch/main.cpp +++ b/src/platform/switch/main.cpp @@ -20,8 +20,27 @@ #include #include #include "player.h" +#include "output.h" -static int nxlinkSocket = -1; +namespace { + int nxlinkSocket = -1; + bool is_nro = true; +} + +static void LogCallback(LogLevel lvl, std::string const& msg, LogCallbackUserData /* userdata */) { + if (!is_nro) { + std::string m = std::string("[" GAME_TITLE "] ") + + Output::LogLevelToString(lvl) + ": " + msg; + + // HLE in yuzu emulator + svcOutputDebugString(m.c_str(), m.length()); + } + + // additional to nxlink server + if(nxlinkSocket >= 0) { + printf("%s: %s\n", prefix.c_str(), message.c_str()); + } +} int main(int argc, char* argv[]) { std::vector args(argv, argv + argc); @@ -29,8 +48,12 @@ int main(int argc, char* argv[]) { appletLockExit(); // yuzu/nso - bool is_nro = envHasArgv(); + is_nro = envHasArgv(); + Output::SetLogCallback(LogCallback); + if(!is_nro) { + Output::Debug("Running inside emulator or as NSO."); + // set arbitrary application path args.push_back("none:/easyrpg-player"); } @@ -46,6 +69,8 @@ int main(int argc, char* argv[]) { // Check if romfs has some files inside or not if(::access("romfs:/RPG_RT.lmt", F_OK) == 0) { + Output::Debug("Running packaged game from RomFS."); + args.push_back("--project-path"); args.push_back("romfs:/"); diff --git a/src/platform/switch/ui.cpp b/src/platform/switch/ui.cpp index 6a69bdf014..1291a15135 100644 --- a/src/platform/switch/ui.cpp +++ b/src/platform/switch/ui.cpp @@ -488,19 +488,6 @@ void NxUi::UpdateDisplay() { eglSwapBuffers(eglDisplay, eglSurface); } -bool NxUi::LogMessage(const std::string &message) { - std::string m = std::string("[" GAME_TITLE "] ") + message + "\n"; - - // HLE in yuzu emulator - svcOutputDebugString(m.c_str(), m.length()); - - // additional to nxlink server - if(envHasArgv()) - return false; - else - return true; -} - void NxUi::ToggleStretch() { vcfg.stretch.Toggle(); vcfg.touch_ui.SetLocked(vcfg.stretch.Get()); diff --git a/src/platform/switch/ui.h b/src/platform/switch/ui.h index e76e85a2a8..4c6ccf0027 100644 --- a/src/platform/switch/ui.h +++ b/src/platform/switch/ui.h @@ -48,7 +48,6 @@ class NxUi final : public BaseUi { */ /** @{ */ void UpdateDisplay() override; - bool LogMessage(const std::string &message) override; void ProcessEvents() override; void ToggleStretch() override; void ToggleTouchUi() override; diff --git a/src/platform/wii/main.cpp b/src/platform/wii/main.cpp index 19192f3871..13e33fdaaf 100644 --- a/src/platform/wii/main.cpp +++ b/src/platform/wii/main.cpp @@ -25,17 +25,16 @@ #include "baseui.h" #include #include -#include "main.h" +#include "output.h" -// Currently for sdl-wii based port is wrapped +// For sdl-wii based port main is wrapped #ifdef USE_SDL # define main SDL_main #endif namespace { - // USBGecko Debugging - bool usbgecko = false; - + // Debugging + bool has_usbgecko = false; bool is_emu = false; } @@ -46,17 +45,41 @@ static void GekkoResetCallback(u32 /* irq */ , void* /* ctx */) { Player::reset_flag = true; } -extern "C" int main(int argc, char* argv[]) { - // Enable USBGecko output - CON_EnableGecko(CARD_SLOTB, true); - usbgecko = usb_isgeckoalive(CARD_SLOTB); +static void LogCallback(LogLevel lvl, std::string const& msg, LogCallbackUserData /* userdata */) { + std::string prefix = Output::LogLevelToString(lvl); + + if (is_emu) { + std::string m = std::string("[" GAME_TITLE "] ") + prefix + ": " + msg; + // maximum size is 256 + if(m.size() > 254) { + m = m.substr(0, 251) + "..."; + } + + // Write to OSReport uart in dolphin emulator + SYS_Report("%s\n", m.c_str()); + } + + if(has_usbgecko) { + printf("%s: %s\n", prefix.c_str(), msg.c_str()); + } +} - // cmdline +extern "C" int main(int argc, char* argv[]) { + // save cmdline std::vector args(argv, argv + argc); - // dolphin + // dolphin support is_emu = argc == 0; + + // Enable USBGecko output + has_usbgecko = usb_isgeckoalive(CARD_SLOTB); + if(has_usbgecko) { + CON_EnableGecko(CARD_SLOTB, true); + } + Output::SetLogCallback(LogCallback); + if(is_emu) { + Output::Debug("Dolphin Emulator detected."); // set arbitrary application path args.push_back("/easyrpg-player"); } @@ -66,20 +89,25 @@ extern "C" int main(int argc, char* argv[]) { // Eliminate overscan / add 5% borders OGC_ChangeSquare(304, 228, 0, 0); - if (is_emu || strchr(argv[0], '/') == 0) { - Output::Debug("USBGecko/Dolphin mode, changing dir to default."); - chdir("/apps/easyrpg"); + // Working directory not correctly handled, provide it manually + bool want_cwd = true; + if(is_emu || argv[0][0] == '/') { + want_cwd = false; + } + // Check if a game directory was provided + if (std::any_of(args.cbegin(), args.cend(), + [](const std::string& a) { return a == "--project-path"; })) { + want_cwd = false; + } + + if (want_cwd) { + char working_dir[256]; + getcwd(working_dir, 255); + args.push_back("--project-path"); + args.push_back(working_dir); } else { - // Check if a game directory was provided - if (std::none_of(args.cbegin(), args.cend(), - [](const std::string& a) { return a == "--project-path"; })) { - - // Working directory not correctly handled, provide it manually - char working_dir[256]; - getcwd(working_dir, 255); - args.push_back("--project-path"); - args.push_back(working_dir); - } + Output::Debug("Changing to default directory."); + chdir("/apps/easyrpg"); } // Run Player @@ -88,18 +116,3 @@ extern "C" int main(int argc, char* argv[]) { return EXIT_SUCCESS; } - -bool Wii::LogMessage(const std::string &message) { - if (is_emu) { - std::string m = std::string("[" GAME_TITLE "] ") + message + "\n"; - - // Write to OSReport uart in dolphin emulator - SYS_Report("%s", m.c_str()); - - // additional usbgecko output not needed - return true; - } - - // let Output class write to USBGecko or eat message if not present - return !usbgecko; -} diff --git a/src/platform/wii/main.h b/src/platform/wii/main.h deleted file mode 100644 index f40929b73d..0000000000 --- a/src/platform/wii/main.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * This file is part of EasyRPG Player. - * - * EasyRPG Player 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 3 of the License, or - * (at your option) any later version. - * - * EasyRPG Player 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 EasyRPG Player. If not, see . - */ - -#ifndef EP_PLATFORM_WII_MAIN_H -#define EP_PLATFORM_WII_MAIN_H - -namespace Wii { - bool LogMessage(const std::string &message); -}; - -#endif From 4f6d3579bd0b468341b911ccf9f9315d89469def Mon Sep 17 00:00:00 2001 From: Carsten Teibes Date: Sun, 31 Dec 2023 04:53:35 +0100 Subject: [PATCH 3/4] Output: Make coloring compatible with fmt 6-8 --- src/output.cpp | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/output.cpp b/src/output.cpp index 3012e48b8e..a12335fe8f 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -43,6 +43,13 @@ #include "font.h" #include "baseui.h" +// fmt 7 has renamed the namespace +#if FMT_VERSION < 70000 +# define FMT_COLOR_TYPE fmt::internal::color_type +#else +# define FMT_COLOR_TYPE fmt::detail::color_type +#endif + using namespace std::chrono_literals; namespace { @@ -79,19 +86,30 @@ namespace { // terminal output std::string prefix = Output::LogLevelToString(lvl) + ":"; + // only support colors with a "recent" fmt 6 + #if FMT_VERSION >= 60000 if (colored_log) { - fmt::detail::color_type log_color = + FMT_COLOR_TYPE log_color = (lvl == LogLevel::Error) ? fmt::terminal_color::red : (lvl == LogLevel::Warning) ? fmt::terminal_color::yellow : (lvl == LogLevel::Debug) ? fmt::terminal_color::white : fmt::terminal_color::bright_white; + # if FMT_VERSION < 90000 + // format using temporary strings + fmt::print(std::cerr, "{} {}\n", + fmt::format(fmt::fg(log_color) | fmt::emphasis::bold, prefix), + fmt::format(fmt::fg(log_color), msg)); + # else + // fmt 9 has styled arguments fmt::print(std::cerr, "{} {}\n", fmt::styled(prefix, fmt::fg(log_color) | fmt::emphasis::bold), fmt::styled(msg, fmt::fg(log_color))); - } else { - std::cerr << prefix << " " << msg << std::endl; + # endif + return; } + #endif + std::cerr << prefix << " " << msg << std::endl; } LogCallbackFn log_cb = LogCallback; From 4bc9cfb32ad6bd85c0f31748eae15db6a5fb374c Mon Sep 17 00:00:00 2001 From: Carsten Teibes Date: Sun, 31 Dec 2023 19:40:28 +0100 Subject: [PATCH 4/4] Update workflow and CMake config Minimum supported is CMake version 3.16 now Drop fmt 5 support with debian 10, only retropie left --- .github/workflows/stable-compilation.yml | 24 +++++++++++++----------- CMakeLists.txt | 21 ++++++++++----------- README.md | 7 ++++--- src/string_view.h | 3 +-- 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/.github/workflows/stable-compilation.yml b/.github/workflows/stable-compilation.yml index 2d3dbce744..5971edd831 100644 --- a/.github/workflows/stable-compilation.yml +++ b/.github/workflows/stable-compilation.yml @@ -9,6 +9,10 @@ on: description: Git Ref (Optional) required: false +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + defaults: run: shell: bash @@ -24,15 +28,14 @@ jobs: fail-fast: false matrix: image: - - debian:10 # oldstable | CMake 3.13.4 | G++ 8.3.0 | SDL 2.0.9 - - ubuntu:20.04 # LTS | CMake 3.16.3 | G++ 9.3.0 | SDL 2.0.10 - - debian:11 # stable | CMake 3.18.4 | G++ 10.2.1 | SDL 2.0.14 - - ubuntu:22.04 # LTS | CMake 3.22.1 | G++ 11.2.0 | SDL 2.0.20 + # version of: # CMake | g++ | SDL | support end # + # ------------------------------------------------------------- + - ubuntu:20.04 # 3.16.3 | 9.3.0 | 2.0.10 | LTS (4/25) # + - debian:11 # 3.18.4 | 10.2.1 | 2.0.14 | oldstable (8/24) # + - ubuntu:22.04 # 3.22.1 | 11.2.0 | 2.0.20 | LTS (6/27) # + - debian:12 # 3.25.1 | 12.2.0 | 2.26.5 | stable (6/26) # steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.8.0 - - name: Install dependencies run: | export DEBIAN_FRONTEND="noninteractive" @@ -45,10 +48,10 @@ jobs: libdrm-dev libgbm-dev # only needed for sdl2 on debian 11 - name: Clone Repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 if: github.event.inputs.git-ref == '' - name: Clone Repository (with custom ref) - uses: actions/checkout@v2 + uses: actions/checkout@v4 if: github.event.inputs.git-ref != '' with: ref: ${{ github.event.inputs.git-ref }} @@ -63,8 +66,7 @@ jobs: - name: Install run: | - # cmake < 3.16 does not support '--install' - cmake --build build --target install + cmake --install build - name: Test run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bc09c406f..88c93137a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.13...3.24 FATAL_ERROR) +cmake_minimum_required(VERSION 3.16...3.28 FATAL_ERROR) project(EasyRPG_Player VERSION 0.8 DESCRIPTION "Interpreter for RPG Maker 2000/2003 games" @@ -22,9 +22,7 @@ set(CMAKE_CXX_EXTENSIONS ON) # Must be at global scope, otherwise breaks -DPLAYER_BUILD_LIBLCF (see CMP0077) option(BUILD_SHARED_LIBS "Build shared easyrpg_libretro core" ON) -if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.15.0") - option(CMAKE_FIND_PACKAGE_PREFER_CONFIG "Prefer config files over bundled FindXXX files. Set this to OFF when configuration fails and report a bug." ON) -endif() +option(CMAKE_FIND_PACKAGE_PREFER_CONFIG "Prefer config files over bundled FindXXX files. Set this to OFF when configuration fails and report a bug." ON) # Source Files add_library(${PROJECT_NAME} OBJECT @@ -740,8 +738,10 @@ endif() if(NOT PLAYER_BUILD_EXECUTABLE AND BUILD_SHARED_LIBS) # Need fPIC when compiling a shared library (e.g. libretro.so) - set(CMAKE_POSITION_INDEPENDENT_CODE ON) - set_property(TARGET ${PROJECT_NAME} PROPERTY POSITION_INDEPENDENT_CODE ON) + include(CheckPIESupported) + check_pie_supported() + set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) + set_property(TARGET ${PROJECT_NAME} PROPERTY POSITION_INDEPENDENT_CODE TRUE) endif() if(ANDROID) @@ -758,6 +758,7 @@ if(WIN32) endif() if(APPLE) + enable_language(OBJCXX) target_sources(${PROJECT_NAME} PRIVATE src/platform/macos/macos_utils.mm src/platform/macos/macos_utils.h @@ -911,11 +912,9 @@ if(${PLAYER_AUDIO_BACKEND} MATCHES "^(SDL2|SDL1|libretro|psvita|3ds|switch|wii|a if(WIN32 OR APPLE) set(SUPPORT_NATIVE_MIDI ON) elseif(UNIX) - if(${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.12) - find_package(ALSA) - if(ALSA_FOUND) - set(SUPPORT_NATIVE_MIDI ON) - endif() + find_package(ALSA) + if(ALSA_FOUND) + set(SUPPORT_NATIVE_MIDI ON) endif() endif() diff --git a/README.md b/README.md index 66be085240..e8416adc66 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,11 @@ Documentation is available at the documentation wiki: https://wiki.easyrpg.org ### minimal / required - [liblcf] for RPG Maker data reading. -- SDL2 for screen backend support. +- SDL2 >= 2.0.5 for screen backend support. - Pixman for low level pixel manipulation. - libpng for PNG image support. - zlib for XYZ image and ZIP archive support. -- fmtlib for text formatting and interal logging. +- fmtlib >= 6 for text formatting/coloring and interal logging. ### extended / recommended @@ -36,7 +36,8 @@ Documentation is available at the documentation wiki: https://wiki.easyrpg.org - SpeexDSP or libsamplerate for proper audio resampling. - lhasa for LHA (.lzh) archive support. -SDL 1.2 is still supported, but deprecated. +The older SDL version 1.2 is still supported, but deprecated. +Please do not add new platform code for this library. ## Daily builds diff --git a/src/string_view.h b/src/string_view.h index 24c3d717e9..096ce8b9a1 100644 --- a/src/string_view.h +++ b/src/string_view.h @@ -21,9 +21,8 @@ #include #include #include - -// Needed to allow building with fmt 5, older versions are untested. #if FMT_VERSION < 60000 +// Remove after 0.8.1 in 2024: allow building with fmt 5 # include #endif