Add shadps4 log + refactor (#4535)

* Log to shadps4.log before game starts

* Log update doc

* Log critical like before

* Log in main file as much as possible
This commit is contained in:
Niram7777
2026-06-12 08:03:48 +02:00
committed by GitHub
parent ba2ecc21ac
commit 4bef2658f6
13 changed files with 195 additions and 148 deletions
+1 -1
View File
@@ -739,7 +739,7 @@ set(COMMON src/common/logging/classes.h
src/common/logging/formatter.h
src/common/logging/log.cpp
src/common/logging/log.h
src/common/logging/thread_name_formatter.h
src/common/logging/log_file_sink.h
src/common/aes.h
src/common/alignment.h
src/common/arch.h
+3 -1
View File
@@ -105,7 +105,7 @@ This section will provide some preliminary steps to take and tips on what to do
<summary>When a game crashes and breaks in the debugger</summary>
1. Analyze the log
- A console will open by default when you launch the emulator. It shows the same log messages that go into the log file found at `<emulator executable>/user/log/shad_log.txt`.
- A console will open by default when you launch the emulator. It shows the same log messages that go into the log files found at `<emulator executable>/user/log/{shadps4.log, shad_log.txt, {GAME ID}.log}`.
- It is recommended that you start analyzing the log bottom-up first:
- Are there any critical or error-level messages at the end of the log that would point to a reason for the game crashing?
@@ -114,6 +114,8 @@ This section will provide some preliminary steps to take and tips on what to do
- Continue analyzing the log from the start to see other errors (such as with initialization, memory mapping, linker errors etc.)
- If `shadps4.log` contains error, it should be reported in your issue.
2. Analyze the stack trace
- When the emulator is launched through a debugger, it will **break** when an exception or violation is encountered.\
_(**breaking** in this context means pausing execution of the program before it continues or stops altogether.
+5 -5
View File
@@ -83,7 +83,7 @@ bool KeyManager::LoadFromFile() {
std::ifstream file(keysPath);
if (!file.is_open()) {
fmt::println("Could not open key file: {}", keysPath.string());
LOG_ERROR(KeyManager, "Could not open key file: {}", keysPath.string());
return false;
}
@@ -106,7 +106,7 @@ bool KeyManager::LoadFromFile() {
return true;
} catch (const std::exception& e) {
fmt::println("Error loading keys, using defaults: {}", e.what());
LOG_ERROR(KeyManager, "Error loading keys, using defaults: {}", e.what());
SetDefaultKeys();
return false;
}
@@ -122,7 +122,7 @@ bool KeyManager::SaveToFile() {
std::ofstream file(keysPath);
if (!file.is_open()) {
fmt::println("Could not open key file for writing: {}", keysPath.string());
LOG_ERROR(KeyManager, "Could not open key file for writing: {}", keysPath.string());
return false;
}
@@ -130,13 +130,13 @@ bool KeyManager::SaveToFile() {
file.flush();
if (file.fail()) {
fmt::println("Failed to write keys to: {}", keysPath.string());
LOG_ERROR(KeyManager, "Failed to write keys to: {}", keysPath.string());
return false;
}
return true;
} catch (const std::exception& e) {
fmt::println("Error saving keys: {}", e.what());
LOG_ERROR(KeyManager, "Error saving keys: {}", e.what());
return false;
}
}
+88 -62
View File
@@ -5,16 +5,18 @@
#include <iostream>
#include <string>
#include <fmt/std.h>
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/logging/thread_name_formatter.h"
#include "common/types.h"
#include "core/emulator_settings.h"
#include <spdlog/sinks/async_sink.h>
#include <spdlog/sinks/dup_filter_sink.h>
#ifdef _WIN32
#include <Windows.h>
#endif
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/logging/log_file_sink.h"
#include "common/types.h"
#include "core/emulator_settings.h"
// return codes above 'standard'
// https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes
enum class ShadPs4ReturnCode : u32 {
@@ -27,7 +29,7 @@ namespace Common::Log {
bool g_should_append = false;
static std::shared_ptr<spdlog_stdout> g_console_sink;
static std::shared_ptr<spdlog::sinks::basic_file_sink_mt> g_shad_file_sink;
static std::shared_ptr<LogFileSink> g_shad_file_sink;
std::unordered_map<std::string_view, std::shared_ptr<spdlog::logger>> ALL_LOGGERS{
{Class::Common, nullptr},
@@ -171,16 +173,21 @@ static auto UpdateColorLevels(T sink) {
return sink;
}
void Setup(std::string_view log_filename) {
static bool already_registered = false;
void Setup(std::string_view shadps4_filename) {
static std::once_flag already_registered;
if (!already_registered) {
already_registered = true;
std::call_once(already_registered, []() {
std::atexit(Shutdown);
std::at_quick_exit(Flush);
std::set_terminate(Terminate);
});
for (auto& [name, logger] : ALL_LOGGERS) {
logger = std::make_shared<spdlog::logger>(std::string(name));
}
// Setup console
#ifdef _WIN32
if (EmulatorSettings.GetLogType() == "wincolor") {
g_console_sink = std::make_shared<spdlog::sinks::wincolor_stdout_sink_mt>();
@@ -192,62 +199,26 @@ void Setup(std::string_view log_filename) {
g_console_sink = UpdateColorLevels(std::make_shared<spdlog_stdout>(spdlog::color_mode::always));
#endif
g_console_sink->set_formatter(std::make_unique<thread_name_formatter>(UNLIMITED_SIZE));
g_console_sink->set_pattern("%^%v%$");
g_shad_file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(
(GetUserPath(Common::FS::PathType::LogDir) / log_filename).string(), !g_should_append);
g_shad_file_sink->set_formatter(
std::make_unique<thread_name_formatter>(EmulatorSettings.GetLogSizeLimit()));
// Setup file
std::initializer_list<spdlog::sink_ptr> sinks{g_console_sink, g_shad_file_sink};
g_shad_file_sink = std::make_shared<LogFileSink>(
(GetUserPath(Common::FS::PathType::LogDir) / shadps4_filename).string(), false,
EmulatorSettings.GetLogSizeLimit());
g_shad_file_sink->set_pattern("%^%v%$");
std::initializer_list<spdlog::sink_ptr> async_sink{std::make_shared<spdlog::sinks::async_sink>(
spdlog::sinks::async_sink::config{.sinks = sinks})};
UpdateSinks();
UpdateLogLevels(EmulatorSettings.GetLogFilter());
}
std::initializer_list<spdlog::sink_ptr> dup_filter{
std::make_shared<spdlog::sinks::dup_filter_sink_mt>(
std::chrono::milliseconds(EmulatorSettings.GetLogMaxSkipDuration()),
EmulatorSettings.IsLogSync() ? sinks : async_sink)};
void Switch(std::string_view game_filename) {
UpdateSinks();
UpdateLogLevels(EmulatorSettings.GetLogFilter());
spdlog::level default_log_level = spdlog::level::info;
std::unordered_map<std::string, spdlog::level> log_level_per_class;
if (EmulatorSettings.IsLogEnable()) {
for (const auto class_level : std::views::split(EmulatorSettings.GetLogFilter(), ' ')) {
const auto class_level_pair =
std::views::split(class_level, ':') | std::ranges::to<std::vector<std::string>>();
if (class_level_pair.size() != 2) {
std::cerr << "bad log filter provided" << std::endl;
continue;
}
if (class_level_pair.front()[0] == '*') {
default_log_level = spdlog::level_from_str(class_level_pair.back() |
std::ranges::to<std::string>());
} else {
log_level_per_class[class_level_pair.front() | std::ranges::to<std::string>()] =
spdlog::level_from_str(class_level_pair.back() |
std::ranges::to<std::string>());
}
}
}
for (auto& [name, logger] : ALL_LOGGERS) {
logger = std::make_shared<spdlog::logger>(
std::string(name), EmulatorSettings.IsLogSkipDuplicate()
? dup_filter
: (EmulatorSettings.IsLogSync() ? sinks : async_sink));
if (EmulatorSettings.IsLogEnable()) {
const auto level_it = log_level_per_class.find(std::string(name));
logger->set_level(level_it != log_level_per_class.end() ? level_it->second
: default_log_level);
} else {
logger->set_level(spdlog::level::off);
}
}
g_shad_file_sink->_size_limit = EmulatorSettings.GetLogSizeLimit();
g_shad_file_sink->session_file_helper_.open(
(GetUserPath(Common::FS::PathType::LogDir) / game_filename).string(), !g_should_append);
}
void Shutdown() {
@@ -288,4 +259,59 @@ void Terminate() {
std::quick_exit(std::to_underlying(ShadPs4ReturnCode::TERMINATE_WITH_UNKNOWN_EXCEPTION));
}
}
void UpdateSinks() {
std::initializer_list<spdlog::sink_ptr> sinks{g_console_sink, g_shad_file_sink};
std::initializer_list<spdlog::sink_ptr> async_sink{std::make_shared<spdlog::sinks::async_sink>(
spdlog::sinks::async_sink::config{.sinks = sinks})};
std::initializer_list<spdlog::sink_ptr> dup_filter{
std::make_shared<spdlog::sinks::dup_filter_sink_mt>(
std::chrono::milliseconds(EmulatorSettings.GetLogMaxSkipDuration()),
EmulatorSettings.IsLogSync() ? sinks : async_sink)};
for (auto& [name, logger] : ALL_LOGGERS) {
logger->sinks() = EmulatorSettings.IsLogSkipDuplicate()
? dup_filter
: (EmulatorSettings.IsLogSync() ? sinks : async_sink);
}
}
void UpdateLogLevels(std::string_view log_filter) {
spdlog::level default_log_level = spdlog::level::info;
std::unordered_map<std::string, spdlog::level> log_level_per_class;
if (EmulatorSettings.IsLogEnable()) {
for (const auto class_level : std::views::split(log_filter, ' ')) {
const auto class_level_pair =
std::views::split(class_level, ':') | std::ranges::to<std::vector<std::string>>();
if (class_level_pair.size() != 2) {
LOG_ERROR(Config, "bad log filter provided");
continue;
}
if (class_level_pair.front()[0] == '*') {
default_log_level = spdlog::level_from_str(class_level_pair.back() |
std::ranges::to<std::string>());
} else {
log_level_per_class[class_level_pair.front() | std::ranges::to<std::string>()] =
spdlog::level_from_str(class_level_pair.back() |
std::ranges::to<std::string>());
}
}
}
for (auto& [name, logger] : ALL_LOGGERS) {
if (EmulatorSettings.IsLogEnable()) {
const auto level_it = log_level_per_class.find(std::string(name));
logger->set_level(level_it != log_level_per_class.end() ? level_it->second
: default_log_level);
} else {
logger->set_level(spdlog::level::off);
}
}
}
} // namespace Common::Log
+7 -4
View File
@@ -7,10 +7,7 @@
#include <unordered_map>
#include <vector>
#include <spdlog/details/fmt_helper.h>
#include <spdlog/sinks/async_sink.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/dup_filter_sink.h>
#include <spdlog/sinks/null_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#ifdef _WIN32
@@ -30,7 +27,9 @@ namespace Common::Log {
extern bool g_should_append;
extern std::unordered_map<std::string_view, std::shared_ptr<spdlog::logger>> ALL_LOGGERS;
void Setup(std::string_view log_filename);
void Setup(std::string_view shadps4_filename);
void Switch(std::string_view game_filename);
void Shutdown();
@@ -38,6 +37,10 @@ void Flush();
void Terminate();
void UpdateSinks();
void UpdateLogLevels(std::string_view log_filter);
static constexpr std::array level_string_views{"Trace", "Debug", "Info", "Warning",
"Error", "Critical", "Off"};
+53
View File
@@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <string_view>
#include <spdlog/common.h>
#include <spdlog/details/log_msg.h>
#include <spdlog/formatter.h>
#include <spdlog/sinks/base_sink.h>
namespace Common::Log {
static constexpr unsigned long long UNLIMITED_SIZE = 0;
/*
* Trivial file sink with two files as target : main and session
*/
class LogFileSink final : public spdlog::sinks::base_sink<std::mutex> {
public:
explicit LogFileSink(const spdlog::filename_t& main_filename, bool truncate = false,
unsigned long long size_limit = UNLIMITED_SIZE)
: _size_limit{size_limit} {
main_file_helper_.open(main_filename, truncate);
}
spdlog::details::file_helper main_file_helper_;
spdlog::details::file_helper session_file_helper_;
unsigned long long _size_limit;
protected:
void sink_it_(const spdlog::details::log_msg& msg) override {
spdlog::memory_buf_t formatted;
this->formatter_->format(msg, formatted);
if (session_file_helper_.filename().empty()) {
// always log in the main file, it shouldnt be too big
main_file_helper_.write(formatted);
} else if (_current_size < _size_limit || msg.log_level == spdlog::level::critical) {
session_file_helper_.write(formatted);
_current_size += formatted.size();
}
}
void flush_() override {
main_file_helper_.flush();
session_file_helper_.flush();
}
unsigned long long _current_size = 0;
};
} // namespace Common::Log
@@ -1,42 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <string_view>
#include <spdlog/common.h>
#include <spdlog/details/log_msg.h>
#include <spdlog/formatter.h>
namespace Common::Log {
static constexpr unsigned long long UNLIMITED_SIZE = 0;
struct thread_name_formatter : spdlog::formatter {
~thread_name_formatter() override = default;
thread_name_formatter(unsigned long long size_limit) : _size_limit(size_limit) {}
void format(const spdlog::details::log_msg& msg, spdlog::memory_buf_t& dest) override {
if (_size_limit != UNLIMITED_SIZE && _current_size >= _size_limit) {
return;
}
msg.color_range_start = dest.size();
spdlog::details::fmt_helper::append_string_view(msg.payload, dest);
spdlog::details::fmt_helper::append_string_view(spdlog::details::os::default_eol, dest);
msg.color_range_end = dest.size();
_current_size += dest.size();
}
std::unique_ptr<formatter> clone() const override {
return std::make_unique<thread_name_formatter>(_size_limit);
}
const unsigned long long _size_limit;
unsigned long long _current_size = 0;
};
} // namespace Common::Log
+11 -11
View File
@@ -91,9 +91,9 @@ void EmulatorSettingsImpl::PrintChangedSummary(const std::vector<std::string>& c
if (changed.empty()) {
return;
}
fmt::println("Game-specific overrides applied:");
LOG_DEBUG(Config, "Game-specific overrides applied:");
for (const auto& k : changed)
fmt::println(" * {}", k);
LOG_DEBUG(Config, " * {}", k);
}
// ── Singleton ────────────────────────────────────────────────────────
@@ -258,7 +258,7 @@ void EmulatorSettingsImpl::ResetGameSpecificValue(const std::string& key) {
return;
if (tryGroup(m_vulkan))
return;
fmt::println("ResetGameSpecificValue: key '{}' not found", key);
LOG_DEBUG(Config, "ResetGameSpecificValue: key '{}' not found", key);
}
bool EmulatorSettingsImpl::Save(const std::string& serial) {
@@ -300,7 +300,7 @@ bool EmulatorSettingsImpl::Save(const std::string& serial) {
std::ofstream out(path);
if (!out) {
fmt::println("Failed to open game config for writing: {}", path.string());
LOG_DEBUG(Config, "Failed to open game config for writing: {}", path.string());
return false;
}
out << std::setw(2) << j;
@@ -342,14 +342,14 @@ bool EmulatorSettingsImpl::Save(const std::string& serial) {
std::ofstream out(path);
if (!out) {
fmt::println("Failed to open config for writing: {}", path.string());
LOG_DEBUG(Config, "Failed to open config for writing: {}", path.string());
return false;
}
out << std::setw(2) << existing;
return !out.fail();
}
} catch (const std::exception& e) {
fmt::println("Error saving settings: {}", e.what());
LOG_DEBUG(Config, "Error saving settings: {}", e.what());
return false;
}
}
@@ -469,7 +469,7 @@ bool EmulatorSettingsImpl::Load(const std::string& serial) {
return true;
}
} catch (const std::exception& e) {
fmt::println("Error loading settings: {}", e.what());
LOG_DEBUG(Config, "Error loading settings: {}", e.what());
return false;
}
}
@@ -669,7 +669,7 @@ bool EmulatorSettingsImpl::TransferSettings() {
}
s.install_dirs.value = settings_install_dirs;
} catch (const std::exception& e) {
fmt::println("Failed to transfer install directories: {}", e.what());
LOG_DEBUG(Config, "Failed to transfer install directories: {}", e.what());
}
// Transfer addon install directory
@@ -685,7 +685,7 @@ bool EmulatorSettingsImpl::TransferSettings() {
}
}
} catch (const std::exception& e) {
fmt::println("Failed to transfer addon install directory: {}", e.what());
LOG_DEBUG(Config, "Failed to transfer addon install directory: {}", e.what());
}
}
if (og_data.contains("General")) {
@@ -704,7 +704,7 @@ bool EmulatorSettingsImpl::TransferSettings() {
}
}
} catch (const std::exception& e) {
fmt::println("Failed to transfer sysmodules install directory: {}", e.what());
LOG_DEBUG(Config, "Failed to transfer sysmodules install directory: {}", e.what());
}
// Transfer font install directory
@@ -720,7 +720,7 @@ bool EmulatorSettingsImpl::TransferSettings() {
}
}
} catch (const std::exception& e) {
fmt::println("Failed to transfer font install directory: {}", e.what());
LOG_DEBUG(Config, "Failed to transfer font install directory: {}", e.what());
}
}
+3 -3
View File
@@ -127,9 +127,9 @@ inline OverrideItem make_override(const char* key, Setting<T> Struct::* member)
}
dst.game_specific_value = newValue;
} catch (const std::exception& e) {
fmt::println("[make_override] error parsing {}: {}", key, e.what());
fmt::println("[make_override] Entry was: {}", entry.dump());
fmt::println("[make_override] Type name: {}", entry.type_name());
LOG_DEBUG(Config, "[make_override] error parsing {}: {}", key, e.what());
LOG_DEBUG(Config, "[make_override] Entry was: {}", entry.dump());
LOG_DEBUG(Config, "[make_override] Type name: {}", entry.type_name());
}
},
+4 -4
View File
@@ -59,13 +59,13 @@ bool UserSettingsImpl::Save() const {
std::ofstream out(path);
if (!out) {
fmt::println("Failed to open user settings for writing: {}", path.string());
LOG_DEBUG(Config, "Failed to open user settings for writing: {}", path.string());
return false;
}
out << std::setw(2) << existing;
return !out.fail();
} catch (const std::exception& e) {
fmt::println("Error saving user settings: {}", e.what());
LOG_DEBUG(Config, "Error saving user settings: {}", e.what());
return false;
}
}
@@ -83,7 +83,7 @@ bool UserSettingsImpl::Load() {
std::ifstream in(path);
if (!in) {
fmt::println("Failed to open user settings: {}", path.string());
LOG_DEBUG(Config, "Failed to open user settings: {}", path.string());
return false;
}
@@ -108,7 +108,7 @@ bool UserSettingsImpl::Load() {
return true;
} catch (const std::exception& e) {
fmt::println("Error loading user settings: {}", e.what());
LOG_DEBUG(Config, "Error loading user settings: {}", e.what());
if (m_userManager.GetUsers().user.empty())
m_userManager.GetUsers() = m_userManager.CreateDefaultUsers();
return false;
+3 -3
View File
@@ -275,10 +275,10 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
}
}
// Initialize logging as soon as possible
EmulatorSettings.Load(id);
Common::Log::Setup((!id.empty() && EmulatorSettings.IsLogSeparate()) ? id + ".log"
: "shad_log.txt");
// Switch to configured log
Common::Log::Switch((!id.empty() && EmulatorSettings.IsLogSeparate()) ? id + ".log"
: "shad_log.txt");
auto guest_eboot_path = "/app0/" + eboot_name.generic_string();
const auto eboot_path = mnt->GetHostPath(guest_eboot_path);
+6 -6
View File
@@ -115,18 +115,18 @@ SDL_Texture* LoadSdlTextureData(std::vector<u8> data) {
unsigned char* image_data = stbi_load_from_memory(
(const unsigned char*)data.data(), (int)data.size(), &image_width, &image_height, NULL, 4);
if (image_data == nullptr) {
fmt::println("Failed to load image: {}", stbi_failure_reason());
LOG_ERROR(ImGui, "Failed to load image: {}", stbi_failure_reason());
}
SDL_Surface* surface = SDL_CreateSurfaceFrom(image_width, image_height, SDL_PIXELFORMAT_RGBA32,
(void*)image_data, channels * image_width);
if (surface == nullptr) {
fmt::println("Unable to create SDL surface: {}", SDL_GetError());
LOG_ERROR(ImGui, "Unable to create SDL surface: {}", SDL_GetError());
}
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
if (texture == nullptr) {
fmt::println("Unable to create SDL texture: {}", SDL_GetError());
LOG_ERROR(ImGui, "Unable to create SDL texture: {}", SDL_GetError());
}
SDL_DestroySurface(surface);
@@ -190,13 +190,13 @@ void GetGameIconInfo(std::vector<IconInfo>& icons) {
void Launch(char* executableName) {
if (!SDL_Init(SDL_INIT_VIDEO)) {
fmt::println("SDL_INIT_VIDEO Error: {}", SDL_GetError());
LOG_ERROR(ImGui, "SDL_INIT_VIDEO Error: {}", SDL_GetError());
SDL_Quit();
return;
}
if (!SDL_Init(SDL_INIT_GAMEPAD)) {
fmt::println("SDL_INIT_GAMEPAD Error: {}", SDL_GetError());
LOG_ERROR(ImGui, "SDL_INIT_GAMEPAD Error: {}", SDL_GetError());
}
SDL_Window* window =
@@ -204,7 +204,7 @@ void Launch(char* executableName) {
renderer = SDL_CreateRenderer(window, nullptr);
if (window == nullptr) {
fmt::println("SDL Window Creation Error: {}", SDL_GetError());
LOG_ERROR(ImGui, "SDL Window Creation Error: {}", SDL_GetError());
SDL_DestroyRenderer(renderer);
SDL_Quit();
return;
+11 -6
View File
@@ -103,6 +103,11 @@ int main(int argc, char* argv[]) {
if (waitPid)
Core::Debugger::WaitForPid(*waitPid);
// Initialize main log with default config
Common::Log::Setup("shadps4.log");
LOG_INFO(Debug, "Run: {}", std::span(argv, argc));
IPC::Instance().Init();
auto emu_state = std::make_shared<EmulatorState>();
@@ -130,14 +135,14 @@ int main(int argc, char* argv[]) {
if (addGameFolder) {
EmulatorSettings.AddGameInstallDir(*addGameFolder);
EmulatorSettings.Save();
std::cout << "Game folder successfully saved.\n";
LOG_INFO(Config, "Game folder successfully saved.");
return 0;
}
if (setAddonFolder) {
EmulatorSettings.SetAddonInstallDir(*setAddonFolder);
EmulatorSettings.Save();
std::cout << "Addon folder successfully saved.\n";
LOG_INFO(Config, "Addon folder successfully saved.");
return 0;
}
@@ -146,7 +151,7 @@ int main(int argc, char* argv[]) {
gamePath = gameArgs.front();
gameArgs.erase(gameArgs.begin());
} else {
std::cerr << "Error: Please provide a game path or ID.\n";
LOG_ERROR(Debug, "Please provide a game path or ID.");
return 1;
}
}
@@ -154,7 +159,7 @@ int main(int argc, char* argv[]) {
if (gameArgs.front() == "--") {
gameArgs.erase(gameArgs.begin());
} else {
std::cerr << "Error: unhandled flags\n";
LOG_ERROR(Debug, "unhandled flags");
return 1;
}
}
@@ -172,7 +177,7 @@ int main(int argc, char* argv[]) {
} else if (*fullscreenStr == "false") {
EmulatorSettings.SetFullScreen(false);
} else {
std::cerr << "Error: Invalid argument for --fullscreen (use true|false)\n";
LOG_ERROR(Debug, "Invalid argument for --fullscreen (use true|false)");
return 1;
}
}
@@ -199,7 +204,7 @@ int main(int argc, char* argv[]) {
}
}
if (!found) {
std::cerr << "Error: Game ID or file path not found: " << *gamePath << "\n";
LOG_ERROR(Debug, "Game ID or file path not found: {}", *gamePath);
return 1;
}
}