Import Strong Logger & Config System

This commit is contained in:
Xphalnos 2025-06-18 18:07:20 +02:00
parent 92d4e7a8d3
commit 014b236228
48 changed files with 3281 additions and 734 deletions

View file

@ -105,16 +105,22 @@ jobs:
submodules: recursive
fetch-depth: 0
- name: Setup latest Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- name: Install Dependencies
run: |
brew update
brew install cmake ninja ccache
brew install ninja llvm
- name: Configure CMake (x86_64)
run: >
cmake -G Ninja -B "${{env.BUILD_DIR}}"
-DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
-DCMAKE_OSX_ARCHITECTURES=x86_64
-DCMAKE_C_COMPILER=$(brew --prefix llvm)/bin/clang
-DCMAKE_CXX_COMPILER=$(brew --prefix llvm)/bin/clang++
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.15
- name: Build (x86_64)
@ -141,15 +147,21 @@ jobs:
submodules: recursive
fetch-depth: 0
- name: Setup latest Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- name: Install Dependencies
run: |
brew update
brew install cmake ninja ccache
brew install ninja llvm
- name: Configure CMake (ARM64)
run: >
cmake -G Ninja -B "${{env.BUILD_DIR}}"
-DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
-DCMAKE_C_COMPILER=$(brew --prefix llvm)/bin/clang
-DCMAKE_CXX_COMPILER=$(brew --prefix llvm)/bin/clang++
-DCMAKE_OSX_ARCHITECTURES=arm64
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.15

3
.gitmodules vendored
View file

@ -1,3 +1,6 @@
[submodule "3rd_Party/fmt"]
path = 3rd_Party/fmt
url = https://github.com/fmtlib/fmt.git
[submodule "3rd_Party/toml11"]
path = 3rd_Party/toml11
url = https://github.com/ToruNiina/toml11.git

View file

@ -11,3 +11,16 @@ set(CMAKE_POLICY_DEFAULT_CMP0069 NEW)
if (NOT TARGET fmt::fmt)
add_subdirectory(fmt)
endif()
# Toml11
if (NOT TARGET toml11::toml11)
add_subdirectory(toml11)
set(TOML11_INSTALL OFF)
# This removes the warnings generated by toml11
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
get_target_property(_toml11_compile_options toml11 INTERFACE_COMPILE_OPTIONS)
list(REMOVE_ITEM _toml11_compile_options "/Zc:preprocessor")
set_target_properties(toml11 PROPERTIES INTERFACE_COMPILE_OPTIONS ${_toml11_compile_options})
endif()
endif()

1
3rd_Party/toml11 vendored Submodule

@ -0,0 +1 @@
Subproject commit be08ba2be2a964edcdb3d3e3ea8d100abc26f286

View file

@ -21,6 +21,7 @@ endif()
project(Pound)
find_package(fmt 10.2.1 CONFIG)
find_package(toml11 4.4.0 CONFIG)
include_directories(core)
add_subdirectory(3rd_Party)
@ -32,10 +33,14 @@ add_executable(Pound
${Core}
)
target_precompile_headers(Pound PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/core/Base/Types.h)
# Link libraries
target_link_libraries(Pound PRIVATE fmt::fmt)
target_link_libraries(Pound PRIVATE fmt::fmt toml11::toml11)
if (WIN32)
add_compile_definitions(NOMINMAX WIN32_LEAN_AND_MEAN)
# Disables Warnings
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
endif()

View file

@ -1,39 +1,39 @@
#ifndef CPU_H
#define CPU_H
#include <cstdint>
#include <cstdio>
// Copyright 2025 Pound Emulator Project. All rights reserved.
#pragma once
#include <cstring>
#include "Base/Logging/Log.h"
struct CPU {
uint64_t regs[31] = {0}; // X0X30
uint64_t pc = 0;
static const size_t MEM_SIZE = 64 * 1024;
uint8_t memory[MEM_SIZE];
u64 regs[31] = {0}; // X0X30
u64 pc = 0;
static constexpr size_t MEM_SIZE = 64 * 1024;
u8 memory[MEM_SIZE];
CPU() { std::memset(memory, 0, MEM_SIZE); }
uint64_t &x(int i) { return regs[i]; }
u64 &x(int i) { return regs[i]; }
uint8_t read_byte(uint64_t addr) {
u8 read_byte(u64 addr) {
if (addr >= MEM_SIZE) {
printf("%llu out of bounds\n", addr);
LOG_INFO(ARM, "{} out of bounds", addr);
}
return memory[addr];
}
void write_byte(uint64_t addr, uint8_t byte) {
void write_byte(u64 addr, u8 byte) {
if (addr >= MEM_SIZE) {
printf("%llu out of bounds\n", addr);
LOG_INFO(ARM, "{} out of bounds", addr);
}
memory[addr] = byte;
}
void print_debug_information() {
printf("PC = %llu\n", pc);
LOG_INFO(ARM, "PC = {}", pc);
for (int reg = 0; reg < 32; reg++) {
printf("X%i = %llu\n", reg, x(reg)); // X0 = 0...
LOG_INFO(ARM, "X{} = {}", reg, x(reg)); // X0 = 0...
}
}
};
#endif

13
core/Base/Arch.h Normal file
View file

@ -0,0 +1,13 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#if defined(__x86_64__) || defined(_M_X64)
#define ARCH_X86_64 1
#elif defined(i386) || defined(__i386__) || defined(__i386) || defined(_M_IX86)
#define ARCH_X86 1
#elif defined(__aarch64__) || defined(_M_ARM64)
#define ARCH_AARCH64 1
#elif defined(__powerpc) || defined(__powerpc__) || defined(__powerpc64__) || defined(__POWERPC__) || defined(__ppc__) || defined(__PPC__) || defined(_ARCH_PPC)
#define ARCH_PPC 1
#endif

54
core/Base/Assert.cpp Normal file
View file

@ -0,0 +1,54 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#include <stdio.h>
#include "Logging/Backend.h"
#include "Assert.h"
#include "Arch.h"
#ifdef _MSC_VER
#define Crash() __debugbreak()
#else
#if defined(ARCH_X86_64)
#define Crash() __asm__ __volatile__("int $3")
#elif defined(ARCH_X86)
#define Crash() __asm__ __volatile__("int $3")
#elif defined(ARCH_AARCH64)
#define Crash() __asm__ __volatile__("brk 0")
#elif defined(ARCH_PPC)
#include <signal.h>
#ifdef SIGTRAP
#define Crash() raise(SIGTRAP)
#else
#define Crash() raise(SIGABRT)
#endif
#else
#define Crash() __builtin_trap()
#endif
#endif // _MSVC_VER
void throw_fail_impl() {
::fflush(stdout);
Crash();
}
void assert_fail_impl() {
printf("Assertion Failed!\n");
throw_fail_impl();
}
[[noreturn]] void unreachable_impl() {
::fflush(stdout);
Crash();
throw std::runtime_error("Unreachable code");
}
void assert_fail_debug_msg(const std::string& msg) {
printf("Assertion Failed! %s\n", msg.c_str());
assert_fail_impl();
}
void throw_fail_debug_msg(const std::string& msg) {
printf("Assertion Failed! %s\n", msg.c_str());
throw_fail_impl();
}

101
core/Base/Assert.h Normal file
View file

@ -0,0 +1,101 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include "Logging/Log.h"
// Sometimes we want to try to continue even after hitting an assert.
// However touching this file yields a global recompilation as this header is included almost
// everywhere. So let's just move the handling of the failed assert to a single cpp file.
void assert_fail_impl();
void throw_fail_impl();
[[noreturn]] void unreachable_impl();
void assert_fail_debug_msg(const std::string& msg);
void throw_fail_debug_msg(const std::string& msg);
#ifdef _MSC_VER
#define POUND_NO_INLINE __declspec(noinline)
#else
#define POUND_NO_INLINE __attribute__((noinline))
#endif
#define THROW(_a_) \
([&]() POUND_NO_INLINE { \
if (!(_a_)) [[unlikely]] { \
LOG_CRITICAL(Debug, "Assertion Failed!"); \
throw_fail_impl(); \
} \
})
#define ASSERT(_a_) \
([&]() POUND_NO_INLINE { \
if (!(_a_)) [[unlikely]] { \
LOG_CRITICAL(Debug, "Assertion Failed!"); \
assert_fail_impl(); \
} \
})
#define THROW_MSG(_a_, ...) \
([&]() POUND_NO_INLINE { \
if (!(_a_)) [[unlikely]] { \
LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \
throw_fail_impl(); \
} \
})
#define ASSERT_MSG(_a_, ...) \
([&]() POUND_NO_INLINE { \
if (!(_a_)) [[unlikely]] { \
LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \
assert_fail_impl(); \
} \
})
#define UNREACHABLE() \
do { \
LOG_CRITICAL(Debug, "Unreachable code!"); \
unreachable_impl(); \
} while (0)
#define UNREACHABLE_MSG(...) \
do { \
LOG_CRITICAL(Debug, "Unreachable code!\n" __VA_ARGS__); \
unreachable_impl(); \
} while (0)
#ifdef _DEBUG
#define DEBUG_ASSERT(_a_) ASSERT(_a_)
#define DEBUG_ASSERT_MSG(_a_, ...) ASSERT_MSG(_a_, __VA_ARGS__)
#else // not debug
#define DEBUG_ASSERT(_a_) \
do { \
} while (0)
#define DEBUG_ASSERT_MSG(_a_, _desc_, ...) \
do { \
} while (0)
#endif
#define UNIMPLEMENTED() THROW_MSG(false, "Unimplemented code!")
#define UNIMPLEMENTED_MSG(...) THROW_MSG(false, __VA_ARGS__)
#define UNIMPLEMENTED_IF(cond) ASSERT_MSG(!(cond), "Unimplemented code!")
#define UNIMPLEMENTED_IF_MSG(cond, ...) ASSERT_MSG(!(cond), __VA_ARGS__)
// If the assert is ignored, execute _b_
#define ASSERT_OR_EXECUTE(_a_, _b_) \
do { \
ASSERT(_a_); \
if (!(_a_)) [[unlikely]] { \
_b_ \
} \
} while (0)
// If the assert is ignored, execute _b_
#define ASSERT_OR_EXECUTE_MSG(_a_, _b_, ...) \
do { \
ASSERT_MSG(_a_, __VA_ARGS__); \
if (!(_a_)) [[unlikely]] { \
_b_ \
} \
} while (0)

242
core/Base/BoundedQueue.h Normal file
View file

@ -0,0 +1,242 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include "PolyfillThread.h"
namespace Base {
namespace detail {
constexpr size_t DefaultCapacity = 0x1000;
} // namespace detail
template <typename T, size_t Capacity = detail::DefaultCapacity>
class SPSCQueue {
static_assert((Capacity & (Capacity - 1)) == 0, "Capacity must be a power of two.");
public:
template <typename... Args>
bool TryEmplace(Args&&... args) {
return Emplace<PushMode::Try>(std::forward<Args>(args)...);
}
template <typename... Args>
void EmplaceWait(Args&&... args) {
Emplace<PushMode::Wait>(std::forward<Args>(args)...);
}
bool TryPop(T& t) {
return Pop<PopMode::Try>(t);
}
bool PopWait(T& t) {
return Pop<PopMode::Wait>(t);
}
bool PopWait(T& t, std::stop_token stopToken) {
return Pop<PopMode::WaitWithStopToken>(t, stopToken);
}
T PopWait() {
T t;
Pop<PopMode::Wait>(t);
return t;
}
T PopWait(std::stop_token stopToken) {
T t;
Pop<PopMode::WaitWithStopToken>(t, stopToken);
return t;
}
private:
enum class PushMode {
Try,
Wait,
Count
};
enum class PopMode {
Try,
Wait,
WaitWithStopToken,
Count
};
template <PushMode Mode, typename... Args>
bool Emplace(Args&&... args) {
const size_t write_index = m_write_index.load(std::memory_order::relaxed);
if constexpr (Mode == PushMode::Try) {
// Check if we have free slots to write to.
if ((write_index - m_read_index.load(std::memory_order::acquire)) == Capacity) {
return false;
}
} else if constexpr (Mode == PushMode::Wait) {
// Wait until we have free slots to write to.
std::unique_lock lock{producer_cv_mutex};
producer_cv.wait(lock, [this, write_index] {
return (write_index - m_read_index.load(std::memory_order::acquire)) < Capacity;
});
} else {
static_assert(Mode < PushMode::Count, "Invalid PushMode.");
}
// Determine the position to write to.
const size_t pos = write_index % Capacity;
// Emplace into the queue.
new (std::addressof(m_data[pos])) T(std::forward<Args>(args)...);
// Increment the write index.
++m_write_index;
// Notify the consumer that we have pushed into the queue.
std::scoped_lock lock{consumer_cv_mutex};
consumer_cv.notify_one();
return true;
}
template <PopMode Mode>
bool Pop(T& t, [[maybe_unused]] std::stop_token stop_token = {}) {
const size_t read_index = m_read_index.load(std::memory_order::relaxed);
if constexpr (Mode == PopMode::Try) {
// Check if the queue is empty.
if (read_index == m_write_index.load(std::memory_order::acquire)) {
return false;
}
} else if constexpr (Mode == PopMode::Wait) {
// Wait until the queue is not empty.
std::unique_lock lock{consumer_cv_mutex};
consumer_cv.wait(lock, [this, read_index] {
return read_index != m_write_index.load(std::memory_order::acquire);
});
} else if constexpr (Mode == PopMode::WaitWithStopToken) {
// Wait until the queue is not empty.
std::unique_lock lock{consumer_cv_mutex};
Base::CondvarWait(consumer_cv, lock, stop_token, [this, read_index] {
return read_index != m_write_index.load(std::memory_order::acquire);
});
if (stop_token.stop_requested()) {
return false;
}
} else {
static_assert(Mode < PopMode::Count, "Invalid PopMode.");
}
// Determine the position to read from.
const size_t pos = read_index % Capacity;
// Pop the data off the queue, moving it.
t = std::move(m_data[pos]);
// Increment the read index.
++m_read_index;
// Notify the producer that we have popped off the queue.
std::scoped_lock lock{producer_cv_mutex};
producer_cv.notify_one();
return true;
}
alignas(128) std::atomic_size_t m_read_index{0};
alignas(128) std::atomic_size_t m_write_index{0};
std::array<T, Capacity> m_data;
std::condition_variable_any producer_cv;
std::mutex producer_cv_mutex;
std::condition_variable_any consumer_cv;
std::mutex consumer_cv_mutex;
};
template <typename T, size_t Capacity = detail::DefaultCapacity>
class MPSCQueue {
public:
template <typename... Args>
bool TryEmplace(Args&&... args) {
std::scoped_lock lock{writeMutex};
return spscQueue.TryEmplace(std::forward<Args>(args)...);
}
template <typename... Args>
void EmplaceWait(Args&&... args) {
std::scoped_lock lock{writeMutex};
spscQueue.EmplaceWait(std::forward<Args>(args)...);
}
bool TryPop(T& t) {
return spscQueue.TryPop(t);
}
bool PopWait(T& t) {
return spscQueue.PopWait(t);
}
bool PopWait(T& t, std::stop_token stop_token) {
return spscQueue.PopWait(t, stop_token);
}
T PopWait() {
return spscQueue.PopWait();
}
T PopWait(std::stop_token stop_token) {
return spscQueue.PopWait(stop_token);
}
private:
SPSCQueue<T, Capacity> spscQueue;
std::mutex writeMutex;
};
template <typename T, size_t Capacity = detail::DefaultCapacity>
class MPMCQueue {
public:
template <typename... Args>
bool TryEmplace(Args&&... args) {
std::scoped_lock lock{ writeMutex };
return spscQueue.TryEmplace(std::forward<Args>(args)...);
}
template <typename... Args>
void EmplaceWait(Args&&... args) {
std::scoped_lock lock{ writeMutex };
spscQueue.EmplaceWait(std::forward<Args>(args)...);
}
bool TryPop(T& t) {
std::scoped_lock lock{ readMutex };
return spscQueue.TryPop(t);
}
bool PopWait(T& t) {
std::scoped_lock lock{ readMutex };
return spscQueue.PopWait(t);
}
bool PopWait(T& t, std::stop_token stopToken) {
std::scoped_lock lock{ readMutex };
return spscQueue.PopWait(t, stopToken);
}
T PopWait() {
std::scoped_lock lock{ readMutex };
return spscQueue.PopWait();
}
T PopWait(std::stop_token stop_token) {
std::scoped_lock lock{ readMutex };
return spscQueue.PopWait(stop_token);
}
private:
SPSCQueue<T, Capacity> spscQueue;
std::mutex writeMutex;
std::mutex readMutex;
};
} // namespace Base

72
core/Base/Config.cpp Normal file
View file

@ -0,0 +1,72 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.
#include "Config.h"
#include <fmt/core.h>
#include <toml.hpp>
namespace Config {
static bool logAdvanced = false;
static std::string typeLog = "async";
bool isLogAdvanced() {
return logAdvanced;
}
std::string logType() {
return typeLog;
}
void Load(const std::filesystem::path& path) {
// If the configuration file does not exist, create it and return
std::error_code error;
if (!std::filesystem::exists(path, error)) {
Save(path);
return;
}
toml::value data;
try {
data = toml::parse(path);
} catch (std::exception& ex) {
fmt::print("Got exception trying to load config file. Exception: {}\n", ex.what());
return;
}
if (data.contains("General")) {
const toml::value& general = data.at("General");
logAdvanced = toml::find_or<bool>(general, "Advanced Log", false);
typeLog = toml::find_or<std::string>(general, "Log Type", "async");
}
}
void Save(const std::filesystem::path& path) {
toml::ordered_value data;
std::error_code error;
if (std::filesystem::exists(path, error)) {
try {
data = toml::parse(path);
} catch (const std::exception& ex) {
fmt::print("Exception trying to parse config file. Exception: {}\n", ex.what());
return;
}
} else {
if (error) {
fmt::print("Filesystem error: {}\n", error.message());
}
fmt::print("Saving new configuration file {}\n", path.string());
}
data["General"]["Advanced Log"] = logAdvanced;
data["General"]["Log Type"] = typeLog;
std::ofstream file(path, std::ios::binary);
file << data;
file.close();
}
} // namespace Config

16
core/Base/Config.h Normal file
View file

@ -0,0 +1,16 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.
#pragma once
#include <filesystem>
namespace Config {
void Load(const std::filesystem::path& path);
void Save(const std::filesystem::path& path);
bool isLogAdvanced();
std::string logType();
} // namespace Config

157
core/Base/Enum.h Normal file
View file

@ -0,0 +1,157 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#define DECLARE_ENUM_FLAG_OPERATORS(type) \
[[nodiscard]] constexpr type operator|(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) | static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator&(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator^(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) ^ static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator<<(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) << static_cast<T>(b)); \
} \
[[nodiscard]] constexpr type operator>>(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(static_cast<T>(a) >> static_cast<T>(b)); \
} \
constexpr type& operator|=(type& a, type b) noexcept { \
a = a | b; \
return a; \
} \
constexpr type& operator&=(type& a, type b) noexcept { \
a = a & b; \
return a; \
} \
constexpr type& operator^=(type& a, type b) noexcept { \
a = a ^ b; \
return a; \
} \
constexpr type& operator<<=(type& a, type b) noexcept { \
a = a << b; \
return a; \
} \
constexpr type& operator>>=(type& a, type b) noexcept { \
a = a >> b; \
return a; \
} \
[[nodiscard]] constexpr type operator~(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<type>(~static_cast<T>(key)); \
} \
[[nodiscard]] constexpr bool True(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<T>(key) != 0; \
} \
[[nodiscard]] constexpr bool False(type key) noexcept { \
using T = std::underlying_type_t<type>; \
return static_cast<T>(key) == 0; \
}
namespace Base {
template <typename T>
class Flags {
public:
using IntType = std::underlying_type_t<T>;
Flags() {}
Flags(IntType t) : m_bits(t) {}
template <typename... Tx>
Flags(T f, Tx... fx) {
set(f, fx...);
}
template <typename... Tx>
void set(Tx... fx) {
m_bits |= bits(fx...);
}
void set(Flags flags) {
m_bits |= flags.m_bits;
}
template <typename... Tx>
void clr(Tx... fx) {
m_bits &= ~bits(fx...);
}
void clr(Flags flags) {
m_bits &= ~flags.m_bits;
}
template <typename... Tx>
bool any(Tx... fx) const {
return (m_bits & bits(fx...)) != 0;
}
template <typename... Tx>
bool all(Tx... fx) const {
const IntType mask = bits(fx...);
return (m_bits & mask) == mask;
}
bool test(T f) const {
return any(f);
}
bool isClear() const {
return m_bits == 0;
}
void clrAll() {
m_bits = 0;
}
u32 raw() const {
return m_bits;
}
Flags operator&(const Flags& other) const {
return Flags(m_bits & other.m_bits);
}
Flags operator|(const Flags& other) const {
return Flags(m_bits | other.m_bits);
}
Flags operator^(const Flags& other) const {
return Flags(m_bits ^ other.m_bits);
}
bool operator==(const Flags& other) const {
return m_bits == other.m_bits;
}
bool operator!=(const Flags& other) const {
return m_bits != other.m_bits;
}
private:
IntType m_bits = 0;
static IntType bit(T f) {
return IntType(1) << static_cast<IntType>(f);
}
template <typename... Tx>
static IntType bits(T f, Tx... fx) {
return bit(f) | bits(fx...);
}
static IntType bits() {
return 0;
}
};
} // namespace Base

19
core/Base/Error.h Normal file
View file

@ -0,0 +1,19 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include <string>
namespace Base {
// Like GetLastErrorMsg(), but passing an explicit error code.
// Defined in error.cpp.
[[nodiscard]] std::string NativeErrorToString(const s32 e);
// Generic function to get last error message.
// Call directly after the command or use the error num.
// This function might change the error code.
// Defined in error.cpp.
[[nodiscard]] std::string GetLastErrorMsg();
} // namespace Base

428
core/Base/IoFile.cpp Normal file
View file

@ -0,0 +1,428 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#include <vector>
#include "IoFile.h"
#include "Assert.h"
#include "Error.h"
#include "Logging/Log.h"
#include "PathUtil.h"
#ifdef _WIN32
#define ftruncate _chsize_s
#define fsync _commit
#include <io.h>
#include <share.h>
#include <Windows.h>
#else
#include <unistd.h>
#endif
#ifdef _MSC_VER
#define fileno _fileno
#define fseeko _fseeki64
#define ftello _ftelli64
#endif
namespace fs = std::filesystem;
namespace Base::FS {
#ifdef _WIN32
[[nodiscard]] constexpr const wchar_t* AccessModeToWStr(FileAccessMode mode, FileMode type) {
switch (type) {
case FileMode::BinaryMode:
switch (mode) {
case FileAccessMode::Read:
return L"rb";
case FileAccessMode::Write:
return L"wb";
case FileAccessMode::Append:
return L"ab";
case FileAccessMode::ReadWrite:
return L"r+b";
case FileAccessMode::ReadAppend:
return L"a+b";
}
break;
case FileMode::TextMode:
switch (mode) {
case FileAccessMode::Read:
return L"r";
case FileAccessMode::Write:
return L"w";
case FileAccessMode::Append:
return L"a";
case FileAccessMode::ReadWrite:
return L"r+";
case FileAccessMode::ReadAppend:
return L"a+";
}
break;
}
return L"";
}
[[nodiscard]] constexpr int ToWindowsFileShareFlag(FileShareFlag flag) {
switch (flag) {
case FileShareFlag::ShareNone:
default:
return _SH_DENYRW;
case FileShareFlag::ShareReadOnly:
return _SH_DENYWR;
case FileShareFlag::ShareWriteOnly:
return _SH_DENYRD;
case FileShareFlag::ShareReadWrite:
return _SH_DENYNO;
}
}
#else
[[nodiscard]] constexpr const char* AccessModeToStr(FileAccessMode mode, FileMode type) {
switch (type) {
case FileMode::BinaryMode:
switch (mode) {
case FileAccessMode::Read:
return "rb";
case FileAccessMode::Write:
return "wb";
case FileAccessMode::Append:
return "ab";
case FileAccessMode::ReadWrite:
return "r+b";
case FileAccessMode::ReadAppend:
return "a+b";
}
break;
case FileMode::TextMode:
switch (mode) {
case FileAccessMode::Read:
return "r";
case FileAccessMode::Write:
return "w";
case FileAccessMode::Append:
return "a";
case FileAccessMode::ReadWrite:
return "r+";
case FileAccessMode::ReadAppend:
return "a+";
}
break;
}
return "";
}
#endif
[[nodiscard]] constexpr int ToSeekOrigin(SeekOrigin origin) {
switch (origin) {
case SeekOrigin::SetOrigin:
default:
return SEEK_SET;
case SeekOrigin::CurrentPosition:
return SEEK_CUR;
case SeekOrigin::End:
return SEEK_END;
}
}
IOFile::IOFile() = default;
IOFile::IOFile(const std::string& path, FileAccessMode mode, FileMode type, FileShareFlag flag) {
Open(path, mode, type, flag);
}
IOFile::IOFile(std::string_view path, FileAccessMode mode, FileMode type, FileShareFlag flag) {
Open(path, mode, type, flag);
}
IOFile::IOFile(const fs::path &path, FileAccessMode mode, FileMode type, FileShareFlag flag) {
Open(path, mode, type, flag);
}
IOFile::~IOFile() {
Close();
}
IOFile::IOFile(IOFile&& other) noexcept {
std::swap(filePath, other.filePath);
std::swap(fileAccessMode, other.fileAccessMode);
std::swap(fileType, other.fileType);
std::swap(file, other.file);
}
IOFile& IOFile::operator=(IOFile&& other) noexcept {
std::swap(filePath, other.filePath);
std::swap(fileAccessMode, other.fileAccessMode);
std::swap(fileType, other.fileType);
std::swap(file, other.file);
return *this;
}
int IOFile::Open(const fs::path& path, FileAccessMode mode, FileMode type, FileShareFlag flag) {
Close();
filePath = path;
fileAccessMode = mode;
fileType = type;
errno = 0;
int result = 0;
#ifdef _WIN32
if (flag != FileShareFlag::ShareNone) {
file = ::_wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag));
result = errno;
} else {
result = ::_wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type));
}
#else
file = std::fopen(path.c_str(), AccessModeToStr(mode, type));
result = errno;
#endif
if (!IsOpen()) {
const auto ec = std::error_code{result, std::generic_category()};
LOG_ERROR(Base_Filesystem, "Failed to open the file at path={}, error_message={}",
PathToUTF8String(filePath), ec.message());
}
return result;
}
void IOFile::Close() {
if (!IsOpen()) {
return;
}
errno = 0;
const bool closeResult = ::fclose(file) == 0;
if (!closeResult) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Base_Filesystem, "Failed to close the file at path={}, ec_message={}",
PathToUTF8String(filePath), ec.message());
}
file = nullptr;
#ifdef _WIN64
if (fileMapping && fileAccessMode == FileAccessMode::ReadWrite) {
::CloseHandle(std::bit_cast<HANDLE>(fileMapping));
}
#endif
}
void IOFile::Unlink() {
if (!IsOpen()) {
return;
}
// Mark the file for deletion
std::error_code fsError;
fs::remove_all(filePath, fsError);
if (fsError) {
LOG_ERROR(Base_Filesystem, "Failed to remove the file at '{}'. Reason: {}",
PathToUTF8String(filePath), fsError.message());
}
}
uptr IOFile::GetFileMapping() {
if (fileMapping) {
return fileMapping;
}
#ifdef _WIN64
const s32 fd = fileno(file);
HANDLE hfile = reinterpret_cast<HANDLE>(::_get_osfhandle(fd));
HANDLE mapping = nullptr;
/* if (fileAccessMode == FileAccessMode::ReadWrite) {
mapping = CreateFileMapping2(hfile, NULL, FILE_MAP_WRITE, PAGE_READWRITE, SEC_COMMIT, 0,
NULL, NULL, 0);
} else {
mapping = hfile;
}*/
mapping = hfile;
fileMapping = std::bit_cast<uptr>(mapping);
ASSERT_MSG(fileMapping, "{}", Base::GetLastErrorMsg());
return fileMapping;
#else
fileMapping = fileno(file);
return fileMapping;
#endif
}
std::string IOFile::ReadString(size_t length) const {
std::vector<char> string_buffer(length);
const u64 charsRead = ReadSpan<char>(string_buffer);
const auto stringSize = charsRead != length ? charsRead : length;
return std::string{ string_buffer.data(), stringSize };
}
bool IOFile::Flush() const {
if (!IsOpen()) {
return false;
}
errno = 0;
const bool flushResult = ::fflush(file) == 0;
if (!flushResult) {
const std::error_code ec = { errno, std::generic_category() };
LOG_ERROR(Base_Filesystem, "Failed to flush the file at path={}, ec_message={}",
PathToUTF8String(filePath), ec.message());
}
return flushResult;
}
bool IOFile::Commit() const {
if (!IsOpen()) {
return false;
}
errno = 0;
bool commitResult = ::fflush(file) == 0 && ::fsync(::fileno(file)) == 0;
if (!commitResult) {
const std::error_code ec = { errno, std::generic_category() };
LOG_ERROR(Base_Filesystem, "Failed to commit the file at path={}, ec_message={}",
PathToUTF8String(filePath), ec.message());
}
return commitResult;
}
bool IOFile::SetSize(u64 size) const {
if (!IsOpen()) {
return false;
}
errno = 0;
const bool setSizeResult = ::ftruncate(::fileno(file), static_cast<s64>(size)) == 0;
if (!setSizeResult) {
const std::error_code ec = { errno, std::generic_category() };
LOG_ERROR(Base_Filesystem, "Failed to resize the file at path={}, size={}, ec_message={}",
PathToUTF8String(filePath), size, ec.message());
}
return setSizeResult;
}
u64 IOFile::GetSize() const {
if (!IsOpen()) {
return 0;
}
// Flush any unwritten buffered data into the file prior to retrieving the file size.
std::fflush(file);
u64 fSize = 0;
// fs::file_size can cause a exception if it is not a valid file
try {
std::error_code ec{};
fSize = fs::file_size(filePath, ec);
if (fSize == -1 || !fSize) {
LOG_ERROR(Base_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}",
PathToUTF8String(filePath), ec.message());
return 0;
}
} catch (const std::exception &ex) {
LOG_ERROR(Base_Filesystem, "Exception trying to get file size. Exception: {}",
ex.what());
return 0;
}
return fSize;
}
bool IOFile::Seek(s64 offset, SeekOrigin origin) const {
if (!IsOpen()) {
return false;
}
if (False(fileAccessMode & (FileAccessMode::Write | FileAccessMode::Append))) {
u64 size = GetSize();
if (origin == SeekOrigin::CurrentPosition && Tell() + offset > size) {
LOG_ERROR(Base_Filesystem, "Seeking past the end of the file");
return false;
} else if (origin == SeekOrigin::SetOrigin && static_cast<u64>(offset) > size) {
LOG_ERROR(Base_Filesystem, "Seeking past the end of the file");
return false;
} else if (origin == SeekOrigin::End && offset > 0) {
LOG_ERROR(Base_Filesystem, "Seeking past the end of the file");
return false;
}
}
errno = 0;
const s32 seekResult = fseeko(file, offset, ToSeekOrigin(origin)) == 0;
if (!seekResult) {
const std::error_code ec = { errno, std::generic_category() };
LOG_ERROR(Base_Filesystem,
"Failed to seek the file at path={}, offset={}, origin={}, ec_message={}",
PathToUTF8String(filePath), offset, static_cast<u32>(origin), ec.message());
}
return seekResult;
}
s64 IOFile::Tell() const {
if (!IsOpen()) {
return 0;
}
errno = 0;
return ftello(file);
}
u64 GetDirectorySize(const std::filesystem::path &path) {
if (!fs::exists(path)) {
return 0;
}
u64 total = 0;
for (const auto& entry : fs::recursive_directory_iterator(path)) {
if (fs::is_regular_file(entry.path())) {
// fs::file_size can cause a exception if it is not a valid file
try {
std::error_code ec{};
u64 fileSize = fs::file_size(entry.path(), ec);
if (fileSize != -1 && fileSize) {
total += fileSize;
}
else {
LOG_ERROR(Base_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}",
PathToUTF8String(entry.path()), ec.message());
}
}
catch (const std::exception& ex) {
LOG_ERROR(Base_Filesystem, "Exception trying to get file size. Exception: {}",
ex.what());
}
}
}
return total;
}
} // namespace Base::FS

222
core/Base/IoFile.h Normal file
View file

@ -0,0 +1,222 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include <filesystem>
#include <span>
#include "Enum.h"
#include "PathUtil.h"
namespace Base::FS {
enum class FileAccessMode {
/**
* If the file at path exists, it opens the file for reading.
* If the file at path does not exist, it fails to open the file.
*/
Read = 1 << 0,
/**
* If the file at path exists, the existing contents of the file are erased.
* The empty file is then opened for writing.
* If the file at path does not exist, it creates and opens a new empty file for writing.
*/
Write = 1 << 1,
/**
* If the file at path exists, it opens the file for reading and writing.
* If the file at path does not exist, it fails to open the file.
*/
ReadWrite = Read | Write,
/**
* If the file at path exists, it opens the file for appending.
* If the file at path does not exist, it creates and opens a new empty file for appending.
*/
Append = 1 << 2,
/**
* If the file at path exists, it opens the file for both reading and appending.
* If the file at path does not exist, it creates and opens a new empty file for both
* reading and appending.
*/
ReadAppend = Read | Append
};
DECLARE_ENUM_FLAG_OPERATORS(FileAccessMode)
enum class FileMode {
BinaryMode,
TextMode
};
enum class FileShareFlag {
ShareNone, // Provides exclusive access to the file.
ShareReadOnly, // Provides read only shared access to the file.
ShareWriteOnly, // Provides write only shared access to the file.
ShareReadWrite // Provides read and write shared access to the file.
};
enum class SeekOrigin : u32 {
SetOrigin, // Seeks from the start of the file.
CurrentPosition, // Seeks from the current file pointer position.
End // Seeks from the end of the file.
};
class IOFile final {
public:
IOFile();
explicit IOFile(const std::string& path, FileAccessMode mode,
FileMode type = FileMode::BinaryMode,
FileShareFlag flag = FileShareFlag::ShareReadOnly);
explicit IOFile(std::string_view path, FileAccessMode mode,
FileMode type = FileMode::BinaryMode,
FileShareFlag flag = FileShareFlag::ShareReadOnly);
explicit IOFile(const fs::path &path, FileAccessMode mode,
FileMode type = FileMode::BinaryMode,
FileShareFlag flag = FileShareFlag::ShareReadOnly);
~IOFile();
IOFile(const IOFile&) = delete;
IOFile& operator=(const IOFile&) = delete;
IOFile(IOFile&& other) noexcept;
IOFile& operator=(IOFile&& other) noexcept;
fs::path GetPath() const {
return filePath;
}
FileAccessMode GetAccessMode() const {
return fileAccessMode;
}
FileMode GetType() const {
return fileType;
}
bool IsOpen() const {
return file != nullptr;
}
uptr GetFileMapping();
int Open(const fs::path& path, FileAccessMode mode,
FileMode type = FileMode::BinaryMode,
FileShareFlag flag = FileShareFlag::ShareReadOnly);
void Close();
void Unlink();
bool Flush() const;
bool Commit() const;
bool SetSize(u64 size) const;
u64 GetSize() const;
bool Seek(s64 offset, SeekOrigin origin = SeekOrigin::SetOrigin) const;
s64 Tell() const;
template <typename T>
size_t Read(T& data) const {
if constexpr (std::contiguous_iterator<typename T::iterator>) {
using ContiguousType = typename T::value_type;
static_assert(std::is_trivially_copyable_v<ContiguousType>,
"Data type must be trivially copyable.");
return ReadSpan<ContiguousType>(data);
} else {
return ReadObject(data) ? 1 : 0;
}
}
template <typename T>
size_t Write(const T& data) const {
if constexpr (std::contiguous_iterator<typename T::iterator>) {
using ContiguousType = typename T::value_type;
static_assert(std::is_trivially_copyable_v<ContiguousType>,
"Data type must be trivially copyable.");
return WriteSpan<ContiguousType>(data);
} else {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
return WriteObject(data) ? 1 : 0;
}
}
template <typename T>
size_t ReadSpan(std::span<T> data) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
if (!IsOpen()) {
return 0;
}
return ReadRaw<T>(data.data(), data.size());
}
template <typename T>
size_t ReadRaw(void* data, size_t size) const {
return std::fread(data, sizeof(T), size, file);
}
template <typename T>
size_t WriteSpan(std::span<const T> data) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
if (!IsOpen()) {
return 0;
}
return std::fwrite(data.data(), sizeof(T), data.size(), file);
}
template <typename T>
bool ReadObject(T& object) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");
if (!IsOpen()) {
return false;
}
return std::fread(&object, sizeof(T), 1, file) == 1;
}
template <typename T>
size_t WriteRaw(const void* data, size_t size) const {
return std::fwrite(data, sizeof(T), size, file);
}
template <typename T>
bool WriteObject(const T& object) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");
if (!IsOpen()) {
return false;
}
return std::fwrite(&object, sizeof(T), 1, file) == 1;
}
std::string ReadString(size_t length) const;
size_t WriteString(std::span<const char> string) const {
return WriteSpan(string);
}
static size_t WriteBytes(const fs::path path, const auto& data) {
IOFile out(path, FileAccessMode::Write);
return out.Write(data);
}
private:
fs::path filePath{};
FileAccessMode fileAccessMode{};
FileMode fileType{};
std::FILE *file = nullptr;
uptr fileMapping = 0;
};
u64 GetDirectorySize(const fs::path &path);
} // namespace Base::FS

View file

@ -0,0 +1,442 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#include <fmt/format.h>
#include <functional>
#include <map>
#include "Base/BoundedQueue.h"
#include "Base/IoFile.h"
#include "Base/PathUtil.h"
#include "Base/StringUtil.h"
#include "Base/Thread.h"
#include "Backend.h"
#include "Log.h"
#include "LogEntry.h"
#include "TextFormatter.h"
namespace Base {
namespace Log {
using namespace Base::FS;
// Base backend with shell functions
class BaseBackend {
public:
virtual ~BaseBackend() = default;
virtual void Write(const Entry &entry) = 0;
virtual void Flush() = 0;
};
// Backend that writes to stdout and with color
class ColorConsoleBackend : public BaseBackend {
public:
explicit ColorConsoleBackend() = default;
~ColorConsoleBackend() = default;
void Write(const Entry &entry) override {
if (enabled.load(std::memory_order_relaxed)) {
PrintColoredMessage(entry);
}
}
void Flush() override {
// stdout shouldn't be buffered
}
void SetEnabled(bool enabled_) {
enabled = enabled_;
}
private:
std::atomic<bool> enabled = true;
};
// Backend that writes to a file passed into the constructor
class FileBackend : public BaseBackend {
public:
explicit FileBackend(const fs::path &filename)
: file(filename, FS::FileAccessMode::Write, FS::FileMode::TextMode)
{}
~FileBackend() {
file.Close();
}
void Write(const Entry &entry) override {
if (!enabled) {
return;
}
if (entry.formatted) {
bytesWritten += file.WriteString(FormatLogMessage(entry).append(1, '\n'));
}
else {
bytesWritten += file.WriteString(entry.message);
}
// Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
constexpr u64 writeLimit = 100_MB;
const bool writeLimitExceeded = bytesWritten > writeLimit;
if (entry.logLevel >= Level::Error || writeLimitExceeded) {
if (writeLimitExceeded) {
// Stop writing after the write limit is exceeded.
// Don't close the file so we can print a stacktrace if necessary
enabled = false;
}
file.Flush();
}
}
void Flush() override {
file.Flush();
}
private:
Base::FS::IOFile file;
std::atomic<bool> enabled = true;
size_t bytesWritten = 0;
};
bool currentlyInitialising = true;
// Static state as a singleton.
class Impl {
public:
static Impl& Instance() {
if (!instance) {
throw std::runtime_error("Using Logging instance before its initialization");
}
return *instance;
}
static void Initialize(const std::string_view logFile) {
if (instance) {
LOG_WARNING(Log, "Reinitializing logging backend");
return;
}
const auto logDir = GetUserPath(PathType::LogDir);
Filter filter;
//filter.ParseFilterString(Config::getLogFilter());
instance = std::unique_ptr<Impl, decltype(&Deleter)>(new Impl(logDir / logFile, filter), Deleter);
currentlyInitialising = false;
}
static bool IsActive() {
return instance != nullptr;
}
static void Start() {
instance->StartBackendThread();
}
static void Stop() {
instance->StopBackendThread();
}
Impl(const Impl&) = delete;
Impl& operator=(const Impl&) = delete;
Impl(Impl&&) = delete;
Impl& operator=(Impl&&) = delete;
void SetGlobalFilter(const Filter& f) {
filter = f;
}
void SetColorConsoleBackendEnabled(bool enabled) {
colorConsoleBackend->SetEnabled(enabled);
}
void PushEntry(Class logClass, Level logLevel, const char *filename, u32 lineNum,
const char *function, const std::string &message) {
if (!filter.CheckMessage(logClass, logLevel)) {
return;
}
using std::chrono::duration_cast;
using std::chrono::microseconds;
using std::chrono::steady_clock;
const Entry entry = {
.timestamp = duration_cast<microseconds>(steady_clock::now() - timeOrigin),
.logClass = logClass,
.logLevel = logLevel,
.filename = filename,
.lineNum = lineNum,
.function = function,
.message = message,
};
if (Config::logType() == "async") {
messageQueue.EmplaceWait(entry);
} else {
ForEachBackend([&entry](BaseBackend* backend) { if (backend) { backend->Write(entry); } });
std::fflush(stdout);
}
}
void PushEntryNoFmt(Class logClass, Level logLevel, const std::string &message) {
if (!filter.CheckMessage(logClass, logLevel)) {
return;
}
using std::chrono::duration_cast;
using std::chrono::microseconds;
using std::chrono::steady_clock;
const Entry entry = {
.timestamp = duration_cast<microseconds>(steady_clock::now() - timeOrigin),
.logClass = logClass,
.logLevel = logLevel,
.message = message,
.formatted = false
};
if (Config::logType() == "async") {
messageQueue.EmplaceWait(entry);
} else {
ForEachBackend([&entry](BaseBackend* backend) { if (backend) { backend->Write(entry); } });
std::fflush(stdout);
}
}
private:
Impl(const fs::path &fileBackendFilename, const Filter &filter) :
filter(filter) {
#ifdef _WIN32
HANDLE conOut = GetStdHandle(STD_OUTPUT_HANDLE);
// Get current console mode
ul32 mode = 0;
GetConsoleMode(conOut, &mode);
// Set WinAPI to use a more 'modern' approach, by enabling VT
// Allows ASCII escape codes
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
// Write adjusted mode back
SetConsoleMode(conOut, mode);
#endif
colorConsoleBackend = std::make_unique<ColorConsoleBackend>();
fileBackend = std::make_unique<FileBackend>(fileBackendFilename);
}
~Impl() {
Stop();
fileBackend.reset();
colorConsoleBackend.reset();
}
void StartBackendThread() {
backendThread = std::jthread([this](std::stop_token stopToken) {
Base::SetCurrentThreadName("[Xe] Log");
Entry entry = {};
const auto writeLogs = [this, &entry]() {
ForEachBackend([&entry](BaseBackend *backend) { backend->Write(entry); });
};
while (!stopToken.stop_requested()) {
if (messageQueue.PopWait(entry, stopToken))
writeLogs();
}
// Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a
// case where a system is repeatedly spamming logs even on close.
s32 maxLogsToWrite = filter.IsDebug() ? std::numeric_limits<s32>::max() : 100;
while (maxLogsToWrite-- > 0) {
if (messageQueue.TryPop(entry)) {
writeLogs();
} else {
break;
}
}
});
}
void StopBackendThread() {
backendThread.request_stop();
if (backendThread.joinable()) {
backendThread.join();
}
ForEachBackend([](BaseBackend *backend) { backend->Flush(); });
}
void ForEachBackend(std::function<void(BaseBackend*)> lambda) {
lambda(colorConsoleBackend.get());
lambda(fileBackend.get());
}
static void Deleter(Impl* ptr) {
delete ptr;
}
static inline std::unique_ptr<Impl, decltype(&Deleter)> instance{ nullptr, Deleter };
Filter filter;
std::unique_ptr<ColorConsoleBackend> colorConsoleBackend = {};
std::unique_ptr<FileBackend> fileBackend = {};
MPSCQueue<Entry> messageQueue = {};
std::chrono::steady_clock::time_point timeOrigin = std::chrono::steady_clock::now();
std::jthread backendThread;
};
std::vector<fs::path> filepaths{};
void DeleteOldLogs(const fs::path& path, u64 num_logs, const u16 logLimit) {
const std::string filename = path.filename().string();
const std::chrono::time_point Now = std::chrono::system_clock::now();
const time_t timeNow = std::chrono::system_clock::to_time_t(Now);
const tm* time = std::localtime(&timeNow);
// We want to get rid of anything that isn't that current day's date
const std::string currentDate = fmt::format("{}-{}-{}", time->tm_mon + 1, time->tm_mday, 1900 + time->tm_year);
if (filename.find(currentDate) == std::string::npos) {
fs::remove_all(path);
return;
}
// We want to delete in date of creation, so just add it to a array
if (num_logs >= logLimit) {
filepaths.push_back(path);
}
}
u64 CreateIntegralTimestamp(const std::string &date) {
const u64 monthPos = date.find('-');
if (monthPos == std::string::npos) {
return 0;
}
const std::string month = date.substr(0, monthPos);
const u64 dayPos = date.find('-', monthPos);
if (dayPos == std::string::npos) {
return 0;
}
const u64 yearPos = date.find('-', dayPos);
const std::string day = date.substr(monthPos+1);
if (yearPos == std::string::npos) {
return 0;
}
const std::string year = date.substr(yearPos + 1);
const u64 yearInt = std::stoull(year);
const std::string timestamp = fmt::format("{}{}{}", month, day, yearInt - 1900);
return std::stoull(timestamp);
}
void CleanupOldLogs(const std::string_view &logFileBase, const fs::path &logDir, const u16 logLimit) {
const fs::path LogFile = logFileBase;
// Track how many logs we have
size_t numLogs = 0;
for (auto &entry : fs::directory_iterator(logDir)) {
if (entry.is_regular_file()) {
const fs::path path = entry.path();
const std::string ext = path.extension().string();
if (!path.has_extension()) {
// Skip anything that isn't a log file
continue;
}
if (ext != LogFile.extension()) {
// Skip anything that isn't a log file
continue;
}
numLogs++;
DeleteOldLogs(path, numLogs, logLimit);
} else {
// Skip anything that isn't a file
continue;
}
}
if (filepaths.empty()) {
return;
}
u64 numToDelete{ logLimit };
std::map<u64, fs::path> date_sorted_paths{};
for (const auto &path : filepaths) {
const std::string stem = path.stem().string();
u64 basePos = stem.find('_');
// If we cannot get the base, just delete it
if (basePos == std::string::npos) {
numToDelete--;
fs::remove_all(path);
} else {
const std::string base = stem.substr(0, basePos);
const u64 datePos = base.find('_', basePos+1);
const std::string date = base.substr(datePos+1);
const u64 dateInt = CreateIntegralTimestamp(date);
if (datePos == std::string::npos) {
// If we cannot find the date, just delete it
numToDelete--;
fs::remove_all(path);
} else {
const u64 timePos = base.find('_', datePos+1);
const std::string time = base.substr(timePos+1);
const u64 timestamp = CreateIntegralTimestamp(time);
if (!timestamp) {
numToDelete--;
fs::remove_all(path);
continue;
}
date_sorted_paths.insert({ dateInt + timestamp, path });
}
}
}
// Start deleting based off timestamp
for (const auto &entry : date_sorted_paths) {
fs::remove_all(entry.second);
}
}
void Initialize(const std::string_view &logFile) {
// Create directory vars to so we can use fs::path::stem
const fs::path LogDir = GetUserPath(PathType::LogDir);
const fs::path LogFile = LOG_FILE;
const fs::path LogFileStem = LogFile.stem();
const fs::path LogFileName = LogFile.filename();
// This is to make string_view happy
const std::string LogFileStemStr = LogFileStem.string();
const std::string LogFileNameStr = LogFileName.string();
// Setup filename
const std::string_view filestemBase = logFile.empty() ? LogFileStemStr : logFile;
const std::string_view filenameBase = logFile.empty() ? LogFileNameStr : logFile;
const std::chrono::time_point now = std::chrono::system_clock::now();
const time_t timeNow = std::chrono::system_clock::to_time_t(now);
const tm *time = std::localtime(&timeNow);
const std::string currentTime = fmt::format("{}-{}-{}", time->tm_hour, time->tm_min, time->tm_sec);
const std::string currentDate = fmt::format("{}-{}-{}", time->tm_mon + 1, time->tm_mday, 1900 + time->tm_year);
const std::string filename = fmt::format("{}_{}_{}.txt", filestemBase, currentDate, currentTime);
CleanupOldLogs(filenameBase, LogDir);
Impl::Initialize(logFile.empty() ? filename : logFile);
}
bool IsActive() {
return Impl::IsActive();
}
void Start() {
Impl::Start();
}
void Stop() {
Impl::Stop();
}
void SetGlobalFilter(const Filter &filter) {
Impl::Instance().SetGlobalFilter(filter);
}
void SetColorConsoleBackendEnabled(bool enabled) {
Impl::Instance().SetColorConsoleBackendEnabled(enabled);
}
void FmtLogMessageImpl(Class logClass, Level logLevel, const char *filename,
u32 lineNum, const char *function, const char *format,
const fmt::format_args &args) {
if (!currentlyInitialising) [[likely]] {
Impl::Instance().PushEntry(logClass, logLevel, filename, lineNum, function,
fmt::vformat(format, args));
}
}
void NoFmtMessage(Class logClass, Level logLevel, const std::string &message) {
if (!currentlyInitialising) [[likely]] {
Impl::Instance().PushEntryNoFmt(logClass, logLevel, message);
}
}
} // namespace Log
} // namespace Base

View file

@ -0,0 +1,37 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include <string_view>
#include <filesystem>
#include "Base/PathUtil.h"
#include "Filter.h"
namespace Base {
namespace Log {
class Filter;
/// Cleans up logs from previous days, and any logs within the desired limit
void CleanupOldLogs(const std::string_view &logFileBase, const fs::path &logDir, const u16 logLimit = 50);
/// Initializes the logging system
void Initialize(const std::string_view &logFile = {});
bool IsActive();
/// Starts the logging threads
void Start();
/// Explictily stops the logger thread and flushes the buffers
void Stop();
/// The global filter will prevent any messages from even being processed if they are filtered
void SetGlobalFilter(const Filter &filter);
void SetColorConsoleBackendEnabled(bool enabled);
} // namespace Log
} // namespace Base

View file

@ -0,0 +1,153 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#include "Base/Assert.h"
#include "Filter.h"
namespace Base {
namespace Log {
template <typename It>
Level GetLevelByName(const It begin, const It end) {
for (u8 i = 0; i < static_cast<u8>(Level::Count); ++i) {
const char* level_name = GetLevelName(static_cast<Level>(i));
if (std::string_view(begin, end).compare(level_name) == 0) {
return static_cast<Level>(i);
}
}
return Level::Count;
}
template <typename It>
Class GetClassByName(const It begin, const It end) {
for (u8 i = 0; i < static_cast<u8>(Class::Count); ++i) {
const char* level_name = GetLogClassName(static_cast<Class>(i));
if (std::string_view(begin, end).compare(level_name) == 0) {
return static_cast<Class>(i);
}
}
return Class::Count;
}
template <typename Iterator>
bool ParseFilterRule(Filter &instance, Iterator begin, Iterator end) {
const auto levelSeparator = std::find(begin, end, ':');
if (levelSeparator == end) {
LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: {}", std::string_view(begin, end));
return false;
}
const Level level = GetLevelByName(levelSeparator + 1, end);
if (level == Level::Count) {
LOG_ERROR(Log, "Unknown log level in filter: {}", std::string_view(begin, end));
return false;
}
if (std::string_view(begin, levelSeparator).compare("*") == 0) {
instance.ResetAll(level);
return true;
}
const Class logClass = GetClassByName(begin, levelSeparator);
if (logClass == Class::Count) {
LOG_ERROR(Log, "Unknown log class in filter: {}", std::string(begin, end));
return false;
}
instance.SetClassLevel(logClass, level);
return true;
}
/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
#define ALL_LOG_CLASSES() \
CLS(Log) \
CLS(Base) \
SUB(Base, Filesystem) \
CLS(Config) \
CLS(Debug) \
CLS(System) \
CLS(Render) \
CLS(ARM) \
// GetClassName is a macro defined by Windows.h, grrr...
const char* GetLogClassName(Class logClass) {
switch (logClass) {
#define CLS(x) \
case Class::x: \
return #x;
#define SUB(x, y) \
case Class::x##_##y: \
return #x "." #y;
ALL_LOG_CLASSES()
#undef CLS
#undef SUB
case Class::Count:
default:
break;
}
UNREACHABLE();
}
const char* GetLevelName(Level logLevel) {
#define LVL(x) \
case Level::x: \
return #x
switch (logLevel) {
LVL(Trace);
LVL(Debug);
LVL(Info);
LVL(Warning);
LVL(Error);
LVL(Critical);
LVL(Guest);
case Level::Count:
default:
break;
}
#undef LVL
UNREACHABLE();
}
Filter::Filter(Level defaultLevel) {
ResetAll(defaultLevel);
}
void Filter::ResetAll(Level level) {
classLevels.fill(level);
}
void Filter::SetClassLevel(Class logClass, Level level) {
classLevels[static_cast<size_t>(logClass)] = level;
}
void Filter::ParseFilterString(const std::string_view &filterView) {
auto clause_begin = filterView.cbegin();
while (clause_begin != filterView.cend()) {
auto clause_end = std::find(clause_begin, filterView.cend(), ' ');
// If clause isn't empty
if (clause_end != clause_begin) {
ParseFilterRule(*this, clause_begin, clause_end);
}
if (clause_end != filterView.cend()) {
// Skip over the whitespace
++clause_end;
}
clause_begin = clause_end;
}
}
bool Filter::CheckMessage(Class logClass, Level level) const {
return static_cast<u8>(level) >=
static_cast<u8>(classLevels[static_cast<size_t>(logClass)]);
}
bool Filter::IsDebug() const {
return std::any_of(classLevels.begin(), classLevels.end(), [](const Level& l) {
return static_cast<u8>(l) <= static_cast<u8>(Level::Debug);
});
}
} // namespace Log
} // namespace Base

View file

@ -0,0 +1,66 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include <array>
#include "LogTypes.h"
namespace Base {
namespace Log {
/*
* Returns the name of the passed log class as a C-string. Subclasses are separated by periods
* instead of underscores as in the enumeration.
*/
const char* GetLogClassName(Class log_class);
/*
* Returns the name of the passed log level as a C-string.
*/
const char* GetLevelName(Level log_level);
/*
* Implements a log message filter which allows different log classes to have different minimum
* severity levels. The filter can be changed at runtime and can be parsed from a string to allow
* editing via the interface or loading from a configuration file.
*/
class Filter {
public:
/// Initializes the filter with all classes having `default_level` as the minimum level.
explicit Filter(Level defaultLevel = Level::Info);
/// Resets the filter so that all classes have `level` as the minimum displayed level.
void ResetAll(Level level);
/// Sets the minimum level of `log_class` (and not of its subclasses) to `level`.
void SetClassLevel(Class logClass, Level level);
/*
* Parses a filter string and applies it to this filter.
*
* A filter string consists of a space-separated list of filter rules, each of the format
* `<class>:<level>`. `<class>` is a log class name, with subclasses separated using periods.
* `*` is allowed as a class name and will reset all filters to the specified level. `<level>`
* a severity level name which will be set as the minimum logging level of the matched classes.
* Rules are applied left to right, with each rule overriding previous ones in the sequence.
*
* A few examples of filter rules:
* - `*:Info` -- Resets the level of all classes to Info.
* - `Service:Info` -- Sets the level of Service to Info.
* - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace.
*/
void ParseFilterString(const std::string_view &filterView);
/// Matches class/level combination against the filter, returning true if it passed.
bool CheckMessage(Class logClass, Level level) const;
/// Returns true if any logging classes are set to debug
bool IsDebug() const;
private:
std::array<Level, static_cast<const size_t>(Class::Count)> classLevels;
};
} // namespace Log
} // namespace Base

78
core/Base/Logging/Log.h Normal file
View file

@ -0,0 +1,78 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include <algorithm>
#include <fmt/format.h>
#include "Base/Config.h"
#include "LogTypes.h"
namespace Base {
namespace Log {
constexpr const char* TrimSourcePath(const std::string_view &source) {
const auto rfind = [source](const std::string_view match) {
return source.rfind(match) == source.npos ? 0 : (source.rfind(match) + match.size());
};
const auto idx = std::max({ rfind("/"), rfind("\\") });
return source.data() + idx;
}
/// Logs a message to the global logger, using fmt
void FmtLogMessageImpl(Class logClass, Level logLevel, const char *filename,
u32 lineNum, const char *function, const char *format,
const fmt::format_args& args);
/// Logs a message without any formatting
void NoFmtMessage(Class logClass, Level logLevel, const std::string &message);
template <typename... Args>
void FmtLogMessage(Class logClass, Level logLevel, const char *filename, u32 lineNum,
const char *function, const char *format, const Args&... args) {
FmtLogMessageImpl(logClass, logLevel, filename, lineNum, function, format,
fmt::make_format_args(args...));
}
} // namespace Log
} // namespace Base
// Define the fmt lib macros
#define LOG_GENERIC(logClass, logLevel, ...) \
Base::Log::FmtLogMessage(logClass, logLevel, Base::Log::TrimSourcePath(__FILE__), \
__LINE__, __func__, __VA_ARGS__)
#ifdef DEBUG_BUILD
#define LOG_TRACE(logClass, ...) \
if (Config::log.debugOnly) \
Base::Log::FmtLogMessage(Base::Log::Class::logClass, Base::Log::Level::Trace, \
Base::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
__VA_ARGS__)
#else
#define LOG_TRACE(logClass, ...) ;
#endif
#ifdef DEBUG_BUILD
#define LOG_DEBUG(logClass, ...) \
Base::Log::FmtLogMessage(Base::Log::Class::logClass, Base::Log::Level::Debug, \
Base::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
__VA_ARGS__)
#else
#define LOG_DEBUG(logClass, ...) ;
#endif
#define LOG_INFO(logClass, ...) \
Base::Log::FmtLogMessage(Base::Log::Class::logClass, Base::Log::Level::Info, \
Base::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
__VA_ARGS__)
#define LOG_WARNING(logClass, ...) \
Base::Log::FmtLogMessage(Base::Log::Class::logClass, Base::Log::Level::Warning, \
Base::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
__VA_ARGS__)
#define LOG_ERROR(logClass, ...) \
Base::Log::FmtLogMessage(Base::Log::Class::logClass, Base::Log::Level::Error, \
Base::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
__VA_ARGS__)
#define LOG_CRITICAL(logClass, ...) \
Base::Log::FmtLogMessage(Base::Log::Class::logClass, Base::Log::Level::Critical, \
Base::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
__VA_ARGS__)

View file

@ -0,0 +1,28 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include <chrono>
#include "LogTypes.h"
namespace Base {
namespace Log {
/*
* A log entry. Log entries are store in a structured format to permit more varied output
* formatting on different frontends, as well as facilitating filtering and aggregation.
*/
struct Entry {
std::chrono::microseconds timestamp = {};
Class logClass = {};
Level logLevel = {};
const char *filename = nullptr;
u32 lineNum = 0;
std::string function = {};
std::string message = {};
bool formatted = true;
};
} // namespace Log
} // namespace Base

View file

@ -0,0 +1,41 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include "Base/Types.h"
namespace Base {
namespace Log {
/// Specifies the severity or level of detail of the log message
enum class Level : const u8 {
Trace, ///< Extremely detailed and repetitive debugging information that is likely to pollute logs
Debug, ///< Less detailed debugging information
Info, ///< Status information from important points during execution
Warning, ///< Minor or potential problems found during execution of a task
Error, ///< Major problems found during execution of a task that prevent it from being completed
Critical, ///< Major problems during execution that threaten the stability of the entire application
Guest, ///< Output from the guest system
Count ///< Total number of logging levels
};
/*
* Specifies the sub-system that generated the log message
*
* @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in
* filtercpp
*/
enum class Class : const u8 {
Log, // Messages about the log system itself
Base, // System base routines: FS, logging, etc
Base_Filesystem, // Filesystem messages
Config, // Emulator configuration (including commandline)
Debug, // Debugging tools
System, // Base System messages
Render, // OpenGL and Window messages
ARM,
Count // Total number of logging classes
};
} // namespace Log
} // namespace Base

View file

@ -0,0 +1,68 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#include "Base/Assert.h"
#include "Base/Config.h"
#include "TextFormatter.h"
#include "Filter.h"
#include "LogEntry.h"
namespace Base {
namespace Log {
std::string FormatLogMessage(const Entry &entry) {
const char *className = GetLogClassName(entry.logClass);
const char *levelName = GetLevelName(entry.logLevel);
if (Config::isLogAdvanced() && entry.filename) {
return fmt::format("[{}] <{}> {}:{}:{}: {}", className, levelName, entry.filename,
entry.function, entry.lineNum, entry.message);
} else {
return fmt::format("[{}] <{}> {}", className, levelName, entry.message);
}
}
#define ESC "\x1b"
void PrintMessage(const std::string &color, const Entry &entry) {
std::string msg = entry.formatted ? FormatLogMessage(entry) : entry.message;
const std::string str = color + msg.append(ESC "[0m") + (entry.formatted ? "\n" : "");
fputs(str.c_str(), stdout);
}
void PrintColoredMessage(const Entry &entry) {
// NOTE: Custom colors can be achieved
// std::format("\x1b[{};2;{};{};{}m", color.bg ? 48 : 38, color.r, color.g, color.b)
const char *color = "";
switch (entry.logLevel) {
case Level::Trace: // Grey
color = ESC "[1;30m";
break;
case Level::Debug: // Cyan
color = ESC "[0;36m";
break;
case Level::Info: // Bright gray
color = ESC "[0;37m";
break;
case Level::Warning: // Bright yellow
color = ESC "[1;33m";
break;
case Level::Error: // Bright red
color = ESC "[1;31m";
break;
case Level::Critical: // Bright magenta
color = ESC "[1;35m";
break;
case Level::Guest: // Green
color = ESC "[0;92m";
break;
case Level::Count:
UNREACHABLE();
}
PrintMessage(color, entry);
}
#undef ESC
} // namespace Log
} // namespace Base

View file

@ -0,0 +1,23 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include <string>
#include <string_view>
namespace Base {
namespace Log {
struct Entry;
/// Formats a log entry into the provided text buffer.
std::string FormatLogMessage(const Entry &entry);
/// Formats and prints a log entry to stderr.
void PrintMessage(const std::string &color, const Entry &entry);
/// Prints the same message as `PrintMessage`, but colored according to the severity level.
void PrintColoredMessage(const Entry &entry);
} // namespace Log
} // namespace Base

115
core/Base/PathUtil.cpp Normal file
View file

@ -0,0 +1,115 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#include "PathUtil.h"
#include "Base/Logging/Log.h"
#include <fmt/format.h>
#include <unordered_map>
#include <fstream>
#ifdef _WIN32
#include <Windows.h>
#endif // _WIN32
#ifdef __APPLE__
#include <unistd.h>
#include <libproc.h>
#endif // __APPLE__
namespace Base {
namespace FS {
const fs::path GetBinaryDirectory() {
fs::path fspath = {};
#ifdef _WIN32
char path[256];
GetModuleFileNameA(nullptr, path, sizeof(path));
fspath = path;
#elif __linux__
fspath = fs::canonical("/proc/self/exe");
#elif __APPLE__
pid_t pid = getpid();
char path[PROC_PIDPATHINFO_MAXSIZE];
// While this is fine for a raw executable,
// an application bundle is read-only and these files
// should instead be placed in Application Support.
proc_pidpath(pid, path, sizeof(path));
fspath = path;
#else
// Unknown, just return rootdir
fspath = fs::current_path() / "Pound";
#endif
return fs::weakly_canonical(fmt::format("{}/..", fspath.string()));
}
static auto UserPaths = [] {
auto currentDir = fs::current_path();
auto binaryDir = GetBinaryDirectory();
bool nixos = false;
std::unordered_map<PathType, fs::path> paths;
const auto insert_path = [&](PathType pound_path, const fs::path &new_path, bool create = true) {
if (create && !fs::exists(new_path))
fs::create_directory(new_path);
paths.insert_or_assign(pound_path, new_path);
};
insert_path(PathType::BinaryDir, binaryDir, false);
// If we are in the nix store, it's read-only. Change to currentDir if needed
if (binaryDir.string().find("/nix/store/") != std::string::npos) {
nixos = true;
}
if (nixos) {
currentDir /= "files";
insert_path(PathType::RootDir, currentDir);
insert_path(PathType::LogDir, currentDir / LOG_DIR);
}
else {
insert_path(PathType::RootDir, currentDir, false);
insert_path(PathType::LogDir, binaryDir / LOG_DIR);
}
return paths;
}();
std::string PathToUTF8String(const fs::path &path) {
const auto u8_string = path.u8string();
return std::string{u8_string.begin(), u8_string.end()};
}
const fs::path &GetUserPath(PathType pound_path) {
return UserPaths.at(pound_path);
}
std::string GetUserPathString(PathType pound_path) {
return PathToUTF8String(GetUserPath(pound_path));
}
std::vector<FileInfo> ListFilesFromPath(const fs::path &path) {
std::vector<FileInfo> fileList;
fs::path _path = fs::weakly_canonical(path);
for (auto &entry : fs::directory_iterator{ _path }) {
FileInfo fileInfo;
if (entry.is_directory()) {
fileInfo.fileSize = 0;
fileInfo.fileType = FileType::Directory;
} else {
fileInfo.fileSize = fs::file_size(_path);
fileInfo.fileType = FileType::File;
}
fileInfo.filePath = entry.path();
fileInfo.fileName = entry.path().filename();
fileList.push_back(fileInfo);
}
return fileList;
}
void SetUserPath(PathType pound_path, const fs::path &new_path) {
UserPaths.insert_or_assign(pound_path, new_path);
}
} // namespace FS
} // namespace Base

52
core/Base/PathUtil.h Normal file
View file

@ -0,0 +1,52 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include <filesystem>
#include <vector>
namespace fs = std::filesystem;
namespace Base {
namespace FS {
enum class PathType {
BinaryDir, // Binary Path
RootDir, // Execution Path
LogDir, // Where log files are stored
};
enum FileType {
Directory,
File
};
// Represents a given file inside a directory.
typedef struct _FileInfo {
fs::path fileName; // The file name and extension
fs::path filePath; // The file path
size_t fileSize; // File size
FileType fileType; // File Type (directory/file)
} FileInfo;
constexpr auto LOG_DIR = "log";
constexpr auto LOG_FILE = "pound_log.txt";
// Converts a given fs::path to a UTF8 string.
[[nodiscard]] std::string PathToUTF8String(const fs::path &path);
// Returns a fs::path object containing the current 'User' path.
[[nodiscard]] const fs::path &GetUserPath(PathType user_path);
// Returns a string containing the current 'User' path.
[[nodiscard]] std::string GetUserPathString(PathType user_path);
// Returns a container with a list of the files inside the specified path.
[[nodiscard]] std::vector<FileInfo> ListFilesFromPath(const fs::path &path);
// Sets the current Path for a given PathType.
void SetUserPath(PathType user_path, const fs::path &new_path);
} // namespace FS
} // namespace Base

373
core/Base/PolyfillThread.h Normal file
View file

@ -0,0 +1,373 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
//
// TODO: remove this file when jthread is supported by all compilation targets
//
#pragma once
#if defined(__cpp_lib_jthread) || !defined(_MSVC_VER)
#include <mutex>
#include <chrono>
#include <condition_variable>
#include <stop_token>
#include <thread>
#include <utility>
namespace Base {
template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) {
cv.wait(lk, token, std::forward<Pred>(pred));
}
template <typename Rep, typename Period>
bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep, Period>& rel_time) {
std::condition_variable_any cv;
std::mutex m;
// Perform the timed wait.
std::unique_lock lk{m};
return !cv.wait_for(lk, token, rel_time, [&] { return token.stop_requested(); });
}
} // namespace Base
#else
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <thread>
#include <type_traits>
#include <utility>
namespace std {
namespace polyfill {
using stop_state_callback = size_t;
class stop_state {
public:
stop_state() = default;
~stop_state() = default;
bool request_stop() {
unique_lock lk{m_lock};
if (m_stop_requested) {
// Already set, nothing to do.
return false;
}
// Mark stop requested.
m_stop_requested = true;
while (!m_callbacks.empty()) {
// Get an iterator to the first element.
const auto it = m_callbacks.begin();
// Move the callback function out of the map.
function<void()> f;
swap(it->second, f);
// Erase the now-empty map element.
m_callbacks.erase(it);
// Run the callback.
if (f) {
f();
}
}
return true;
}
bool stop_requested() const {
unique_lock lk{m_lock};
return m_stop_requested;
}
stop_state_callback insert_callback(function<void()> f) {
unique_lock lk{m_lock};
if (m_stop_requested) {
// Stop already requested. Don't insert anything,
// just run the callback synchronously.
if (f) {
f();
}
return 0;
}
// Insert the callback.
stop_state_callback ret = ++m_next_callback;
m_callbacks.emplace(ret, std::move(f));
return ret;
}
void remove_callback(stop_state_callback cb) {
unique_lock lk{m_lock};
m_callbacks.erase(cb);
}
private:
mutable recursive_mutex m_lock;
map<stop_state_callback, function<void()>> m_callbacks;
stop_state_callback m_next_callback{0};
bool m_stop_requested{false};
};
} // namespace polyfill
class stop_token;
class stop_source;
struct nostopstate_t {
explicit nostopstate_t() = default;
};
inline constexpr nostopstate_t nostopstate{};
template <class Callback>
class stop_callback;
class stop_token {
public:
stop_token() noexcept = default;
stop_token(const stop_token&) noexcept = default;
stop_token(stop_token&&) noexcept = default;
stop_token& operator=(const stop_token&) noexcept = default;
stop_token& operator=(stop_token&&) noexcept = default;
~stop_token() = default;
void swap(stop_token& other) noexcept {
m_stop_state.swap(other.m_stop_state);
}
[[nodiscard]] bool stop_requested() const noexcept {
return m_stop_state && m_stop_state->stop_requested();
}
[[nodiscard]] bool stop_possible() const noexcept {
return m_stop_state != nullptr;
}
private:
friend class stop_source;
template <typename Callback>
friend class stop_callback;
stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(std::move(stop_state)) {}
private:
shared_ptr<polyfill::stop_state> m_stop_state;
};
class stop_source {
public:
stop_source() : m_stop_state(make_shared<polyfill::stop_state>()) {}
explicit stop_source(nostopstate_t) noexcept {}
stop_source(const stop_source&) noexcept = default;
stop_source(stop_source&&) noexcept = default;
stop_source& operator=(const stop_source&) noexcept = default;
stop_source& operator=(stop_source&&) noexcept = default;
~stop_source() = default;
void swap(stop_source& other) noexcept {
m_stop_state.swap(other.m_stop_state);
}
[[nodiscard]] stop_token get_token() const noexcept {
return stop_token(m_stop_state);
}
[[nodiscard]] bool stop_possible() const noexcept {
return m_stop_state != nullptr;
}
[[nodiscard]] bool stop_requested() const noexcept {
return m_stop_state && m_stop_state->stop_requested();
}
bool request_stop() noexcept {
return m_stop_state && m_stop_state->request_stop();
}
private:
friend class jthread;
explicit stop_source(shared_ptr<polyfill::stop_state> stop_state)
: m_stop_state(std::move(stop_state)) {}
private:
shared_ptr<polyfill::stop_state> m_stop_state;
};
template <typename Callback>
class stop_callback {
static_assert(is_nothrow_destructible_v<Callback>);
static_assert(is_invocable_v<Callback>);
public:
using callback_type = Callback;
template <typename C>
requires constructible_from<Callback, C>
explicit stop_callback(const stop_token& st,
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
: m_stop_state(st.m_stop_state) {
if (m_stop_state) {
m_callback = m_stop_state->insert_callback(std::move(cb));
}
}
template <typename C>
requires constructible_from<Callback, C>
explicit stop_callback(stop_token&& st,
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
: m_stop_state(std::move(st.m_stop_state)) {
if (m_stop_state) {
m_callback = m_stop_state->insert_callback(std::move(cb));
}
}
~stop_callback() {
if (m_stop_state && m_callback) {
m_stop_state->remove_callback(m_callback);
}
}
stop_callback(const stop_callback&) = delete;
stop_callback(stop_callback&&) = delete;
stop_callback& operator=(const stop_callback&) = delete;
stop_callback& operator=(stop_callback&&) = delete;
private:
shared_ptr<polyfill::stop_state> m_stop_state;
polyfill::stop_state_callback m_callback;
};
template <typename Callback>
stop_callback(stop_token, Callback) -> stop_callback<Callback>;
class jthread {
public:
using id = thread::id;
using native_handle_type = thread::native_handle_type;
jthread() noexcept = default;
template <typename F, typename... Args,
typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>>
explicit jthread(F&& f, Args&&... args)
: m_stop_state(make_shared<polyfill::stop_state>()),
m_thread(make_thread(std::forward<F>(f), std::forward<Args>(args)...)) {}
~jthread() {
if (joinable()) {
request_stop();
join();
}
}
jthread(const jthread&) = delete;
jthread(jthread&&) noexcept = default;
jthread& operator=(const jthread&) = delete;
jthread& operator=(jthread&& other) noexcept {
m_thread.swap(other.m_thread);
m_stop_state.swap(other.m_stop_state);
return *this;
}
void swap(jthread& other) noexcept {
m_thread.swap(other.m_thread);
m_stop_state.swap(other.m_stop_state);
}
[[nodiscard]] bool joinable() const noexcept {
return m_thread.joinable();
}
void join() {
m_thread.join();
}
void detach() {
m_thread.detach();
m_stop_state.reset();
}
[[nodiscard]] id get_id() const noexcept {
return m_thread.get_id();
}
[[nodiscard]] native_handle_type native_handle() {
return m_thread.native_handle();
}
[[nodiscard]] stop_source get_stop_source() noexcept {
return stop_source(m_stop_state);
}
[[nodiscard]] stop_token get_stop_token() const noexcept {
return stop_source(m_stop_state).get_token();
}
bool request_stop() noexcept {
return get_stop_source().request_stop();
}
[[nodiscard]] static u32 hardware_concurrency() noexcept {
return thread::hardware_concurrency();
}
private:
template <typename F, typename... Args>
thread make_thread(F&& f, Args&&... args) {
if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) {
return thread(std::forward<F>(f), get_stop_token(), std::forward<Args>(args)...);
} else {
return thread(std::forward<F>(f), std::forward<Args>(args)...);
}
}
shared_ptr<polyfill::stop_state> m_stop_state;
thread m_thread;
};
} // namespace std
namespace Base {
template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred pred) {
if (token.stop_requested()) {
return;
}
std::stop_callback callback(token, [&] {
{ std::scoped_lock lk2{*lk.mutex()}; }
cv.notify_all();
});
cv.wait(lk, [&] { return pred() || token.stop_requested(); });
}
template <typename Rep, typename Period>
bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep, Period>& rel_time) {
if (token.stop_requested()) {
return false;
}
bool stop_requested = false;
std::condition_variable cv;
std::mutex m;
std::stop_callback cb(token, [&] {
// Wake up the waiting thread.
{
std::scoped_lock lk{m};
stop_requested = true;
}
cv.notify_one();
});
// Perform the timed wait.
std::unique_lock lk{m};
return !cv.wait_for(lk, rel_time, [&] { return stop_requested; });
}
} // namespace Base
#endif

65
core/Base/StringUtil.cpp Normal file
View file

@ -0,0 +1,65 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#include "StringUtil.h"
namespace Base {
#ifdef _WIN32
static std::wstring CPToUTF16(u32 code_page, std::string_view input) {
const s32 size =
::MultiByteToWideChar(code_page, 0, input.data(), static_cast<s32>(input.size()), nullptr, 0);
if (size == 0) {
return {};
}
std::wstring output(size, L'\0');
if (size != ::MultiByteToWideChar(code_page, 0, input.data(), static_cast<s32>(input.size()),
&output[0], static_cast<s32>(output.size()))) {
output.clear();
}
return output;
}
#endif // ifdef _WIN32
std::string UTF16ToUTF8(const std::wstring_view &input) {
#ifdef _WIN32
const s32 size = ::WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast<s32>(input.size()),
nullptr, 0, nullptr, nullptr);
if (size == 0) {
return {};
}
std::string output(size, '\0');
if (size != ::WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast<s32>(input.size()),
&output[0], static_cast<s32>(output.size()), nullptr,
nullptr)) {
output.clear();
}
return output;
#else
// Very hacky way to get cross-platform wstring conversion
return std::filesystem::path(input).string();
#endif // ifdef _WIN32
}
std::wstring UTF8ToUTF16W(const std::string_view &input) {
#ifdef _WIN32
return CPToUTF16(CP_UTF8, input);
#else
// Very hacky way to get cross-platform wstring conversion
return std::filesystem::path(input).wstring();
#endif // ifdef _WIN32
}
std::string ToLower(const std::string &str) {
std::string out = str;
std::transform(str.begin(), str.end(), out.data(), ::tolower);
return out;
}
} // namespace Base

22
core/Base/StringUtil.h Normal file
View file

@ -0,0 +1,22 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include <string>
#include <string_view>
#include <algorithm>
#include <cctype>
#ifdef _WIN32
#include <Windows.h>
#else
#include <filesystem>
#endif
namespace Base {
[[nodiscard]] std::string UTF16ToUTF8(const std::wstring_view &input);
[[nodiscard]] std::wstring UTF8ToUTF16W(const std::string_view &str);
[[nodiscard]] std::string ToLower(const std::string &str);
} // namespace Base

214
core/Base/Thread.cpp Normal file
View file

@ -0,0 +1,214 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#include "Thread.h"
#include "Error.h"
#include "Logging/Log.h"
#ifdef __APPLE__
#include <mach/mach.h>
#include <mach/mach_time.h>
#include <pthread.h>
#elif defined(_WIN32)
#include <Windows.h>
#include "StringUtil.h"
#else
#if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
#include <pthread_np.h>
#else
#include <pthread.h>
#endif
#include <sched.h>
#endif
#ifndef _WIN32
#include <unistd.h>
#endif
#include <thread>
#include <algorithm>
#ifdef __FreeBSD__
#define cpu_set_t cpuset_t
#endif
namespace Base {
#ifdef __APPLE__
void SetCurrentThreadRealtime(const std::chrono::nanoseconds period_ns) {
// CPU time to grant.
const std::chrono::nanoseconds computation_ns = period_ns / 2;
// Determine the timebase for converting time to ticks.
struct mach_timebase_info timebase {};
mach_timebase_info(&timebase);
const auto ticks_per_ns =
static_cast<f64>(timebase.denom) / static_cast<f64>(timebase.numer);
const auto period_ticks =
static_cast<u32>(static_cast<f64>(period_ns.count()) * ticks_per_ns);
const auto computation_ticks =
static_cast<u32>(static_cast<f64>(computation_ns.count()) * ticks_per_ns);
thread_time_constraint_policy policy = {
.period = period_ticks,
.computation = computation_ticks,
// Should not matter since preemptible is false, but needs to be >= computation regardless.
.constraint = computation_ticks,
.preemptible = false,
};
int ret = thread_policy_set(
pthread_mach_thread_np(pthread_self()), THREAD_TIME_CONSTRAINT_POLICY,
reinterpret_cast<thread_policy_t>(&policy), THREAD_TIME_CONSTRAINT_POLICY_COUNT);
if (ret != KERN_SUCCESS) {
LOG_ERROR(Base, "Could not set thread to real-time with period {} ns: {}",
period_ns.count(), ret);
}
}
#else
void SetCurrentThreadRealtime(const std::chrono::nanoseconds period_ns) {
// Not implemented
}
#endif
#ifdef _WIN32
void SetCurrentThreadPriority(ThreadPriority new_priority) {
const auto handle = GetCurrentThread();
int windows_priority = 0;
switch (new_priority) {
case ThreadPriority::Low:
windows_priority = THREAD_PRIORITY_BELOW_NORMAL;
break;
case ThreadPriority::Normal:
windows_priority = THREAD_PRIORITY_NORMAL;
break;
case ThreadPriority::High:
windows_priority = THREAD_PRIORITY_ABOVE_NORMAL;
break;
case ThreadPriority::VeryHigh:
windows_priority = THREAD_PRIORITY_HIGHEST;
break;
case ThreadPriority::Critical:
windows_priority = THREAD_PRIORITY_TIME_CRITICAL;
break;
default:
windows_priority = THREAD_PRIORITY_NORMAL;
break;
}
SetThreadPriority(handle, windows_priority);
}
static void AccurateSleep(std::chrono::nanoseconds duration) {
LARGE_INTEGER interval{
.QuadPart = -1 * (duration.count() / 100u),
};
const HANDLE timer = ::CreateWaitableTimer(nullptr, TRUE, nullptr);
SetWaitableTimer(timer, &interval, 0, nullptr, nullptr, 0);
WaitForSingleObject(timer, INFINITE);
::CloseHandle(timer);
}
#else
void SetCurrentThreadPriority(ThreadPriority new_priority) {
pthread_t this_thread = pthread_self();
constexpr auto scheduling_type = SCHED_OTHER;
const s32 max_prio = sched_get_priority_max(scheduling_type);
const s32 min_prio = sched_get_priority_min(scheduling_type);
const u32 level = std::max(static_cast<u32>(new_priority) + 1, 4U);
struct sched_param params;
if (max_prio > min_prio) {
params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4;
} else {
params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4;
}
pthread_setschedparam(this_thread, scheduling_type, &params);
}
static void AccurateSleep(std::chrono::nanoseconds duration) {
std::this_thread::sleep_for(duration);
}
#endif
#if defined(_MSC_VER) || defined(__MINGW32__)
// Sets the debugger-visible name of the current thread.
void SetCurrentThreadName(const std::string_view &name) {
SetThreadDescription(GetCurrentThread(), UTF8ToUTF16W(name).data());
}
void SetThreadName(void *thread, const std::string_view &name) {
const char* nchar = name.data();
std::string truncated(nchar, std::min(name.size(), static_cast<size_t>(15)));
SetThreadDescription(thread, UTF8ToUTF16W(name).data());
}
#else // !MSVC_VER, so must be POSIX threads
// MinGW with the POSIX threading model does not support pthread_setname_np
#if !defined(_WIN32) || defined(_MSC_VER)
void SetCurrentThreadName(const std::string_view &name) {
const char* nchar = name.data();
#ifdef __APPLE__
pthread_setname_np(nchar);
#elif defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
pthread_set_name_np(pthread_self(), nchar);
#elif defined(__NetBSD__)
pthread_setname_np(pthread_self(), "%s", (void*)nchar);
#elif defined(__linux__)
// Linux limits thread names to 15 characters and will outright reject any
// attempt to set a longer name with ERANGE.
std::string truncated(nchar, std::min(name.size(), static_cast<size_t>(15)));
if (int e = pthread_setname_np(pthread_self(), truncated.c_str())) {
errno = e;
}
#else
pthread_setname_np(pthread_self(), nchar);
#endif
}
void SetThreadName(void *thread, const std::string_view &name) {
// TODO
}
#endif
#if defined(_WIN32)
void SetCurrentThreadName(const std::string_view &name) {
// Do Nothing on MinGW
}
void SetThreadName(void *thread, const std::string_view &name) {
// Do Nothing on MinGW
}
#endif
#endif
AccurateTimer::AccurateTimer(std::chrono::nanoseconds target_interval) :
target_interval(target_interval)
{}
void AccurateTimer::Start() {
const auto begin_sleep = std::chrono::high_resolution_clock::now();
if (total_wait.count() > 0) {
AccurateSleep(total_wait);
}
start_time = std::chrono::high_resolution_clock::now();
total_wait -= std::chrono::duration_cast<std::chrono::nanoseconds>(start_time - begin_sleep);
}
void AccurateTimer::End() {
const auto now = std::chrono::high_resolution_clock::now();
total_wait +=
target_interval - std::chrono::duration_cast<std::chrono::nanoseconds>(now - start_time);
}
} // namespace Base

43
core/Base/Thread.h Normal file
View file

@ -0,0 +1,43 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include <chrono>
namespace Base {
enum class ThreadPriority : u32 {
Low = 0,
Normal = 1,
High = 2,
VeryHigh = 3,
Critical = 4
};
void SetCurrentThreadRealtime(std::chrono::nanoseconds period_ns);
void SetCurrentThreadPriority(ThreadPriority new_priority);
void SetCurrentThreadName(const std::string_view &name);
void SetThreadName(void *thread, const std::string_view &name);
class AccurateTimer {
std::chrono::nanoseconds target_interval{};
std::chrono::nanoseconds total_wait{};
std::chrono::high_resolution_clock::time_point start_time;
public:
explicit AccurateTimer(std::chrono::nanoseconds target_interval);
void Start();
void End();
std::chrono::nanoseconds GetTotalWait() const {
return total_wait;
}
};
} // namespace Base

52
core/Base/Types.h Normal file
View file

@ -0,0 +1,52 @@
// Copyright 2025 Xenon Emulator Project. All rights reserved.
#pragma once
#include <fmt/format.h>
#include "Arch.h"
// Signed
using s8 = signed char;
using s16 = signed short;
using s32 = signed int;
using sl32 = signed long;
using s64 = signed long long;
// Unsigned
using u8 = unsigned char;
using u16 = unsigned short;
using u32 = unsigned int;
using ul32 = unsigned long;
using u64 = unsigned long long;
using uptr = uintptr_t;
// Floating point
using f32 = float;
using f64 = double;
// Function pointer
template <typename T>
requires std::is_function_v<T>
using fptr = std::add_pointer_t<T>;
// UDLs for memory size values
[[nodiscard]] constexpr u64 operator""_KB(const u64 x) {
return 1000ULL * x;
}
[[nodiscard]] constexpr u64 operator""_KiB(const u64 x) {
return 1024ULL * x;
}
[[nodiscard]] constexpr u64 operator""_MB(const u64 x) {
return 1000_KB * x;
}
[[nodiscard]] constexpr u64 operator""_MiB(const u64 x) {
return 1024_KiB * x;
}
[[nodiscard]] constexpr u64 operator""_GB(const u64 x) {
return 1000_MB * x;
}
[[nodiscard]] constexpr u64 operator""_GiB(const u64 x) {
return 1024_MiB * x;
}

View file

@ -1,3 +1,5 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.
#include "jit.h"
#ifdef WIN32
@ -6,8 +8,6 @@
#include <sys/mman.h>
#endif
#include <cstring>
#include <cstdio>
#include <vector>
using JitFunc = void (*)();
@ -15,9 +15,9 @@ using JitFunc = void (*)();
void JIT::translate_and_run(CPU &cpu)
{
#ifdef WIN32
uint8_t *code = (uint8_t*)VirtualAlloc(NULL, 64, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
u8 *code = (u8 *)VirtualAlloc(NULL, 64, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
#else
uint8_t *code = (uint8_t *)mmap(nullptr, 64, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0);
u8 *code = (u8 *)mmap(nullptr, 64, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0);
#endif
size_t offset = 0;
@ -27,7 +27,7 @@ void JIT::translate_and_run(CPU &cpu)
{ // MOVZ placeholder
code[offset++] = 0x48; // mov rax, imm64
code[offset++] = 0xB8;
uint64_t imm = 5;
u64 imm = 5;
std::memcpy(&code[offset], &imm, sizeof(imm));
offset += 8;
}
@ -36,7 +36,7 @@ void JIT::translate_and_run(CPU &cpu)
{ // ADD placeholder
code[offset++] = 0x48; // add rax, imm32
code[offset++] = 0x05;
uint32_t addval = 3;
u32 addval = 3;
std::memcpy(&code[offset], &addval, sizeof(addval));
offset += 4;
}
@ -44,7 +44,7 @@ void JIT::translate_and_run(CPU &cpu)
code[offset++] = 0xC3; // ret
JitFunc fn = reinterpret_cast<JitFunc>(code);
uint64_t result;
u64 result;
asm volatile(
"call *%1\n"
"mov %%rax, %0\n"

View file

@ -1,5 +1,7 @@
#ifndef JIT_H
#define JIT_H
// Copyright 2025 Pound Emulator Project. All rights reserved.
#pragma once
#include "ARM/cpu.h"
class JIT
@ -7,5 +9,3 @@ class JIT
public:
void translate_and_run(CPU &cpu);
};
#endif

View file

@ -1,8 +0,0 @@
# Core folder
### !! The file system implementation is copied from Yuzu as the filesystem is believed to be the same betweeen both consoles
dir: /core/
This is where all of the main universal code for emulating the Switch/Switch2 goes.

View file

@ -0,0 +1 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.

View file

@ -1,84 +0,0 @@
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/logging/log.h"
// Sometimes we want to try to continue even after hitting an assert.
// However touching this file yields a global recompilation as this header is included almost
// everywhere. So let's just move the handling of the failed assert to a single cpp file.
void assert_fail_impl();
[[noreturn]] void unreachable_impl();
#ifdef _MSC_VER
#define YUZU_NO_INLINE __declspec(noinline)
#else
#define YUZU_NO_INLINE __attribute__((noinline))
#endif
#define ASSERT(_a_) \
([&]() YUZU_NO_INLINE { \
if (!(_a_)) [[unlikely]] { \
LOG_CRITICAL(Debug, "Assertion Failed!"); \
assert_fail_impl(); \
} \
}())
#define ASSERT_MSG(_a_, ...) \
([&]() YUZU_NO_INLINE { \
if (!(_a_)) [[unlikely]] { \
LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \
assert_fail_impl(); \
} \
}())
#define UNREACHABLE() \
do { \
LOG_CRITICAL(Debug, "Unreachable code!"); \
unreachable_impl(); \
} while (0)
#define UNREACHABLE_MSG(...) \
do { \
LOG_CRITICAL(Debug, "Unreachable code!\n" __VA_ARGS__); \
unreachable_impl(); \
} while (0)
#ifdef _DEBUG
#define DEBUG_ASSERT(_a_) ASSERT(_a_)
#define DEBUG_ASSERT_MSG(_a_, ...) ASSERT_MSG(_a_, __VA_ARGS__)
#else // not debug
#define DEBUG_ASSERT(_a_) \
do { \
} while (0)
#define DEBUG_ASSERT_MSG(_a_, _desc_, ...) \
do { \
} while (0)
#endif
#define UNIMPLEMENTED() ASSERT_MSG(false, "Unimplemented code!")
#define UNIMPLEMENTED_MSG(...) ASSERT_MSG(false, __VA_ARGS__)
#define UNIMPLEMENTED_IF(cond) ASSERT_MSG(!(cond), "Unimplemented code!")
#define UNIMPLEMENTED_IF_MSG(cond, ...) ASSERT_MSG(!(cond), __VA_ARGS__)
// If the assert is ignored, execute _b_
#define ASSERT_OR_EXECUTE(_a_, _b_) \
do { \
ASSERT(_a_); \
if (!(_a_)) [[unlikely]] { \
_b_ \
} \
} while (0)
// If the assert is ignored, execute _b_
#define ASSERT_OR_EXECUTE_MSG(_a_, _b_, ...) \
do { \
ASSERT_MSG(_a_, __VA_ARGS__); \
if (!(_a_)) [[unlikely]] { \
_b_ \
} \
} while (0)

View file

@ -1,53 +0,0 @@
// SPDX-FileCopyrightText: 2012 Gekko Emulator
// SPDX-FileContributor: ShizZy <shizzy247@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
/**
* Copyright (C) 2005-2012 Gekko Emulator
*
* @file common_types.h
* @author ShizZy <shizzy247@gmail.com>
* @date 2012-02-11
* @brief Common types used throughout the project
*
* @section LICENSE
* This program 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.
*
* This program 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 at
* http://www.gnu.org/copyleft/gpl.html
*
* Official project repository can be found at:
* http://code.google.com/p/gekko-gc-emu/
*/
#pragma once
#include <array>
#include <cstdint>
using u8 = std::uint8_t; ///< 8-bit unsigned byte
using u16 = std::uint16_t; ///< 16-bit unsigned short
using u32 = std::uint32_t; ///< 32-bit unsigned word
using u64 = std::uint64_t; ///< 64-bit unsigned int
using s8 = std::int8_t; ///< 8-bit signed byte
using s16 = std::int16_t; ///< 16-bit signed short
using s32 = std::int32_t; ///< 32-bit signed word
using s64 = std::int64_t; ///< 64-bit signed int
using f32 = float; ///< 32-bit floating point
using f64 = double; ///< 64-bit floating point
using VAddr = u64; ///< Represents a pointer in the userspace virtual address space.
using DAddr = u64; ///< Represents a pointer in the device specific virtual address space.
using PAddr = u64; ///< Represents a pointer in the ARM11 physical address space.
using GPUVAddr = u64; ///< Represents a pointer in the GPU virtual address space.
using u128 = std::array<std::uint64_t, 2>;
static_assert(sizeof(u128) == 16, "u128 must be 128 bits wide");

View file

@ -1,316 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include <vector>
// #include "common/fs/fs_util.h"
namespace Common::FS {
enum class YuzuPath {
YuzuDir, // Where yuzu stores its data.
AmiiboDir, // Where Amiibo backups are stored.
CacheDir, // Where cached filesystem data is stored.
ConfigDir, // Where config files are stored.
CrashDumpsDir, // Where crash dumps are stored.
DumpDir, // Where dumped data is stored.
KeysDir, // Where key files are stored.
LoadDir, // Where cheat/mod files are stored.
LogDir, // Where log files are stored.
NANDDir, // Where the emulated NAND is stored.
PlayTimeDir, // Where play time data is stored.
ScreenshotsDir, // Where yuzu screenshots are stored.
SDMCDir, // Where the emulated SDMC is stored.
ShaderDir, // Where shaders are stored.
TASDir, // Where TAS scripts are stored.
IconsDir, // Where Icons for Windows shortcuts are stored.
};
/**
* Validates a given path.
*
* A given path is valid if it meets these conditions:
* - The path is not empty
* - The path is not too long
*
* @param path Filesystem path
*
* @returns True if the path is valid, false otherwise.
*/
[[nodiscard]] bool ValidatePath(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] bool ValidatePath(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return ValidatePath(ToU8String(path));
} else {
return ValidatePath(std::filesystem::path{path});
}
}
#endif
/**
* Concatenates two filesystem paths together.
*
* This is needed since the following occurs when using std::filesystem::path's operator/:
* first: "/first/path"
* second: "/second/path" (Note that the second path has a directory separator in the front)
* first / second yields "/second/path" when the desired result is first/path/second/path
*
* @param first First filesystem path
* @param second Second filesystem path
*
* @returns A concatenated filesystem path.
*/
[[nodiscard]] std::filesystem::path ConcatPath(const std::filesystem::path& first,
const std::filesystem::path& second);
#ifdef _WIN32
template <typename Path1, typename Path2>
[[nodiscard]] std::filesystem::path ConcatPath(const Path1& first, const Path2& second) {
using ValueType1 = typename Path1::value_type;
using ValueType2 = typename Path2::value_type;
if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) {
return ConcatPath(ToU8String(first), ToU8String(second));
} else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) {
return ConcatPath(ToU8String(first), second);
} else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) {
return ConcatPath(first, ToU8String(second));
} else {
return ConcatPath(std::filesystem::path{first}, std::filesystem::path{second});
}
}
#endif
/**
* Safe variant of ConcatPath that takes in a base path and an offset path from the given base path.
*
* If ConcatPath(base, offset) resolves to a path that is sandboxed within the base path,
* this will return the concatenated path. Otherwise this will return the base path.
*
* @param base Base filesystem path
* @param offset Offset filesystem path
*
* @returns A concatenated filesystem path if it is within the base path,
* returns the base path otherwise.
*/
[[nodiscard]] std::filesystem::path ConcatPathSafe(const std::filesystem::path& base,
const std::filesystem::path& offset);
#ifdef _WIN32
template <typename Path1, typename Path2>
[[nodiscard]] std::filesystem::path ConcatPathSafe(const Path1& base, const Path2& offset) {
using ValueType1 = typename Path1::value_type;
using ValueType2 = typename Path2::value_type;
if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) {
return ConcatPathSafe(ToU8String(base), ToU8String(offset));
} else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) {
return ConcatPathSafe(ToU8String(base), offset);
} else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) {
return ConcatPathSafe(base, ToU8String(offset));
} else {
return ConcatPathSafe(std::filesystem::path{base}, std::filesystem::path{offset});
}
}
#endif
/**
* Checks whether a given path is sandboxed within a given base path.
*
* @param base Base filesystem path
* @param path Filesystem path
*
* @returns True if the given path is sandboxed within the given base path, false otherwise.
*/
[[nodiscard]] bool IsPathSandboxed(const std::filesystem::path& base,
const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path1, typename Path2>
[[nodiscard]] bool IsPathSandboxed(const Path1& base, const Path2& path) {
using ValueType1 = typename Path1::value_type;
using ValueType2 = typename Path2::value_type;
if constexpr (IsChar<ValueType1> && IsChar<ValueType2>) {
return IsPathSandboxed(ToU8String(base), ToU8String(path));
} else if constexpr (IsChar<ValueType1> && !IsChar<ValueType2>) {
return IsPathSandboxed(ToU8String(base), path);
} else if constexpr (!IsChar<ValueType1> && IsChar<ValueType2>) {
return IsPathSandboxed(base, ToU8String(path));
} else {
return IsPathSandboxed(std::filesystem::path{base}, std::filesystem::path{path});
}
}
#endif
/**
* Checks if a character is a directory separator (either a forward slash or backslash).
*
* @param character Character
*
* @returns True if the character is a directory separator, false otherwise.
*/
[[nodiscard]] bool IsDirSeparator(char character);
/**
* Checks if a character is a directory separator (either a forward slash or backslash).
*
* @param character Character
*
* @returns True if the character is a directory separator, false otherwise.
*/
[[nodiscard]] bool IsDirSeparator(char8_t character);
/**
* Removes any trailing directory separators if they exist in the given path.
*
* @param path Filesystem path
*
* @returns The filesystem path without any trailing directory separators.
*/
[[nodiscard]] std::filesystem::path RemoveTrailingSeparators(const std::filesystem::path& path);
#ifdef _WIN32
template <typename Path>
[[nodiscard]] std::filesystem::path RemoveTrailingSeparators(const Path& path) {
if constexpr (IsChar<typename Path::value_type>) {
return RemoveTrailingSeparators(ToU8String(path));
} else {
return RemoveTrailingSeparators(std::filesystem::path{path});
}
}
#endif
/**
* Sets the directory used for application storage. Used on Android where we do not know internal
* storage until informed by the frontend.
*
* @param app_directory Directory to use for application storage.
*/
void SetAppDirectory(const std::string& app_directory);
/**
* Gets the filesystem path associated with the YuzuPath enum.
*
* @param yuzu_path YuzuPath enum
*
* @returns The filesystem path associated with the YuzuPath enum.
*/
[[nodiscard]] const std::filesystem::path& GetYuzuPath(YuzuPath yuzu_path);
/**
* Gets the filesystem path associated with the YuzuPath enum as a UTF-8 encoded std::string.
*
* @param yuzu_path YuzuPath enum
*
* @returns The filesystem path associated with the YuzuPath enum as a UTF-8 encoded std::string.
*/
[[nodiscard]] std::string GetYuzuPathString(YuzuPath yuzu_path);
/**
* Sets a new filesystem path associated with the YuzuPath enum.
* If the filesystem object at new_path is not a directory, this function will not do anything.
*
* @param yuzu_path YuzuPath enum
* @param new_path New filesystem path
*/
void SetYuzuPath(YuzuPath yuzu_path, const std::filesystem::path& new_path);
#ifdef _WIN32
template <typename Path>
void SetYuzuPath(YuzuPath yuzu_path, const Path& new_path) {
if constexpr (IsChar<typename Path::value_type>) {
SetYuzuPath(yuzu_path, ToU8String(new_path));
} else {
SetYuzuPath(yuzu_path, std::filesystem::path{new_path});
}
}
#endif
#ifdef _WIN32
/**
* Gets the path of the directory containing the executable of the current process.
*
* @returns The path of the directory containing the executable of the current process.
*/
[[nodiscard]] std::filesystem::path GetExeDirectory();
/**
* Gets the path of the current user's %APPDATA% directory (%USERPROFILE%/AppData/Roaming).
*
* @returns The path of the current user's %APPDATA% directory.
*/
[[nodiscard]] std::filesystem::path GetAppDataRoamingDirectory();
#else
/**
* Gets the path of the directory specified by the #HOME environment variable.
* If $HOME is not defined, it will attempt to query the user database in passwd instead.
*
* @returns The path of the current user's home directory.
*/
[[nodiscard]] std::filesystem::path GetHomeDirectory();
/**
* Gets the relevant paths for yuzu to store its data based on the given XDG environment variable.
* See https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
* Defaults to $HOME/.local/share for main application data,
* $HOME/.cache for cached data, and $HOME/.config for configuration files.
*
* @param env_name XDG environment variable name
*
* @returns The path where yuzu should store its data.
*/
[[nodiscard]] std::filesystem::path GetDataDirectory(const std::string& env_name);
#endif
#ifdef __APPLE__
[[nodiscard]] std::filesystem::path GetBundleDirectory();
#endif
// vvvvvvvvvv Deprecated vvvvvvvvvv //
// Removes the final '/' or '\' if one exists
[[nodiscard]] std::string_view RemoveTrailingSlash(std::string_view path);
enum class DirectorySeparator {
ForwardSlash,
BackwardSlash,
PlatformDefault,
};
// Splits the path on '/' or '\' and put the components into a vector
// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
[[nodiscard]] std::vector<std::string_view> SplitPathComponents(std::string_view filename);
// Splits the path on '/' or '\' and put the components into a vector
// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" }
[[nodiscard]] std::vector<std::string> SplitPathComponentsCopy(std::string_view filename);
// Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\'
// depending if directory_separator is BackwardSlash or PlatformDefault and running on windows
[[nodiscard]] std::string SanitizePath(
std::string_view path,
DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
// Gets all of the text up to the last '/' or '\' in the path.
[[nodiscard]] std::string GetParentPath(std::string_view path);
// Gets all of the text after the first '/' or '\' in the path.
[[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path);
// Gets the filename of the path
[[nodiscard]] std::string_view GetFilename(std::string_view path);
// Gets the extension of the filename
[[nodiscard]] std::string_view GetExtensionFromFilename(std::string_view name);
} // namespace Common::FS

View file

@ -1,22 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <type_traits>
#include <fmt/format.h>
// adapted from https://github.com/fmtlib/fmt/issues/2704
// a generic formatter for enum classes
#if FMT_VERSION >= 80100
template <typename T>
struct fmt::formatter<T, std::enable_if_t<std::is_enum_v<T>, char>>
: formatter<std::underlying_type_t<T>> {
template <typename FormatContext>
auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) {
return fmt::formatter<std::underlying_type_t<T>>::format(
static_cast<std::underlying_type_t<T>>(value), ctx);
}
};
#endif

View file

@ -1,67 +0,0 @@
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <algorithm>
#include <string_view>
#include <fmt/format.h>
#include "common/logging/formatter.h"
#include "common/logging/types.h"
namespace Common::Log {
// trims up to and including the last of ../, ..\, src/, src\ in a string
constexpr const char* TrimSourcePath(std::string_view source) {
const auto rfind = [source](const std::string_view match) {
return source.rfind(match) == source.npos ? 0 : (source.rfind(match) + match.size());
};
auto idx = std::max({rfind("src/"), rfind("src\\"), rfind("../"), rfind("..\\")});
return source.data() + idx;
}
/// Logs a message to the global logger, using fmt
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
unsigned int line_num, const char* function, const char* format,
const fmt::format_args& args);
template <typename... Args>
void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num,
const char* function, const char* format, const Args&... args) {
FmtLogMessageImpl(log_class, log_level, filename, line_num, function, format,
fmt::make_format_args(args...));
}
} // namespace Common::Log
#ifdef _DEBUG
#define LOG_TRACE(log_class, ...) \
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Trace, \
Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
__VA_ARGS__)
#else
#define LOG_TRACE(log_class, fmt, ...) (void(0))
#endif
#define LOG_DEBUG(log_class, ...) \
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Debug, \
Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
__VA_ARGS__)
#define LOG_INFO(log_class, ...) \
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Info, \
Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
__VA_ARGS__)
#define LOG_WARNING(log_class, ...) \
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Warning, \
Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
__VA_ARGS__)
#define LOG_ERROR(log_class, ...) \
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Error, \
Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
__VA_ARGS__)
#define LOG_CRITICAL(log_class, ...) \
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Critical, \
Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
__VA_ARGS__)

View file

@ -1,135 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_types.h"
namespace Common::Log {
/// Specifies the severity or level of detail of the log message.
enum class Level : u8 {
Trace, ///< Extremely detailed and repetitive debugging information that is likely to
///< pollute logs.
Debug, ///< Less detailed debugging information.
Info, ///< Status information from important points during execution.
Warning, ///< Minor or potential problems found during execution of a task.
Error, ///< Major problems found during execution of a task that prevent it from being
///< completed.
Critical, ///< Major problems during execution that threaten the stability of the entire
///< application.
Count ///< Total number of logging levels
};
/**
* Specifies the sub-system that generated the log message.
*
* @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in
* filter.cpp.
*/
enum class Class : u8 {
Log, ///< Messages about the log system itself
Common, ///< Library routines
Common_Filesystem, ///< Filesystem interface library
Common_Memory, ///< Memory mapping and management functions
Core, ///< LLE emulation core
Core_ARM, ///< ARM CPU core
Core_Timing, ///< CoreTiming functions
Config, ///< Emulator configuration (including commandline)
Debug, ///< Debugging tools
Debug_Emulated, ///< Debug messages from the emulated programs
Debug_GPU, ///< GPU debugging tools
Debug_Breakpoint, ///< Logging breakpoints and watchpoints
Debug_GDBStub, ///< GDB Stub
Kernel, ///< The HLE implementation of the CTR kernel
Kernel_SVC, ///< Kernel system calls
Service, ///< HLE implementation of system services. Each major service
///< should have its own subclass.
Service_ACC, ///< The ACC (Accounts) service
Service_AM, ///< The AM (Applet manager) service
Service_AOC, ///< The AOC (AddOn Content) service
Service_APM, ///< The APM (Performance) service
Service_ARP, ///< The ARP service
Service_Audio, ///< The Audio (Audio control) service
Service_BCAT, ///< The BCAT service
Service_BGTC, ///< The BGTC (Background Task Controller) service
Service_BPC, ///< The BPC service
Service_BTDRV, ///< The Bluetooth driver service
Service_BTM, ///< The BTM service
Service_Capture, ///< The capture service
Service_ERPT, ///< The error reporting service
Service_ETicket, ///< The ETicket service
Service_EUPLD, ///< The error upload service
Service_Fatal, ///< The Fatal service
Service_FGM, ///< The FGM service
Service_Friend, ///< The friend service
Service_FS, ///< The FS (Filesystem) service
Service_GRC, ///< The game recording service
Service_HID, ///< The HID (Human interface device) service
Service_IRS, ///< The IRS service
Service_JIT, ///< The JIT service
Service_LBL, ///< The LBL (LCD backlight) service
Service_LDN, ///< The LDN (Local domain network) service
Service_LDR, ///< The loader service
Service_LM, ///< The LM (Logger) service
Service_Migration, ///< The migration service
Service_Mii, ///< The Mii service
Service_MM, ///< The MM (Multimedia) service
Service_MNPP, ///< The MNPP service
Service_NCM, ///< The NCM service
Service_NFC, ///< The NFC (Near-field communication) service
Service_NFP, ///< The NFP service
Service_NGC, ///< The NGC (No Good Content) service
Service_NIFM, ///< The NIFM (Network interface) service
Service_NIM, ///< The NIM service
Service_NOTIF, ///< The NOTIF (Notification) service
Service_NPNS, ///< The NPNS service
Service_NS, ///< The NS services
Service_NVDRV, ///< The NVDRV (Nvidia driver) service
Service_Nvnflinger, ///< The Nvnflinger service
Service_OLSC, ///< The OLSC service
Service_PCIE, ///< The PCIe service
Service_PCTL, ///< The PCTL (Parental control) service
Service_PCV, ///< The PCV service
Service_PM, ///< The PM service
Service_PREPO, ///< The PREPO (Play report) service
Service_PSC, ///< The PSC service
Service_PTM, ///< The PTM service
Service_SET, ///< The SET (Settings) service
Service_SM, ///< The SM (Service manager) service
Service_SPL, ///< The SPL service
Service_SSL, ///< The SSL service
Service_TCAP, ///< The TCAP service.
Service_Time, ///< The time service
Service_USB, ///< The USB (Universal Serial Bus) service
Service_VI, ///< The VI (Video interface) service
Service_WLAN, ///< The WLAN (Wireless local area network) service
HW, ///< Low-level hardware emulation
HW_Memory, ///< Memory-map and address translation
HW_LCD, ///< LCD register emulation
HW_GPU, ///< GPU control emulation
HW_AES, ///< AES engine emulation
IPC, ///< IPC interface
Frontend, ///< Emulator UI
Render, ///< Emulator video output and hardware acceleration
Render_Software, ///< Software renderer backend
Render_OpenGL, ///< OpenGL backend
Render_Vulkan, ///< Vulkan backend
Shader, ///< Shader recompiler
Shader_SPIRV, ///< Shader SPIR-V code generation
Shader_GLASM, ///< Shader GLASM code generation
Shader_GLSL, ///< Shader GLSL code generation
Audio, ///< Audio emulation
Audio_DSP, ///< The HLE implementation of the DSP
Audio_Sink, ///< Emulator audio output backend
Loader, ///< ROM loader
CheatEngine, ///< Memory manipulation and engine VM functions
Crypto, ///< Cryptographic engine/functions
Input, ///< Input emulation
Network, ///< Network emulation
WebService, ///< Interface to yuzu Web Services
Count ///< Total number of logging classes
};
} // namespace Common::Log

View file

@ -1,11 +0,0 @@
# GPU core folder
dir: /core/gpu/
This is where all of the main universal code for emulating the Switch 2 GPU for both OpenGL and Vulkan goes
Directories:
/core/gpu/emu/ - OpenGL EmuGPU (macOS, Android)
/core/gpu/vk/ - Vulkan EmuGPU (Linux, Android)

View file

@ -0,0 +1 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.

View file

@ -0,0 +1 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.

View file

@ -1,9 +1,18 @@
// Copyright 2025 Pound Emulator Project. All rights reserved.
#include "Base/Logging/Backend.h"
#include "ARM/cpu.h"
#include "JIT/jit.h"
#include <cstdio>
int main() {
Base::Log::Initialize();
Base::Log::Start();
const auto config_dir = Base::FS::GetUserPath(Base::FS::PathType::BinaryDir);
Config::Load(config_dir / "config.toml");
CPU cpu;
cpu.pc = 0;
@ -13,13 +22,14 @@ int main() {
cpu.write_byte(4, 0x03); // ADD placeholder
cpu.write_byte(8, 0xFF); // RET placeholder
printf("%u\n", cpu.read_byte(0));
LOG_INFO(ARM, "{}", cpu.read_byte(0));
JIT jit;
jit.translate_and_run(cpu);
cpu.print_debug_information();
printf("X0 = %llu\n", cpu.x(0));
LOG_INFO(ARM, "X0 = {}", cpu.x(0));
return 0;
}
}