common: Rewrite logging based on cut down citra logger (#86)

* common: Rewrite logging based on cut down Citra logger

* code: Misc fixes

* core: Bring back tls handler

* linker: Cleanup

* config: Remove log level

* logging: Enable console output by default

* core: Fix windows build
This commit is contained in:
GPUCode 2024-02-28 00:10:34 +02:00 committed by GitHub
parent b3084646a8
commit 79d6c8a377
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
70 changed files with 3212 additions and 1541 deletions

View File

@ -29,7 +29,7 @@ jobs:
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -T ClangCL
- name: Build
# Build your program with the given configuration
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel
- name: Upload a Build Artifact
uses: actions/upload-artifact@v3.1.2
with:

4
.gitmodules vendored
View File

@ -6,10 +6,6 @@
path = third-party/fmt
url = https://github.com/fmtlib/fmt.git
shallow = true
[submodule "third-party/spdlog"]
path = third-party/spdlog
url = https://github.com/gabime/spdlog
shallow = true
[submodule "third-party/magic_enum"]
path = third-party/magic_enum
url = https://github.com/Neargye/magic_enum.git

View File

@ -128,15 +128,31 @@ set(HOST_SOURCES src/Emulator/Host/controller.cpp
)
add_executable(shadps4
src/common/assert.cpp
src/common/assert.h
src/common/bounded_threadsafe_queue.h
src/common/concepts.h
src/common/debug.h
src/common/disassembler.cpp
src/common/disassembler.h
src/common/discord.cpp
src/common/discord.h
src/common/fs_file.cpp
src/common/fs_file.h
src/common/log.cpp
src/common/log.h
src/common/error.cpp
src/common/error.h
src/common/io_file.cpp
src/common/io_file.h
src/common/path_util.cpp
src/common/path_util.h
src/common/logging/backend.cpp
src/common/logging/backend.h
src/common/logging/filter.cpp
src/common/logging/filter.h
src/common/logging/formatter.h
src/common/logging/log_entry.h
src/common/logging/log.h
src/common/logging/text_formatter.cpp
src/common/logging/text_formatter.h
src/common/logging/types.h
src/common/native_clock.cpp
src/common/native_clock.h
src/common/rdtsc.cpp
@ -144,6 +160,8 @@ add_executable(shadps4
src/common/singleton.h
src/common/string_util.cpp
src/common/string_util.h
src/common/thread.cpp
src/common/thread.h
src/common/types.h
src/common/uint128.h
src/common/version.h
@ -207,14 +225,18 @@ add_executable(shadps4
src/core/PS4/GPU/tile_manager.h
src/core/hle/libraries/libkernel/time_management.cpp
src/core/hle/libraries/libkernel/time_management.h
"src/common/io_file.cpp" "src/common/io_file.h")
src/core/tls.cpp
src/core/tls.h
)
create_target_directory_groups(shadps4)
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt spdlog::spdlog toml11::toml11)
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11)
target_link_libraries(shadps4 PRIVATE discord-rpc SDL3-shared vulkan-1 xxhash Zydis)
if (WIN32)
target_link_libraries(shadps4 PRIVATE mincore winpthread clang_rt.builtins-x86_64.lib)
add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS)
add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN)
endif()
add_custom_command(TARGET shadps4 POST_BUILD

View File

@ -12,19 +12,22 @@ namespace Config {
bool isNeo = false;
u32 screenWidth = 1280;
u32 screenHeight = 720;
u32 logLevel = 0; // TRACE = 0 , DEBUG = 1 , INFO = 2 , WARN = 3 , ERROR = 4 , CRITICAL = 5, OFF = 6
std::string logFilter;
bool isNeoMode() {
return isNeo;
}
u32 getScreenWidth() {
return screenWidth;
}
u32 getScreenHeight() {
return screenHeight;
}
u32 getLogLevel() {
return logLevel;
std::string getLogFilter() {
return logFilter;
}
void load(const std::filesystem::path& path) {
@ -50,7 +53,7 @@ void load(const std::filesystem::path& path) {
auto general = generalResult.unwrap();
isNeo = toml::find_or<toml::boolean>(general, "isPS4Pro", false);
logLevel = toml::find_or<toml::integer>(general, "logLevel", false);
logFilter = toml::find_or<toml::string>(general, "logFilter", "");
}
}
if (data.contains("GPU")) {
@ -62,7 +65,6 @@ void load(const std::filesystem::path& path) {
screenHeight = toml::find_or<toml::integer>(general, "screenHeight", false);
}
}
int k = 0;
}
void save(const std::filesystem::path& path) {
toml::basic_value<toml::preserve_comments> data;
@ -84,7 +86,7 @@ void save(const std::filesystem::path& path) {
}
data["General"]["isPS4Pro"] = isNeo;
data["General"]["logLevel"] = logLevel;
data["General"]["logFilter"] = logFilter;
data["GPU"]["screenWidth"] = screenWidth;
data["GPU"]["screenHeight"] = screenHeight;

View File

@ -11,7 +11,7 @@ void load(const std::filesystem::path& path);
void save(const std::filesystem::path& path);
bool isNeoMode();
u32 getLogLevel();
std::string getLogFilter();
u32 getScreenWidth();
u32 getScreenHeight();

18
src/common/assert.cpp Normal file
View File

@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/logging/backend.h"
#define Crash() __asm__ __volatile__("int $3")
void assert_fail_impl() {
Common::Log::Stop();
Crash();
}
[[noreturn]] void unreachable_impl() {
Common::Log::Stop();
Crash();
throw std::runtime_error("Unreachable code");
}

84
src/common/assert.h Normal file
View File

@ -0,0 +1,84 @@
// 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 SHAD_NO_INLINE __declspec(noinline)
#else
#define SHAD_NO_INLINE __attribute__((noinline))
#endif
#define ASSERT(_a_) \
([&]() SHAD_NO_INLINE { \
if (!(_a_)) [[unlikely]] { \
LOG_CRITICAL(Debug, "Assertion Failed!"); \
assert_fail_impl(); \
} \
}())
#define ASSERT_MSG(_a_, ...) \
([&]() SHAD_NO_INLINE { \
if (!(_a_)) [[unlikely]] { \
LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \
assert_fail_impl(); \
} \
}())
#define UNREACHABLE() \
do { \
LOG_CRITICAL(Debug, "Unreachable code!"); \
unreachable_impl(); \
} while (0)
#define UNREACHABLE_MSG(...) \
do { \
LOG_CRITICAL(Debug, "Unreachable code!\n" __VA_ARGS__); \
unreachable_impl(); \
} while (0)
#ifdef _DEBUG
#define DEBUG_ASSERT(_a_) ASSERT(_a_)
#define DEBUG_ASSERT_MSG(_a_, ...) ASSERT_MSG(_a_, __VA_ARGS__)
#else // not debug
#define DEBUG_ASSERT(_a_) \
do { \
} while (0)
#define DEBUG_ASSERT_MSG(_a_, _desc_, ...) \
do { \
} while (0)
#endif
#define UNIMPLEMENTED() ASSERT_MSG(false, "Unimplemented code!")
#define UNIMPLEMENTED_MSG(...) ASSERT_MSG(false, __VA_ARGS__)
#define UNIMPLEMENTED_IF(cond) ASSERT_MSG(!(cond), "Unimplemented code!")
#define UNIMPLEMENTED_IF_MSG(cond, ...) ASSERT_MSG(!(cond), __VA_ARGS__)
// If the assert is ignored, execute _b_
#define ASSERT_OR_EXECUTE(_a_, _b_) \
do { \
ASSERT(_a_); \
if (!(_a_)) [[unlikely]] { \
_b_ \
} \
} while (0)
// If the assert is ignored, execute _b_
#define ASSERT_OR_EXECUTE_MSG(_a_, _b_, ...) \
do { \
ASSERT_MSG(_a_, __VA_ARGS__); \
if (!(_a_)) [[unlikely]] { \
_b_ \
} \
} while (0)

View File

@ -0,0 +1,248 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <condition_variable>
#include <cstddef>
#include <memory>
#include <mutex>
#include <new>
namespace Common {
namespace detail {
constexpr std::size_t DefaultCapacity = 0x1000;
} // namespace detail
template <typename T, std::size_t Capacity = detail::DefaultCapacity>
class SPSCQueue {
static_assert((Capacity & (Capacity - 1)) == 0, "Capacity must be a power of two.");
public:
template <typename... Args>
bool TryEmplace(Args&&... args) {
return Emplace<PushMode::Try>(std::forward<Args>(args)...);
}
template <typename... Args>
void EmplaceWait(Args&&... args) {
Emplace<PushMode::Wait>(std::forward<Args>(args)...);
}
bool TryPop(T& t) {
return Pop<PopMode::Try>(t);
}
void PopWait(T& t) {
Pop<PopMode::Wait>(t);
}
void PopWait(T& t, std::stop_token stop_token) {
Pop<PopMode::WaitWithStopToken>(t, stop_token);
}
T PopWait() {
T t;
Pop<PopMode::Wait>(t);
return t;
}
T PopWait(std::stop_token stop_token) {
T t;
Pop<PopMode::WaitWithStopToken>(t, stop_token);
return t;
}
private:
enum class PushMode {
Try,
Wait,
Count,
};
enum class PopMode {
Try,
Wait,
WaitWithStopToken,
Count,
};
template <PushMode Mode, typename... Args>
bool Emplace(Args&&... args) {
const std::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 std::size_t pos = write_index % Capacity;
// Emplace into the queue.
new (std::addressof(m_data[pos])) T(std::forward<Args>(args)...);
// Increment the write index.
++m_write_index;
// Notify the consumer that we have pushed into the queue.
std::scoped_lock lock{consumer_cv_mutex};
consumer_cv.notify_one();
return true;
}
template <PopMode Mode>
bool Pop(T& t, [[maybe_unused]] std::stop_token stop_token = {}) {
const std::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};
consumer_cv.wait(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 std::size_t pos = read_index % Capacity;
// Pop the data off the queue, moving it.
t = std::move(m_data[pos]);
// Increment the read index.
++m_read_index;
// Notify the producer that we have popped off the queue.
std::scoped_lock lock{producer_cv_mutex};
producer_cv.notify_one();
return true;
}
alignas(128) std::atomic_size_t m_read_index{0};
alignas(128) std::atomic_size_t m_write_index{0};
std::array<T, Capacity> m_data;
std::condition_variable_any producer_cv;
std::mutex producer_cv_mutex;
std::condition_variable_any consumer_cv;
std::mutex consumer_cv_mutex;
};
template <typename T, std::size_t Capacity = detail::DefaultCapacity>
class MPSCQueue {
public:
template <typename... Args>
bool TryEmplace(Args&&... args) {
std::scoped_lock lock{write_mutex};
return spsc_queue.TryEmplace(std::forward<Args>(args)...);
}
template <typename... Args>
void EmplaceWait(Args&&... args) {
std::scoped_lock lock{write_mutex};
spsc_queue.EmplaceWait(std::forward<Args>(args)...);
}
bool TryPop(T& t) {
return spsc_queue.TryPop(t);
}
void PopWait(T& t) {
spsc_queue.PopWait(t);
}
void PopWait(T& t, std::stop_token stop_token) {
spsc_queue.PopWait(t, stop_token);
}
T PopWait() {
return spsc_queue.PopWait();
}
T PopWait(std::stop_token stop_token) {
return spsc_queue.PopWait(stop_token);
}
private:
SPSCQueue<T, Capacity> spsc_queue;
std::mutex write_mutex;
};
template <typename T, std::size_t Capacity = detail::DefaultCapacity>
class MPMCQueue {
public:
template <typename... Args>
bool TryEmplace(Args&&... args) {
std::scoped_lock lock{write_mutex};
return spsc_queue.TryEmplace(std::forward<Args>(args)...);
}
template <typename... Args>
void EmplaceWait(Args&&... args) {
std::scoped_lock lock{write_mutex};
spsc_queue.EmplaceWait(std::forward<Args>(args)...);
}
bool TryPop(T& t) {
std::scoped_lock lock{read_mutex};
return spsc_queue.TryPop(t);
}
void PopWait(T& t) {
std::scoped_lock lock{read_mutex};
spsc_queue.PopWait(t);
}
void PopWait(T& t, std::stop_token stop_token) {
std::scoped_lock lock{read_mutex};
spsc_queue.PopWait(t, stop_token);
}
T PopWait() {
std::scoped_lock lock{read_mutex};
return spsc_queue.PopWait();
}
T PopWait(std::stop_token stop_token) {
std::scoped_lock lock{read_mutex};
return spsc_queue.PopWait(stop_token);
}
private:
SPSCQueue<T, Capacity> spsc_queue;
std::mutex write_mutex;
std::mutex read_mutex;
};
} // namespace Common

31
src/common/concepts.h Normal file
View File

@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <concepts>
#include <iterator>
#include <type_traits>
namespace Common {
// Check if type satisfies the ContiguousContainer named requirement.
template <typename T>
concept IsContiguousContainer = std::contiguous_iterator<typename T::iterator>;
template <typename Derived, typename Base>
concept DerivedFrom = std::derived_from<Derived, Base>;
// TODO: Replace with std::convertible_to when libc++ implements it.
template <typename From, typename To>
concept ConvertibleTo = std::is_convertible_v<From, To>;
// No equivalents in the stdlib
template <typename T>
concept IsArithmetic = std::is_arithmetic_v<T>;
template <typename T>
concept IsIntegral = std::is_integral_v<T>;
} // namespace Common

57
src/common/error.cpp Normal file
View File

@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstddef>
#ifdef _WIN32
#include <windows.h>
#else
#include <cerrno>
#include <cstring>
#endif
#include "common/error.h"
namespace Common {
std::string NativeErrorToString(int e) {
#ifdef _WIN32
LPSTR err_str;
DWORD res = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, e, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPSTR>(&err_str), 1, nullptr);
if (!res) {
return "(FormatMessageA failed to format error)";
}
std::string ret(err_str);
LocalFree(err_str);
return ret;
#else
char err_str[255];
#if defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600)) || \
defined(ANDROID)
// Thread safe (GNU-specific)
const char* str = strerror_r(e, err_str, sizeof(err_str));
return std::string(str);
#else
// Thread safe (XSI-compliant)
int second_err = strerror_r(e, err_str, sizeof(err_str));
if (second_err != 0) {
return "(strerror_r failed to format error)";
}
return std::string(err_str);
#endif // GLIBC etc.
#endif // _WIN32
}
std::string GetLastErrorMsg() {
#ifdef _WIN32
return NativeErrorToString(GetLastError());
#else
return NativeErrorToString(errno);
#endif
}
} // namespace Common

21
src/common/error.h Normal file
View File

@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
namespace Common {
// 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();
// Like GetLastErrorMsg(), but passing an explicit error code.
// Defined in error.cpp.
[[nodiscard]] std::string NativeErrorToString(int e);
} // namespace Common

View File

@ -1,96 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/fs_file.h"
namespace Common::FS {
File::File() = default;
File::File(const std::string& path, OpenMode mode) {
open(path, mode);
}
File::~File() {
close();
}
bool File::open(const std::string& path, OpenMode mode) {
close();
#ifdef _WIN64
fopen_s(&m_file, path.c_str(), getOpenMode(mode));
#else
m_file = std::fopen(path.c_str(), getOpenMode(mode));
#endif
return isOpen();
}
bool File::close() {
if (!isOpen() || std::fclose(m_file) != 0) [[unlikely]] {
m_file = nullptr;
return false;
}
m_file = nullptr;
return true;
}
bool File::write(std::span<const u8> data) {
return isOpen() && std::fwrite(data.data(), 1, data.size(), m_file) == data.size();
}
bool File::read(void* data, u64 size) const {
return isOpen() && std::fread(data, 1, size, m_file) == size;
}
bool File::seek(s64 offset, SeekMode mode) {
#ifdef _WIN64
if (!isOpen() || _fseeki64(m_file, offset, getSeekMode(mode)) != 0) {
return false;
}
#else
if (!isOpen() || fseeko64(m_file, offset, getSeekMode(mode)) != 0) {
return false;
}
#endif
return true;
}
u64 File::tell() const {
if (isOpen()) [[likely]] {
#ifdef _WIN64
return _ftelli64(m_file);
#else
return ftello64(m_file);
#endif
}
return -1;
}
u64 File::getFileSize() {
#ifdef _WIN64
const u64 pos = _ftelli64(m_file);
if (_fseeki64(m_file, 0, SEEK_END) != 0) {
return 0;
}
const u64 size = _ftelli64(m_file);
if (_fseeki64(m_file, pos, SEEK_SET) != 0) {
return 0;
}
#else
const u64 pos = ftello64(m_file);
if (fseeko64(m_file, 0, SEEK_END) != 0) {
return 0;
}
const u64 size = ftello64(m_file);
if (fseeko64(m_file, pos, SEEK_SET) != 0) {
return 0;
}
#endif
return size;
}
} // namespace Common::FS

View File

@ -1,86 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <bit>
#include <cstdio>
#include <span>
#include <string>
#include <vector>
#include "common/types.h"
namespace Common::FS {
enum class OpenMode : u32 { Read = 0x1, Write = 0x2, ReadWrite = Read | Write };
enum class SeekMode : u32 {
Set,
Cur,
End,
};
class File {
public:
File();
explicit File(const std::string& path, OpenMode mode = OpenMode::Read);
~File();
bool open(const std::string& path, OpenMode mode = OpenMode::Read);
bool close();
bool read(void* data, u64 size) const;
bool write(std::span<const u8> data);
bool seek(s64 offset, SeekMode mode);
u64 getFileSize();
u64 tell() const;
template <typename T>
bool read(T& data) const {
return read(&data, sizeof(T));
}
template <typename T>
bool read(std::vector<T>& data) const {
return read(data.data(), data.size() * sizeof(T));
}
bool isOpen() const {
return m_file != nullptr;
}
const char* getOpenMode(OpenMode mode) const {
switch (mode) {
case OpenMode::Read:
return "rb";
case OpenMode::Write:
return "wb";
case OpenMode::ReadWrite:
return "r+b";
default:
return "r";
}
}
int getSeekMode(SeekMode mode) const {
switch (mode) {
case SeekMode::Set:
return SEEK_SET;
case SeekMode::Cur:
return SEEK_CUR;
case SeekMode::End:
return SEEK_END;
default:
return SEEK_SET;
}
}
[[nodiscard]] std::FILE* fileDescr() const {
return m_file;
}
private:
std::FILE* m_file{};
};
} // namespace Common::FS

View File

@ -1,141 +1,335 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/io_file.h"
#include <vector>
// #include "helpers.hpp"
#include "common/io_file.h"
#include "common/logging/log.h"
#include "common/path_util.h"
#ifdef _WIN32
#include <io.h>
#include <share.h>
#else
#include <unistd.h>
#endif
#ifdef _MSC_VER
// 64 bit offsets for MSVC
#define fileno _fileno
#define fseeko _fseeki64
#define ftello _ftelli64
#define fileno _fileno
#pragma warning(disable : 4996)
#endif
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
namespace Common::FS {
namespace fs = std::filesystem;
namespace {
#ifdef _WIN32
[[nodiscard]] constexpr const wchar_t* AccessModeToWStr(FileAccessMode mode, FileType type) {
switch (type) {
case FileType::BinaryFile:
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 FileType::TextFile:
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;
}
}
#ifdef WIN32
#include <io.h> // For _chsize_s
#else
#include <unistd.h> // For ftruncate
[[nodiscard]] constexpr const char* AccessModeToStr(FileAccessMode mode, FileType type) {
switch (type) {
case FileType::BinaryFile:
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 FileType::TextFile:
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
IOFile::IOFile(const std::filesystem::path& path, const char* permissions) : handle(nullptr) {
open(path, permissions);
}
bool IOFile::open(const std::filesystem::path& path, const char* permissions) {
const auto str =
path.string(); // For some reason converting paths directly with c_str() doesn't work
return open(str.c_str(), permissions);
}
bool IOFile::open(const char* filename, const char* permissions) {
// If this IOFile is already bound to an open file descriptor, release the file descriptor
// To avoid leaking it and/or erroneously locking the file
if (isOpen()) {
close();
}
handle = std::fopen(filename, permissions);
return isOpen();
}
void IOFile::close() {
if (isOpen()) {
fclose(handle);
handle = nullptr;
[[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;
}
}
std::pair<bool, std::size_t> IOFile::read(void* data, std::size_t length, std::size_t dataSize) {
if (!isOpen()) {
return {false, std::numeric_limits<std::size_t>::max()};
}
} // Anonymous namespace
if (length == 0)
return {true, 0};
return {true, std::fread(data, dataSize, length, handle)};
IOFile::IOFile() = default;
IOFile::IOFile(const std::string& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
Open(path, mode, type, flag);
}
std::pair<bool, std::size_t> IOFile::write(const void* data, std::size_t length,
std::size_t dataSize) {
if (!isOpen()) {
return {false, std::numeric_limits<std::size_t>::max()};
}
IOFile::IOFile(std::string_view path, FileAccessMode mode, FileType type, FileShareFlag flag) {
Open(path, mode, type, flag);
}
if (length == 0) {
return {true, 0};
IOFile::IOFile(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
Open(path, mode, type, flag);
}
IOFile::~IOFile() {
Close();
}
IOFile::IOFile(IOFile&& other) noexcept {
std::swap(file_path, other.file_path);
std::swap(file_access_mode, other.file_access_mode);
std::swap(file_type, other.file_type);
std::swap(file, other.file);
}
IOFile& IOFile::operator=(IOFile&& other) noexcept {
std::swap(file_path, other.file_path);
std::swap(file_access_mode, other.file_access_mode);
std::swap(file_type, other.file_type);
std::swap(file, other.file);
return *this;
}
void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
Close();
file_path = path;
file_access_mode = mode;
file_type = type;
errno = 0;
#ifdef _WIN32
if (flag != FileShareFlag::ShareNone) {
file = _wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag));
} else {
return {true, std::fwrite(data, dataSize, length, handle)};
_wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type));
}
}
std::pair<bool, std::size_t> IOFile::readBytes(void* data, std::size_t count) {
return read(data, count, sizeof(std::uint8_t));
}
std::pair<bool, std::size_t> IOFile::writeBytes(const void* data, std::size_t count) {
return write(data, count, sizeof(std::uint8_t));
}
std::optional<std::uint64_t> IOFile::size() {
if (!isOpen())
return {};
std::uint64_t pos = ftello(handle);
if (fseeko(handle, 0, SEEK_END) != 0) {
return {};
}
std::uint64_t size = ftello(handle);
if ((size != pos) && (fseeko(handle, pos, SEEK_SET) != 0)) {
return {};
}
return size;
}
bool IOFile::seek(std::int64_t offset, int origin) {
if (!isOpen() || fseeko(handle, offset, origin) != 0)
return false;
return true;
}
bool IOFile::flush() {
if (!isOpen() || fflush(handle))
return false;
return true;
}
bool IOFile::rewind() {
return seek(0, SEEK_SET);
}
FILE* IOFile::getHandle() {
return handle;
}
void IOFile::setAppDataDir(const std::filesystem::path& dir) {
// if (dir == "")
// Helpers::panic("Failed to set app data directory");
appData = dir;
}
bool IOFile::setSize(std::uint64_t size) {
if (!isOpen())
return false;
bool success;
#ifdef WIN32
success = _chsize_s(_fileno(handle), size) == 0;
#else
success = ftruncate(fileno(handle), size) == 0;
file = std::fopen(path.c_str(), AccessModeToStr(mode, type));
#endif
fflush(handle);
return success;
if (!IsOpen()) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Common_Filesystem, "Failed to open the file at path={}, ec_message={}",
PathToUTF8String(file_path), ec.message());
}
}
void IOFile::Close() {
if (!IsOpen()) {
return;
}
errno = 0;
const auto close_result = std::fclose(file) == 0;
if (!close_result) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Common_Filesystem, "Failed to close the file at path={}, ec_message={}",
PathToUTF8String(file_path), ec.message());
}
file = nullptr;
}
std::string IOFile::ReadString(size_t length) const {
std::vector<char> string_buffer(length);
const auto chars_read = ReadSpan<char>(string_buffer);
const auto string_size = chars_read != length ? chars_read : length;
return std::string{string_buffer.data(), string_size};
}
bool IOFile::Flush() const {
if (!IsOpen()) {
return false;
}
errno = 0;
#ifdef _WIN32
const auto flush_result = std::fflush(file) == 0;
#else
const auto flush_result = std::fflush(file) == 0;
#endif
if (!flush_result) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Common_Filesystem, "Failed to flush the file at path={}, ec_message={}",
PathToUTF8String(file_path), ec.message());
}
return flush_result;
}
bool IOFile::Commit() const {
if (!IsOpen()) {
return false;
}
errno = 0;
#ifdef _WIN32
const auto commit_result = std::fflush(file) == 0 && _commit(fileno(file)) == 0;
#else
const auto commit_result = std::fflush(file) == 0 && fsync(fileno(file)) == 0;
#endif
if (!commit_result) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Common_Filesystem, "Failed to commit the file at path={}, ec_message={}",
PathToUTF8String(file_path), ec.message());
}
return commit_result;
}
bool IOFile::SetSize(u64 size) const {
if (!IsOpen()) {
return false;
}
errno = 0;
#ifdef _WIN32
const auto set_size_result = _chsize_s(fileno(file), static_cast<s64>(size)) == 0;
#else
const auto set_size_result = ftruncate(fileno(file), static_cast<s64>(size)) == 0;
#endif
if (!set_size_result) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={}, size={}, ec_message={}",
PathToUTF8String(file_path), size, ec.message());
}
return set_size_result;
}
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);
std::error_code ec;
const auto file_size = fs::file_size(file_path, ec);
if (ec) {
LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}",
PathToUTF8String(file_path), ec.message());
return 0;
}
return file_size;
}
bool IOFile::Seek(s64 offset, SeekOrigin origin) const {
if (!IsOpen()) {
return false;
}
errno = 0;
const auto seek_result = fseeko(file, offset, ToSeekOrigin(origin)) == 0;
if (!seek_result) {
const auto ec = std::error_code{errno, std::generic_category()};
LOG_ERROR(Common_Filesystem,
"Failed to seek the file at path={}, offset={}, origin={}, ec_message={}",
PathToUTF8String(file_path), offset, static_cast<u32>(origin), ec.message());
}
return seek_result;
}
s64 IOFile::Tell() const {
if (!IsOpen()) {
return 0;
}
errno = 0;
return ftello(file);
}
} // namespace Common::FS

View File

@ -1,45 +1,207 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstdint>
#include <cstdio>
#include <filesystem>
#include <optional>
#include <span>
#include <type_traits>
class IOFile {
FILE* handle = nullptr;
static inline std::filesystem::path appData =
""; // Directory for holding app data. AppData on Windows
#include "common/concepts.h"
#include "common/types.h"
public:
IOFile() : handle(nullptr) {}
IOFile(FILE* handle) : handle(handle) {}
IOFile(const std::filesystem::path& path, const char* permissions = "rb");
namespace Common::FS {
bool isOpen() {
return handle != nullptr;
}
bool open(const std::filesystem::path& path, const char* permissions = "rb");
bool open(const char* filename, const char* permissions = "rb");
void close();
std::pair<bool, std::size_t> read(void* data, std::size_t length, std::size_t dataSize);
std::pair<bool, std::size_t> readBytes(void* data, std::size_t count);
std::pair<bool, std::size_t> write(const void* data, std::size_t length, std::size_t dataSize);
std::pair<bool, std::size_t> writeBytes(const void* data, std::size_t count);
std::optional<std::uint64_t> size();
bool seek(std::int64_t offset, int origin = SEEK_SET);
bool rewind();
bool flush();
FILE* getHandle();
static void setAppDataDir(const std::filesystem::path& dir);
static std::filesystem::path getAppData() {
return appData;
}
// Sets the size of the file to "size" and returns whether it succeeded or not
bool setSize(std::uint64_t size);
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,
};
enum class FileType {
BinaryFile,
TextFile,
};
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,
FileType type = FileType::BinaryFile,
FileShareFlag flag = FileShareFlag::ShareReadOnly);
explicit IOFile(std::string_view path, FileAccessMode mode,
FileType type = FileType::BinaryFile,
FileShareFlag flag = FileShareFlag::ShareReadOnly);
explicit IOFile(const std::filesystem::path& path, FileAccessMode mode,
FileType type = FileType::BinaryFile,
FileShareFlag flag = FileShareFlag::ShareReadOnly);
~IOFile();
IOFile(const IOFile&) = delete;
IOFile& operator=(const IOFile&) = delete;
IOFile(IOFile&& other) noexcept;
IOFile& operator=(IOFile&& other) noexcept;
std::filesystem::path GetPath() const {
return file_path;
}
FileAccessMode GetAccessMode() const {
return file_access_mode;
}
FileType GetType() const {
return file_type;
}
bool IsOpen() const {
return file != nullptr;
}
void Open(const std::filesystem::path& path, FileAccessMode mode,
FileType type = FileType::BinaryFile,
FileShareFlag flag = FileShareFlag::ShareReadOnly);
void Close();
bool Flush() const;
bool Commit() const;
bool SetSize(u64 size) const;
u64 GetSize() const;
bool Seek(s64 offset, SeekOrigin origin = SeekOrigin::SetOrigin) const;
s64 Tell() const;
template <typename T>
size_t Read(T& data) const {
if constexpr (IsContiguousContainer<T>) {
using ContiguousType = typename T::value_type;
static_assert(std::is_trivially_copyable_v<ContiguousType>,
"Data type must be trivially copyable.");
return ReadSpan<ContiguousType>(data);
} else {
return ReadObject(data) ? 1 : 0;
}
}
template <typename T>
size_t Write(const T& data) const {
if constexpr (IsContiguousContainer<T>) {
using ContiguousType = typename T::value_type;
static_assert(std::is_trivially_copyable_v<ContiguousType>,
"Data type must be trivially copyable.");
return WriteSpan<ContiguousType>(data);
} else {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
return WriteObject(data) ? 1 : 0;
}
}
template <typename T>
size_t ReadSpan(std::span<T> data) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
if (!IsOpen()) {
return 0;
}
return ReadRaw<T>(data.data(), data.size());
}
template <typename T>
size_t ReadRaw(void* data, size_t size) const {
return std::fread(data, sizeof(T), size, file);
}
template <typename T>
size_t WriteSpan(std::span<const T> data) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
if (!IsOpen()) {
return 0;
}
return std::fwrite(data.data(), sizeof(T), data.size(), file);
}
template <typename T>
bool ReadObject(T& object) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");
if (!IsOpen()) {
return false;
}
return std::fread(&object, sizeof(T), 1, file) == 1;
}
template <typename T>
bool WriteObject(const T& object) const {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");
if (!IsOpen()) {
return false;
}
return std::fwrite(&object, sizeof(T), 1, file) == 1;
}
std::string ReadString(size_t length) const;
size_t WriteString(std::span<const char> string) const {
return WriteSpan(string);
}
private:
std::filesystem::path file_path;
FileAccessMode file_access_mode{};
FileType file_type{};
std::FILE* file = nullptr;
};
} // namespace Common::FS

View File

@ -1,159 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <vector>
#include <Util/config.h>
#include <spdlog/common.h>
#include <spdlog/pattern_formatter.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/spdlog.h>
#include "common/log.h"
#ifdef _WIN64
#include <windows.h>
#endif
namespace Common::Log {
std::vector<spdlog::sink_ptr> sinks;
constexpr bool log_file_exceptions = true;
void Flush() {
spdlog::details::registry::instance().flush_all();
}
thread_local uint8_t TLS[1024];
uint64_t tls_access(int64_t tls_offset) {
if (tls_offset == 0) {
return (uint64_t)TLS;
}
}
#ifdef _WIN64
static LONG WINAPI ExceptionHandler(PEXCEPTION_POINTERS pExp) noexcept {
auto orig_rip = pExp->ContextRecord->Rip;
while (*(uint8_t*)pExp->ContextRecord->Rip == 0x66)
pExp->ContextRecord->Rip++;
if (*(uint8_t*)pExp->ContextRecord->Rip == 0xcd) {
int reg = *(uint8_t*)(pExp->ContextRecord->Rip + 1) - 0x80;
int sizes = *(uint8_t*)(pExp->ContextRecord->Rip + 2);
int pattern_size = sizes & 0xF;
int imm_size = sizes >> 4;
int64_t tls_offset;
if (imm_size == 4)
tls_offset = *(int32_t*)(pExp->ContextRecord->Rip + pattern_size);
else
tls_offset = *(int64_t*)(pExp->ContextRecord->Rip + pattern_size);
(&pExp->ContextRecord->Rax)[reg] = tls_access(tls_offset); /* TLS_ACCESS */
pExp->ContextRecord->Rip += pattern_size + imm_size;
return EXCEPTION_CONTINUE_EXECUTION;
}
pExp->ContextRecord->Rip = orig_rip;
const u32 ec = pExp->ExceptionRecord->ExceptionCode;
switch (ec) {
case EXCEPTION_ACCESS_VIOLATION: {
LOG_CRITICAL_IF(log_file_exceptions, "Exception EXCEPTION_ACCESS_VIOLATION ({:#x}). ", ec);
const auto info = pExp->ExceptionRecord->ExceptionInformation;
switch (info[0]) {
case 0:
LOG_CRITICAL_IF(log_file_exceptions, "Read violation at address {:#x}.", info[1]);
break;
case 1:
LOG_CRITICAL_IF(log_file_exceptions, "Write violation at address {:#x}.", info[1]);
break;
case 8:
LOG_CRITICAL_IF(log_file_exceptions, "DEP violation at address {:#x}.", info[1]);
break;
default:
break;
}
break;
}
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
LOG_CRITICAL_IF(log_file_exceptions, "Exception EXCEPTION_ARRAY_BOUNDS_EXCEEDED ({:#x}). ",
ec);
break;
case EXCEPTION_DATATYPE_MISALIGNMENT:
LOG_CRITICAL_IF(log_file_exceptions, "Exception EXCEPTION_DATATYPE_MISALIGNMENT ({:#x}). ",
ec);
break;
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
LOG_CRITICAL_IF(log_file_exceptions, "Exception EXCEPTION_FLT_DIVIDE_BY_ZERO ({:#x}). ",
ec);
break;
case EXCEPTION_ILLEGAL_INSTRUCTION:
LOG_CRITICAL_IF(log_file_exceptions, "Exception EXCEPTION_ILLEGAL_INSTRUCTION ({:#x}). ",
ec);
break;
case EXCEPTION_IN_PAGE_ERROR:
LOG_CRITICAL_IF(log_file_exceptions, "Exception EXCEPTION_IN_PAGE_ERROR ({:#x}). ", ec);
break;
case EXCEPTION_INT_DIVIDE_BY_ZERO:
LOG_CRITICAL_IF(log_file_exceptions, "Exception EXCEPTION_INT_DIVIDE_BY_ZERO ({:#x}). ",
ec);
break;
case EXCEPTION_PRIV_INSTRUCTION:
LOG_CRITICAL_IF(log_file_exceptions, "Exception EXCEPTION_PRIV_INSTRUCTION ({:#x}). ", ec);
break;
case EXCEPTION_STACK_OVERFLOW:
LOG_CRITICAL_IF(log_file_exceptions, "Exception EXCEPTION_STACK_OVERFLOW ({:#x}). ", ec);
break;
default:
return EXCEPTION_CONTINUE_SEARCH;
}
Flush();
return EXCEPTION_CONTINUE_SEARCH;
}
#endif
int Init(bool use_stdout) {
sinks.clear();
if (use_stdout) {
sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
}
#ifdef _WIN64
sinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>(L"shadps4.txt", true));
#else
sinks.push_back(std::make_shared<spdlog::sinks::basic_file_sink_mt>("shadps4.txt", true));
#endif
spdlog::set_default_logger(
std::make_shared<spdlog::logger>("shadps4 logger", begin(sinks), end(sinks)));
auto f = std::make_unique<spdlog::pattern_formatter>(
"%^|%L|: %v%$", spdlog::pattern_time_type::local, std::string("")); // disable eol
spdlog::set_formatter(std::move(f));
spdlog::set_level(static_cast<spdlog::level::level_enum>(Config::getLogLevel()));
#ifdef _WIN64
if (!AddVectoredExceptionHandler(0, ExceptionHandler)) {
LOG_CRITICAL_IF(log_file_exceptions, "Failed to register an exception handler");
}
#endif
static std::terminate_handler old_terminate = nullptr;
old_terminate = std::set_terminate([]() {
try {
std::rethrow_exception(std::current_exception());
} catch (const std::exception& e) {
LOG_CRITICAL_IF(log_file_exceptions, "Unhandled C++ exception. {}", e.what());
} catch (...) {
LOG_CRITICAL_IF(log_file_exceptions, "Unhandled C++ exception. UNKNOWN");
}
Flush();
if (old_terminate)
old_terminate();
});
return 0;
}
void SetLevel(spdlog::level::level_enum log_level) {
spdlog::set_level(log_level);
}
} // namespace Common::Log

View File

@ -1,42 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_TRACE
#include <spdlog/spdlog.h>
namespace Common::Log {
#define LOG_TRACE SPDLOG_TRACE
#define LOG_DEBUG SPDLOG_DEBUG
#define LOG_INFO SPDLOG_INFO
#define LOG_WARN SPDLOG_WARN
#define LOG_ERROR SPDLOG_ERROR
#define LOG_CRITICAL SPDLOG_CRITICAL
#define LOG_TRACE_IF(flag, ...) \
if (flag) \
LOG_TRACE(__VA_ARGS__)
#define LOG_DEBUG_IF(flag, ...) \
if (flag) \
LOG_DEBUG(__VA_ARGS__)
#define LOG_INFO_IF(flag, ...) \
if (flag) \
LOG_INFO(__VA_ARGS__)
#define LOG_WARN_IF(flag, ...) \
if (flag) \
LOG_WARN(__VA_ARGS__)
#define LOG_ERROR_IF(flag, ...) \
if (flag) \
LOG_ERROR(__VA_ARGS__)
#define LOG_CRITICAL_IF(flag, ...) \
if (flag) \
LOG_CRITICAL(__VA_ARGS__)
int Init(bool use_stdout);
void SetLevel(spdlog::level::level_enum log_level);
} // namespace Common::Log

View File

@ -0,0 +1,277 @@
// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
#include <filesystem>
#include <thread>
#include <fmt/format.h>
#include "Util/config.h"
#ifdef _WIN32
#include <windows.h> // For OutputDebugStringW
#endif
#include "common/bounded_threadsafe_queue.h"
#include "common/io_file.h"
#include "common/logging/backend.h"
#include "common/logging/log.h"
#include "common/logging/log_entry.h"
#include "common/logging/text_formatter.h"
#include "common/path_util.h"
#include "common/string_util.h"
#include "common/thread.h"
namespace Common::Log {
using namespace Common::FS;
namespace {
/**
* Backend that writes to stderr and with color
*/
class ColorConsoleBackend {
public:
explicit ColorConsoleBackend() = default;
~ColorConsoleBackend() = default;
void Write(const Entry& entry) {
if (enabled.load(std::memory_order_relaxed)) {
PrintColoredMessage(entry);
}
}
void Flush() {
// stderr shouldn't be buffered
}
void SetEnabled(bool enabled_) {
enabled = enabled_;
}
private:
std::atomic_bool enabled{true};
};
/**
* Backend that writes to a file passed into the constructor
*/
class FileBackend {
public:
explicit FileBackend(const std::filesystem::path& filename)
: file{filename, FS::FileAccessMode::Write, FS::FileType::TextFile} {}
~FileBackend() = default;
void Write(const Entry& entry) {
if (!enabled) {
return;
}
bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n'));
// Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
const auto write_limit = 100_MB;
const bool write_limit_exceeded = bytes_written > write_limit;
if (entry.log_level >= Level::Error || write_limit_exceeded) {
if (write_limit_exceeded) {
// 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() {
file.Flush();
}
private:
Common::FS::IOFile file;
bool enabled = true;
std::size_t bytes_written = 0;
};
/**
* Backend that writes to Visual Studio's output window
*/
class DebuggerBackend {
public:
explicit DebuggerBackend() = default;
~DebuggerBackend() = default;
void Write(const Entry& entry) {
#ifdef _WIN32
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
#endif
}
void Flush() {}
void EnableForStacktrace() {}
};
bool initialization_in_progress_suppress_logging = 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(std::string_view log_file) {
if (instance) {
LOG_WARNING(Log, "Reinitializing logging backend");
return;
}
const auto& log_dir = GetUserPath(PathType::LogDir);
std::filesystem::create_directory(log_dir);
Filter filter;
filter.ParseFilterString(Config::getLogFilter());
instance = std::unique_ptr<Impl, decltype(&Deleter)>(new Impl(log_dir / LOG_FILE, filter),
Deleter);
initialization_in_progress_suppress_logging = false;
}
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) {
color_console_backend.SetEnabled(enabled);
}
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
const char* function, std::string message) {
if (!filter.CheckMessage(log_class, log_level)) {
return;
}
using std::chrono::duration_cast;
using std::chrono::microseconds;
using std::chrono::steady_clock;
message_queue.EmplaceWait(Entry{
.timestamp = duration_cast<microseconds>(steady_clock::now() - time_origin),
.log_class = log_class,
.log_level = log_level,
.filename = filename,
.line_num = line_num,
.function = function,
.message = std::move(message),
});
}
private:
Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_)
: filter{filter_}, file_backend{file_backend_filename} {}
~Impl() = default;
void StartBackendThread() {
backend_thread = std::jthread([this](std::stop_token stop_token) {
Common::SetCurrentThreadName("shadPS4:Log");
Entry entry;
const auto write_logs = [this, &entry]() {
ForEachBackend([&entry](auto& backend) { backend.Write(entry); });
};
while (!stop_token.stop_requested()) {
message_queue.PopWait(entry, stop_token);
if (entry.filename != nullptr) {
write_logs();
}
}
// 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.
int max_logs_to_write = filter.IsDebug() ? std::numeric_limits<s32>::max() : 100;
while (max_logs_to_write-- && message_queue.TryPop(entry)) {
write_logs();
}
});
}
void StopBackendThread() {
backend_thread.request_stop();
if (backend_thread.joinable()) {
backend_thread.join();
}
ForEachBackend([](auto& backend) { backend.Flush(); });
}
void ForEachBackend(auto lambda) {
lambda(debugger_backend);
lambda(color_console_backend);
lambda(file_backend);
}
static void Deleter(Impl* ptr) {
delete ptr;
}
static inline std::unique_ptr<Impl, decltype(&Deleter)> instance{nullptr, Deleter};
Filter filter;
DebuggerBackend debugger_backend{};
ColorConsoleBackend color_console_backend{};
FileBackend file_backend;
MPSCQueue<Entry> message_queue{};
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
std::jthread backend_thread;
};
} // namespace
void Initialize(std::string_view log_file) {
Impl::Initialize(log_file.empty() ? LOG_FILE : log_file);
}
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 log_class, Level log_level, const char* filename,
unsigned int line_num, const char* function, const char* format,
const fmt::format_args& args) {
if (!initialization_in_progress_suppress_logging) [[likely]] {
Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,
fmt::vformat(format, args));
}
}
} // namespace Common::Log

View File

@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string_view>
#include "common/logging/filter.h"
namespace Common::Log {
class Filter;
/// Initializes the logging system. This should be the first thing called in main.
void Initialize(std::string_view log_file = "");
/// 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 Common::Log

View File

@ -0,0 +1,173 @@
// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include "common/assert.h"
#include "common/logging/filter.h"
namespace Common::Log {
namespace {
template <typename It>
Level GetLevelByName(const It begin, const It end) {
for (u8 i = 0; i < static_cast<u8>(Level::Count); ++i) {
const char* level_name = GetLevelName(static_cast<Level>(i));
if (std::string_view(begin, end).compare(level_name) == 0) {
return static_cast<Level>(i);
}
}
return Level::Count;
}
template <typename It>
Class GetClassByName(const It begin, const It end) {
for (u8 i = 0; i < static_cast<u8>(Class::Count); ++i) {
const char* level_name = GetLogClassName(static_cast<Class>(i));
if (std::string_view(begin, end).compare(level_name) == 0) {
return static_cast<Class>(i);
}
}
return Class::Count;
}
template <typename Iterator>
bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
auto level_separator = std::find(begin, end, ':');
if (level_separator == end) {
LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: {}",
std::string_view(begin, end));
return false;
}
const Level level = GetLevelByName(level_separator + 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, level_separator).compare("*") == 0) {
instance.ResetAll(level);
return true;
}
const Class log_class = GetClassByName(begin, level_separator);
if (log_class == Class::Count) {
LOG_ERROR(Log, "Unknown log class in filter: {}", std::string(begin, end));
return false;
}
instance.SetClassLevel(log_class, level);
return true;
}
} // Anonymous namespace
/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
#define ALL_LOG_CLASSES() \
CLS(Log) \
CLS(Common) \
SUB(Common, Filesystem) \
SUB(Common, Memory) \
CLS(Core) \
SUB(Core, Linker) \
CLS(Config) \
CLS(Debug) \
CLS(Kernel) \
SUB(Kernel, Pthread) \
SUB(Kernel, Vmm) \
SUB(Kernel, Fs) \
SUB(Kernel, Event) \
SUB(Kernel, Sce) \
CLS(Lib) \
SUB(Lib, LibC) \
SUB(Lib, Kernel) \
SUB(Lib, Pad) \
SUB(Lib, GnmDriver) \
SUB(Lib, SystemService) \
SUB(Lib, UserService) \
SUB(Lib, VideoOut) \
CLS(Frontend) \
CLS(Render) \
SUB(Render, Vulkan) \
CLS(Input) \
CLS(Loader)
// GetClassName is a macro defined by Windows.h, grrr...
const char* GetLogClassName(Class log_class) {
switch (log_class) {
#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 log_level) {
#define LVL(x) \
case Level::x: \
return #x
switch (log_level) {
LVL(Trace);
LVL(Debug);
LVL(Info);
LVL(Warning);
LVL(Error);
LVL(Critical);
case Level::Count:
default:
break;
}
#undef LVL
UNREACHABLE();
}
Filter::Filter(Level default_level) {
ResetAll(default_level);
}
void Filter::ResetAll(Level level) {
class_levels.fill(level);
}
void Filter::SetClassLevel(Class log_class, Level level) {
class_levels[static_cast<std::size_t>(log_class)] = level;
}
void Filter::ParseFilterString(std::string_view filter_view) {
auto clause_begin = filter_view.cbegin();
while (clause_begin != filter_view.cend()) {
auto clause_end = std::find(clause_begin, filter_view.cend(), ' ');
// If clause isn't empty
if (clause_end != clause_begin) {
ParseFilterRule(*this, clause_begin, clause_end);
}
if (clause_end != filter_view.cend()) {
// Skip over the whitespace
++clause_end;
}
clause_begin = clause_end;
}
}
bool Filter::CheckMessage(Class log_class, Level level) const {
return static_cast<u8>(level) >=
static_cast<u8>(class_levels[static_cast<std::size_t>(log_class)]);
}
bool Filter::IsDebug() const {
return std::any_of(class_levels.begin(), class_levels.end(), [](const Level& l) {
return static_cast<u8>(l) <= static_cast<u8>(Level::Debug);
});
}
} // namespace Common::Log

View File

@ -0,0 +1,66 @@
// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <cstddef>
#include <string_view>
#include "common/logging/types.h"
namespace Common::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 default_level = 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 log_class, Level level);
/**
* Parses a filter string and applies it to this filter.
*
* A filter string consists of a space-separated list of filter rules, each of the format
* `<class>:<level>`. `<class>` is a log class name, with subclasses separated using periods.
* `*` is allowed as a class name and will reset all filters to the specified level. `<level>`
* a severity level name which will be set as the minimum logging level of the matched classes.
* Rules are applied left to right, with each rule overriding previous ones in the sequence.
*
* A few examples of filter rules:
* - `*:Info` -- Resets the level of all classes to Info.
* - `Service:Info` -- Sets the level of Service to Info.
* - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace.
*/
void ParseFilterString(std::string_view filter_view);
/// Matches class/level combination against the filter, returning true if it passed.
bool CheckMessage(Class log_class, Level level) const;
/// Returns true if any logging classes are set to debug
bool IsDebug() const;
private:
std::array<Level, static_cast<std::size_t>(Class::Count)> class_levels;
};
} // namespace Common::Log

View File

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

70
src/common/logging/log.h Normal file
View File

@ -0,0 +1,70 @@
// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <algorithm>
#include <array>
#include <string_view>
#include "common/logging/formatter.h"
#include "common/logging/types.h"
namespace Common::Log {
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("/"), rfind("\\")});
return source.data() + idx;
}
/// Logs a message to the global logger, using fmt
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
unsigned int line_num, const char* function, const char* format,
const fmt::format_args& args);
template <typename... Args>
void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num,
const char* function, const char* format, const Args&... args) {
FmtLogMessageImpl(log_class, log_level, filename, line_num, function, format,
fmt::make_format_args(args...));
}
} // namespace Common::Log
// Define the fmt lib macros
#define LOG_GENERIC(log_class, log_level, ...) \
Common::Log::FmtLogMessage(log_class, log_level, Common::Log::TrimSourcePath(__FILE__), \
__LINE__, __func__, __VA_ARGS__)
#ifdef _DEBUG
#define LOG_TRACE(log_class, ...) \
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Trace, \
Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
__VA_ARGS__)
#else
#define LOG_TRACE(log_class, fmt, ...) (void(0))
#endif
#define LOG_DEBUG(log_class, ...) \
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Debug, \
Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
__VA_ARGS__)
#define LOG_INFO(log_class, ...) \
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Info, \
Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
__VA_ARGS__)
#define LOG_WARNING(log_class, ...) \
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Warning, \
Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
__VA_ARGS__)
#define LOG_ERROR(log_class, ...) \
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Error, \
Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
__VA_ARGS__)
#define LOG_CRITICAL(log_class, ...) \
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Critical, \
Common::Log::TrimSourcePath(__FILE__), __LINE__, __func__, \
__VA_ARGS__)

View File

@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <chrono>
#include "common/logging/types.h"
namespace Common::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 log_class{};
Level log_level{};
const char* filename = nullptr;
u32 line_num = 0;
std::string function;
std::string message;
};
} // namespace Common::Log

View File

@ -0,0 +1,109 @@
// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <cstdio>
#ifdef _WIN32
#include <windows.h>
#endif
#include "common/assert.h"
#include "common/logging/filter.h"
#include "common/logging/log.h"
#include "common/logging/log_entry.h"
#include "common/logging/text_formatter.h"
namespace Common::Log {
std::string FormatLogMessage(const Entry& entry) {
const u32 time_seconds = static_cast<u32>(entry.timestamp.count() / 1000000);
const u32 time_fractional = static_cast<u32>(entry.timestamp.count() % 1000000);
const char* class_name = GetLogClassName(entry.log_class);
const char* level_name = GetLevelName(entry.log_level);
return fmt::format("[{}] <{}> {}:{}:{}: {}", class_name, level_name, entry.filename,
entry.function, entry.line_num, entry.message);
}
void PrintMessage(const Entry& entry) {
const auto str = FormatLogMessage(entry).append(1, '\n');
fputs(str.c_str(), stdout);
}
void PrintColoredMessage(const Entry& entry) {
#ifdef _WIN32
HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE);
if (console_handle == INVALID_HANDLE_VALUE) {
return;
}
CONSOLE_SCREEN_BUFFER_INFO original_info{};
GetConsoleScreenBufferInfo(console_handle, &original_info);
WORD color = 0;
switch (entry.log_level) {
case Level::Trace: // Grey
color = FOREGROUND_INTENSITY;
break;
case Level::Debug: // Cyan
color = FOREGROUND_GREEN | FOREGROUND_BLUE;
break;
case Level::Info: // Bright gray
color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
break;
case Level::Warning: // Bright yellow
color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY;
break;
case Level::Error: // Bright red
color = FOREGROUND_RED | FOREGROUND_INTENSITY;
break;
case Level::Critical: // Bright magenta
color = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY;
break;
case Level::Count:
UNREACHABLE();
}
SetConsoleTextAttribute(console_handle, color);
#else
#define ESC "\x1b"
const char* color = "";
switch (entry.log_level) {
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::Count:
UNREACHABLE();
}
fputs(color, stdout);
#endif
PrintMessage(entry);
#ifdef _WIN32
SetConsoleTextAttribute(console_handle, original_info.wAttributes);
#else
fputs(ESC "[0m", stdout);
#undef ESC
#endif
}
} // namespace Common::Log

View File

@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: Copyright 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
namespace Common::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 Entry& entry);
/// Prints the same message as `PrintMessage`, but colored according to the severity level.
void PrintColoredMessage(const Entry& entry);
} // namespace Common::Log

View File

@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: Copyright 2023 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "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
* backend.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_Linker, ///< The module linker
Config, ///< Emulator configuration (including commandline)
Debug, ///< Debugging tools
Kernel, ///< The HLE implementation of the PS4 kernel.
Kernel_Pthread, ///< The pthread implementation of the kernel.
Kernel_Fs, ///< The filesystem implementation of the kernel.
Kernel_Vmm, ///< The virtual memory implementation of the kernel.
Kernel_Event, ///< The event management implementation of the kernel.
Kernel_Sce, ///< The sony specific interfaces provided by the kernel.
Lib, ///< HLE implementation of system library. Each major library
///< should have its own subclass.
Lib_LibC, ///< The LibC implementation.
Lib_Kernel, ///< The LibKernel implementation.
Lib_Pad, ///< The LibScePad implementation.
Lib_GnmDriver, ///< The LibSceGnmDriver implementation.
Lib_SystemService, ///< The LibSceSystemService implementation.
Lib_UserService, ///< The LibSceUserService implementation.
Lib_VideoOut, ///< The LibSceVideoOut implementation.
Frontend, ///< Emulator UI
Render, ///< Video Core
Render_Vulkan, ///< Vulkan backend
Loader, ///< ROM loader
Input, ///< Input emulation
Count ///< Total number of logging classes
};
} // namespace Common::Log

87
src/common/path_util.cpp Normal file
View File

@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <sstream>
#include <unordered_map>
#include "common/logging/log.h"
#include "common/path_util.h"
#ifndef MAX_PATH
#ifdef _WIN32
// This is the maximum number of UTF-16 code units permissible in Windows file paths
#define MAX_PATH 260
#else
// This is the maximum number of UTF-8 code units permissible in all other OSes' file paths
#define MAX_PATH 1024
#endif
#endif
namespace Common::FS {
namespace fs = std::filesystem;
static auto UserPaths = [] {
std::unordered_map<PathType, fs::path> paths;
const auto user_dir = std::filesystem::current_path() / PORTABLE_DIR;
const auto create_path = [&](PathType shad_path, const fs::path& new_path) {
std::filesystem::create_directory(new_path);
paths.insert_or_assign(shad_path, new_path);
};
create_path(PathType::UserDir, user_dir);
create_path(PathType::LogDir, user_dir / LOG_DIR);
create_path(PathType::ScreenshotsDir, user_dir / SCREENSHOTS_DIR);
create_path(PathType::ShaderDir, user_dir / SHADER_DIR);
create_path(PathType::App0, user_dir / APP0_DIR);
return paths;
}();
bool ValidatePath(const fs::path& path) {
if (path.empty()) {
LOG_ERROR(Common_Filesystem, "Input path is empty, path={}", PathToUTF8String(path));
return false;
}
#ifdef _WIN32
if (path.u16string().size() >= MAX_PATH) {
LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path));
return false;
}
#else
if (path.u8string().size() >= MAX_PATH) {
LOG_ERROR(Common_Filesystem, "Input path is too long, path={}", PathToUTF8String(path));
return false;
}
#endif
return true;
}
std::string PathToUTF8String(const std::filesystem::path& path) {
const auto u8_string = path.u8string();
return std::string{u8_string.begin(), u8_string.end()};
}
const fs::path& GetUserPath(PathType shad_path) {
return UserPaths.at(shad_path);
}
std::string GetUserPathString(PathType shad_path) {
return PathToUTF8String(GetUserPath(shad_path));
}
void SetUserPath(PathType shad_path, const fs::path& new_path) {
if (!std::filesystem::is_directory(new_path)) {
LOG_ERROR(Common_Filesystem, "Filesystem object at new_path={} is not a directory",
PathToUTF8String(new_path));
return;
}
UserPaths.insert_or_assign(shad_path, new_path);
}
} // namespace Common::FS

79
src/common/path_util.h Normal file
View File

@ -0,0 +1,79 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <filesystem>
#include <vector>
namespace Common::FS {
enum class PathType {
UserDir, // Where shadPS4 stores its data.
LogDir, // Where log files are stored.
ScreenshotsDir, // Where screenshots are stored.
ShaderDir, // Where shaders are stored.
App0, // Where guest application data is stored.
};
constexpr auto PORTABLE_DIR = "user";
// Sub-directories contained within a user data directory
constexpr auto LOG_DIR = "log";
constexpr auto SCREENSHOTS_DIR = "screenshots";
constexpr auto SHADER_DIR = "shader";
constexpr auto APP0_DIR = "app0";
// Filenames
constexpr auto LOG_FILE = "shad_log.txt";
/**
* 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);
/**
* Converts a filesystem path to a UTF-8 encoded std::string.
*
* @param path Filesystem path
*
* @returns UTF-8 encoded std::string.
*/
[[nodiscard]] std::string PathToUTF8String(const std::filesystem::path& path);
/**
* Gets the filesystem path associated with the PathType enum.
*
* @param user_path PathType enum
*
* @returns The filesystem path associated with the PathType enum.
*/
[[nodiscard]] const std::filesystem::path& GetUserPath(PathType user_path);
/**
* Gets the filesystem path associated with the PathType enum as a UTF-8 encoded std::string.
*
* @param user_path PathType enum
*
* @returns The filesystem path associated with the PathType enum as a UTF-8 encoded std::string.
*/
[[nodiscard]] std::string GetUserPathString(PathType user_path);
/**
* Sets a new filesystem path associated with the PathType enum.
* If the filesystem object at new_path is not a directory, this function will not do anything.
*
* @param user_path PathType enum
* @param new_path New filesystem path
*/
void SetUserPath(PathType user_path, const std::filesystem::path& new_path);
} // namespace Common::FS

View File

@ -6,6 +6,11 @@
#include <sstream>
#include <string>
#include "common/string_util.h"
#include "common/types.h"
#ifdef _WIN32
#include <windows.h>
#endif
namespace Common {
@ -21,4 +26,46 @@ std::vector<std::string> SplitString(const std::string& str, char delimiter) {
return output;
}
#ifdef _WIN32
static std::wstring CPToUTF16(u32 code_page, std::string_view input) {
const auto size =
MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()), nullptr, 0);
if (size == 0) {
return {};
}
std::wstring output(size, L'\0');
if (size != MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()),
&output[0], static_cast<int>(output.size()))) {
output.clear();
}
return output;
}
std::string UTF16ToUTF8(std::wstring_view input) {
const auto size = WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast<int>(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<int>(input.size()),
&output[0], static_cast<int>(output.size()), nullptr,
nullptr)) {
output.clear();
}
return output;
}
std::wstring UTF8ToUTF16W(std::string_view input) {
return CPToUTF16(CP_UTF8, input);
}
#endif
} // namespace Common

View File

@ -11,4 +11,9 @@ namespace Common {
std::vector<std::string> SplitString(const std::string& str, char delimiter);
#ifdef _WIN32
[[nodiscard]] std::string UTF16ToUTF8(std::wstring_view input);
[[nodiscard]] std::wstring UTF8ToUTF16W(std::string_view str);
#endif
} // namespace Common

123
src/common/thread.cpp Normal file
View File

@ -0,0 +1,123 @@
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
#include "common/error.h"
#include "common/logging/log.h"
#include "common/thread.h"
#ifdef __APPLE__
#include <mach/mach.h>
#elif defined(_WIN32)
#include <windows.h>
#include "common/string_util.h"
#else
#if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
#include <pthread_np.h>
#else
#include <pthread.h>
#endif
#include <sched.h>
#endif
#ifndef _WIN32
#include <unistd.h>
#endif
#ifdef __FreeBSD__
#define cpu_set_t cpuset_t
#endif
namespace Common {
#ifdef _WIN32
void SetCurrentThreadPriority(ThreadPriority new_priority) {
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);
}
#else
void SetCurrentThreadPriority(ThreadPriority new_priority) {
pthread_t this_thread = pthread_self();
const auto scheduling_type = SCHED_OTHER;
s32 max_prio = sched_get_priority_max(scheduling_type);
s32 min_prio = sched_get_priority_min(scheduling_type);
u32 level = std::max(static_cast<u32>(new_priority) + 1, 4U);
struct sched_param params;
if (max_prio > min_prio) {
params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4;
} else {
params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4;
}
pthread_setschedparam(this_thread, scheduling_type, &params);
}
#endif
#ifdef _MSC_VER
// Sets the debugger-visible name of the current thread.
void SetCurrentThreadName(const char* name) {
SetThreadDescription(GetCurrentThread(), 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 char* name) {
#ifdef __APPLE__
pthread_setname_np(name);
#elif defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
pthread_set_name_np(pthread_self(), name);
#elif defined(__NetBSD__)
pthread_setname_np(pthread_self(), "%s", (void*)name);
#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(name, std::min(strlen(name), static_cast<std::size_t>(15)));
if (int e = pthread_setname_np(pthread_self(), truncated.c_str())) {
errno = e;
LOG_ERROR(Common, "Failed to set thread name to '{}': {}", truncated, GetLastErrorMsg());
}
#else
pthread_setname_np(pthread_self(), name);
#endif
}
#endif
#if defined(_WIN32)
void SetCurrentThreadName(const char*) {
// Do Nothing on MingW
}
#endif
#endif
} // namespace Common

23
src/common/thread.h Normal file
View File

@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Common {
enum class ThreadPriority : u32 {
Low = 0,
Normal = 1,
High = 2,
VeryHigh = 3,
Critical = 4,
};
void SetCurrentThreadPriority(ThreadPriority new_priority);
void SetCurrentThreadName(const char* name);
} // namespace Common

View File

@ -1,14 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/debug.h"
#include "common/log.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/PS4/GPU/tile_manager.h"
#include "core/PS4/GPU/video_out_buffer.h"
#include "vulkan_util.h"
constexpr bool log_file_videoOutBuffer = true; // disable it to disable logging
static void update_func(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params, void* obj,
const u64* virtual_addr, const u64* size, int virtual_addr_num) {
@ -55,8 +53,7 @@ static void* create_func(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params
vk_format = VK_FORMAT_B8G8R8A8_SRGB;
break;
default:
LOG_CRITICAL_IF(log_file_videoOutBuffer, "unknown pixelFormat = {}\n", pixel_format);
std::exit(0);
UNREACHABLE_MSG("Unknown pixelFormat = {}", pixel_format);
}
vk_obj->extent.width = width;
@ -91,8 +88,7 @@ static void* create_func(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params
vkCreateImage(ctx->m_device, &image_info, nullptr, &vk_obj->image);
if (vk_obj->image == nullptr) {
LOG_CRITICAL_IF(log_file_videoOutBuffer, "vk_obj->image is null\n");
std::exit(0);
UNREACHABLE_MSG("vk_obj->image is null");
}
vkGetImageMemoryRequirements(ctx->m_device, vk_obj->image, &mem->requirements);
@ -102,19 +98,15 @@ static void* create_func(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params
bool allocated = GPU::vulkanAllocateMemory(ctx, mem);
if (!allocated) {
LOG_CRITICAL_IF(log_file_videoOutBuffer, "can't allocate vulkan memory\n");
std::exit(0);
UNREACHABLE_MSG("Can't allocate vulkan memory");
}
vkBindImageMemory(ctx->m_device, vk_obj->image, mem->memory, mem->offset);
vk_obj->memory = *mem;
LOG_INFO_IF(log_file_videoOutBuffer, "videoOutBuffer create object\n");
LOG_INFO_IF(log_file_videoOutBuffer, "mem requirements.size = {}\n", mem->requirements.size);
LOG_INFO_IF(log_file_videoOutBuffer, "width = {}\n", width);
LOG_INFO_IF(log_file_videoOutBuffer, "height = {}\n", height);
LOG_INFO_IF(log_file_videoOutBuffer, "size = {}\n", *size);
LOG_INFO(Lib_VideoOut, "videoOutBuffer create object width = {}, height = {}, size = {}", width,
height, *size);
update_func(ctx, params, vk_obj, virtual_addr, size, virtual_addr_num);
@ -139,8 +131,7 @@ static void* create_func(HLE::Libs::Graphics::GraphicCtx* ctx, const u64* params
&vk_obj->image_view[HLE::Libs::Graphics::VulkanImage::VIEW_DEFAULT]);
if (vk_obj->image_view[HLE::Libs::Graphics::VulkanImage::VIEW_DEFAULT] == nullptr) {
LOG_CRITICAL_IF(log_file_videoOutBuffer, "vk_obj->image_view is null\n");
std::exit(0);
UNREACHABLE_MSG("vk_obj->image_view is null");
}
return vk_obj;

View File

@ -3,11 +3,10 @@
#include <cstdio>
#include <string>
#include <magic_enum.hpp>
#include "Objects/video_out_ctx.h"
#include "Util/config.h"
#include "common/debug.h"
#include "common/log.h"
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/PS4/GPU/gpu_memory.h"
#include "core/PS4/GPU/video_out_buffer.h"
@ -58,17 +57,11 @@ std::string getPixelFormatString(s32 format) {
void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(SceVideoOutBufferAttribute* attribute,
u32 pixelFormat, u32 tilingMode, u32 aspectRatio,
u32 width, u32 height, u32 pitchInPixel) {
PRINT_FUNCTION_NAME();
auto tileMode = magic_enum::enum_cast<SceVideoOutTilingMode>(tilingMode);
auto aspectR = magic_enum::enum_cast<AspectRatioMode>(aspectRatio);
LOG_INFO_IF(log_file_videoout, "pixelFormat = {}\n", getPixelFormatString(pixelFormat));
LOG_INFO_IF(log_file_videoout, "tilingMode = {}\n", magic_enum::enum_name(tileMode.value()));
LOG_INFO_IF(log_file_videoout, "aspectRatio = {}\n", magic_enum::enum_name(aspectR.value()));
LOG_INFO_IF(log_file_videoout, "width = {}\n", width);
LOG_INFO_IF(log_file_videoout, "height = {}\n", height);
LOG_INFO_IF(log_file_videoout, "pitchInPixel = {}\n", pitchInPixel);
LOG_INFO(Lib_VideoOut,
"pixelFormat = {}, tilingMode = {}, aspectRatio = {}, width = {}, height = {}, "
"pitchInPixel = {}",
getPixelFormatString(pixelFormat), tilingMode, aspectRatio, width, height,
pitchInPixel);
std::memset(attribute, 0, sizeof(SceVideoOutBufferAttribute));
@ -100,7 +93,8 @@ static void flip_delete_event_func(Core::Kernel::SceKernelEqueue eq,
s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Core::Kernel::SceKernelEqueue eq, s32 handle,
void* udata) {
PRINT_FUNCTION_NAME();
LOG_INFO(Lib_VideoOut, "handle = {}", handle);
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
auto* ctx = videoOut->getCtx(handle);
@ -136,46 +130,43 @@ s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Core::Kernel::SceKernelEqueue eq, s32 h
s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses,
s32 bufferNum,
const SceVideoOutBufferAttribute* attribute) {
PRINT_FUNCTION_NAME();
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
auto* ctx = videoOut->getCtx(handle);
if (handle == 1) { // main port
if (startIndex < 0 || startIndex > 15) {
LOG_TRACE_IF(log_file_videoout, "invalid startIndex = {}\n", startIndex);
LOG_ERROR(Lib_VideoOut, "Invalid startIndex = {}", startIndex);
return SCE_VIDEO_OUT_ERROR_INVALID_VALUE;
}
if (bufferNum < 1 || bufferNum > 16) {
LOG_TRACE_IF(log_file_videoout, "invalid bufferNum = {}\n", bufferNum);
LOG_ERROR(Lib_VideoOut, "Invalid bufferNum = {}", bufferNum);
return SCE_VIDEO_OUT_ERROR_INVALID_VALUE;
}
}
if (addresses == nullptr) {
LOG_TRACE_IF(log_file_videoout, "addresses are null\n");
LOG_ERROR(Lib_VideoOut, "Addresses are null");
return SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS;
}
if (attribute == nullptr) {
LOG_TRACE_IF(log_file_videoout, "attribute is null\n");
LOG_ERROR(Lib_VideoOut, "Attribute is null");
return SCE_VIDEO_OUT_ERROR_INVALID_OPTION;
}
if (attribute->aspectRatio != 0) {
LOG_TRACE_IF(log_file_videoout, "invalid aspect ratio = {}\n", attribute->aspectRatio);
LOG_ERROR(Lib_VideoOut, "Invalid aspect ratio = {}", attribute->aspectRatio);
return SCE_VIDEO_OUT_ERROR_INVALID_ASPECT_RATIO;
}
if (attribute->tilingMode < 0 || attribute->tilingMode > 1) {
LOG_TRACE_IF(log_file_videoout, "invalid tilingMode = {}\n", attribute->tilingMode);
LOG_ERROR(Lib_VideoOut, "Invalid tilingMode = {}", attribute->tilingMode);
return SCE_VIDEO_OUT_ERROR_INVALID_TILING_MODE;
}
LOG_INFO_IF(log_file_videoout, "startIndex = {}\n", startIndex);
LOG_INFO_IF(log_file_videoout, "bufferNum = {}\n", bufferNum);
LOG_INFO_IF(log_file_videoout, "pixelFormat = {:#x}\n", attribute->pixelFormat);
LOG_INFO_IF(log_file_videoout, "tilingMode = {}\n", attribute->tilingMode);
LOG_INFO_IF(log_file_videoout, "aspectRatio = {}\n", attribute->aspectRatio);
LOG_INFO_IF(log_file_videoout, "width = {}\n", attribute->width);
LOG_INFO_IF(log_file_videoout, "height = {}\n", attribute->height);
LOG_INFO_IF(log_file_videoout, "pitchInPixel = {}\n", attribute->pitchInPixel);
LOG_INFO_IF(log_file_videoout, "option = {}\n", attribute->option);
LOG_INFO(Lib_VideoOut,
"handle = {}, startIndex = {}, bufferNum = {}, pixelFormat = {:#x}, aspectRatio = {}, "
"tilingMode = {}, width = {}, height = {}, pitchInPixel = {}, option = {:#x}",
handle, startIndex, bufferNum, attribute->pixelFormat, attribute->aspectRatio,
attribute->tilingMode, attribute->width, attribute->height, attribute->pitchInPixel,
attribute->option);
int registration_index = ctx->buffers_registration_index++;
@ -214,7 +205,7 @@ s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* co
for (int i = 0; i < bufferNum; i++) {
if (ctx->buffers[i + startIndex].buffer != nullptr) {
LOG_TRACE_IF(log_file_videoout, "buffer slot {} is occupied!\n", i + startIndex);
LOG_ERROR(Lib_VideoOut, "Buffer slot {} is occupied!", i + startIndex);
return SCE_VIDEO_OUT_ERROR_SLOT_OCCUPIED;
}
@ -227,51 +218,49 @@ s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* co
0, videoOut->getGraphicCtx(), nullptr, reinterpret_cast<uint64_t>(addresses[i]),
buffer_size, buffer_info));
LOG_INFO_IF(log_file_videoout, "buffers[{}] = {:#x}\n", i + startIndex,
reinterpret_cast<u64>(addresses[i]));
LOG_INFO(Lib_VideoOut, "buffers[{}] = {:#x}", i + startIndex,
reinterpret_cast<u64>(addresses[i]));
}
return registration_index;
}
s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate) {
PRINT_FUNCTION_NAME();
LOG_INFO(Lib_VideoOut, "called");
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
videoOut->getCtx(handle)->m_flip_rate = rate;
return SCE_OK;
}
s32 PS4_SYSV_ABI sceVideoOutIsFlipPending(s32 handle) {
PRINT_FUNCTION_NAME();
LOG_INFO(Lib_VideoOut, "called");
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
s32 pending = videoOut->getCtx(handle)->m_flip_status.flipPendingNum;
return pending;
}
s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode, s64 flipArg) {
PRINT_FUNCTION_NAME();
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
auto* ctx = videoOut->getCtx(handle);
if (flipMode != 1) {
// BREAKPOINT(); // only flipmode==1 is supported
LOG_TRACE_IF(log_file_videoout, "sceVideoOutSubmitFlip flipmode {}\n",
bufferIndex); // openBOR needs 2 but seems to work
LOG_WARNING(Lib_VideoOut, "flipmode = {}",
flipMode); // openBOR needs 2 but seems to work
}
if (bufferIndex == -1) {
BREAKPOINT(); // blank output not supported
}
if (bufferIndex < -1 || bufferIndex > 15) {
LOG_TRACE_IF(log_file_videoout, "sceVideoOutSubmitFlip invalid bufferIndex {}\n",
bufferIndex);
LOG_ERROR(Lib_VideoOut, "Invalid bufferIndex = {}", bufferIndex);
return SCE_VIDEO_OUT_ERROR_INVALID_INDEX;
}
LOG_INFO_IF(log_file_videoout, "bufferIndex = {}\n", bufferIndex);
LOG_INFO_IF(log_file_videoout, "flipMode = {}\n", flipMode);
LOG_INFO_IF(log_file_videoout, "flipArg = {}\n", flipArg);
LOG_INFO(Lib_VideoOut, "bufferIndex = {}, flipMode = {}, flipArg = {}", bufferIndex, flipMode,
flipArg);
if (!videoOut->getFlipQueue().submitFlip(ctx, bufferIndex, flipArg)) {
LOG_TRACE_IF(log_file_videoout, "sceVideoOutSubmitFlip flip queue is full\n");
LOG_ERROR(Lib_VideoOut, "Flip queue is full");
return SCE_VIDEO_OUT_ERROR_FLIP_QUEUE_FULL;
}
Core::Libraries::LibSceGnmDriver::sceGnmFlushGarlic(); // hackish should be done that neccesary
@ -280,24 +269,20 @@ s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode
}
s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, SceVideoOutFlipStatus* status) {
PRINT_FUNCTION_NAME();
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
auto* ctx = videoOut->getCtx(handle);
videoOut->getFlipQueue().getFlipStatus(ctx, status);
LOG_INFO_IF(log_file_videoout, "count = {}\n", status->count);
LOG_INFO_IF(log_file_videoout, "processTime = {}\n", status->processTime);
LOG_INFO_IF(log_file_videoout, "tsc = {}\n", status->tsc);
LOG_INFO_IF(log_file_videoout, "submitTsc = {}\n", status->submitTsc);
LOG_INFO_IF(log_file_videoout, "flipArg = {}\n", status->flipArg);
LOG_INFO_IF(log_file_videoout, "gcQueueNum = {}\n", status->gcQueueNum);
LOG_INFO_IF(log_file_videoout, "flipPendingNum = {}\n", status->flipPendingNum);
LOG_INFO_IF(log_file_videoout, "currentBuffer = {}\n", status->currentBuffer);
LOG_INFO(Lib_VideoOut,
"count = {}, processTime = {}, tsc = {}, submitTsc = {}, flipArg = {}, gcQueueNum = "
"{}, flipPendingNum = {}, currentBuffer = {}",
status->count, status->processTime, status->tsc, status->submitTsc, status->flipArg,
status->gcQueueNum, status->flipPendingNum, status->currentBuffer);
return 0;
}
s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status) {
PRINT_FUNCTION_NAME();
LOG_INFO(Lib_VideoOut, "called");
auto* videoOut = Common::Singleton<HLE::Graphics::Objects::VideoOutCtx>::Instance();
*status = videoOut->getCtx(handle)->m_resolution;
return SCE_OK;
@ -305,7 +290,7 @@ s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutio
s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index,
const void* param) {
PRINT_FUNCTION_NAME();
LOG_INFO(Lib_VideoOut, "called");
if (userId != SCE_USER_SERVICE_USER_ID_SYSTEM && userId != 0) {
BREAKPOINT();
}
@ -313,7 +298,7 @@ s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 i
BREAKPOINT();
}
if (index != 0) {
LOG_TRACE_IF(log_file_videoout, "sceVideoOutOpen index!=0\n");
LOG_ERROR(Lib_VideoOut, "Index != 0");
return SCE_VIDEO_OUT_ERROR_INVALID_VALUE;
}
if (param != nullptr) {
@ -323,7 +308,7 @@ s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 i
int handle = videoOut->Open();
if (handle < 0) {
LOG_TRACE_IF(log_file_videoout, "sceVideoOutOpen all available handles are open\n");
LOG_ERROR(Lib_VideoOut, "All available handles are open");
return SCE_VIDEO_OUT_ERROR_RESOURCE_BUSY; // it is alreadyOpened
}
@ -342,7 +327,6 @@ s32 PS4_SYSV_ABI sceVideoOutUnregisterBuffers(s32 handle, s32 attributeIndex) {
}
void videoOutRegisterLib(Core::Loader::SymbolsResolver* sym) {
using namespace Core;
LIB_FUNCTION("SbU3dwp80lQ", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutGetFlipStatus);
LIB_FUNCTION("U46NwOiJpys", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutSubmitFlip);

View File

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/log.h"
#include "common/logging/log.h"
#include "core/aerolib/aerolib.h"
#include "core/aerolib/stubs.h"
@ -22,13 +22,12 @@ namespace Core::AeroLib {
constexpr u32 MAX_STUBS = 128;
u64 UnresolvedStub() {
LOG_ERROR("Unresolved Stub: called, returning zero to {}\n", __builtin_return_address(0));
LOG_ERROR(Core, "Returning zero to {}", __builtin_return_address(0));
return 0;
}
static u64 UnknownStub() {
LOG_ERROR("Stub: Unknown (nid: Unknown) called, returning zero to {}\n",
__builtin_return_address(0));
LOG_ERROR(Core, "Returning zero to {}", __builtin_return_address(0));
return 0;
}
@ -39,10 +38,10 @@ template <int stub_index>
static u64 CommonStub() {
auto entry = stub_nids[stub_index];
if (entry) {
LOG_ERROR("Stub: {} (nid: {}) called, returning zero to {}\n", entry->name, entry->nid,
LOG_ERROR(Core, "Stub: {} (nid: {}) called, returning zero to {}", entry->name, entry->nid,
__builtin_return_address(0));
} else {
LOG_ERROR("Stub: Unknown (nid: {}) called, returning zero to {}\n",
LOG_ERROR(Core, "Stub: Unknown (nid: {}) called, returning zero to {}",
stub_nids_unknown[stub_index], __builtin_return_address(0));
}
return 0;

View File

@ -8,21 +8,24 @@ namespace Core::FileSys {
constexpr int RESERVED_HANDLES = 3; // First 3 handles are stdin,stdout,stderr
void MntPoints::mount(const std::string& host_folder, const std::string& guest_folder) {
void MntPoints::Mount(const std::filesystem::path& host_folder, const std::string& guest_folder) {
std::scoped_lock lock{m_mutex};
MntPair pair;
pair.host_path = host_folder;
pair.host_path = host_folder.string();
pair.guest_path = guest_folder;
m_mnt_pairs.push_back(pair);
}
void MntPoints::unmount(const std::string& path) {} // TODO!
void MntPoints::unmountAll() {
void MntPoints::Unmount(const std::string& path) {} // TODO!
void MntPoints::UnmountAll() {
std::scoped_lock lock{m_mutex};
m_mnt_pairs.clear();
}
std::string MntPoints::getHostDirectory(const std::string& guest_directory) {
std::string MntPoints::GetHostDirectory(const std::string& guest_directory) {
std::scoped_lock lock{m_mutex};
for (auto& pair : m_mnt_pairs) {
if (pair.guest_path.starts_with(guest_directory)) {
@ -38,7 +41,8 @@ std::string MntPoints::getHostDirectory(const std::string& guest_directory) {
}
return "";
}
std::string MntPoints::getHostFile(const std::string& guest_file) {
std::string MntPoints::GetHostFile(const std::string& guest_file) {
std::scoped_lock lock{m_mutex};
for (auto& pair : m_mnt_pairs) {
@ -52,11 +56,13 @@ std::string MntPoints::getHostFile(const std::string& guest_file) {
}
return "";
}
int HandleTable::createHandle() {
int HandleTable::CreateHandle() {
std::scoped_lock lock{m_mutex};
auto* file = new File{};
file->isDirectory = false;
file->isOpened = false;
file->is_directory = false;
file->is_opened = false;
int existingFilesNum = m_files.size();
@ -68,19 +74,20 @@ int HandleTable::createHandle() {
}
m_files.push_back(file);
return m_files.size() + RESERVED_HANDLES - 1;
}
void HandleTable::deleteHandle(int d) {
void HandleTable::DeleteHandle(int d) {
std::scoped_lock lock{m_mutex};
delete m_files.at(d - RESERVED_HANDLES);
m_files[d - RESERVED_HANDLES] = nullptr;
}
File* HandleTable::getFile(int d) {
File* HandleTable::GetFile(int d) {
std::scoped_lock lock{m_mutex};
return m_files.at(d - RESERVED_HANDLES);
}
File* HandleTable::getFile(const std::string& host_name) {
std::scoped_lock lock{m_mutex};
for (auto* file : m_files) {

View File

@ -7,7 +7,6 @@
#include <mutex>
#include <string>
#include <vector>
#include "common/fs_file.h"
#include "common/io_file.h"
namespace Core::FileSys {
@ -21,11 +20,12 @@ public:
MntPoints() = default;
virtual ~MntPoints() = default;
void mount(const std::string& host_folder, const std::string& guest_folder);
void unmount(const std::string& path);
void unmountAll();
std::string getHostDirectory(const std::string& guest_directory);
std::string getHostFile(const std::string& guest_file);
void Mount(const std::filesystem::path& host_folder, const std::string& guest_folder);
void Unmount(const std::string& path);
void UnmountAll();
std::string GetHostDirectory(const std::string& guest_directory);
std::string GetHostFile(const std::string& guest_file);
private:
std::vector<MntPair> m_mnt_pairs;
@ -33,22 +33,24 @@ private:
};
struct File {
std::atomic_bool isOpened;
std::atomic_bool isDirectory;
std::atomic_bool is_opened{};
std::atomic_bool is_directory{};
std::string m_host_name;
std::string m_guest_name;
IOFile f;
Common::FS::IOFile f;
// std::vector<Common::FS::DirEntry> dirents;
u32 dirents_index;
std::mutex m_mutex;
};
class HandleTable {
public:
HandleTable() {}
virtual ~HandleTable() {}
int createHandle();
void deleteHandle(int d);
File* getFile(int d);
HandleTable() = default;
virtual ~HandleTable() = default;
int CreateHandle();
void DeleteHandle(int d);
File* GetFile(int d);
File* getFile(const std::string& host_name);
private:

View File

@ -2,14 +2,13 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Util/config.h"
#include "common/log.h"
#include "common/logging/log.h"
#include "core/hle/kernel/cpu_management.h"
#include "core/hle/libraries/libs.h"
namespace Core::Kernel {
int PS4_SYSV_ABI sceKernelIsNeoMode() {
PRINT_FUNCTION_NAME();
LOG_INFO(Kernel_Sce, "called");
return Config::isNeoMode();
}

View File

@ -1,51 +1,44 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/debug.h"
#include "common/log.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/error_codes.h"
#include "core/hle/kernel/event_queues.h"
#include "core/hle/libraries/libs.h"
namespace Core::Kernel {
constexpr bool log_file_equeues = true; // disable it to disable logging
int PS4_SYSV_ABI sceKernelCreateEqueue(SceKernelEqueue* eq, const char* name) {
PRINT_FUNCTION_NAME();
if (eq == nullptr) {
LOG_TRACE_IF(log_file_equeues,
"sceKernelCreateEqueue returned SCE_KERNEL_ERROR_EINVAL eq invalid\n");
LOG_ERROR(Kernel_Event, "Event queue is null!");
return SCE_KERNEL_ERROR_EINVAL;
}
if (name == nullptr) {
LOG_TRACE_IF(log_file_equeues,
"sceKernelCreateEqueue returned SCE_KERNEL_ERROR_EFAULT name invalid\n");
LOG_ERROR(Kernel_Event, "Event queue name is invalid!");
return SCE_KERNEL_ERROR_EFAULT;
}
if (name == NULL) {
LOG_TRACE_IF(log_file_equeues,
"sceKernelCreateEqueue returned SCE_KERNEL_ERROR_EINVAL name is null\n");
LOG_ERROR(Kernel_Event, "Event queue name is null!");
return SCE_KERNEL_ERROR_EINVAL;
}
if (strlen(name) > 31) { // max is 32 including null terminator
LOG_TRACE_IF(log_file_equeues,
"sceKernelCreateEqueue returned SCE_KERNEL_ERROR_ENAMETOOLONG name size "
"exceeds 32 bytes\n");
// Maximum is 32 including null terminator
static constexpr size_t MaxEventQueueNameSize = 32;
if (std::strlen(name) > MaxEventQueueNameSize) {
LOG_ERROR(Kernel_Event, "Event queue name exceeds 32 bytes!");
return SCE_KERNEL_ERROR_ENAMETOOLONG;
}
LOG_INFO(Kernel_Event, "name = {}", name);
*eq = new EqueueInternal;
(*eq)->setName(std::string(name));
LOG_INFO_IF(log_file_equeues, "sceKernelCreateEqueue created with name \"{}\"\n", name);
return SCE_OK;
}
int PS4_SYSV_ABI sceKernelWaitEqueue(SceKernelEqueue eq, SceKernelEvent* ev, int num, int* out,
SceKernelUseconds* timo) {
PRINT_FUNCTION_NAME();
LOG_INFO(Kernel_Event, "num = {}", num);
if (eq == nullptr) {
return SCE_KERNEL_ERROR_EBADF;
@ -66,10 +59,10 @@ int PS4_SYSV_ABI sceKernelWaitEqueue(SceKernelEqueue eq, SceKernelEvent* ev, int
if (timo != nullptr) {
// Only events that have already arrived at the time of this function call can be received
if (*timo == 0) {
BREAKPOINT();
UNREACHABLE();
} else {
// Wait until an event arrives with timing out
BREAKPOINT();
UNREACHABLE();
}
}

View File

@ -2,106 +2,85 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <bit>
#include <magic_enum.hpp>
#include "common/debug.h"
#include "common/log.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/PS4/GPU/gpu_memory.h"
#include "core/hle/error_codes.h"
#include "core/hle/kernel/Objects/physical_memory.h"
#include "core/hle/kernel/memory_management.h"
#include "core/hle/libraries/libs.h"
#include "core/virtual_memory.h"
namespace Core::Kernel {
constexpr bool log_file_memory = true; // disable it to disable logging
bool is16KBAligned(u64 n) {
return ((n % (16ull * 1024) == 0));
}
u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize() {
PRINT_FUNCTION_NAME();
LOG_WARNING(Kernel_Vmm, "(STUBBED) called");
return SCE_KERNEL_MAIN_DMEM_SIZE;
}
int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len,
u64 alignment, int memoryType, s64* physAddrOut) {
PRINT_FUNCTION_NAME();
LOG_INFO(Kernel_Vmm,
"searchStart = {:#x}, searchEnd = {:#x}, len = {:#x}, alignment = {:#x}, memoryType = "
"{:#x}",
searchStart, searchEnd, len, alignment, memoryType);
if (searchStart < 0 || searchEnd <= searchStart) {
LOG_TRACE_IF(log_file_memory, "sceKernelAllocateDirectMemory returned "
"SCE_KERNEL_ERROR_EINVAL searchStart,searchEnd invalid\n");
LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
bool isInRange = (searchStart < len && searchEnd > len);
if (len <= 0 || !is16KBAligned(len) || !isInRange) {
LOG_TRACE_IF(log_file_memory, "sceKernelAllocateDirectMemory returned "
"SCE_KERNEL_ERROR_EINVAL memory range invalid\n");
const bool is_in_range = (searchStart < len && searchEnd > len);
if (len <= 0 || !is16KBAligned(len) || !is_in_range) {
LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
if ((alignment != 0 || is16KBAligned(alignment)) && !std::has_single_bit(alignment)) {
LOG_TRACE_IF(
log_file_memory,
"sceKernelAllocateDirectMemory returned SCE_KERNEL_ERROR_EINVAL alignment invalid\n");
LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
if (physAddrOut == nullptr) {
LOG_TRACE_IF(
log_file_memory,
"sceKernelAllocateDirectMemory returned SCE_KERNEL_ERROR_EINVAL physAddrOut is null\n");
LOG_ERROR(Kernel_Vmm, "Result physical address pointer is null!");
return SCE_KERNEL_ERROR_EINVAL;
}
auto memtype = magic_enum::enum_cast<MemoryTypes>(memoryType);
LOG_INFO_IF(log_file_memory, "search_start = {:#x}\n", searchStart);
LOG_INFO_IF(log_file_memory, "search_end = {:#x}\n", searchEnd);
LOG_INFO_IF(log_file_memory, "len = {:#x}\n", len);
LOG_INFO_IF(log_file_memory, "alignment = {:#x}\n", alignment);
LOG_INFO_IF(log_file_memory, "memory_type = {}\n", magic_enum::enum_name(memtype.value()));
u64 physical_addr = 0;
auto* physical_memory = Common::Singleton<PhysicalMemory>::Instance();
if (!physical_memory->Alloc(searchStart, searchEnd, len, alignment, &physical_addr,
memoryType)) {
LOG_TRACE_IF(log_file_memory, "sceKernelAllocateDirectMemory returned "
"SCE_KERNEL_ERROR_EAGAIN can't allocate physical memory\n");
LOG_CRITICAL(Kernel_Vmm, "Unable to allocate physical memory");
return SCE_KERNEL_ERROR_EAGAIN;
}
*physAddrOut = static_cast<s64>(physical_addr);
LOG_INFO_IF(true, "physAddrOut = {:#x}\n", physical_addr);
LOG_INFO(Kernel_Vmm, "physAddrOut = {:#x}", physical_addr);
return SCE_OK;
}
int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int flags,
s64 directMemoryStart, u64 alignment) {
PRINT_FUNCTION_NAME();
LOG_INFO(
Kernel_Vmm,
"len = {:#x}, prot = {:#x}, flags = {:#x}, directMemoryStart = {:#x}, alignment = {:#x}",
len, prot, flags, directMemoryStart, alignment);
if (len == 0 || !is16KBAligned(len)) {
LOG_TRACE_IF(log_file_memory,
"sceKernelMapDirectMemory returned SCE_KERNEL_ERROR_EINVAL len invalid\n");
LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 16KB aligned!");
return SCE_KERNEL_ERROR_EINVAL;
}
if (!is16KBAligned(directMemoryStart)) {
LOG_TRACE_IF(log_file_memory, "sceKernelMapDirectMemory returned SCE_KERNEL_ERROR_EINVAL "
"directMemoryStart invalid\n");
LOG_ERROR(Kernel_Vmm, "Start address is not 16KB aligned!");
return SCE_KERNEL_ERROR_EINVAL;
}
if (alignment != 0) {
if ((!std::has_single_bit(alignment) && !is16KBAligned(alignment))) {
LOG_TRACE_IF(
log_file_memory,
"sceKernelMapDirectMemory returned SCE_KERNEL_ERROR_EINVAL alignment invalid\n");
LOG_ERROR(Kernel_Vmm, "Alignment value is invalid!");
return SCE_KERNEL_ERROR_EINVAL;
}
}
LOG_INFO_IF(log_file_memory, "len = {:#x}\n", len);
LOG_INFO_IF(log_file_memory, "prot = {:#x}\n", prot);
LOG_INFO_IF(log_file_memory, "flags = {:#x}\n", flags);
LOG_INFO_IF(log_file_memory, "directMemoryStart = {:#x}\n", directMemoryStart);
LOG_INFO_IF(log_file_memory, "alignment = {:#x}\n", alignment);
VirtualMemory::MemoryMode cpu_mode = VirtualMemory::MemoryMode::NoAccess;
GPU::MemoryMode gpu_mode = GPU::MemoryMode::NoAccess;
@ -112,7 +91,7 @@ int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int fl
gpu_mode = GPU::MemoryMode::ReadWrite;
break;
default:
BREAKPOINT();
UNREACHABLE();
}
auto in_addr = reinterpret_cast<u64>(*addr);
@ -121,8 +100,7 @@ int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int fl
if (flags == 0) {
out_addr = VirtualMemory::memory_alloc_aligned(in_addr, len, cpu_mode, alignment);
}
LOG_INFO_IF(log_file_memory, "in_addr = {:#x}\n", in_addr);
LOG_INFO_IF(log_file_memory, "out_addr = {:#x}\n", out_addr);
LOG_INFO(Kernel_Vmm, "in_addr = {:#x}, out_addr = {:#x}", in_addr, out_addr);
*addr = reinterpret_cast<void*>(out_addr); // return out_addr to first functions parameter
@ -132,7 +110,7 @@ int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int fl
auto* physical_memory = Common::Singleton<PhysicalMemory>::Instance();
if (!physical_memory->Map(out_addr, directMemoryStart, len, prot, cpu_mode, gpu_mode)) {
BREAKPOINT();
UNREACHABLE();
}
if (gpu_mode != GPU::MemoryMode::NoAccess) {

View File

@ -3,7 +3,7 @@
#include <cstdlib>
#include "common/debug.h"
#include "common/log.h"
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/hle/libraries/libc/libc.h"
#include "core/hle/libraries/libc/libc_cxa.h"
@ -49,15 +49,15 @@ void PS4_SYSV_ABI ps4___cxa_pure_virtual() {
}
static PS4_SYSV_ABI void ps4_init_env() {
PRINT_DUMMY_FUNCTION_NAME();
LOG_INFO(Lib_LibC, "called");
}
static PS4_SYSV_ABI void ps4_catchReturnFromMain(int status) {
LOG_INFO_IF(log_file_libc, "catchReturnFromMain returned ={}\n", status);
LOG_INFO(Lib_LibC, "returned = {}", status);
}
static PS4_SYSV_ABI void ps4__Assert() {
PRINT_DUMMY_FUNCTION_NAME();
LOG_INFO(Lib_LibC, "called");
BREAKPOINT();
}
@ -66,18 +66,18 @@ PS4_SYSV_ABI void ps4__ZdlPv(void* ptr) {
}
PS4_SYSV_ABI void ps4__ZSt11_Xbad_allocv() {
PRINT_DUMMY_FUNCTION_NAME();
LOG_INFO(Lib_LibC, "called");
BREAKPOINT();
}
PS4_SYSV_ABI void ps4__ZSt14_Xlength_errorPKc() {
PRINT_DUMMY_FUNCTION_NAME();
LOG_INFO(Lib_LibC, "called");
BREAKPOINT();
}
PS4_SYSV_ABI void* ps4__Znwm(u64 count) {
if (count == 0) {
LOG_ERROR_IF(log_file_libc, "_Znwm count ={}\n", count);
LOG_INFO(Lib_LibC, "_Znwm count ={}", count);
BREAKPOINT();
}
void* ptr = std::malloc(count);
@ -457,6 +457,7 @@ void libcSymbolsRegister(Loader::SymbolsResolver* sym) {
LIB_FUNCTION("8zTFvBIAIN8", "libc", 1, "libc", 1, 1, ps4_memset);
// stdio functions
LIB_FUNCTION("xeYO4u7uyJ0", "libc", 1, "libc", 1, 1, ps4_fopen);
LIB_FUNCTION("hcuQgD53UxM", "libc", 1, "libc", 1, 1, ps4_printf);
LIB_FUNCTION("Q2V+iqvjgC0", "libc", 1, "libc", 1, 1, ps4_vsnprintf);
LIB_FUNCTION("YQ0navp+YIc", "libc", 1, "libc", 1, 1, ps4_puts);

View File

@ -1,8 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/debug.h"
#include "common/log.h"
#include "common/logging/log.h"
#include "core/hle/libraries/libc/libc_cxa.h"
// adapted from
@ -10,8 +9,6 @@
namespace Core::Libraries::LibC {
constexpr bool log_file_cxa = true; // disable it to disable logging
// This file implements the __cxa_guard_* functions as defined at:
// http://www.codesourcery.com/public/cxx-abi/abi.html
//
@ -103,8 +100,7 @@ int PS4_SYSV_ABI ps4___cxa_guard_acquire(u64* guard_object) {
int result = ::pthread_mutex_lock(guard_mutex());
if (result != 0) {
LOG_TRACE_IF(log_file_cxa, "__cxa_guard_acquire(): pthread_mutex_lock failed with {}\n",
result);
LOG_ERROR(Lib_LibC, "pthread_mutex_lock failed with {}", result);
}
// At this point all other threads will block in __cxa_guard_acquire()
@ -112,8 +108,7 @@ int PS4_SYSV_ABI ps4___cxa_guard_acquire(u64* guard_object) {
if (initializerHasRun(guard_object)) {
int result = ::pthread_mutex_unlock(guard_mutex());
if (result != 0) {
LOG_TRACE_IF(log_file_cxa,
"__cxa_guard_acquire(): pthread_mutex_unlock failed with {}\n", result);
LOG_ERROR(Lib_LibC, "pthread_mutex_lock failed with {}", result);
}
return 0;
}
@ -123,8 +118,8 @@ int PS4_SYSV_ABI ps4___cxa_guard_acquire(u64* guard_object) {
// But if the same thread can call __cxa_guard_acquire() on the
// *same* guard object again, we call abort();
if (inUse(guard_object)) {
LOG_TRACE_IF(log_file_cxa, "__cxa_guard_acquire(): initializer for function local static "
"variable called enclosing function\n");
LOG_ERROR(Lib_LibC,
"initializer for function local static variable called enclosing function");
}
// mark this guard object as being in use
@ -146,8 +141,7 @@ void PS4_SYSV_ABI ps4___cxa_guard_release(u64* guard_object) {
// release global mutex
int result = ::pthread_mutex_unlock(guard_mutex());
if (result != 0) {
LOG_TRACE_IF(log_file_cxa, "__cxa_guard_acquire(): pthread_mutex_unlock failed with {}\n",
result);
LOG_ERROR(Lib_LibC, "pthread_mutex_unlock failed with {}", result);
}
}
@ -157,8 +151,7 @@ void PS4_SYSV_ABI ps4___cxa_guard_release(u64* guard_object) {
void PS4_SYSV_ABI ps4___cxa_guard_abort(u64* guard_object) {
int result = ::pthread_mutex_unlock(guard_mutex());
if (result != 0) {
LOG_TRACE_IF(log_file_cxa, "__cxa_guard_abort(): pthread_mutex_unlock failed with {}\n",
result);
LOG_ERROR(Lib_LibC, "pthread_mutex_unlock failed with {}", result);
}
// now reset state, so possible to try to initialize again

View File

@ -1,13 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/debug.h"
#include "common/log.h"
#include "common/assert.h"
#include "common/singleton.h"
#include "core/file_sys/fs.h"
#include "core/hle/libraries/libc/libc_stdio.h"
namespace Core::Libraries::LibC {
constexpr bool log_file_libc = true; // disable it to disable logging
std::FILE* PS4_SYSV_ABI ps4_fopen(const char* filename, const char* mode) {
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
return std::fopen(mnt->GetHostFile(filename).c_str(), mode);
}
int PS4_SYSV_ABI ps4_printf(VA_ARGS) {
VA_CTX(ctx);
@ -20,8 +24,8 @@ int PS4_SYSV_ABI ps4_fprintf(FILE* file, VA_ARGS) {
VA_CTX(ctx);
return printf_ctx(&ctx);
}
LOG_ERROR_IF(log_file_libc, "libc:Unimplemented fprintf case\n");
BREAKPOINT();
UNREACHABLE_MSG("Unimplemented fprintf case");
return 0;
}

View File

@ -8,6 +8,7 @@
namespace Core::Libraries::LibC {
std::FILE* PS4_SYSV_ABI ps4_fopen(const char* filename, const char* mode);
int PS4_SYSV_ABI ps4_printf(VA_ARGS);
int PS4_SYSV_ABI ps4_vsnprintf(char* s, size_t n, const char* format, VaList* arg);
int PS4_SYSV_ABI ps4_puts(const char* s);

View File

@ -2,24 +2,18 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstdlib>
#include "common/debug.h"
#include "common/log.h"
#include "common/assert.h"
#include "core/hle/libraries/libc/libc_stdlib.h"
namespace Core::Libraries::LibC {
constexpr bool log_file_libc = true; // disable it to disable logging
void PS4_SYSV_ABI ps4_exit(int code) {
std::exit(code);
}
int PS4_SYSV_ABI ps4_atexit(void (*func)()) {
int rt = std::atexit(func);
if (rt != 0) {
LOG_ERROR_IF(log_file_libc, "atexit returned {}\n", rt);
BREAKPOINT();
}
ASSERT_MSG(rt == 0, "atexit returned {}", rt);
return rt;
}

View File

@ -59,6 +59,7 @@
#include <cstdbool>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <string>
#include "va_ctx.h"

View File

@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/debug.h"
#include "common/log.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/file_sys/fs.h"
#include "core/hle/error_codes.h"
@ -11,45 +11,41 @@
namespace Core::Libraries::LibKernel {
constexpr bool log_file_fs = true; // disable it to disable logging
int PS4_SYSV_ABI sceKernelOpen(const char* path, int flags, u16 mode) {
LOG_INFO_IF(log_file_fs, "sceKernelOpen path = {} flags = {:#x} mode = {:#x}\n", path, flags,
mode);
LOG_INFO(Kernel_Fs, "path = {} flags = {:#x} mode = {:#x}", path, flags, mode);
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
// only open files support!
u32 handle = h->createHandle();
auto* file = h->getFile(handle);
u32 handle = h->CreateHandle();
auto* file = h->GetFile(handle);
file->m_guest_name = path;
file->m_host_name = mnt->getHostFile(file->m_guest_name);
file->m_host_name = mnt->GetHostFile(file->m_guest_name);
bool result = file->f.open(file->m_host_name);
if (!result) {
h->deleteHandle(handle);
file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Read);
if (!file->f.IsOpen()) {
h->DeleteHandle(handle);
return SCE_KERNEL_ERROR_EACCES;
}
file->isOpened = true;
file->is_opened = true;
return handle;
}
int PS4_SYSV_ABI posix_open(const char* path, int flags, /* SceKernelMode*/ u16 mode) {
LOG_INFO_IF(log_file_fs, "posix open redirect to sceKernelOpen\n");
LOG_INFO(Kernel_Fs, "posix open redirect to sceKernelOpen\n");
int result = sceKernelOpen(path, flags, mode);
if (result < 0) {
BREAKPOINT(); // posix calls different only for their return values
}
// Posix calls different only for their return values
ASSERT(result >= 0);
return result;
}
size_t PS4_SYSV_ABI _readv(int d, const SceKernelIovec* iov, int iovcnt) {
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->getFile(d);
auto* file = h->GetFile(d);
size_t total_read = 0;
file->m_mutex.lock();
for (int i = 0; i < iovcnt; i++) {
total_read += file->f.readBytes(iov[i].iov_base, iov[i].iov_len).second;
total_read += file->f.ReadRaw<u8>(iov[i].iov_base, iov[i].iov_len);
}
file->m_mutex.unlock();
return total_read;

View File

@ -13,7 +13,7 @@ namespace Core::Libraries::LibKernel {
struct SceKernelIovec {
void* iov_base;
size_t iov_len;
std::size_t iov_len;
};
int PS4_SYSV_ABI sceKernelOpen(const char* path, int flags, /* SceKernelMode*/ u16 mode);

View File

@ -1,10 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/debug.h"
#include "common/log.h"
#include "common/singleton.h"
#include "core/hle/kernel/Objects/physical_memory.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/kernel/cpu_management.h"
#include "core/hle/kernel/event_queues.h"
#include "core/hle/kernel/memory_management.h"
@ -24,21 +22,19 @@
namespace Core::Libraries::LibKernel {
constexpr bool log_libkernel_file = true; // disable it to disable logging
static u64 g_stack_chk_guard = 0xDEADBEEF54321ABC; // dummy return
int32_t PS4_SYSV_ABI sceKernelReleaseDirectMemory(off_t start, size_t len) {
BREAKPOINT();
UNREACHABLE();
return 0;
}
static PS4_SYSV_ABI void stack_chk_fail() {
BREAKPOINT();
UNREACHABLE();
}
int PS4_SYSV_ABI sceKernelMunmap(void* addr, size_t len) {
BREAKPOINT();
UNREACHABLE();
}
void PS4_SYSV_ABI sceKernelUsleep(unsigned int microseconds) {
@ -71,10 +67,9 @@ int* PS4_SYSV_ABI __Error() {
int PS4_SYSV_ABI sceKernelMmap(void* addr, u64 len, int prot, int flags, int fd, off_t offset,
void** res) {
#ifdef _WIN64
PRINT_FUNCTION_NAME();
if (prot > 3) // READ,WRITE or bitwise READ | WRITE supported
{
LOG_ERROR_IF(log_libkernel_file, "sceKernelMmap prot ={} not supported\n", prot);
LOG_INFO(Kernel_Vmm, "called");
if (prot > 3) {
LOG_ERROR(Kernel_Vmm, "prot = {} not supported", prot);
}
DWORD flProtect;
if (prot & PROT_WRITE) {
@ -114,13 +109,11 @@ int PS4_SYSV_ABI sceKernelMmap(void* addr, u64 len, int prot, int flags, int fd,
PS4_SYSV_ABI void* posix_mmap(void* addr, u64 len, int prot, int flags, int fd, u64 offset) {
void* ptr;
LOG_INFO_IF(log_libkernel_file, "posix mmap redirect to sceKernelMmap\n");
LOG_INFO(Kernel_Vmm, "posix mmap redirect to sceKernelMmap\n");
// posix call the difference is that there is a different behaviour when it doesn't return 0 or
// SCE_OK
int result = sceKernelMmap(addr, len, prot, flags, fd, offset, &ptr);
if (result != 0) {
BREAKPOINT();
}
ASSERT(result == 0);
return ptr;
}

View File

@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/debug.h"
#include "common/log.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/error_codes.h"
#include "core/hle/libraries/libkernel/thread_management.h"
#include "core/hle/libraries/libs.h"
@ -12,8 +12,6 @@ namespace Core::Libraries::LibKernel {
thread_local ScePthread g_pthread_self{};
PThreadCxt* g_pthread_cxt = nullptr;
constexpr bool log_pthread_file = true; // disable it to disable logging
void init_pthreads() {
g_pthread_cxt = new PThreadCxt{};
// default mutex init
@ -73,9 +71,7 @@ int PS4_SYSV_ABI scePthreadAttrSetdetachstate(ScePthreadAttr* attr, int detachst
pstate = PTHREAD_CREATE_DETACHED;
break;
default:
LOG_TRACE_IF(log_pthread_file, "scePthreadAttrSetdetachstate invalid detachstate: {}\n",
detachstate);
std::exit(0);
UNREACHABLE_MSG("Invalid detachstate: {}", detachstate);
}
// int result = pthread_attr_setdetachstate(&(*attr)->pth_attr, pstate); doesn't seem to work
@ -101,9 +97,7 @@ int PS4_SYSV_ABI scePthreadAttrSetinheritsched(ScePthreadAttr* attr, int inherit
pinherit_sched = PTHREAD_INHERIT_SCHED;
break;
default:
LOG_TRACE_IF(log_pthread_file, "scePthreadAttrSetinheritsched invalid inheritSched: {}\n",
inheritSched);
std::exit(0);
UNREACHABLE_MSG("Invalid inheritSched: {}", inheritSched);
}
int result = pthread_attr_setinheritsched(&(*attr)->pth_attr, pinherit_sched);
@ -138,9 +132,7 @@ int PS4_SYSV_ABI scePthreadAttrSetschedpolicy(ScePthreadAttr* attr, int policy)
int ppolicy = SCHED_OTHER; // winpthreads only supports SCHED_OTHER
if (policy != SCHED_OTHER) {
LOG_TRACE_IF(log_pthread_file,
"scePthreadAttrSetschedpolicy policy={} not supported by winpthreads\n",
policy);
LOG_ERROR(Kernel_Pthread, "policy={} not supported by winpthreads\n", policy);
}
(*attr)->policy = policy;
@ -154,7 +146,7 @@ ScePthread PS4_SYSV_ABI scePthreadSelf() {
int PS4_SYSV_ABI scePthreadAttrSetaffinity(ScePthreadAttr* pattr,
const /*SceKernelCpumask*/ u64 mask) {
PRINT_FUNCTION_NAME();
LOG_INFO(Kernel_Pthread, "called");
if (pattr == nullptr || *pattr == nullptr) {
return SCE_KERNEL_ERROR_EINVAL;
@ -166,7 +158,7 @@ int PS4_SYSV_ABI scePthreadAttrSetaffinity(ScePthreadAttr* pattr,
}
int PS4_SYSV_ABI scePthreadSetaffinity(ScePthread thread, const /*SceKernelCpumask*/ u64 mask) {
PRINT_FUNCTION_NAME();
LOG_INFO(Kernel_Pthread, "called");
if (thread == nullptr) {
return SCE_KERNEL_ERROR_ESRCH;
@ -178,12 +170,10 @@ int PS4_SYSV_ABI scePthreadSetaffinity(ScePthread thread, const /*SceKernelCpuma
}
int PS4_SYSV_ABI scePthreadCreate(ScePthread* thread, const ScePthreadAttr* attr,
pthreadEntryFunc start_routine, void* arg, const char* name) {
PRINT_DUMMY_FUNCTION_NAME();
LOG_INFO(Kernel_Pthread, "(STUBBED) called");
return 0;
}
/****
* Mutex calls
*/
void* createMutex(void* addr) {
if (addr == nullptr || *static_cast<ScePthreadMutex*>(addr) != nullptr) {
return addr;
@ -197,7 +187,7 @@ void* createMutex(void* addr) {
int PS4_SYSV_ABI scePthreadMutexInit(ScePthreadMutex* mutex, const ScePthreadMutexattr* attr,
const char* name) {
PRINT_FUNCTION_NAME();
LOG_INFO(Kernel_Pthread, "called");
if (mutex == nullptr) {
return SCE_KERNEL_ERROR_EINVAL;
}
@ -215,7 +205,7 @@ int PS4_SYSV_ABI scePthreadMutexInit(ScePthreadMutex* mutex, const ScePthreadMut
int result = pthread_mutex_init(&(*mutex)->pth_mutex, &(*attr)->pth_mutex_attr);
if (name != nullptr) {
LOG_INFO_IF(log_pthread_file, "mutex_init name={},result={}\n", name, result);
LOG_INFO(Kernel_Pthread, "name={}, result={}", name, result);
}
switch (result) {
@ -264,8 +254,7 @@ int PS4_SYSV_ABI scePthreadMutexattrSettype(ScePthreadMutexattr* attr, int type)
ptype = PTHREAD_MUTEX_NORMAL;
break;
default:
LOG_TRACE_IF(log_pthread_file, "scePthreadMutexattrSettype invalid type: {}\n", type);
std::exit(0);
UNREACHABLE_MSG("Invalid type: {}", type);
}
int result = pthread_mutexattr_settype(&(*attr)->pth_mutex_attr, ptype);
@ -286,9 +275,7 @@ int PS4_SYSV_ABI scePthreadMutexattrSetprotocol(ScePthreadMutexattr* attr, int p
pprotocol = PTHREAD_PRIO_PROTECT;
break;
default:
LOG_TRACE_IF(log_pthread_file, "scePthreadMutexattrSetprotocol invalid protocol: {}\n",
protocol);
std::exit(0);
UNREACHABLE_MSG("Invalid protocol: {}", protocol);
}
int result = 0; // pthread_mutexattr_setprotocol(&(*attr)->p, pprotocol); //it appears that
@ -298,7 +285,7 @@ int PS4_SYSV_ABI scePthreadMutexattrSetprotocol(ScePthreadMutexattr* attr, int p
return result == 0 ? SCE_OK : SCE_KERNEL_ERROR_EINVAL;
}
int PS4_SYSV_ABI scePthreadMutexLock(ScePthreadMutex* mutex) {
PRINT_FUNCTION_NAME();
LOG_INFO(Kernel_Pthread, "called");
mutex = static_cast<ScePthreadMutex*>(createMutex(mutex));
if (mutex == nullptr) {
@ -306,8 +293,7 @@ int PS4_SYSV_ABI scePthreadMutexLock(ScePthreadMutex* mutex) {
}
int result = pthread_mutex_lock(&(*mutex)->pth_mutex);
LOG_INFO_IF(log_pthread_file, "scePthreadMutexLock name={} result={}\n", (*mutex)->name,
result);
LOG_INFO(Kernel_Pthread, "name={}, result={}", (*mutex)->name, result);
switch (result) {
case 0:
return SCE_OK;
@ -322,15 +308,14 @@ int PS4_SYSV_ABI scePthreadMutexLock(ScePthreadMutex* mutex) {
}
}
int PS4_SYSV_ABI scePthreadMutexUnlock(ScePthreadMutex* mutex) {
PRINT_FUNCTION_NAME();
LOG_INFO(Kernel_Pthread, "called");
mutex = static_cast<ScePthreadMutex*>(createMutex(mutex));
if (mutex == nullptr) {
return SCE_KERNEL_ERROR_EINVAL;
}
int result = pthread_mutex_unlock(&(*mutex)->pth_mutex);
LOG_INFO_IF(log_pthread_file, "scePthreadMutexUnlock name={} result={}\n", (*mutex)->name,
result);
LOG_INFO(Kernel_Pthread, "name={}, result={}", (*mutex)->name, result);
switch (result) {
case 0:
return SCE_OK;
@ -344,9 +329,6 @@ int PS4_SYSV_ABI scePthreadMutexUnlock(ScePthreadMutex* mutex) {
}
}
/****
* Cond calls
*/
void* createCond(void* addr) {
if (addr == nullptr || *static_cast<ScePthreadCond*>(addr) != nullptr) {
return addr;
@ -379,7 +361,7 @@ int PS4_SYSV_ABI scePthreadCondInit(ScePthreadCond* cond, const ScePthreadCondat
int result = pthread_cond_init(&(*cond)->cond, &(*attr)->cond_attr);
if (name != nullptr) {
LOG_INFO_IF(log_pthread_file, "cond init name={},result={}\n", (*cond)->name, result);
LOG_INFO(Kernel_Pthread, "name={}, result={}", (*cond)->name, result);
}
switch (result) {
@ -412,7 +394,7 @@ int PS4_SYSV_ABI scePthreadCondattrInit(ScePthreadCondattr* attr) {
}
int PS4_SYSV_ABI scePthreadCondBroadcast(ScePthreadCond* cond) {
PRINT_FUNCTION_NAME();
LOG_INFO(Kernel_Pthread, "called");
cond = static_cast<ScePthreadCond*>(createCond(cond));
if (cond == nullptr) {
@ -421,15 +403,13 @@ int PS4_SYSV_ABI scePthreadCondBroadcast(ScePthreadCond* cond) {
int result = pthread_cond_broadcast(&(*cond)->cond);
LOG_INFO_IF(log_pthread_file, "cond broadcast name={},result={}\n", (*cond)->name, result);
LOG_INFO(Kernel_Pthread, "name={}, result={}", (*cond)->name, result);
return (result == 0 ? SCE_OK : SCE_KERNEL_ERROR_EINVAL);
}
/****
* Posix calls
*/
int PS4_SYSV_ABI posix_pthread_mutex_init(ScePthreadMutex* mutex, const ScePthreadMutexattr* attr) {
LOG_INFO_IF(log_pthread_file, "posix pthread_mutex_init redirect to scePthreadMutexInit\n");
LOG_INFO(Kernel_Pthread, "posix pthread_mutex_init redirect to scePthreadMutexInit");
int result = scePthreadMutexInit(mutex, attr, nullptr);
if (result < 0) {
int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP
@ -441,7 +421,7 @@ int PS4_SYSV_ABI posix_pthread_mutex_init(ScePthreadMutex* mutex, const ScePthre
}
int PS4_SYSV_ABI posix_pthread_mutex_lock(ScePthreadMutex* mutex) {
LOG_INFO_IF(log_pthread_file, "posix pthread_mutex_lock redirect to scePthreadMutexLock\n");
LOG_INFO(Kernel_Pthread, "posix pthread_mutex_lock redirect to scePthreadMutexLock");
int result = scePthreadMutexLock(mutex);
if (result < 0) {
int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP
@ -453,7 +433,7 @@ int PS4_SYSV_ABI posix_pthread_mutex_lock(ScePthreadMutex* mutex) {
}
int PS4_SYSV_ABI posix_pthread_mutex_unlock(ScePthreadMutex* mutex) {
LOG_INFO_IF(log_pthread_file, "posix pthread_mutex_unlock redirect to scePthreadMutexUnlock\n");
LOG_INFO(Kernel_Pthread, "posix pthread_mutex_unlock redirect to scePthreadMutexUnlock");
int result = scePthreadMutexUnlock(mutex);
if (result < 0) {
int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP
@ -465,8 +445,8 @@ int PS4_SYSV_ABI posix_pthread_mutex_unlock(ScePthreadMutex* mutex) {
}
int PS4_SYSV_ABI posix_pthread_cond_broadcast(ScePthreadCond* cond) {
LOG_INFO_IF(log_pthread_file,
"posix posix_pthread_cond_broadcast redirect to scePthreadCondBroadcast\n");
LOG_INFO(Kernel_Pthread,
"posix posix_pthread_cond_broadcast redirect to scePthreadCondBroadcast");
int result = scePthreadCondBroadcast(cond);
if (result != 0) {
int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP
@ -510,4 +490,4 @@ void pthreadSymbolsRegister(Loader::SymbolsResolver* sym) {
LIB_FUNCTION("mkx2fVhNMsg", "libkernel", 1, "libkernel", 1, 1, posix_pthread_cond_broadcast);
}
} // namespace Core::Libraries::LibKernel
} // namespace Core::Libraries::LibKernel

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Emulator/Host/controller.h"
#include "common/log.h"
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/hle/error_codes.h"
#include "core/hle/libraries/libpad/pad.h"
@ -10,15 +10,14 @@
namespace Core::Libraries::LibPad {
constexpr bool log_file_pad = true; // disable it to disable logging
int PS4_SYSV_ABI scePadInit() {
LOG_WARNING(Lib_Pad, "(STUBBED) called");
return SCE_OK;
}
int PS4_SYSV_ABI scePadOpen(Core::Libraries::LibUserService::SceUserServiceUserId userId, s32 type,
int PS4_SYSV_ABI scePadOpen(Core::Libraries::LibUserService::SceUserServiceUserId user_id, s32 type,
s32 index, const ScePadOpenParam* pParam) {
LOG_INFO_IF(log_file_pad, "scePadOpen userid = {} type = {} index = {}\n", userId, type, index);
LOG_INFO(Lib_Pad, "(STUBBED) called user_id = {} type = {} index = {}", user_id, type, index);
return 1; // dummy
}

View File

@ -34,15 +34,6 @@
sym->AddSymbol(sr, func); \
}
#define PRINT_FUNCTION_NAME() \
{ LOG_INFO_IF(true, "{}()\n", __func__); }
#define PRINT_DUMMY_FUNCTION_NAME() \
{ LOG_WARN_IF(true, "dummy {}()\n", __func__); }
#define PRINT_UNIMPLEMENTED_FUNCTION_NAME() \
{ LOG_ERROR_IF(true, "{}()\n", __func__); }
namespace Core::Libraries {
void InitHLELibs(Loader::SymbolsResolver* sym);

View File

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/log.h"
#include "common/logging/log.h"
#include "core/PS4/GPU/gpu_memory.h"
#include "core/hle/libraries/libs.h"
#include "core/hle/libraries/libscegnmdriver/libscegnmdriver.h"
@ -10,12 +10,12 @@
namespace Core::Libraries::LibSceGnmDriver {
int32_t sceGnmSubmitDone() {
PRINT_DUMMY_FUNCTION_NAME();
LOG_WARNING(Lib_GnmDriver, "(STUBBED) called");
return 0;
}
void sceGnmFlushGarlic() {
PRINT_FUNCTION_NAME();
LOG_WARNING(Lib_GnmDriver, "(STUBBED) called");
GPU::flushGarlic(Emu::getGraphicCtx());
}

View File

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/log.h"
#include "common/logging/log.h"
#include "core/hle/error_codes.h"
#include "core/hle/libraries/libs.h"
#include "core/hle/libraries/libsystemservice/system_service.h"
@ -9,7 +9,7 @@
namespace Core::Libraries::LibSystemService {
s32 PS4_SYSV_ABI sceSystemServiceHideSplashScreen() {
PRINT_DUMMY_FUNCTION_NAME();
LOG_WARNING(Lib_SystemService, "(STUBBED) called");
return SCE_OK;
}

View File

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/log.h"
#include "common/logging/log.h"
#include "core/hle/error_codes.h"
#include "core/hle/libraries/libs.h"
#include "core/hle/libraries/libuserservice/libuserservice.h"
@ -9,12 +9,12 @@
namespace Core::Libraries::LibUserService {
s32 PS4_SYSV_ABI sceUserServiceInitialize(const SceUserServiceInitializeParams* initParams) {
PRINT_DUMMY_FUNCTION_NAME();
LOG_WARNING(Lib_UserService, "(STUBBED) called");
return SCE_OK;
}
s32 PS4_SYSV_ABI sceUserServiceGetLoginUserIdList(SceUserServiceLoginUserIdList* userIdList) {
PRINT_DUMMY_FUNCTION_NAME();
LOG_WARNING(Lib_UserService, "(STUBBED) called");
userIdList->user_id[0] = 1;
userIdList->user_id[1] = -1;
userIdList->user_id[2] = -1;

File diff suppressed because it is too large Load Diff

View File

@ -2,31 +2,29 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <Zydis/Zydis.h>
#include <fmt/core.h>
#include "common/log.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/aerolib/aerolib.h"
#include "core/aerolib/stubs.h"
#include "core/hle/libraries/libkernel/thread_management.h"
#include "core/linker.h"
#include "core/tls.h"
#include "core/virtual_memory.h"
namespace Core {
constexpr bool debug_loader = true;
static constexpr u64 LoadAddress = SYSTEM_RESERVED + CODE_BASE_OFFSET;
static u64 g_load_addr = SYSTEM_RESERVED + CODE_BASE_OFFSET;
static u64 get_aligned_size(const elf_program_header& phdr) {
static u64 GetAlignedSize(const elf_program_header& phdr) {
return (phdr.p_align != 0 ? (phdr.p_memsz + (phdr.p_align - 1)) & ~(phdr.p_align - 1)
: phdr.p_memsz);
}
static u64 calculate_base_size(const elf_header& ehdr, std::span<const elf_program_header> phdr) {
static u64 CalculateBaseSize(const elf_header& ehdr, std::span<const elf_program_header> phdr) {
u64 base_size = 0;
for (u16 i = 0; i < ehdr.e_phnum; i++) {
if (phdr[i].p_memsz != 0 && (phdr[i].p_type == PT_LOAD || phdr[i].p_type == PT_SCE_RELRO)) {
u64 last_addr = phdr[i].p_vaddr + get_aligned_size(phdr[i]);
u64 last_addr = phdr[i].p_vaddr + GetAlignedSize(phdr[i]);
if (last_addr > base_size) {
base_size = last_addr;
}
@ -35,7 +33,7 @@ static u64 calculate_base_size(const elf_header& ehdr, std::span<const elf_progr
return base_size;
}
static std::string encodeId(u64 nVal) {
static std::string EncodeId(u64 nVal) {
std::string enc;
const char pCodes[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-";
if (nVal < 0x40u) {
@ -57,97 +55,37 @@ Linker::Linker() = default;
Linker::~Linker() = default;
Module* Linker::LoadModule(const std::string& elf_name) {
Module* Linker::LoadModule(const std::filesystem::path& elf_name) {
std::scoped_lock lock{m_mutex};
auto& m = m_modules.emplace_back();
m.linker = this;
m.elf.Open(elf_name);
if (!std::filesystem::exists(elf_name)) {
LOG_ERROR(Core_Linker, "Provided module {} does not exist", elf_name.string());
return nullptr;
}
if (m.elf.isElfFile()) {
LoadModuleToMemory(&m);
LoadDynamicInfo(&m);
LoadSymbols(&m);
Relocate(&m);
auto& m = m_modules.emplace_back();
m = std::make_unique<Module>();
m->elf.Open(elf_name);
if (m->elf.IsElfFile()) {
LoadModuleToMemory(m.get());
LoadDynamicInfo(m.get());
LoadSymbols(m.get());
Relocate(m.get());
} else {
m_modules.pop_back();
return nullptr; // It is not a valid elf file //TODO check it why!
}
return &m;
return m.get();
}
Module* Linker::FindModule(/*u32 id*/) {
Module* Linker::FindModule(u32 id) {
// TODO atm we only have 1 module so we don't need to iterate on vector
if (m_modules.empty()) [[unlikely]] {
return nullptr;
}
return &m_modules[0];
}
struct TLSPattern {
uint8_t pattern[5];
uint8_t pattern_size;
uint8_t imm_size;
uint8_t target_reg;
};
constexpr TLSPattern tls_patterns[] = {
{{0x64, 0x48, 0xA1},
3,
8,
0}, // 64 48 A1 | 00 00 00 00 00 00 00 00 # mov rax, qword ptr fs:[64b imm]
{{0x64, 0x48, 0x8B, 0x4, 0x25},
5,
4,
0}, // 64 48 8B 04 25 | 00 00 00 00 # mov rax,qword ptr fs:[0]
{{0x64, 0x48, 0x8B, 0xC, 0x25}, 5, 4, 1}, // rcx
{{0x64, 0x48, 0x8B, 0x14, 0x25}, 5, 4, 2}, // rdx
{{0x64, 0x48, 0x8B, 0x1C, 0x25}, 5, 4, 3}, // rbx
{{0x64, 0x48, 0x8B, 0x24, 0x25}, 5, 4, 4}, // rsp
{{0x64, 0x48, 0x8B, 0x2C, 0x25}, 5, 4, 5}, // rbp
{{0x64, 0x48, 0x8B, 0x34, 0x25}, 5, 4, 6}, // rsi
{{0x64, 0x48, 0x8B, 0x3C, 0x25}, 5, 4, 7}, // rdi
{{0x64, 0x4C, 0x8B, 0x4, 0x25}, 5, 4, 8}, // r8
{{0x64, 0x4C, 0x8B, 0xC, 0x25}, 5, 4, 9}, // r9
{{0x64, 0x4C, 0x8B, 0x14, 0x25}, 5, 4, 10}, // r10
{{0x64, 0x4C, 0x8B, 0x1C, 0x25}, 5, 4, 11}, // r11
{{0x64, 0x4C, 0x8B, 0x24, 0x25}, 5, 4, 12}, // r12
{{0x64, 0x4C, 0x8B, 0x2C, 0x25}, 5, 4, 13}, // r13
{{0x64, 0x4C, 0x8B, 0x34, 0x25}, 5, 4, 14}, // r14
{{0x64, 0x4C, 0x8B, 0x3C, 0x25}, 5, 4, 15}, // r15
};
void PatchTLS(u64 segment_addr, u64 segment_size) {
uint8_t* code = (uint8_t*)segment_addr;
auto remaining_size = segment_size;
while (remaining_size) {
for (auto& tls_pattern : tls_patterns) {
auto total_size = tls_pattern.pattern_size + tls_pattern.imm_size;
if (remaining_size >= total_size) {
if (memcmp(code, tls_pattern.pattern, tls_pattern.pattern_size) == 0) {
if (tls_pattern.imm_size == 4)
printf("PATTERN32 FOUND @ %p, reg: %d offset: %X\n", code,
tls_pattern.target_reg,
*(uint32_t*)(code + tls_pattern.pattern_size));
else
printf("PATTERN64 FOUND @ %p, reg: %d offset: %lX\n", code,
tls_pattern.target_reg,
*(uint64_t*)(code + tls_pattern.pattern_size));
code[0] = 0xcd;
code[1] = 0x80 + tls_pattern.target_reg;
code[2] = tls_pattern.pattern_size | (tls_pattern.imm_size << 4);
code += total_size - 1;
remaining_size -= total_size - 1;
break;
}
}
}
code++;
remaining_size--;
}
return m_modules[0].get();
}
void Linker::LoadModuleToMemory(Module* m) {
@ -155,18 +93,18 @@ void Linker::LoadModuleToMemory(Module* m) {
const auto elf_header = m->elf.GetElfHeader();
const auto elf_pheader = m->elf.GetProgramHeader();
u64 base_size = calculate_base_size(elf_header, elf_pheader);
u64 base_size = CalculateBaseSize(elf_header, elf_pheader);
m->aligned_base_size = (base_size & ~(static_cast<u64>(0x1000) - 1)) +
0x1000; // align base size to 0x1000 block size (TODO is that the default
// block size or it can be changed?
m->base_virtual_addr = VirtualMemory::memory_alloc(g_load_addr, m->aligned_base_size,
m->base_virtual_addr = VirtualMemory::memory_alloc(LoadAddress, m->aligned_base_size,
VirtualMemory::MemoryMode::ExecuteReadWrite);
LOG_INFO_IF(debug_loader, "====Load Module to Memory ========\n");
LOG_INFO_IF(debug_loader, "base_virtual_addr ......: {:#018x}\n", m->base_virtual_addr);
LOG_INFO_IF(debug_loader, "base_size ..............: {:#018x}\n", base_size);
LOG_INFO_IF(debug_loader, "aligned_base_size ......: {:#018x}\n", m->aligned_base_size);
LOG_INFO(Core_Linker, "====Load Module to Memory ========");
LOG_INFO(Core_Linker, "base_virtual_addr ......: {:#018x}", m->base_virtual_addr);
LOG_INFO(Core_Linker, "base_size ..............: {:#018x}", base_size);
LOG_INFO(Core_Linker, "aligned_base_size ......: {:#018x}", m->aligned_base_size);
for (u16 i = 0; i < elf_header.e_phnum; i++) {
switch (elf_pheader[i].p_type) {
@ -175,14 +113,14 @@ void Linker::LoadModuleToMemory(Module* m) {
if (elf_pheader[i].p_memsz != 0) {
u64 segment_addr = elf_pheader[i].p_vaddr + m->base_virtual_addr;
u64 segment_file_size = elf_pheader[i].p_filesz;
u64 segment_memory_size = get_aligned_size(elf_pheader[i]);
u64 segment_memory_size = GetAlignedSize(elf_pheader[i]);
auto segment_mode = m->elf.ElfPheaderFlagsStr(elf_pheader[i].p_flags);
LOG_INFO_IF(debug_loader, "program header = [{}] type = {}\n", i,
m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type));
LOG_INFO_IF(debug_loader, "segment_addr ..........: {:#018x}\n", segment_addr);
LOG_INFO_IF(debug_loader, "segment_file_size .....: {}\n", segment_file_size);
LOG_INFO_IF(debug_loader, "segment_memory_size ...: {}\n", segment_memory_size);
LOG_INFO_IF(debug_loader, "segment_mode ..........: {}\n", segment_mode);
LOG_INFO(Core_Linker, "program header = [{}] type = {}", i,
m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type));
LOG_INFO(Core_Linker, "segment_addr ..........: {:#018x}", segment_addr);
LOG_INFO(Core_Linker, "segment_file_size .....: {}", segment_file_size);
LOG_INFO(Core_Linker, "segment_memory_size ...: {}", segment_memory_size);
LOG_INFO(Core_Linker, "segment_mode ..........: {}", segment_mode);
m->elf.LoadSegment(segment_addr, elf_pheader[i].p_offset, segment_file_size);
@ -190,8 +128,8 @@ void Linker::LoadModuleToMemory(Module* m) {
PatchTLS(segment_addr, segment_file_size);
}
} else {
LOG_ERROR_IF(debug_loader, "p_memsz==0 in type {}\n",
m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type));
LOG_ERROR(Core_Linker, "p_memsz==0 in type {}",
m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type));
}
break;
case PT_DYNAMIC:
@ -200,8 +138,8 @@ void Linker::LoadModuleToMemory(Module* m) {
m->elf.LoadSegment(reinterpret_cast<u64>(m->m_dynamic.data()),
elf_pheader[i].p_offset, elf_pheader[i].p_filesz);
} else {
LOG_ERROR_IF(debug_loader, "p_filesz==0 in type {}\n",
m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type));
LOG_ERROR(Core_Linker, "p_filesz==0 in type {}",
m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type));
}
break;
case PT_SCE_DYNLIBDATA:
@ -210,23 +148,23 @@ void Linker::LoadModuleToMemory(Module* m) {
m->elf.LoadSegment(reinterpret_cast<u64>(m->m_dynamic_data.data()),
elf_pheader[i].p_offset, elf_pheader[i].p_filesz);
} else {
LOG_ERROR_IF(debug_loader, "p_filesz==0 in type {}\n",
m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type));
LOG_ERROR(Core_Linker, "p_filesz==0 in type {}",
m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type));
}
break;
case PT_TLS:
m->tls.image_virtual_addr = elf_pheader[i].p_vaddr + m->base_virtual_addr;
m->tls.image_size = get_aligned_size(elf_pheader[i]);
LOG_INFO_IF(debug_loader, "tls virtual address ={:#x}\n", m->tls.image_virtual_addr);
LOG_INFO_IF(debug_loader, "tls image size ={}\n", m->tls.image_size);
m->tls.image_size = GetAlignedSize(elf_pheader[i]);
LOG_INFO(Core_Linker, "tls virtual address ={:#x}", m->tls.image_virtual_addr);
LOG_INFO(Core_Linker, "tls image size ={}", m->tls.image_size);
break;
default:
LOG_ERROR_IF(debug_loader, "Unimplemented type {}\n",
m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type));
LOG_ERROR(Core_Linker, "Unimplemented type {}",
m->elf.ElfPheaderTypeStr(elf_pheader[i].p_type));
}
}
LOG_INFO_IF(debug_loader, "program entry addr ..........: {:#018x}\n",
m->elf.GetElfEntry() + m->base_virtual_addr);
LOG_INFO(Core_Linker, "program entry addr ..........: {:#018x}",
m->elf.GetElfEntry() + m->base_virtual_addr);
}
void Linker::LoadDynamicInfo(Module* m) {
@ -273,7 +211,7 @@ void Linker::LoadDynamicInfo(Module* m) {
case DT_SCE_PLTREL: // The type of relocations in the relocation table. Should be DT_RELA
m->dynamic_info.jmp_relocation_type = dyn->d_un.d_val;
if (m->dynamic_info.jmp_relocation_type != DT_RELA) {
LOG_WARN_IF(debug_loader, "DT_SCE_PLTREL is NOT DT_RELA should check!");
LOG_WARNING(Core_Linker, "DT_SCE_PLTREL is NOT DT_RELA should check!");
}
break;
case DT_SCE_RELA: // Offset of the relocation table.
@ -288,7 +226,7 @@ void Linker::LoadDynamicInfo(Module* m) {
if (m->dynamic_info.relocation_table_entries_size !=
0x18) // this value should always be 0x18
{
LOG_WARN_IF(debug_loader, "DT_SCE_RELAENT is NOT 0x18 should check!");
LOG_WARNING(Core_Linker, "DT_SCE_RELAENT is NOT 0x18 should check!");
}
break;
case DT_INIT_ARRAY: // Address of the array of pointers to initialization functions
@ -314,7 +252,7 @@ void Linker::LoadDynamicInfo(Module* m) {
if (m->dynamic_info.symbol_table_entries_size !=
0x18) // this value should always be 0x18
{
LOG_WARN_IF(debug_loader, "DT_SCE_SYMENT is NOT 0x18 should check!");
LOG_WARNING(Core_Linker, "DT_SCE_SYMENT is NOT 0x18 should check!");
}
break;
case DT_DEBUG:
@ -327,7 +265,7 @@ void Linker::LoadDynamicInfo(Module* m) {
m->dynamic_info.flags = dyn->d_un.d_val;
if (m->dynamic_info.flags != 0x04) // this value should always be DF_TEXTREL (0x04)
{
LOG_WARN_IF(debug_loader, "DT_FLAGS is NOT 0x04 should check!");
LOG_WARNING(Core_Linker, "DT_FLAGS is NOT 0x04 should check!");
}
break;
case DT_NEEDED: // Offset of the library string in the string table to be linked in.
@ -336,21 +274,21 @@ void Linker::LoadDynamicInfo(Module* m) {
{
m->dynamic_info.needed.push_back(m->dynamic_info.str_table + dyn->d_un.d_val);
} else {
LOG_ERROR_IF(debug_loader, "DT_NEEDED str table is not loaded should check!");
LOG_ERROR(Core_Linker, "DT_NEEDED str table is not loaded should check!");
}
break;
case DT_SCE_NEEDED_MODULE: {
ModuleInfo info{};
info.value = dyn->d_un.d_val;
info.name = m->dynamic_info.str_table + info.name_offset;
info.enc_id = encodeId(info.id);
info.enc_id = EncodeId(info.id);
m->dynamic_info.import_modules.push_back(info);
} break;
case DT_SCE_IMPORT_LIB: {
LibraryInfo info{};
info.value = dyn->d_un.d_val;
info.name = m->dynamic_info.str_table + info.name_offset;
info.enc_id = encodeId(info.id);
info.enc_id = EncodeId(info.id);
m->dynamic_info.import_libs.push_back(info);
} break;
case DT_SCE_FINGERPRINT:
@ -358,16 +296,14 @@ void Linker::LoadDynamicInfo(Module* m) {
// the given app. How exactly this is generated isn't known, however it is not necessary
// to have a valid fingerprint. While an invalid fingerprint will cause a warning to be
// printed to the kernel log, the ELF will still load and run.
LOG_INFO_IF(debug_loader,
"unsupported DT_SCE_FINGERPRINT value = ..........: {:#018x}\n",
dyn->d_un.d_val);
LOG_INFO(Core_Linker, "unsupported DT_SCE_FINGERPRINT value = ..........: {:#018x}",
dyn->d_un.d_val);
break;
case DT_SCE_IMPORT_LIB_ATTR:
// The upper 32-bits should contain the module index multiplied by 0x10000. The lower
// 32-bits should be a constant 0x9.
LOG_INFO_IF(debug_loader,
"unsupported DT_SCE_IMPORT_LIB_ATTR value = ......: {:#018x}\n",
dyn->d_un.d_val);
LOG_INFO(Core_Linker, "unsupported DT_SCE_IMPORT_LIB_ATTR value = ......: {:#018x}",
dyn->d_un.d_val);
break;
case DT_SCE_ORIGINAL_FILENAME:
m->dynamic_info.filename = m->dynamic_info.str_table + dyn->d_un.d_val;
@ -377,24 +313,23 @@ void Linker::LoadDynamicInfo(Module* m) {
ModuleInfo info{};
info.value = dyn->d_un.d_val;
info.name = m->dynamic_info.str_table + info.name_offset;
info.enc_id = encodeId(info.id);
info.enc_id = EncodeId(info.id);
m->dynamic_info.export_modules.push_back(info);
} break;
case DT_SCE_MODULE_ATTR:
// TODO?
LOG_INFO_IF(debug_loader,
"unsupported DT_SCE_MODULE_ATTR value = ..........: {:#018x}\n",
dyn->d_un.d_val);
LOG_INFO(Core_Linker, "unsupported DT_SCE_MODULE_ATTR value = ..........: {:#018x}",
dyn->d_un.d_val);
break;
case DT_SCE_EXPORT_LIB: {
LibraryInfo info{};
info.value = dyn->d_un.d_val;
info.name = m->dynamic_info.str_table + info.name_offset;
info.enc_id = encodeId(info.id);
info.enc_id = EncodeId(info.id);
m->dynamic_info.export_libs.push_back(info);
} break;
default:
LOG_INFO_IF(debug_loader, "unsupported dynamic tag ..........: {:#018x}\n", dyn->d_tag);
LOG_INFO(Core_Linker, "unsupported dynamic tag ..........: {:#018x}", dyn->d_tag);
}
}
}
@ -442,7 +377,7 @@ const LibraryInfo* Linker::FindLibrary(const Module& m, const std::string& id) {
void Linker::LoadSymbols(Module* m) {
if (m->dynamic_info.symbol_table == nullptr || m->dynamic_info.str_table == nullptr ||
m->dynamic_info.symbol_table_total_size == 0) {
LOG_INFO_IF(debug_loader, "Symbol table not found!\n");
LOG_INFO(Core_Linker, "Symbol table not found!");
return;
}
@ -466,8 +401,8 @@ void Linker::LoadSymbols(Module* m) {
case STB_WEAK:
break;
default:
LOG_INFO_IF(debug_loader, "Unsupported bind {} for name symbol {} \n", bind,
ids.at(0));
LOG_INFO(Core_Linker, "Unsupported bind {} for name symbol {}", bind,
ids.at(0));
continue;
}
switch (type) {
@ -475,16 +410,16 @@ void Linker::LoadSymbols(Module* m) {
case STT_FUN:
break;
default:
LOG_INFO_IF(debug_loader, "Unsupported type {} for name symbol {} \n", type,
ids.at(0));
LOG_INFO(Core_Linker, "Unsupported type {} for name symbol {}", type,
ids.at(0));
continue;
}
switch (visibility) {
case STV_DEFAULT:
break;
default:
LOG_INFO_IF(debug_loader, "Unsupported visibility {} for name symbol {} \n",
visibility, ids.at(0));
LOG_INFO(Core_Linker, "Unsupported visibility {} for name symbol {}",
visibility, ids.at(0));
continue;
}
// if st_value!=0 then it's export symbol
@ -515,97 +450,98 @@ void Linker::LoadSymbols(Module* m) {
m->import_sym.AddSymbol(sym_r, 0);
}
LOG_INFO_IF(
debug_loader,
"name {} function {} library {} module {} bind {} type {} visibility {}\n",
ids.at(0), nidName, library->name, module->name, bind, type, visibility);
LOG_INFO(Core_Linker,
"name = {}, function = {}, library = {}, module = {}, bind = {}, type = "
"{}, visibility = {}",
ids.at(0), nidName, library->name, module->name, bind, type, visibility);
}
}
}
}
static void relocate(u32 idx, elf_relocation* rel, Module* m, bool isJmpRel) {
auto type = rel->GetType();
auto symbol = rel->GetSymbol();
auto addend = rel->rel_addend;
auto* symbolsTlb = m->dynamic_info.symbol_table;
auto* namesTlb = m->dynamic_info.str_table;
u64 rel_value = 0;
u64 rel_base_virtual_addr = m->base_virtual_addr;
u64 rel_virtual_addr = m->base_virtual_addr + rel->rel_offset;
bool rel_isResolved = false;
u8 rel_sym_type = 0;
std::string rel_name;
switch (type) {
case R_X86_64_RELATIVE:
if (symbol != 0) // should be always zero
{
// LOG_INFO_IF(debug_loader, "R_X86_64_RELATIVE symbol not zero = {:#010x}\n", type,
// symbol);//found it openorbis but i am not sure it worth logging
}
rel_value = rel_base_virtual_addr + addend;
rel_isResolved = true;
break;
case R_X86_64_64:
case R_X86_64_JUMP_SLOT: // similar but addend is not take into account
{
auto sym = symbolsTlb[symbol];
auto sym_bind = sym.GetBind();
auto sym_type = sym.GetType();
auto sym_visibility = sym.GetVisibility();
u64 symbol_vitrual_addr = 0;
Loader::SymbolRecord symrec{};
switch (sym_type) {
case STT_FUN:
rel_sym_type = 2;
break;
case STT_OBJECT:
rel_sym_type = 1;
break;
default:
LOG_INFO_IF(debug_loader, "unknown symbol type {}\n", sym_type);
}
if (sym_visibility != 0) // should be zero log if else
{
LOG_INFO_IF(debug_loader, "symbol visilibity !=0\n");
}
switch (sym_bind) {
case STB_GLOBAL:
rel_name = namesTlb + sym.st_name;
m->linker->Resolve(rel_name, rel_sym_type, m, &symrec);
symbol_vitrual_addr = symrec.virtual_address;
rel_isResolved = (symbol_vitrual_addr != 0);
rel_name = symrec.name;
if (type == R_X86_64_JUMP_SLOT) {
addend = 0;
}
rel_value = (rel_isResolved ? symbol_vitrual_addr + addend : 0);
if (!rel_isResolved) {
LOG_INFO_IF(debug_loader,
"R_X86_64_64-R_X86_64_JUMP_SLOT sym_type {} bind STB_GLOBAL symbol : "
"{:#010x}\n",
sym_type, symbol);
}
break;
default:
LOG_INFO_IF(debug_loader, "UNK bind {}\n", sym_bind);
}
} break;
default:
LOG_INFO_IF(debug_loader, "UNK type {:#010x} rel symbol : {:#010x}\n", type, symbol);
}
if (rel_isResolved) {
VirtualMemory::memory_patch(rel_virtual_addr, rel_value);
} else {
LOG_INFO_IF(debug_loader, "function not patched! {}\n", rel_name);
}
}
void Linker::Relocate(Module* m) {
const auto relocate = [this](u32 idx, elf_relocation* rel, Module* m, bool isJmpRel) {
auto type = rel->GetType();
auto symbol = rel->GetSymbol();
auto addend = rel->rel_addend;
auto* symbolsTlb = m->dynamic_info.symbol_table;
auto* namesTlb = m->dynamic_info.str_table;
u64 rel_value = 0;
u64 rel_base_virtual_addr = m->base_virtual_addr;
u64 rel_virtual_addr = m->base_virtual_addr + rel->rel_offset;
bool rel_isResolved = false;
u8 rel_sym_type = 0;
std::string rel_name;
switch (type) {
case R_X86_64_RELATIVE:
if (symbol != 0) // should be always zero
{
// LOG_INFO(Core_Linker, "R_X86_64_RELATIVE symbol not zero = {:#010x}\n", type,
// symbol);//found it openorbis but i am not sure it worth logging
}
rel_value = rel_base_virtual_addr + addend;
rel_isResolved = true;
break;
case R_X86_64_64:
case R_X86_64_JUMP_SLOT: // similar but addend is not take into account
{
auto sym = symbolsTlb[symbol];
auto sym_bind = sym.GetBind();
auto sym_type = sym.GetType();
auto sym_visibility = sym.GetVisibility();
u64 symbol_vitrual_addr = 0;
Loader::SymbolRecord symrec{};
switch (sym_type) {
case STT_FUN:
rel_sym_type = 2;
break;
case STT_OBJECT:
rel_sym_type = 1;
break;
default:
LOG_INFO(Core_Linker, "unknown symbol type {}", sym_type);
}
if (sym_visibility != 0) // should be zero log if else
{
LOG_INFO(Core_Linker, "symbol visilibity !=0");
}
switch (sym_bind) {
case STB_GLOBAL:
rel_name = namesTlb + sym.st_name;
Resolve(rel_name, rel_sym_type, m, &symrec);
symbol_vitrual_addr = symrec.virtual_address;
rel_isResolved = (symbol_vitrual_addr != 0);
rel_name = symrec.name;
if (type == R_X86_64_JUMP_SLOT) {
addend = 0;
}
rel_value = (rel_isResolved ? symbol_vitrual_addr + addend : 0);
if (!rel_isResolved) {
LOG_INFO(Core_Linker,
"R_X86_64_64-R_X86_64_JUMP_SLOT sym_type {} bind STB_GLOBAL symbol : "
"{:#010x}",
sym_type, symbol);
}
break;
default:
LOG_INFO(Core_Linker, "UNK bind {}", sym_bind);
}
} break;
default:
LOG_INFO(Core_Linker, "UNK type {:#010x} rel symbol : {:#010x}", type, symbol);
}
if (rel_isResolved) {
VirtualMemory::memory_patch(rel_virtual_addr, rel_value);
} else {
LOG_INFO(Core_Linker, "function not patched! {}", rel_name);
}
};
u32 idx = 0;
for (auto* rel = m->dynamic_info.relocation_table;
reinterpret_cast<u8*>(rel) < reinterpret_cast<u8*>(m->dynamic_info.relocation_table) +
@ -654,8 +590,8 @@ void Linker::Resolve(const std::string& name, int Symtype, Module* m,
return_info->virtual_address = AeroLib::GetStub(sr.name.c_str());
return_info->name = "Unknown !!!";
}
LOG_ERROR_IF(debug_loader, "Linker: Stub resolved {} as {} (lib: {}, mod: {}) \n",
sr.name, return_info->name, library->name, module->name);
LOG_ERROR(Core_Linker, "Linker: Stub resolved {} as {} (lib: {}, mod: {})", sr.name,
return_info->name, library->name, module->name);
}
} else {
//__debugbreak();//den tha prepei na ftasoume edo
@ -672,7 +608,7 @@ static PS4_SYSV_ABI void ProgramExitFunc() {
fmt::print("exit function called\n");
}
static void run_main_entry(u64 addr, EntryParams* params, exit_func_t exit_func) {
static void RunMainEntry(u64 addr, EntryParams* params, exit_func_t exit_func) {
// reinterpret_cast<entry_func_t>(addr)(params, exit_func); // can't be used, stack has to have
// a specific layout
@ -702,7 +638,7 @@ void Linker::Execute() {
p.argv[0] = "eboot.bin"; // hmm should be ok?
const auto& module = m_modules.at(0);
run_main_entry(module.elf.GetElfEntry() + module.base_virtual_addr, &p, ProgramExitFunc);
RunMainEntry(module->elf.GetElfEntry() + module->base_virtual_addr, &p, ProgramExitFunc);
}
} // namespace Core

View File

@ -51,6 +51,7 @@ struct PS4ThreadLocal {
u64 image_size = 0;
u64 handler_virtual_addr = 0;
};
struct DynamicModuleInfo {
void* hash_table = nullptr;
u64 hash_table_size = 0;
@ -97,9 +98,7 @@ struct DynamicModuleInfo {
struct Module {
Loader::Elf elf;
u64 aligned_base_size = 0;
u64 base_virtual_addr = 0; // Base virtual address
Linker* linker = nullptr;
u64 base_virtual_addr = 0;
std::vector<u8> m_dynamic;
std::vector<u8> m_dynamic_data;
@ -116,8 +115,8 @@ public:
Linker();
virtual ~Linker();
Module* LoadModule(const std::string& elf_name);
Module* FindModule(/*u32 id*/);
Module* LoadModule(const std::filesystem::path& elf_name);
Module* FindModule(u32 id = 0);
void LoadModuleToMemory(Module* m);
void LoadDynamicInfo(Module* m);
void LoadSymbols(Module* m);
@ -133,7 +132,7 @@ private:
const ModuleInfo* FindModule(const Module& m, const std::string& id);
const LibraryInfo* FindLibrary(const Module& program, const std::string& id);
std::vector<Module> m_modules;
std::vector<std::unique_ptr<Module>> m_modules;
Loader::SymbolsResolver m_hle_symbols{};
std::mutex m_mutex;
};

View File

@ -2,15 +2,15 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/core.h>
#include "common/debug.h"
#include "common/log.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/loader/elf.h"
namespace Core::Loader {
constexpr bool log_file_loader = false; // disable it to disable logging
using namespace Common::FS;
static std::string_view getProgramTypeName(program_type_es type) {
static std::string_view GetProgramTypeName(program_type_es type) {
switch (type) {
case PT_FAKE:
return "PT_FAKE";
@ -33,7 +33,7 @@ static std::string_view getProgramTypeName(program_type_es type) {
}
}
static std::string_view getIdentClassName(ident_class_es elf_class) {
static std::string_view GetIdentClassName(ident_class_es elf_class) {
switch (elf_class) {
case ELF_CLASS_NONE:
return "ELF_CLASS_NONE";
@ -48,7 +48,7 @@ static std::string_view getIdentClassName(ident_class_es elf_class) {
}
}
static std::string_view getIdentEndianName(ident_endian_es endian) {
static std::string_view GetIdentEndianName(ident_endian_es endian) {
switch (endian) {
case ELF_DATA_NONE:
return "ELF_DATA_NONE";
@ -63,7 +63,7 @@ static std::string_view getIdentEndianName(ident_endian_es endian) {
}
}
static std::string_view getIdentVersionName(ident_version_es version) {
static std::string_view GetIdentVersionName(ident_version_es version) {
switch (version) {
case ELF_VERSION_NONE:
return "ELF_VERSION_NONE";
@ -76,7 +76,7 @@ static std::string_view getIdentVersionName(ident_version_es version) {
}
}
static std::string_view getIdentOsabiName(ident_osabi_es osabi) {
static std::string_view GetIdentOsabiName(ident_osabi_es osabi) {
switch (osabi) {
case ELF_OSABI_NONE:
return "ELF_OSABI_NONE";
@ -117,7 +117,7 @@ static std::string_view getIdentOsabiName(ident_osabi_es osabi) {
}
}
static std::string_view getIdentAbiversionName(ident_abiversion_es version) {
static std::string_view GetIdentAbiversionName(ident_abiversion_es version) {
switch (version) {
case ELF_ABI_VERSION_AMDGPU_HSA_V2:
return "ELF_ABI_VERSION_AMDGPU_HSA_V2";
@ -131,7 +131,7 @@ static std::string_view getIdentAbiversionName(ident_abiversion_es version) {
return "INVALID";
}
}
static std::string_view getVersion(e_version_es version) {
static std::string_view GetVersion(e_version_es version) {
switch (version) {
case EV_NONE:
return "EV_NONE";
@ -142,7 +142,7 @@ static std::string_view getVersion(e_version_es version) {
}
}
static std::string_view getType(e_type_s type) {
static std::string_view GetType(e_type_s type) {
switch (type) {
case ET_NONE:
return "ET_NONE";
@ -167,7 +167,7 @@ static std::string_view getType(e_type_s type) {
}
}
static std::string_view getMachine(e_machine_es machine) {
static std::string_view GetMachine(e_machine_es machine) {
switch (machine) {
case EM_X86_64:
return "EM_X86_64";
@ -176,30 +176,25 @@ static std::string_view getMachine(e_machine_es machine) {
}
}
Elf::~Elf() {
Reset();
}
Elf::~Elf() = default;
void Elf::Reset() {
m_f.close();
}
void Elf::Open(const std::string& file_name) {
Reset();
m_f.open(file_name, Common::FS::OpenMode::Read);
m_f.read(m_self);
if (is_self = isSelfFile(); !is_self) {
m_f.seek(0, Common::FS::SeekMode::Set);
} else {
m_self_segments.resize(m_self.segment_count);
m_f.read(m_self_segments);
void Elf::Open(const std::filesystem::path& file_name) {
m_f.Open(file_name, FileAccessMode::Read);
if (!m_f.ReadObject(m_self)) {
LOG_ERROR(Loader, "Unable to read self header!");
return;
}
const u64 elf_header_pos = m_f.tell();
m_f.read(m_elf_header);
if (!isElfFile()) {
if (is_self = IsSelfFile(); !is_self) {
m_f.Seek(0, SeekOrigin::SetOrigin);
} else {
m_self_segments.resize(m_self.segment_count);
m_f.Read(m_self_segments);
}
const u64 elf_header_pos = m_f.Tell();
m_f.Read(m_elf_header);
if (!IsElfFile()) {
return;
}
@ -209,8 +204,8 @@ void Elf::Open(const std::string& file_name) {
}
out.resize(num);
m_f.seek(offset, Common::FS::SeekMode::Set);
m_f.read(out);
m_f.Seek(offset, SeekOrigin::SetOrigin);
m_f.Read(out);
};
load_headers(m_elf_phdr, elf_header_pos + m_elf_header.e_phoff, m_elf_header.e_phnum);
@ -227,96 +222,95 @@ void Elf::Open(const std::string& file_name) {
header_size &= ~15; // Align
if (m_elf_header.e_ehsize - header_size >= sizeof(elf_program_id_header)) {
m_f.seek(header_size, Common::FS::SeekMode::Set);
m_f.read(m_self_id_header);
m_f.Seek(header_size, SeekOrigin::SetOrigin);
m_f.ReadObject(m_self_id_header);
}
}
DebugDump();
}
bool Elf::isSelfFile() const {
bool Elf::IsSelfFile() const {
if (m_self.magic != self_header::signature) [[unlikely]] {
LOG_ERROR_IF(log_file_loader,
"Not a SELF file.Magic file mismatched !current = {:#x} required = {:#x}\n ",
m_self.magic, self_header::signature);
LOG_INFO(Loader, "Not a SELF file. Magic mismatch current = {:#x} expected = {:#x}",
m_self.magic, self_header::signature);
return false;
}
if (m_self.version != 0x00 || m_self.mode != 0x01 || m_self.endian != 0x01 ||
m_self.attributes != 0x12) [[unlikely]] {
LOG_ERROR_IF(log_file_loader, "Unknown SELF file\n");
LOG_INFO(Loader, "Unknown SELF file");
return false;
}
if (m_self.category != 0x01 || m_self.program_type != 0x01) [[unlikely]] {
LOG_ERROR_IF(log_file_loader, "Unknown SELF file\n");
LOG_INFO(Loader, "Unknown SELF file");
return false;
}
return true;
}
bool Elf::isElfFile() const {
bool Elf::IsElfFile() const {
if (m_elf_header.e_ident.magic[EI_MAG0] != ELFMAG0 ||
m_elf_header.e_ident.magic[EI_MAG1] != ELFMAG1 ||
m_elf_header.e_ident.magic[EI_MAG2] != ELFMAG2 ||
m_elf_header.e_ident.magic[EI_MAG3] != ELFMAG3) {
LOG_ERROR_IF(log_file_loader, "Not an ELF file magic is wrong!\n");
LOG_INFO(Loader, "Not an ELF file magic is wrong!");
return false;
}
if (m_elf_header.e_ident.ei_class != ELF_CLASS_64) {
LOG_ERROR_IF(log_file_loader, "e_ident[EI_CLASS] expected 0x02 is ({:#x})\n",
static_cast<u32>(m_elf_header.e_ident.ei_class));
LOG_INFO(Loader, "e_ident[EI_CLASS] expected 0x02 is ({:#x})",
static_cast<u32>(m_elf_header.e_ident.ei_class));
return false;
}
if (m_elf_header.e_ident.ei_data != ELF_DATA_2LSB) {
LOG_ERROR_IF(log_file_loader, "e_ident[EI_DATA] expected 0x01 is ({:#x})\n",
static_cast<u32>(m_elf_header.e_ident.ei_data));
LOG_INFO(Loader, "e_ident[EI_DATA] expected 0x01 is ({:#x})",
static_cast<u32>(m_elf_header.e_ident.ei_data));
return false;
}
if (m_elf_header.e_ident.ei_version != ELF_VERSION_CURRENT) {
LOG_ERROR_IF(log_file_loader, "e_ident[EI_VERSION] expected 0x01 is ({:#x})\n",
static_cast<u32>(m_elf_header.e_ident.ei_version));
LOG_INFO(Loader, "e_ident[EI_VERSION] expected 0x01 is ({:#x})",
static_cast<u32>(m_elf_header.e_ident.ei_version));
return false;
}
if (m_elf_header.e_ident.ei_osabi != ELF_OSABI_FREEBSD) {
LOG_ERROR_IF(log_file_loader, "e_ident[EI_OSABI] expected 0x09 is ({:#x})\n",
static_cast<u32>(m_elf_header.e_ident.ei_osabi));
LOG_INFO(Loader, "e_ident[EI_OSABI] expected 0x09 is ({:#x})",
static_cast<u32>(m_elf_header.e_ident.ei_osabi));
return false;
}
if (m_elf_header.e_ident.ei_abiversion != ELF_ABI_VERSION_AMDGPU_HSA_V2) {
LOG_ERROR_IF(log_file_loader, "e_ident[EI_ABIVERSION] expected 0x00 is ({:#x})\n",
static_cast<u32>(m_elf_header.e_ident.ei_abiversion));
LOG_INFO(Loader, "e_ident[EI_ABIVERSION] expected 0x00 is ({:#x})",
static_cast<u32>(m_elf_header.e_ident.ei_abiversion));
return false;
}
if (m_elf_header.e_type != ET_SCE_DYNEXEC && m_elf_header.e_type != ET_SCE_DYNAMIC &&
m_elf_header.e_type != ET_SCE_EXEC) {
LOG_ERROR_IF(log_file_loader, "e_type expected 0xFE10 OR 0xFE18 OR 0xfe00 is ({:#x})\n",
static_cast<u32>(m_elf_header.e_type));
LOG_INFO(Loader, "e_type expected 0xFE10 OR 0xFE18 OR 0xfe00 is ({:#x})",
static_cast<u32>(m_elf_header.e_type));
return false;
}
if (m_elf_header.e_machine != EM_X86_64) {
LOG_ERROR_IF(log_file_loader, "e_machine expected 0x3E is ({:#x})\n",
static_cast<u32>(m_elf_header.e_machine));
LOG_INFO(Loader, "e_machine expected 0x3E is ({:#x})",
static_cast<u32>(m_elf_header.e_machine));
return false;
}
if (m_elf_header.e_version != EV_CURRENT) {
LOG_ERROR_IF(log_file_loader, "m_elf_header.e_version expected 0x01 is ({:#x})\n",
static_cast<u32>(m_elf_header.e_version));
LOG_INFO(Loader, "m_elf_header.e_version expected 0x01 is ({:#x})",
static_cast<u32>(m_elf_header.e_version));
return false;
}
if (m_elf_header.e_phentsize != sizeof(elf_program_header)) {
LOG_ERROR_IF(log_file_loader, "e_phentsize ({}) != sizeof(elf_program_header)\n",
static_cast<u32>(m_elf_header.e_phentsize));
LOG_INFO(Loader, "e_phentsize ({}) != sizeof(elf_program_header)",
m_elf_header.e_phentsize);
return false;
}
@ -324,8 +318,8 @@ bool Elf::isElfFile() const {
m_elf_header.e_shentsize !=
sizeof(elf_section_header)) // Commercial games doesn't appear to have section headers
{
LOG_ERROR_IF(log_file_loader, "e_shentsize ({}) != sizeof(elf_section_header)\n",
m_elf_header.e_shentsize);
LOG_INFO(Loader, "e_shentsize ({}) != sizeof(elf_section_header)",
m_elf_header.e_shentsize);
return false;
}
@ -334,50 +328,49 @@ bool Elf::isElfFile() const {
void Elf::DebugDump() {
if (is_self) { // If we load elf instead
LOG_INFO_IF(log_file_loader, (SElfHeaderStr()));
LOG_INFO(Loader, "{}", SElfHeaderStr());
for (u16 i = 0; i < m_self.segment_count; i++) {
LOG_INFO_IF(log_file_loader, SELFSegHeader(i));
LOG_INFO(Loader, "{}", SELFSegHeader(i));
}
}
LOG_INFO_IF(log_file_loader, ElfHeaderStr());
LOG_INFO(Loader, "{}", ElfHeaderStr());
if (m_elf_header.e_phentsize > 0) {
LOG_INFO_IF(log_file_loader, "Program headers:\n");
LOG_INFO(Loader, "Program headers:");
for (u16 i = 0; i < m_elf_header.e_phnum; i++) {
LOG_INFO_IF(log_file_loader, ElfPHeaderStr(i));
LOG_INFO(Loader, "{}", ElfPHeaderStr(i));
}
}
if (m_elf_header.e_shentsize > 0) {
LOG_INFO_IF(log_file_loader, "Section headers:\n");
LOG_INFO(Loader, "Section headers:");
for (u16 i = 0; i < m_elf_header.e_shnum; i++) {
LOG_INFO_IF(log_file_loader, "--- shdr {} --\n", i);
LOG_INFO_IF(log_file_loader, "sh_name ........: {}\n", m_elf_shdr[i].sh_name);
LOG_INFO_IF(log_file_loader, "sh_type ........: {:#010x}\n", m_elf_shdr[i].sh_type);
LOG_INFO_IF(log_file_loader, "sh_flags .......: {:#018x}\n", m_elf_shdr[i].sh_flags);
LOG_INFO_IF(log_file_loader, "sh_addr ........: {:#018x}\n", m_elf_shdr[i].sh_addr);
LOG_INFO_IF(log_file_loader, "sh_offset ......: {:#018x}\n", m_elf_shdr[i].sh_offset);
LOG_INFO_IF(log_file_loader, "sh_size ........: {:#018x}\n", m_elf_shdr[i].sh_size);
LOG_INFO_IF(log_file_loader, "sh_link ........: {:#010x}\n", m_elf_shdr[i].sh_link);
LOG_INFO_IF(log_file_loader, "sh_info ........: {:#010x}\n", m_elf_shdr[i].sh_info);
LOG_INFO_IF(log_file_loader, "sh_addralign ...: {:#018x}\n",
m_elf_shdr[i].sh_addralign);
LOG_INFO_IF(log_file_loader, "sh_entsize .....: {:#018x}\n", m_elf_shdr[i].sh_entsize);
LOG_INFO(Loader, "--- shdr {} --", i);
LOG_INFO(Loader, "sh_name ........: {}", m_elf_shdr[i].sh_name);
LOG_INFO(Loader, "sh_type ........: {:#010x}", m_elf_shdr[i].sh_type);
LOG_INFO(Loader, "sh_flags .......: {:#018x}", m_elf_shdr[i].sh_flags);
LOG_INFO(Loader, "sh_addr ........: {:#018x}", m_elf_shdr[i].sh_addr);
LOG_INFO(Loader, "sh_offset ......: {:#018x}", m_elf_shdr[i].sh_offset);
LOG_INFO(Loader, "sh_size ........: {:#018x}", m_elf_shdr[i].sh_size);
LOG_INFO(Loader, "sh_link ........: {:#010x}", m_elf_shdr[i].sh_link);
LOG_INFO(Loader, "sh_info ........: {:#010x}", m_elf_shdr[i].sh_info);
LOG_INFO(Loader, "sh_addralign ...: {:#018x}", m_elf_shdr[i].sh_addralign);
LOG_INFO(Loader, "sh_entsize .....: {:#018x}", m_elf_shdr[i].sh_entsize);
}
}
if (is_self) {
LOG_INFO_IF(log_file_loader, "SELF info:\n");
LOG_INFO_IF(log_file_loader, "auth id ............: {:#018x}\n", m_self_id_header.authid);
LOG_INFO_IF(log_file_loader, "program type .......: {}\n",
getProgramTypeName(m_self_id_header.program_type));
LOG_INFO_IF(log_file_loader, "app version ........: {:#018x}\n", m_self_id_header.appver);
LOG_INFO_IF(log_file_loader, "fw version .........: {:#018x}\n", m_self_id_header.firmver);
LOG_INFO(Loader, "SELF info:");
LOG_INFO(Loader, "auth id ............: {:#018x}", m_self_id_header.authid);
LOG_INFO(Loader, "program type .......: {}",
GetProgramTypeName(m_self_id_header.program_type));
LOG_INFO(Loader, "app version ........: {:#018x}", m_self_id_header.appver);
LOG_INFO(Loader, "fw version .........: {:#018x}", m_self_id_header.firmver);
std::string digest;
for (int i = 0; i < 32; i++) {
digest += fmt::format("{:02X}", m_self_id_header.digest[i]);
}
LOG_INFO_IF(log_file_loader, "digest..............: 0x{}\n", digest);
LOG_INFO(Loader, "digest..............: 0x{}", digest);
}
}
@ -420,15 +413,15 @@ std::string Elf::ElfHeaderStr() {
header += fmt::format("\n");
header +=
fmt::format("ident class.......: {}\n", getIdentClassName(m_elf_header.e_ident.ei_class));
fmt::format("ident class.......: {}\n", GetIdentClassName(m_elf_header.e_ident.ei_class));
header +=
fmt::format("ident data .......: {}\n", getIdentEndianName(m_elf_header.e_ident.ei_data));
fmt::format("ident data .......: {}\n", GetIdentEndianName(m_elf_header.e_ident.ei_data));
header += fmt::format("ident version.....: {}\n",
getIdentVersionName(m_elf_header.e_ident.ei_version));
GetIdentVersionName(m_elf_header.e_ident.ei_version));
header +=
fmt::format("ident osabi .....: {}\n", getIdentOsabiName(m_elf_header.e_ident.ei_osabi));
fmt::format("ident osabi .....: {}\n", GetIdentOsabiName(m_elf_header.e_ident.ei_osabi));
header += fmt::format("ident abiversion..: {}\n",
getIdentAbiversionName(m_elf_header.e_ident.ei_abiversion));
GetIdentAbiversionName(m_elf_header.e_ident.ei_abiversion));
header += fmt::format("ident UNK ........: 0x");
for (auto i : m_elf_header.e_ident.pad) {
@ -436,9 +429,9 @@ std::string Elf::ElfHeaderStr() {
}
header += fmt::format("\n");
header += fmt::format("type ............: {}\n", getType(m_elf_header.e_type));
header += fmt::format("machine ..........: {}\n", getMachine(m_elf_header.e_machine));
header += fmt::format("version ..........: {}\n", getVersion(m_elf_header.e_version));
header += fmt::format("type ............: {}\n", GetType(m_elf_header.e_type));
header += fmt::format("machine ..........: {}\n", GetMachine(m_elf_header.e_machine));
header += fmt::format("version ..........: {}\n", GetVersion(m_elf_header.e_version));
header += fmt::format("entry ............: {:#018x}\n", m_elf_header.e_entry);
header += fmt::format("phoff ............: {:#018x}\n", m_elf_header.e_phoff);
header += fmt::format("shoff ............: {:#018x}\n", m_elf_header.e_shoff);
@ -522,8 +515,8 @@ std::string Elf::ElfPHeaderStr(u16 no) {
void Elf::LoadSegment(u64 virtual_addr, u64 file_offset, u64 size) {
if (!is_self) {
// It's elf file
m_f.seek(file_offset, Common::FS::SeekMode::Set);
m_f.read(reinterpret_cast<void*>(static_cast<uintptr_t>(virtual_addr)), size);
m_f.Seek(file_offset, SeekOrigin::SetOrigin);
m_f.ReadRaw<u8>(reinterpret_cast<u8*>(virtual_addr), size);
return;
}
@ -536,13 +529,13 @@ void Elf::LoadSegment(u64 virtual_addr, u64 file_offset, u64 size) {
if (file_offset >= phdr.p_offset && file_offset < phdr.p_offset + phdr.p_filesz) {
auto offset = file_offset - phdr.p_offset;
m_f.seek(offset + seg.file_offset, Common::FS::SeekMode::Set);
m_f.read(reinterpret_cast<void*>(static_cast<uintptr_t>(virtual_addr)), size);
m_f.Seek(offset + seg.file_offset, SeekOrigin::SetOrigin);
m_f.ReadRaw<u8>(reinterpret_cast<u8*>(virtual_addr), size);
return;
}
}
}
BREAKPOINT(); // Hmm we didn't return something...
UNREACHABLE();
}
} // namespace Core::Loader

View File

@ -8,11 +8,11 @@
#include <string>
#include <vector>
#include "common/fs_file.h"
#include "common/io_file.h"
#include "common/types.h"
struct self_header {
static const u32 signature = 0x1D3D154Fu;
static constexpr u32 signature = 0x1D3D154Fu;
u32 magic;
u8 version;
@ -455,11 +455,11 @@ namespace Core::Loader {
class Elf {
public:
Elf() = default;
virtual ~Elf();
~Elf();
void Open(const std::string& file_name);
bool isSelfFile() const;
bool isElfFile() const;
void Open(const std::filesystem::path& file_name);
bool IsSelfFile() const;
bool IsElfFile() const;
void DebugDump();
[[nodiscard]] self_header GetSElfHeader() const {
@ -492,10 +492,7 @@ public:
void LoadSegment(u64 virtual_addr, u64 file_offset, u64 size);
private:
void Reset();
private:
Common::FS::File m_f{};
Common::FS::IOFile m_f{};
bool is_self{};
self_header m_self{};
std::vector<self_segment_header> m_self_segments;

View File

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/log.h"
#include "common/logging/log.h"
#include "common/types.h"
#include "core/loader/symbols_resolver.h"
@ -27,7 +27,7 @@ const SymbolRecord* SymbolsResolver::FindSymbol(const SymbolRes& s) const {
}
}
LOG_INFO("Unresolved! {}\n", name);
LOG_INFO(Core_Linker, "Unresolved! {}", name);
return nullptr;
}

177
src/core/tls.cpp Normal file
View File

@ -0,0 +1,177 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/types.h"
#include "core/tls.h"
#ifdef _WIN32
#include <windows.h>
#endif
namespace Core {
thread_local u8 TLS[1024];
struct TLSPattern {
uint8_t pattern[5];
uint8_t pattern_size;
uint8_t imm_size;
uint8_t target_reg;
};
constexpr static TLSPattern TlsPatterns[] = {
{{0x64, 0x48, 0xA1},
3,
8,
0}, // 64 48 A1 | 00 00 00 00 00 00 00 00 # mov rax, qword ptr fs:[64b imm]
{{0x64, 0x48, 0x8B, 0x4, 0x25},
5,
4,
0}, // 64 48 8B 04 25 | 00 00 00 00 # mov rax,qword ptr fs:[0]
{{0x64, 0x48, 0x8B, 0xC, 0x25}, 5, 4, 1}, // rcx
{{0x64, 0x48, 0x8B, 0x14, 0x25}, 5, 4, 2}, // rdx
{{0x64, 0x48, 0x8B, 0x1C, 0x25}, 5, 4, 3}, // rbx
{{0x64, 0x48, 0x8B, 0x24, 0x25}, 5, 4, 4}, // rsp
{{0x64, 0x48, 0x8B, 0x2C, 0x25}, 5, 4, 5}, // rbp
{{0x64, 0x48, 0x8B, 0x34, 0x25}, 5, 4, 6}, // rsi
{{0x64, 0x48, 0x8B, 0x3C, 0x25}, 5, 4, 7}, // rdi
{{0x64, 0x4C, 0x8B, 0x4, 0x25}, 5, 4, 8}, // r8
{{0x64, 0x4C, 0x8B, 0xC, 0x25}, 5, 4, 9}, // r9
{{0x64, 0x4C, 0x8B, 0x14, 0x25}, 5, 4, 10}, // r10
{{0x64, 0x4C, 0x8B, 0x1C, 0x25}, 5, 4, 11}, // r11
{{0x64, 0x4C, 0x8B, 0x24, 0x25}, 5, 4, 12}, // r12
{{0x64, 0x4C, 0x8B, 0x2C, 0x25}, 5, 4, 13}, // r13
{{0x64, 0x4C, 0x8B, 0x34, 0x25}, 5, 4, 14}, // r14
{{0x64, 0x4C, 0x8B, 0x3C, 0x25}, 5, 4, 15}, // r15
};
uintptr_t GetGuestTls(s64 tls_offset) {
if (tls_offset == 0) {
return reinterpret_cast<uintptr_t>(TLS);
}
UNREACHABLE_MSG("Unimplemented offset info tls");
}
#ifdef _WIN64
static LONG WINAPI ExceptionHandler(PEXCEPTION_POINTERS pExp) noexcept {
auto orig_rip = pExp->ContextRecord->Rip;
while (*(u8*)pExp->ContextRecord->Rip == 0x66) {
pExp->ContextRecord->Rip++;
}
if (*(u8*)pExp->ContextRecord->Rip == 0xcd) {
int reg = *(u8*)(pExp->ContextRecord->Rip + 1) - 0x80;
int sizes = *(u8*)(pExp->ContextRecord->Rip + 2);
int pattern_size = sizes & 0xF;
int imm_size = sizes >> 4;
int64_t tls_offset;
if (imm_size == 4) {
tls_offset = *(s32*)(pExp->ContextRecord->Rip + pattern_size);
} else {
tls_offset = *(s64*)(pExp->ContextRecord->Rip + pattern_size);
}
(&pExp->ContextRecord->Rax)[reg] = GetGuestTls(tls_offset); /* GetGuestTls */
pExp->ContextRecord->Rip += pattern_size + imm_size;
return EXCEPTION_CONTINUE_EXECUTION;
}
pExp->ContextRecord->Rip = orig_rip;
const u32 ec = pExp->ExceptionRecord->ExceptionCode;
switch (ec) {
case EXCEPTION_ACCESS_VIOLATION: {
LOG_CRITICAL(Core, "Exception EXCEPTION_ACCESS_VIOLATION ({:#x})", ec);
const auto info = pExp->ExceptionRecord->ExceptionInformation;
switch (info[0]) {
case 0:
LOG_CRITICAL(Core, "Read violation at address {:#x}", info[1]);
break;
case 1:
LOG_CRITICAL(Core, "Write violation at address {:#x}", info[1]);
break;
case 8:
LOG_CRITICAL(Core, "DEP violation at address {:#x}", info[1]);
break;
default:
break;
}
break;
}
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
LOG_CRITICAL(Core, "Exception EXCEPTION_ARRAY_BOUNDS_EXCEEDED ({:#x})", ec);
break;
case EXCEPTION_DATATYPE_MISALIGNMENT:
LOG_CRITICAL(Core, "Exception EXCEPTION_DATATYPE_MISALIGNMENT ({:#x})", ec);
break;
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
LOG_CRITICAL(Core, "Exception EXCEPTION_FLT_DIVIDE_BY_ZERO ({:#x})", ec);
break;
case EXCEPTION_ILLEGAL_INSTRUCTION:
LOG_CRITICAL(Core, "Exception EXCEPTION_ILLEGAL_INSTRUCTION ({:#x})", ec);
break;
case EXCEPTION_IN_PAGE_ERROR:
LOG_CRITICAL(Core, "Exception EXCEPTION_IN_PAGE_ERROR ({:#x})", ec);
break;
case EXCEPTION_INT_DIVIDE_BY_ZERO:
LOG_CRITICAL(Core, "Exception EXCEPTION_INT_DIVIDE_BY_ZERO ({:#x})", ec);
break;
case EXCEPTION_PRIV_INSTRUCTION:
LOG_CRITICAL(Core, "Exception EXCEPTION_PRIV_INSTRUCTION ({:#x})", ec);
break;
case EXCEPTION_STACK_OVERFLOW:
LOG_CRITICAL(Core, "Exception EXCEPTION_STACK_OVERFLOW ({:#x})", ec);
break;
default:
return EXCEPTION_CONTINUE_SEARCH;
}
return EXCEPTION_CONTINUE_SEARCH;
}
#endif
void InstallTlsHandler() {
#ifdef _WIN64
if (!AddVectoredExceptionHandler(0, ExceptionHandler)) {
LOG_CRITICAL(Core, "Failed to register an exception handler");
}
#endif
}
void PatchTLS(u64 segment_addr, u64 segment_size) {
u8* code = reinterpret_cast<u8*>(segment_addr);
auto remaining_size = segment_size;
while (remaining_size) {
for (const auto& tls_pattern : TlsPatterns) {
const auto total_size = tls_pattern.pattern_size + tls_pattern.imm_size;
if (remaining_size < total_size) {
continue;
}
if (std::memcmp(code, tls_pattern.pattern, tls_pattern.pattern_size) != 0) {
continue;
}
if (tls_pattern.imm_size == 4) {
LOG_INFO(Core_Linker, "PATTERN32 FOUND at {}, reg: {} offset: {:#x}",
fmt::ptr(code), tls_pattern.target_reg,
*(u32*)(code + tls_pattern.pattern_size));
} else {
LOG_INFO(Core_Linker, "PATTERN64 FOUND at {}, reg: {} offset: {:#x}",
fmt::ptr(code), tls_pattern.target_reg,
*(u32*)(code + tls_pattern.pattern_size));
}
code[0] = 0xcd;
code[1] = 0x80 + tls_pattern.target_reg;
code[2] = tls_pattern.pattern_size | (tls_pattern.imm_size << 4);
code += total_size - 1;
remaining_size -= total_size - 1;
break;
}
code++;
remaining_size--;
}
}
} // namespace Core

16
src/core/tls.h Normal file
View File

@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core {
/// Installs a host exception handler to handle guest TLS access.
void InstallTlsHandler();
/// Patches any instructions that access TLS to trigger the exception handler.
void PatchTLS(u64 segment_addr, u64 segment_size);
} // namespace Core

View File

@ -1,10 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "common/error.h"
#include "common/logging/log.h"
#include "core/virtual_memory.h"
#include "common/log.h"
#ifdef _WIN64
#include <windows.h>
#else
@ -72,7 +73,7 @@ u64 memory_alloc(u64 address, u64 size, MemoryMode mode) {
if (ptr == 0) {
auto err = static_cast<u32>(GetLastError());
LOG_ERROR_IF(true, "VirtualAlloc() failed: 0x{:X}\n", err);
LOG_ERROR(Common_Memory, "VirtualAlloc() failed: 0x{:X}", err);
}
#else
auto ptr = reinterpret_cast<uintptr_t>(
@ -80,7 +81,7 @@ u64 memory_alloc(u64 address, u64 size, MemoryMode mode) {
PROT_EXEC | PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
if (ptr == reinterpret_cast<uintptr_t> MAP_FAILED) {
LOG_ERROR_IF(true, "mmap() failed: {}\n", std::strerror(errno));
LOG_ERROR(Common_Memory, "mmap() failed: {}", std::strerror(errno));
}
#endif
return ptr;
@ -91,7 +92,7 @@ bool memory_protect(u64 address, u64 size, MemoryMode mode, MemoryMode* old_mode
if (VirtualProtect(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
convertMemoryMode(mode), &old_protect) == 0) {
auto err = static_cast<u32>(GetLastError());
LOG_ERROR_IF(true, "VirtualProtect() failed: 0x{:X}\n", err);
LOG_ERROR(Common_Memory, "VirtualProtect() failed: 0x{:X}", err);
return false;
}
if (old_mode != nullptr) {
@ -100,6 +101,10 @@ bool memory_protect(u64 address, u64 size, MemoryMode mode, MemoryMode* old_mode
return true;
#else
int ret = mprotect(reinterpret_cast<void*>(address), size, convertMemoryMode(mode));
if (ret != 0) {
const auto error = Common::GetLastErrorMsg();
ASSERT(false);
}
return true;
#endif
}
@ -110,7 +115,7 @@ bool memory_flush(u64 address, u64 size) {
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)),
size) == 0) {
auto err = static_cast<u32>(GetLastError());
LOG_ERROR_IF(true, "FlushInstructionCache() failed: 0x{:X}\n", err);
LOG_ERROR(Common_Memory, "FlushInstructionCache() failed: 0x{:X}", err);
return false;
}
return true;
@ -120,7 +125,7 @@ bool memory_flush(u64 address, u64 size) {
}
bool memory_patch(u64 vaddr, u64 value) {
MemoryMode old_mode{};
memory_protect(vaddr, 8, MemoryMode::ReadWrite, &old_mode);
// memory_protect(vaddr, 8, MemoryMode::ReadWrite, &old_mode);
auto* ptr = reinterpret_cast<uint64_t*>(vaddr);
@ -128,7 +133,7 @@ bool memory_patch(u64 vaddr, u64 value) {
*ptr = value;
memory_protect(vaddr, 8, old_mode, nullptr);
// memory_protect(vaddr, 8, old_mode, nullptr);
// if mode is executable flush it so insure that cpu finds it
if (containsExecuteMode(old_mode)) {
@ -161,15 +166,13 @@ u64 memory_alloc_aligned(u64 address, u64 size, MemoryMode mode, u64 alignment)
if (ptr == 0) {
auto err = static_cast<u32>(GetLastError());
LOG_ERROR_IF(true, "VirtualAlloc2() failed: 0x{:X}\n", err);
LOG_ERROR(Common_Memory, "VirtualAlloc2() failed: 0x{:X}", err);
}
return ptr;
#else
void* hint_address = reinterpret_cast<void*>(AlignUp(address, alignment));
void* ptr = mmap(hint_address, size, convertMemoryMode(mode), MAP_ANON | MAP_PRIVATE, -1, 0);
if (ptr == MAP_FAILED) {
std::abort();
}
ASSERT(ptr != MAP_FAILED);
return reinterpret_cast<u64>(ptr);
#endif
}

View File

@ -197,7 +197,6 @@ void emuRun() {
}
}
}
std::exit(0);
}
HLE::Libs::Graphics::GraphicCtx* getGraphicCtx() {

View File

@ -12,13 +12,15 @@
#include <core/hle/libraries/libkernel/thread_management.h>
#include "Util/config.h"
#include "common/discord.h"
#include "common/log.h"
#include "common/logging/backend.h"
#include "common/path_util.h"
#include "common/singleton.h"
#include "common/types.h"
#include "core/PS4/HLE/Graphics/video_out.h"
#include "core/file_sys/fs.h"
#include "core/hle/libraries/libs.h"
#include "core/linker.h"
#include "core/tls.h"
#include "emulator.h"
int main(int argc, char* argv[]) {
@ -26,8 +28,10 @@ int main(int argc, char* argv[]) {
fmt::print("Usage: {} <elf or eboot.bin path>\n", argv[0]);
return -1;
}
Config::load("config.toml");
Common::Log::Init(true);
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::load(config_dir / "config.toml");
Common::Log::Initialize();
Common::Log::Start();
Core::Libraries::LibKernel::init_pthreads();
auto width = Config::getScreenWidth();
auto height = Config::getScreenHeight();
@ -39,10 +43,11 @@ int main(int argc, char* argv[]) {
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
std::filesystem::path p = std::string(path);
mnt->mount(p.parent_path().string(), "/app0");
mnt->Mount(p.parent_path(), "/app0");
auto linker = Common::Singleton<Core::Linker>::Instance();
Core::Libraries::InitHLELibs(&linker->getHLESymbols());
Core::InstallTlsHandler();
linker->LoadModule(path);
std::jthread mainthread([linker](std::stop_token stop_token, void*) { linker->Execute(); },
nullptr);

View File

@ -6,15 +6,14 @@
#include <fmt/core.h>
#include <vulkan/vk_enum_string_helper.h>
#include <vulkan/vulkan_core.h>
#include "common/assert.h"
#include "common/debug.h"
#include "common/log.h"
#include "common/logging/log.h"
#include "common/singleton.h"
#include "vulkan_util.h"
#include <algorithm>
constexpr bool log_file_vulkanutil = true; // disable it to disable logging
void Graphics::Vulkan::vulkanCreate(Emu::WindowCtx* ctx) {
Emu::VulkanExt ext;
vulkanGetInstanceExtensions(&ext);
@ -38,19 +37,12 @@ void Graphics::Vulkan::vulkanCreate(Emu::WindowCtx* ctx) {
inst_info.enabledLayerCount = 0;
inst_info.ppEnabledLayerNames = nullptr;
VkResult result = vkCreateInstance(&inst_info, nullptr, &ctx->m_graphic_ctx.m_instance);
if (result == VK_ERROR_INCOMPATIBLE_DRIVER) {
LOG_CRITICAL_IF(log_file_vulkanutil, "Can't find an compatiblie vulkan driver\n");
std::exit(0);
} else if (result != VK_SUCCESS) {
LOG_CRITICAL_IF(log_file_vulkanutil, "Can't create an vulkan instance\n");
std::exit(0);
}
const VkResult result = vkCreateInstance(&inst_info, nullptr, &ctx->m_graphic_ctx.m_instance);
ASSERT_MSG(result == VK_SUCCESS, "Can't create an vulkan instance");
if (SDL_Vulkan_CreateSurface(ctx->m_window, ctx->m_graphic_ctx.m_instance, &ctx->m_surface) ==
SDL_FALSE) {
LOG_CRITICAL_IF(log_file_vulkanutil, "Can't create an vulkan surface\n");
std::exit(0);
UNREACHABLE_MSG("Can't create an vulkan surface");
}
// TODO i am not sure if it's that it is neccesary or if it needs more
@ -64,22 +56,16 @@ void Graphics::Vulkan::vulkanCreate(Emu::WindowCtx* ctx) {
device_extensions, &ctx->m_surface_capabilities,
&ctx->m_graphic_ctx.m_physical_device, &queues);
if (ctx->m_graphic_ctx.m_physical_device == nullptr) {
LOG_CRITICAL_IF(log_file_vulkanutil, "Can't find compatible vulkan device\n");
std::exit(0);
}
ASSERT_MSG(ctx->m_graphic_ctx.m_physical_device, "Can't find compatible vulkan device");
VkPhysicalDeviceProperties device_properties{};
vkGetPhysicalDeviceProperties(ctx->m_graphic_ctx.m_physical_device, &device_properties);
LOG_INFO_IF(log_file_vulkanutil, "GFX device to be used : {}\n", device_properties.deviceName);
LOG_INFO(Render_Vulkan, "GFX device to be used : {}", device_properties.deviceName);
ctx->m_graphic_ctx.m_device = vulkanCreateDevice(
ctx->m_graphic_ctx.m_physical_device, ctx->m_surface, &ext, queues, device_extensions);
if (ctx->m_graphic_ctx.m_device == nullptr) {
LOG_CRITICAL_IF(log_file_vulkanutil, "Can't create vulkan device\n");
std::exit(0);
}
ASSERT_MSG(ctx->m_graphic_ctx.m_device, "Can't create vulkan device");
vulkanCreateQueues(&ctx->m_graphic_ctx, queues);
ctx->swapchain = vulkanCreateSwapchain(&ctx->m_graphic_ctx, 2);
@ -160,11 +146,8 @@ Emu::VulkanSwapchain Graphics::Vulkan::vulkanCreateSwapchain(HLE::Libs::Graphics
vkCreateImageView(ctx->m_device, &create_info, nullptr, &s.swapchain_image_views[i]);
}
if (s.swapchain == nullptr) {
LOG_CRITICAL_IF(log_file_vulkanutil, "Could not create swapchain\n");
std::exit(0);
}
ASSERT_MSG(s.swapchain, "Could not create swapchain");
s.current_index = static_cast<uint32_t>(-1);
VkSemaphoreCreateInfo present_complete_info{};
@ -181,10 +164,7 @@ Emu::VulkanSwapchain Graphics::Vulkan::vulkanCreateSwapchain(HLE::Libs::Graphics
fence_info.flags = 0;
result = vkCreateFence(ctx->m_device, &fence_info, nullptr, &s.present_complete_fence);
if (result != VK_SUCCESS) {
LOG_CRITICAL_IF(log_file_vulkanutil, "Can't create vulkan fence\n");
std::exit(0);
}
ASSERT_MSG(result == VK_SUCCESS, "Can't create vulkan fence");
return s;
}
@ -282,18 +262,18 @@ void Graphics::Vulkan::vulkanGetInstanceExtensions(Emu::VulkanExt* ext) {
vkEnumerateInstanceLayerProperties(&available_layers_count, ext->available_layers.data());
for (const char* ext : ext->required_extensions) {
LOG_INFO_IF(log_file_vulkanutil, "Vulkan required extension = {}\n", ext);
LOG_INFO(Render_Vulkan, "Vulkan required extension = {}", ext);
}
for (const auto& ext : ext->available_extensions) {
LOG_INFO_IF(log_file_vulkanutil, "Vulkan available extension: {}, version = {}\n",
ext.extensionName, ext.specVersion);
LOG_INFO(Render_Vulkan, "Vulkan available extension: {}, version = {}", ext.extensionName,
ext.specVersion);
}
for (const auto& l : ext->available_layers) {
LOG_INFO_IF(log_file_vulkanutil,
"Vulkan available layer: {}, specVersion = {}, implVersion = {}, {}\n",
l.layerName, l.specVersion, l.implementationVersion, l.description);
LOG_INFO(Render_Vulkan,
"Vulkan available layer: {}, specVersion = {}, implVersion = {}, {}", l.layerName,
l.specVersion, l.implementationVersion, l.description);
}
}
@ -320,7 +300,7 @@ void Graphics::Vulkan::vulkanFindCompatiblePhysicalDevice(
continue; // we don't want integrated gpu for now .Later we will check the requirements
// and see what we can support (TODO fix me)
}
LOG_INFO_IF(log_file_vulkanutil, "Vulkan device: {}\n", device_properties.deviceName);
LOG_INFO(Render_Vulkan, "Vulkan device: {}", device_properties.deviceName);
auto qs = vulkanFindQueues(device, surface);
@ -349,9 +329,9 @@ Emu::VulkanQueues Graphics::Vulkan::vulkanFindQueues(VkPhysicalDevice device,
VkBool32 presentation_supported = VK_FALSE;
vkGetPhysicalDeviceSurfaceSupportKHR(device, family, surface, &presentation_supported);
LOG_INFO_IF(log_file_vulkanutil, "queue family: {}, count = {}, present = {}\n",
string_VkQueueFlags(f.queueFlags).c_str(), f.queueCount,
(presentation_supported == VK_TRUE ? "true" : "false"));
LOG_INFO(Render_Vulkan, "queue family: {}, count = {}, present = {}",
string_VkQueueFlags(f.queueFlags).c_str(), f.queueCount,
(presentation_supported == VK_TRUE ? "true" : "false"));
for (uint32_t i = 0; i < f.queueCount; i++) {
Emu::VulkanQueueInfo info;
info.family = family;

View File

@ -12,14 +12,6 @@ add_subdirectory(fmt EXCLUDE_FROM_ALL)
# MagicEnum
add_subdirectory(magic_enum EXCLUDE_FROM_ALL)
# Spdlog
set(SPDLOG_WCHAR_FILENAMES ON CACHE BOOL "")
set(SPDLOG_NO_THREAD_ID ON CACHE BOOL "")
set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "")
add_subdirectory(spdlog EXCLUDE_FROM_ALL)
add_library(stb INTERFACE)
target_include_directories(stb INTERFACE ./stb)
# SDL3
add_subdirectory(SDL EXCLUDE_FROM_ALL)

1
third-party/spdlog vendored

@ -1 +0,0 @@
Subproject commit 134f9194bb93072b72b8cfa27ac3bb30a0fb5b57