diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 92bd094..e8fd77c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 diff --git a/.gitmodules b/.gitmodules index bf49a5e..9e9b454 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/3rd_Party/CMakeLists.txt b/3rd_Party/CMakeLists.txt index 0e5a1d8..0fbad11 100644 --- a/3rd_Party/CMakeLists.txt +++ b/3rd_Party/CMakeLists.txt @@ -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() diff --git a/3rd_Party/toml11 b/3rd_Party/toml11 new file mode 160000 index 0000000..be08ba2 --- /dev/null +++ b/3rd_Party/toml11 @@ -0,0 +1 @@ +Subproject commit be08ba2be2a964edcdb3d3e3ea8d100abc26f286 diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a1db44..e712332 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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() diff --git a/core/ARM/cpu.h b/core/ARM/cpu.h index 31a9214..48eab5a 100644 --- a/core/ARM/cpu.h +++ b/core/ARM/cpu.h @@ -1,39 +1,39 @@ -#ifndef CPU_H -#define CPU_H -#include -#include +// Copyright 2025 Pound Emulator Project. All rights reserved. + +#pragma once + #include +#include "Base/Logging/Log.h" + struct CPU { - uint64_t regs[31] = {0}; // X0–X30 - uint64_t pc = 0; - static const size_t MEM_SIZE = 64 * 1024; - uint8_t memory[MEM_SIZE]; + u64 regs[31] = {0}; // X0–X30 + 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 \ No newline at end of file diff --git a/core/Base/Arch.h b/core/Base/Arch.h new file mode 100644 index 0000000..9a6a9ac --- /dev/null +++ b/core/Base/Arch.h @@ -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 diff --git a/core/Base/Assert.cpp b/core/Base/Assert.cpp new file mode 100644 index 0000000..7735249 --- /dev/null +++ b/core/Base/Assert.cpp @@ -0,0 +1,54 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#include + +#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 +#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(); +} diff --git a/core/Base/Assert.h b/core/Base/Assert.h new file mode 100644 index 0000000..b8595de --- /dev/null +++ b/core/Base/Assert.h @@ -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) diff --git a/core/Base/BoundedQueue.h b/core/Base/BoundedQueue.h new file mode 100644 index 0000000..72c0952 --- /dev/null +++ b/core/Base/BoundedQueue.h @@ -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 +class SPSCQueue { + static_assert((Capacity & (Capacity - 1)) == 0, "Capacity must be a power of two."); + +public: + template + bool TryEmplace(Args&&... args) { + return Emplace(std::forward(args)...); + } + + template + void EmplaceWait(Args&&... args) { + Emplace(std::forward(args)...); + } + + bool TryPop(T& t) { + return Pop(t); + } + + bool PopWait(T& t) { + return Pop(t); + } + + bool PopWait(T& t, std::stop_token stopToken) { + return Pop(t, stopToken); + } + + T PopWait() { + T t; + Pop(t); + return t; + } + + T PopWait(std::stop_token stopToken) { + T t; + Pop(t, stopToken); + return t; + } + +private: + enum class PushMode { + Try, + Wait, + Count + }; + + enum class PopMode { + Try, + Wait, + WaitWithStopToken, + Count + }; + + template + 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)...); + + // 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 + 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 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 +class MPSCQueue { +public: + template + bool TryEmplace(Args&&... args) { + std::scoped_lock lock{writeMutex}; + return spscQueue.TryEmplace(std::forward(args)...); + } + + template + void EmplaceWait(Args&&... args) { + std::scoped_lock lock{writeMutex}; + spscQueue.EmplaceWait(std::forward(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 spscQueue; + std::mutex writeMutex; +}; + +template +class MPMCQueue { +public: + template + bool TryEmplace(Args&&... args) { + std::scoped_lock lock{ writeMutex }; + return spscQueue.TryEmplace(std::forward(args)...); + } + + template + void EmplaceWait(Args&&... args) { + std::scoped_lock lock{ writeMutex }; + spscQueue.EmplaceWait(std::forward(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 spscQueue; + std::mutex writeMutex; + std::mutex readMutex; +}; + +} // namespace Base diff --git a/core/Base/Config.cpp b/core/Base/Config.cpp new file mode 100644 index 0000000..8d917d5 --- /dev/null +++ b/core/Base/Config.cpp @@ -0,0 +1,72 @@ +// Copyright 2025 Pound Emulator Project. All rights reserved. + +#include "Config.h" + +#include +#include + +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(general, "Advanced Log", false); + typeLog = toml::find_or(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 diff --git a/core/Base/Config.h b/core/Base/Config.h new file mode 100644 index 0000000..d6b2bed --- /dev/null +++ b/core/Base/Config.h @@ -0,0 +1,16 @@ +// Copyright 2025 Pound Emulator Project. All rights reserved. + +#pragma once + +#include + +namespace Config { + +void Load(const std::filesystem::path& path); +void Save(const std::filesystem::path& path); + +bool isLogAdvanced(); + +std::string logType(); + +} // namespace Config diff --git a/core/Base/Enum.h b/core/Base/Enum.h new file mode 100644 index 0000000..cbefb51 --- /dev/null +++ b/core/Base/Enum.h @@ -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; \ + return static_cast(static_cast(a) | static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator&(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) & static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator^(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) ^ static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator<<(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) << static_cast(b)); \ + } \ + [[nodiscard]] constexpr type operator>>(type a, type b) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(static_cast(a) >> static_cast(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; \ + return static_cast(~static_cast(key)); \ + } \ + [[nodiscard]] constexpr bool True(type key) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(key) != 0; \ + } \ + [[nodiscard]] constexpr bool False(type key) noexcept { \ + using T = std::underlying_type_t; \ + return static_cast(key) == 0; \ + } + +namespace Base { + +template +class Flags { +public: + using IntType = std::underlying_type_t; + + Flags() {} + + Flags(IntType t) : m_bits(t) {} + + template + Flags(T f, Tx... fx) { + set(f, fx...); + } + + template + void set(Tx... fx) { + m_bits |= bits(fx...); + } + + void set(Flags flags) { + m_bits |= flags.m_bits; + } + + template + void clr(Tx... fx) { + m_bits &= ~bits(fx...); + } + + void clr(Flags flags) { + m_bits &= ~flags.m_bits; + } + + template + bool any(Tx... fx) const { + return (m_bits & bits(fx...)) != 0; + } + + template + 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(f); + } + + template + static IntType bits(T f, Tx... fx) { + return bit(f) | bits(fx...); + } + + static IntType bits() { + return 0; + } +}; + +} // namespace Base diff --git a/core/Base/Error.h b/core/Base/Error.h new file mode 100644 index 0000000..2fed300 --- /dev/null +++ b/core/Base/Error.h @@ -0,0 +1,19 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#pragma once + +#include + +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 diff --git a/core/Base/IoFile.cpp b/core/Base/IoFile.cpp new file mode 100644 index 0000000..72b7d08 --- /dev/null +++ b/core/Base/IoFile.cpp @@ -0,0 +1,428 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#include + +#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 +#include +#include +#else +#include +#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(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(::_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(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 string_buffer(length); + + const u64 charsRead = ReadSpan(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(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(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(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 diff --git a/core/Base/IoFile.h b/core/Base/IoFile.h new file mode 100644 index 0000000..a37f9c2 --- /dev/null +++ b/core/Base/IoFile.h @@ -0,0 +1,222 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#pragma once + +#include +#include + +#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 + size_t Read(T& data) const { + if constexpr (std::contiguous_iterator) { + using ContiguousType = typename T::value_type; + static_assert(std::is_trivially_copyable_v, + "Data type must be trivially copyable."); + return ReadSpan(data); + } else { + return ReadObject(data) ? 1 : 0; + } + } + + template + size_t Write(const T& data) const { + if constexpr (std::contiguous_iterator) { + using ContiguousType = typename T::value_type; + static_assert(std::is_trivially_copyable_v, + "Data type must be trivially copyable."); + return WriteSpan(data); + } else { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + return WriteObject(data) ? 1 : 0; + } + } + + template + size_t ReadSpan(std::span data) const { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + + if (!IsOpen()) { + return 0; + } + + return ReadRaw(data.data(), data.size()); + } + + template + size_t ReadRaw(void* data, size_t size) const { + return std::fread(data, sizeof(T), size, file); + } + + template + size_t WriteSpan(std::span data) const { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + + if (!IsOpen()) { + return 0; + } + + return std::fwrite(data.data(), sizeof(T), data.size(), file); + } + + template + bool ReadObject(T& object) const { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + static_assert(!std::is_pointer_v, "T must not be a pointer to an object."); + + if (!IsOpen()) { + return false; + } + + return std::fread(&object, sizeof(T), 1, file) == 1; + } + + template + size_t WriteRaw(const void* data, size_t size) const { + return std::fwrite(data, sizeof(T), size, file); + } + + template + bool WriteObject(const T& object) const { + static_assert(std::is_trivially_copyable_v, "Data type must be trivially copyable."); + static_assert(!std::is_pointer_v, "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 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 diff --git a/core/Base/Logging/Backend.cpp b/core/Base/Logging/Backend.cpp new file mode 100644 index 0000000..6590da1 --- /dev/null +++ b/core/Base/Logging/Backend.cpp @@ -0,0 +1,442 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#include +#include +#include + +#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 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 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(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(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(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(); + fileBackend = std::make_unique(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::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 lambda) { + lambda(colorConsoleBackend.get()); + lambda(fileBackend.get()); + } + + static void Deleter(Impl* ptr) { + delete ptr; + } + + static inline std::unique_ptr instance{ nullptr, Deleter }; + + Filter filter; + std::unique_ptr colorConsoleBackend = {}; + std::unique_ptr fileBackend = {}; + + MPSCQueue messageQueue = {}; + std::chrono::steady_clock::time_point timeOrigin = std::chrono::steady_clock::now(); + std::jthread backendThread; +}; + +std::vector 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 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 diff --git a/core/Base/Logging/Backend.h b/core/Base/Logging/Backend.h new file mode 100644 index 0000000..c5b2d44 --- /dev/null +++ b/core/Base/Logging/Backend.h @@ -0,0 +1,37 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#pragma once + +#include +#include + +#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 diff --git a/core/Base/Logging/Filter.cpp b/core/Base/Logging/Filter.cpp new file mode 100644 index 0000000..cdb6913 --- /dev/null +++ b/core/Base/Logging/Filter.cpp @@ -0,0 +1,153 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#include "Base/Assert.h" + +#include "Filter.h" + +namespace Base { +namespace Log { + +template +Level GetLevelByName(const It begin, const It end) { + for (u8 i = 0; i < static_cast(Level::Count); ++i) { + const char* level_name = GetLevelName(static_cast(i)); + if (std::string_view(begin, end).compare(level_name) == 0) { + return static_cast(i); + } + } + return Level::Count; +} + +template +Class GetClassByName(const It begin, const It end) { + for (u8 i = 0; i < static_cast(Class::Count); ++i) { + const char* level_name = GetLogClassName(static_cast(i)); + if (std::string_view(begin, end).compare(level_name) == 0) { + return static_cast(i); + } + } + return Class::Count; +} + +template +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(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(level) >= + static_cast(classLevels[static_cast(logClass)]); +} + +bool Filter::IsDebug() const { + return std::any_of(classLevels.begin(), classLevels.end(), [](const Level& l) { + return static_cast(l) <= static_cast(Level::Debug); + }); +} + +} // namespace Log +} // namespace Base diff --git a/core/Base/Logging/Filter.h b/core/Base/Logging/Filter.h new file mode 100644 index 0000000..2e82720 --- /dev/null +++ b/core/Base/Logging/Filter.h @@ -0,0 +1,66 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#pragma once + +#include + +#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 + * `:`. `` 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. `` + * 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(Class::Count)> classLevels; +}; + +} // namespace Log +} // namespace Base diff --git a/core/Base/Logging/Log.h b/core/Base/Logging/Log.h new file mode 100644 index 0000000..ee45a02 --- /dev/null +++ b/core/Base/Logging/Log.h @@ -0,0 +1,78 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#pragma once + +#include +#include + +#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 +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__) diff --git a/core/Base/Logging/LogEntry.h b/core/Base/Logging/LogEntry.h new file mode 100644 index 0000000..a8852a0 --- /dev/null +++ b/core/Base/Logging/LogEntry.h @@ -0,0 +1,28 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#pragma once + +#include + +#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 diff --git a/core/Base/Logging/LogTypes.h b/core/Base/Logging/LogTypes.h new file mode 100644 index 0000000..c16f36b --- /dev/null +++ b/core/Base/Logging/LogTypes.h @@ -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 diff --git a/core/Base/Logging/TextFormatter.cpp b/core/Base/Logging/TextFormatter.cpp new file mode 100644 index 0000000..36f4f0d --- /dev/null +++ b/core/Base/Logging/TextFormatter.cpp @@ -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 diff --git a/core/Base/Logging/TextFormatter.h b/core/Base/Logging/TextFormatter.h new file mode 100644 index 0000000..426c607 --- /dev/null +++ b/core/Base/Logging/TextFormatter.h @@ -0,0 +1,23 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#pragma once + +#include +#include + +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 diff --git a/core/Base/PathUtil.cpp b/core/Base/PathUtil.cpp new file mode 100644 index 0000000..dcb6db7 --- /dev/null +++ b/core/Base/PathUtil.cpp @@ -0,0 +1,115 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#include "PathUtil.h" +#include "Base/Logging/Log.h" + +#include + +#include +#include +#ifdef _WIN32 +#include +#endif // _WIN32 +#ifdef __APPLE__ +#include +#include +#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 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 ListFilesFromPath(const fs::path &path) { + std::vector 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 diff --git a/core/Base/PathUtil.h b/core/Base/PathUtil.h new file mode 100644 index 0000000..6fa1dd1 --- /dev/null +++ b/core/Base/PathUtil.h @@ -0,0 +1,52 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#pragma once + +#include +#include + +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 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 diff --git a/core/Base/PolyfillThread.h b/core/Base/PolyfillThread.h new file mode 100644 index 0000000..9bd825e --- /dev/null +++ b/core/Base/PolyfillThread.h @@ -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 +#include +#include +#include +#include +#include + +namespace Base { + +template +void CondvarWait(Condvar& cv, std::unique_lock& lk, std::stop_token token, Pred&& pred) { + cv.wait(lk, token, std::forward(pred)); +} + +template +bool StoppableTimedWait(std::stop_token token, const std::chrono::duration& 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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 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> 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 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 + friend class stop_callback; + stop_token(shared_ptr stop_state) : m_stop_state(std::move(stop_state)) {} + +private: + shared_ptr m_stop_state; +}; + +class stop_source { +public: + stop_source() : m_stop_state(make_shared()) {} + 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 stop_state) + : m_stop_state(std::move(stop_state)) {} + +private: + shared_ptr m_stop_state; +}; + +template +class stop_callback { + static_assert(is_nothrow_destructible_v); + static_assert(is_invocable_v); + +public: + using callback_type = Callback; + + template + requires constructible_from + explicit stop_callback(const stop_token& st, + C&& cb) noexcept(is_nothrow_constructible_v) + : m_stop_state(st.m_stop_state) { + if (m_stop_state) { + m_callback = m_stop_state->insert_callback(std::move(cb)); + } + } + template + requires constructible_from + explicit stop_callback(stop_token&& st, + C&& cb) noexcept(is_nothrow_constructible_v) + : 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 m_stop_state; + polyfill::stop_state_callback m_callback; +}; + +template +stop_callback(stop_token, Callback) -> stop_callback; + +class jthread { +public: + using id = thread::id; + using native_handle_type = thread::native_handle_type; + + jthread() noexcept = default; + + template , jthread>>> + explicit jthread(F&& f, Args&&... args) + : m_stop_state(make_shared()), + m_thread(make_thread(std::forward(f), std::forward(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 + thread make_thread(F&& f, Args&&... args) { + if constexpr (is_invocable_v, stop_token, decay_t...>) { + return thread(std::forward(f), get_stop_token(), std::forward(args)...); + } else { + return thread(std::forward(f), std::forward(args)...); + } + } + + shared_ptr m_stop_state; + thread m_thread; +}; + +} // namespace std + +namespace Base { + +template +void CondvarWait(Condvar& cv, std::unique_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 +bool StoppableTimedWait(std::stop_token token, const std::chrono::duration& 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 diff --git a/core/Base/StringUtil.cpp b/core/Base/StringUtil.cpp new file mode 100644 index 0000000..db115fc --- /dev/null +++ b/core/Base/StringUtil.cpp @@ -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(input.size()), nullptr, 0); + + if (size == 0) { + return {}; + } + + std::wstring output(size, L'\0'); + + if (size != ::MultiByteToWideChar(code_page, 0, input.data(), static_cast(input.size()), + &output[0], static_cast(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(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(input.size()), + &output[0], static_cast(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 diff --git a/core/Base/StringUtil.h b/core/Base/StringUtil.h new file mode 100644 index 0000000..0c57e9e --- /dev/null +++ b/core/Base/StringUtil.h @@ -0,0 +1,22 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#pragma once + +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#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 diff --git a/core/Base/Thread.cpp b/core/Base/Thread.cpp new file mode 100644 index 0000000..a2425c4 --- /dev/null +++ b/core/Base/Thread.cpp @@ -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 +#include +#include +#elif defined(_WIN32) +#include +#include "StringUtil.h" +#else + +#if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__) +#include +#else +#include +#endif +#include +#endif +#ifndef _WIN32 +#include +#endif +#include +#include + +#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(timebase.denom) / static_cast(timebase.numer); + + const auto period_ticks = + static_cast(static_cast(period_ns.count()) * ticks_per_ns); + const auto computation_ticks = + static_cast(static_cast(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(&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(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, ¶ms); +} + +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(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(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(start_time - begin_sleep); +} + +void AccurateTimer::End() { + const auto now = std::chrono::high_resolution_clock::now(); + total_wait += + target_interval - std::chrono::duration_cast(now - start_time); +} + +} // namespace Base diff --git a/core/Base/Thread.h b/core/Base/Thread.h new file mode 100644 index 0000000..b9855d6 --- /dev/null +++ b/core/Base/Thread.h @@ -0,0 +1,43 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#pragma once + +#include + +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 diff --git a/core/Base/Types.h b/core/Base/Types.h new file mode 100644 index 0000000..bd9a387 --- /dev/null +++ b/core/Base/Types.h @@ -0,0 +1,52 @@ +// Copyright 2025 Xenon Emulator Project. All rights reserved. + +#pragma once + +#include + +#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 + requires std::is_function_v +using fptr = std::add_pointer_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; +} diff --git a/core/common/swap.h b/core/Base/swap.h similarity index 100% rename from core/common/swap.h rename to core/Base/swap.h diff --git a/core/JIT/jit.cpp b/core/JIT/jit.cpp index 7bde29a..8daa5dc 100644 --- a/core/JIT/jit.cpp +++ b/core/JIT/jit.cpp @@ -1,3 +1,5 @@ +// Copyright 2025 Pound Emulator Project. All rights reserved. + #include "jit.h" #ifdef WIN32 @@ -6,8 +8,6 @@ #include #endif -#include -#include #include 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(code); - uint64_t result; + u64 result; asm volatile( "call *%1\n" "mov %%rax, %0\n" diff --git a/core/JIT/jit.h b/core/JIT/jit.h index e71ba59..46f1e7f 100644 --- a/core/JIT/jit.h +++ b/core/JIT/jit.h @@ -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 \ No newline at end of file diff --git a/core/README.md b/core/README.md deleted file mode 100644 index c81c8fd..0000000 --- a/core/README.md +++ /dev/null @@ -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. - diff --git a/core/audio/audio.cpp b/core/audio/audio.cpp index e69de29..60254db 100644 --- a/core/audio/audio.cpp +++ b/core/audio/audio.cpp @@ -0,0 +1 @@ +// Copyright 2025 Pound Emulator Project. All rights reserved. diff --git a/core/common/assert.h b/core/common/assert.h deleted file mode 100644 index 67e7e93..0000000 --- a/core/common/assert.h +++ /dev/null @@ -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) diff --git a/core/common/common_types.h b/core/common/common_types.h deleted file mode 100644 index ae04c4d..0000000 --- a/core/common/common_types.h +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-FileCopyrightText: 2012 Gekko Emulator -// SPDX-FileContributor: ShizZy -// SPDX-License-Identifier: GPL-2.0-or-later - -/** - * Copyright (C) 2005-2012 Gekko Emulator - * - * @file common_types.h - * @author ShizZy - * @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 -#include - -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; -static_assert(sizeof(u128) == 16, "u128 must be 128 bits wide"); diff --git a/core/common/fs/path_util.h b/core/common/fs/path_util.h deleted file mode 100644 index 742e05a..0000000 --- a/core/common/fs/path_util.h +++ /dev/null @@ -1,316 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -// #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 -[[nodiscard]] bool ValidatePath(const Path& path) { - if constexpr (IsChar) { - 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 -[[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 && IsChar) { - return ConcatPath(ToU8String(first), ToU8String(second)); - } else if constexpr (IsChar && !IsChar) { - return ConcatPath(ToU8String(first), second); - } else if constexpr (!IsChar && IsChar) { - 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 -[[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 && IsChar) { - return ConcatPathSafe(ToU8String(base), ToU8String(offset)); - } else if constexpr (IsChar && !IsChar) { - return ConcatPathSafe(ToU8String(base), offset); - } else if constexpr (!IsChar && IsChar) { - 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 -[[nodiscard]] bool IsPathSandboxed(const Path1& base, const Path2& path) { - using ValueType1 = typename Path1::value_type; - using ValueType2 = typename Path2::value_type; - if constexpr (IsChar && IsChar) { - return IsPathSandboxed(ToU8String(base), ToU8String(path)); - } else if constexpr (IsChar && !IsChar) { - return IsPathSandboxed(ToU8String(base), path); - } else if constexpr (!IsChar && IsChar) { - 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 -[[nodiscard]] std::filesystem::path RemoveTrailingSeparators(const Path& path) { - if constexpr (IsChar) { - 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 -void SetYuzuPath(YuzuPath yuzu_path, const Path& new_path) { - if constexpr (IsChar) { - 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 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 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 diff --git a/core/common/logging/formatter.h b/core/common/logging/formatter.h deleted file mode 100644 index 88e5550..0000000 --- a/core/common/logging/formatter.h +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -#include - -// adapted from https://github.com/fmtlib/fmt/issues/2704 -// a generic formatter for enum classes -#if FMT_VERSION >= 80100 -template -struct fmt::formatter, char>> - : formatter> { - template - auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) { - return fmt::formatter>::format( - static_cast>(value), ctx); - } -}; -#endif diff --git a/core/common/logging/log.h b/core/common/logging/log.h deleted file mode 100644 index c00c01a..0000000 --- a/core/common/logging/log.h +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-FileCopyrightText: 2014 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -#include - -#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 -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__) diff --git a/core/common/logging/types.h b/core/common/logging/types.h deleted file mode 100644 index 08af50e..0000000 --- a/core/common/logging/types.h +++ /dev/null @@ -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 diff --git a/core/gpu/README.md b/core/gpu/README.md deleted file mode 100644 index 6db3e43..0000000 --- a/core/gpu/README.md +++ /dev/null @@ -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) diff --git a/core/gpu/emu/emugpu.cpp b/core/gpu/emu/emugpu.cpp index e69de29..60254db 100644 --- a/core/gpu/emu/emugpu.cpp +++ b/core/gpu/emu/emugpu.cpp @@ -0,0 +1 @@ +// Copyright 2025 Pound Emulator Project. All rights reserved. diff --git a/core/gpu/vk/emugpuvk.cpp b/core/gpu/vk/emugpuvk.cpp index e69de29..60254db 100644 --- a/core/gpu/vk/emugpuvk.cpp +++ b/core/gpu/vk/emugpuvk.cpp @@ -0,0 +1 @@ +// Copyright 2025 Pound Emulator Project. All rights reserved. diff --git a/core/main.cpp b/core/main.cpp index 3220ec7..4d6f8b1 100644 --- a/core/main.cpp +++ b/core/main.cpp @@ -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 - 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; -} \ No newline at end of file +}