summaryrefslogtreecommitdiff
path: root/src/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/common')
-rw-r--r--src/common/CMakeLists.txt18
-rw-r--r--src/common/assert.h18
-rw-r--r--src/common/bit_field.h28
-rw-r--r--src/common/bit_util.h47
-rw-r--r--src/common/common_types.h7
-rw-r--r--src/common/detached_tasks.cpp8
-rw-r--r--src/common/file_util.cpp49
-rw-r--r--src/common/file_util.h15
-rw-r--r--src/common/hex_util.cpp7
-rw-r--r--src/common/hex_util.h16
-rw-r--r--src/common/logging/backend.cpp6
-rw-r--r--src/common/lz4_compression.cpp76
-rw-r--r--src/common/lz4_compression.h57
-rw-r--r--src/common/math_util.h3
-rw-r--r--src/common/memory_hook.cpp11
-rw-r--r--src/common/memory_hook.h47
-rw-r--r--src/common/multi_level_queue.h337
-rw-r--r--src/common/page_table.cpp31
-rw-r--r--src/common/page_table.h84
-rw-r--r--src/common/scope_exit.h2
-rw-r--r--src/common/swap.h270
-rw-r--r--src/common/thread.cpp37
-rw-r--r--src/common/thread.h14
-rw-r--r--src/common/thread_queue_list.h6
-rw-r--r--src/common/threadsafe_queue.h4
-rw-r--r--src/common/uint128.cpp45
-rw-r--r--src/common/uint128.h19
-rw-r--r--src/common/uuid.cpp33
-rw-r--r--src/common/uuid.h48
-rw-r--r--src/common/zstd_compression.cpp51
-rw-r--r--src/common/zstd_compression.h44
31 files changed, 1201 insertions, 237 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index bdd885273..198b3fe07 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -47,6 +47,7 @@ add_custom_command(OUTPUT scm_rev.cpp
"${VIDEO_CORE}/shader/decode/integer_set.cpp"
"${VIDEO_CORE}/shader/decode/integer_set_predicate.cpp"
"${VIDEO_CORE}/shader/decode/memory.cpp"
+ "${VIDEO_CORE}/shader/decode/texture.cpp"
"${VIDEO_CORE}/shader/decode/other.cpp"
"${VIDEO_CORE}/shader/decode/predicate_set_predicate.cpp"
"${VIDEO_CORE}/shader/decode/predicate_set_register.cpp"
@@ -55,6 +56,9 @@ add_custom_command(OUTPUT scm_rev.cpp
"${VIDEO_CORE}/shader/decode/video.cpp"
"${VIDEO_CORE}/shader/decode/xmad.cpp"
"${VIDEO_CORE}/shader/decode.cpp"
+ "${VIDEO_CORE}/shader/node.h"
+ "${VIDEO_CORE}/shader/node_helper.cpp"
+ "${VIDEO_CORE}/shader/node_helper.h"
"${VIDEO_CORE}/shader/shader_ir.cpp"
"${VIDEO_CORE}/shader/shader_ir.h"
"${VIDEO_CORE}/shader/track.cpp"
@@ -90,11 +94,18 @@ add_library(common STATIC
logging/log.h
logging/text_formatter.cpp
logging/text_formatter.h
+ lz4_compression.cpp
+ lz4_compression.h
math_util.h
+ memory_hook.cpp
+ memory_hook.h
microprofile.cpp
microprofile.h
microprofileui.h
misc.cpp
+ multi_level_queue.h
+ page_table.cpp
+ page_table.h
param_package.cpp
param_package.h
quaternion.h
@@ -113,8 +124,14 @@ add_library(common STATIC
threadsafe_queue.h
timer.cpp
timer.h
+ uint128.cpp
+ uint128.h
+ uuid.cpp
+ uuid.h
vector_math.h
web_result.h
+ zstd_compression.cpp
+ zstd_compression.h
)
if(ARCHITECTURE_x86_64)
@@ -128,3 +145,4 @@ endif()
create_target_directory_groups(common)
target_link_libraries(common PUBLIC Boost::boost fmt microprofile)
+target_link_libraries(common PRIVATE lz4_static libzstd_static)
diff --git a/src/common/assert.h b/src/common/assert.h
index 6002f7ab1..4b0e3f64e 100644
--- a/src/common/assert.h
+++ b/src/common/assert.h
@@ -57,3 +57,21 @@ __declspec(noinline, noreturn)
#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_)) { \
+ _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_)) { \
+ _b_ \
+ } \
+ } while (0)
diff --git a/src/common/bit_field.h b/src/common/bit_field.h
index 21e07925d..902e668e3 100644
--- a/src/common/bit_field.h
+++ b/src/common/bit_field.h
@@ -34,6 +34,7 @@
#include <limits>
#include <type_traits>
#include "common/common_funcs.h"
+#include "common/swap.h"
/*
* Abstract bitfield class
@@ -108,15 +109,9 @@
* symptoms.
*/
#pragma pack(1)
-template <std::size_t Position, std::size_t Bits, typename T>
+template <std::size_t Position, std::size_t Bits, typename T, typename EndianTag = LETag>
struct BitField {
private:
- // We hide the copy assigment operator here, because the default copy
- // assignment would copy the full storage value, rather than just the bits
- // relevant to this particular bit field.
- // We don't delete it because we want BitField to be trivially copyable.
- constexpr BitField& operator=(const BitField&) = default;
-
// UnderlyingType is T for non-enum types and the underlying type of T if
// T is an enumeration. Note that T is wrapped within an enable_if in the
// former case to workaround compile errors which arise when using
@@ -127,6 +122,8 @@ private:
// We store the value as the unsigned type to avoid undefined behaviour on value shifting
using StorageType = std::make_unsigned_t<UnderlyingType>;
+ using StorageTypeWithEndian = typename AddEndian<StorageType, EndianTag>::type;
+
public:
/// Constants to allow limited introspection of fields if needed
static constexpr std::size_t position = Position;
@@ -163,16 +160,20 @@ public:
BitField(T val) = delete;
BitField& operator=(T val) = delete;
- // Force default constructor to be created
- // so that we can use this within unions
- constexpr BitField() = default;
+ constexpr BitField() noexcept = default;
+
+ constexpr BitField(const BitField&) noexcept = default;
+ constexpr BitField& operator=(const BitField&) noexcept = default;
+
+ constexpr BitField(BitField&&) noexcept = default;
+ constexpr BitField& operator=(BitField&&) noexcept = default;
constexpr FORCE_INLINE operator T() const {
return Value();
}
constexpr FORCE_INLINE void Assign(const T& value) {
- storage = (storage & ~mask) | FormatValue(value);
+ storage = (static_cast<StorageType>(storage) & ~mask) | FormatValue(value);
}
constexpr T Value() const {
@@ -184,7 +185,7 @@ public:
}
private:
- StorageType storage;
+ StorageTypeWithEndian storage;
static_assert(bits + position <= 8 * sizeof(T), "Bitfield out of range");
@@ -195,3 +196,6 @@ private:
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable in a BitField");
};
#pragma pack()
+
+template <std::size_t Position, std::size_t Bits, typename T>
+using BitFieldBE = BitField<Position, Bits, T, BETag>;
diff --git a/src/common/bit_util.h b/src/common/bit_util.h
index 1eea17ba1..d032df413 100644
--- a/src/common/bit_util.h
+++ b/src/common/bit_util.h
@@ -32,7 +32,7 @@ inline u32 CountLeadingZeroes32(u32 value) {
return 32;
}
-inline u64 CountLeadingZeroes64(u64 value) {
+inline u32 CountLeadingZeroes64(u64 value) {
unsigned long leading_zero = 0;
if (_BitScanReverse64(&leading_zero, value) != 0) {
@@ -47,15 +47,54 @@ inline u32 CountLeadingZeroes32(u32 value) {
return 32;
}
- return __builtin_clz(value);
+ return static_cast<u32>(__builtin_clz(value));
}
-inline u64 CountLeadingZeroes64(u64 value) {
+inline u32 CountLeadingZeroes64(u64 value) {
if (value == 0) {
return 64;
}
- return __builtin_clzll(value);
+ return static_cast<u32>(__builtin_clzll(value));
}
#endif
+
+#ifdef _MSC_VER
+inline u32 CountTrailingZeroes32(u32 value) {
+ unsigned long trailing_zero = 0;
+
+ if (_BitScanForward(&trailing_zero, value) != 0) {
+ return trailing_zero;
+ }
+
+ return 32;
+}
+
+inline u32 CountTrailingZeroes64(u64 value) {
+ unsigned long trailing_zero = 0;
+
+ if (_BitScanForward64(&trailing_zero, value) != 0) {
+ return trailing_zero;
+ }
+
+ return 64;
+}
+#else
+inline u32 CountTrailingZeroes32(u32 value) {
+ if (value == 0) {
+ return 32;
+ }
+
+ return static_cast<u32>(__builtin_ctz(value));
+}
+
+inline u32 CountTrailingZeroes64(u64 value) {
+ if (value == 0) {
+ return 64;
+ }
+
+ return static_cast<u32>(__builtin_ctzll(value));
+}
+#endif
+
} // namespace Common
diff --git a/src/common/common_types.h b/src/common/common_types.h
index 6b1766dca..4cec89fbd 100644
--- a/src/common/common_types.h
+++ b/src/common/common_types.h
@@ -40,10 +40,9 @@ using s64 = std::int64_t; ///< 64-bit signed int
using f32 = float; ///< 32-bit floating point
using f64 = double; ///< 64-bit floating point
-// TODO: It would be nice to eventually replace these with strong types that prevent accidental
-// conversion between each other.
-using VAddr = u64; ///< Represents a pointer in the userspace virtual address space.
-using PAddr = u64; ///< Represents a pointer in the ARM11 physical address space.
+using VAddr = u64; ///< Represents a pointer in the userspace virtual address space.
+using PAddr = u64; ///< Represents a pointer in the ARM11 physical address space.
+using GPUVAddr = u64; ///< Represents a pointer in the GPU virtual address space.
using u128 = std::array<std::uint64_t, 2>;
static_assert(sizeof(u128) == 16, "u128 must be 128 bits wide");
diff --git a/src/common/detached_tasks.cpp b/src/common/detached_tasks.cpp
index a347d9e02..f268d6021 100644
--- a/src/common/detached_tasks.cpp
+++ b/src/common/detached_tasks.cpp
@@ -16,22 +16,22 @@ DetachedTasks::DetachedTasks() {
}
void DetachedTasks::WaitForAllTasks() {
- std::unique_lock<std::mutex> lock(mutex);
+ std::unique_lock lock{mutex};
cv.wait(lock, [this]() { return count == 0; });
}
DetachedTasks::~DetachedTasks() {
- std::unique_lock<std::mutex> lock(mutex);
+ std::unique_lock lock{mutex};
ASSERT(count == 0);
instance = nullptr;
}
void DetachedTasks::AddTask(std::function<void()> task) {
- std::unique_lock<std::mutex> lock(instance->mutex);
+ std::unique_lock lock{instance->mutex};
++instance->count;
std::thread([task{std::move(task)}]() {
task();
- std::unique_lock<std::mutex> lock(instance->mutex);
+ std::unique_lock lock{instance->mutex};
--instance->count;
std::notify_all_at_thread_exit(instance->cv, std::move(lock));
})
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index aecb66c32..2d9374783 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -78,16 +78,17 @@ namespace FileUtil {
// Remove any ending forward slashes from directory paths
// Modifies argument.
static void StripTailDirSlashes(std::string& fname) {
- if (fname.length() > 1) {
- std::size_t i = fname.length();
- while (i > 0 && fname[i - 1] == DIR_SEP_CHR)
- --i;
- fname.resize(i);
+ if (fname.length() <= 1) {
+ return;
+ }
+
+ std::size_t i = fname.length();
+ while (i > 0 && fname[i - 1] == DIR_SEP_CHR) {
+ --i;
}
- return;
+ fname.resize(i);
}
-// Returns true if file filename exists
bool Exists(const std::string& filename) {
struct stat file_info;
@@ -107,7 +108,6 @@ bool Exists(const std::string& filename) {
return (result == 0);
}
-// Returns true if filename is a directory
bool IsDirectory(const std::string& filename) {
struct stat file_info;
@@ -132,8 +132,6 @@ bool IsDirectory(const std::string& filename) {
return S_ISDIR(file_info.st_mode);
}
-// Deletes a given filename, return true on success
-// Doesn't supports deleting a directory
bool Delete(const std::string& filename) {
LOG_TRACE(Common_Filesystem, "file {}", filename);
@@ -165,7 +163,6 @@ bool Delete(const std::string& filename) {
return true;
}
-// Returns true if successful, or path already exists.
bool CreateDir(const std::string& path) {
LOG_TRACE(Common_Filesystem, "directory {}", path);
#ifdef _WIN32
@@ -194,7 +191,6 @@ bool CreateDir(const std::string& path) {
#endif
}
-// Creates the full path of fullPath returns true on success
bool CreateFullPath(const std::string& fullPath) {
int panicCounter = 100;
LOG_TRACE(Common_Filesystem, "path {}", fullPath);
@@ -230,7 +226,6 @@ bool CreateFullPath(const std::string& fullPath) {
}
}
-// Deletes a directory filename, returns true on success
bool DeleteDir(const std::string& filename) {
LOG_TRACE(Common_Filesystem, "directory {}", filename);
@@ -252,7 +247,6 @@ bool DeleteDir(const std::string& filename) {
return false;
}
-// renames file srcFilename to destFilename, returns true on success
bool Rename(const std::string& srcFilename, const std::string& destFilename) {
LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
#ifdef _WIN32
@@ -268,7 +262,6 @@ bool Rename(const std::string& srcFilename, const std::string& destFilename) {
return false;
}
-// copies file srcFilename to destFilename, returns true on success
bool Copy(const std::string& srcFilename, const std::string& destFilename) {
LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename);
#ifdef _WIN32
@@ -324,7 +317,6 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) {
#endif
}
-// Returns the size of filename (64bit)
u64 GetSize(const std::string& filename) {
if (!Exists(filename)) {
LOG_ERROR(Common_Filesystem, "failed {}: No such file", filename);
@@ -351,7 +343,6 @@ u64 GetSize(const std::string& filename) {
return 0;
}
-// Overloaded GetSize, accepts file descriptor
u64 GetSize(const int fd) {
struct stat buf;
if (fstat(fd, &buf) != 0) {
@@ -361,7 +352,6 @@ u64 GetSize(const int fd) {
return buf.st_size;
}
-// Overloaded GetSize, accepts FILE*
u64 GetSize(FILE* f) {
// can't use off_t here because it can be 32-bit
u64 pos = ftello(f);
@@ -377,7 +367,6 @@ u64 GetSize(FILE* f) {
return size;
}
-// creates an empty file filename, returns true on success
bool CreateEmptyFile(const std::string& filename) {
LOG_TRACE(Common_Filesystem, "{}", filename);
@@ -502,7 +491,6 @@ bool DeleteDirRecursively(const std::string& directory, unsigned int recursion)
return true;
}
-// Create directory and copy contents (does not overwrite existing files)
void CopyDir(const std::string& source_path, const std::string& dest_path) {
#ifndef _WIN32
if (source_path == dest_path)
@@ -539,8 +527,7 @@ void CopyDir(const std::string& source_path, const std::string& dest_path) {
#endif
}
-// Returns the current directory
-std::string GetCurrentDir() {
+std::optional<std::string> GetCurrentDir() {
// Get the current working directory (getcwd uses malloc)
#ifdef _WIN32
wchar_t* dir;
@@ -550,7 +537,7 @@ std::string GetCurrentDir() {
if (!(dir = getcwd(nullptr, 0))) {
#endif
LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg());
- return nullptr;
+ return {};
}
#ifdef _WIN32
std::string strDir = Common::UTF16ToUTF8(dir);
@@ -561,7 +548,6 @@ std::string GetCurrentDir() {
return strDir;
}
-// Sets the current directory to the given directory
bool SetCurrentDir(const std::string& directory) {
#ifdef _WIN32
return _wchdir(Common::UTF8ToUTF16W(directory).c_str()) == 0;
@@ -673,8 +659,6 @@ std::string GetSysDirectory() {
return sysDir;
}
-// Returns a string with a yuzu data dir or file in the user's home
-// directory. To be used in "multi-user" mode (that is, installed).
const std::string& GetUserPath(UserPath path, const std::string& new_path) {
static std::unordered_map<UserPath, std::string> paths;
auto& user_path = paths[UserPath::UserDir];
@@ -762,11 +746,11 @@ std::string GetNANDRegistrationDir(bool system) {
return GetUserPath(UserPath::NANDDir) + "user/Contents/registered/";
}
-std::size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) {
- return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size());
+std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str) {
+ return IOFile(filename, text_file ? "w" : "wb").WriteString(str);
}
-std::size_t ReadFileToString(bool text_file, const char* filename, std::string& str) {
+std::size_t ReadFileToString(bool text_file, const std::string& filename, std::string& str) {
IOFile file(filename, text_file ? "r" : "rb");
if (!file.IsOpen())
@@ -776,13 +760,6 @@ std::size_t ReadFileToString(bool text_file, const char* filename, std::string&
return file.ReadArray(&str[0], str.size());
}
-/**
- * Splits the filename into 8.3 format
- * Loosely implemented following https://en.wikipedia.org/wiki/8.3_filename
- * @param filename The normal filename to use
- * @param short_name A 9-char array in which the short name will be written
- * @param extension A 4-char array in which the extension will be written
- */
void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name,
std::array<char, 4>& extension) {
const std::string forbidden_characters = ".\"/\\[]:;=, ";
diff --git a/src/common/file_util.h b/src/common/file_util.h
index 38cc7f059..cde7ddf2d 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -9,6 +9,7 @@
#include <fstream>
#include <functional>
#include <limits>
+#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
@@ -118,7 +119,7 @@ u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256);
// Returns the current directory
-std::string GetCurrentDir();
+std::optional<std::string> GetCurrentDir();
// Create directory and copy contents (does not overwrite existing files)
void CopyDir(const std::string& source_path, const std::string& dest_path);
@@ -146,9 +147,9 @@ const std::string& GetExeDirectory();
std::string AppDataRoamingDirectory();
#endif
-std::size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename);
+std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str);
-std::size_t ReadFileToString(bool text_file, const char* filename, std::string& str);
+std::size_t ReadFileToString(bool text_file, const std::string& filename, std::string& str);
/**
* Splits the filename into 8.3 format
@@ -257,8 +258,8 @@ public:
return WriteArray(&object, 1);
}
- std::size_t WriteString(const std::string& str) {
- return WriteArray(str.c_str(), str.length());
+ std::size_t WriteString(std::string_view str) {
+ return WriteArray(str.data(), str.length());
}
bool IsOpen() const {
@@ -286,8 +287,8 @@ private:
template <typename T>
void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) {
#ifdef _MSC_VER
- fstream.open(Common::UTF8ToUTF16W(filename).c_str(), openmode);
+ fstream.open(Common::UTF8ToUTF16W(filename), openmode);
#else
- fstream.open(filename.c_str(), openmode);
+ fstream.open(filename, openmode);
#endif
}
diff --git a/src/common/hex_util.cpp b/src/common/hex_util.cpp
index 5b63f9e81..c2f6cf0f6 100644
--- a/src/common/hex_util.cpp
+++ b/src/common/hex_util.cpp
@@ -30,13 +30,6 @@ std::vector<u8> HexStringToVector(std::string_view str, bool little_endian) {
return out;
}
-std::string HexVectorToString(const std::vector<u8>& vector, bool upper) {
- std::string out;
- for (u8 c : vector)
- out += fmt::format(upper ? "{:02X}" : "{:02x}", c);
- return out;
-}
-
std::array<u8, 16> operator""_array16(const char* str, std::size_t len) {
if (len != 32) {
LOG_ERROR(Common,
diff --git a/src/common/hex_util.h b/src/common/hex_util.h
index 68f003cb6..bb4736f96 100644
--- a/src/common/hex_util.h
+++ b/src/common/hex_util.h
@@ -7,6 +7,7 @@
#include <array>
#include <cstddef>
#include <string>
+#include <type_traits>
#include <vector>
#include <fmt/format.h>
#include "common/common_types.h"
@@ -30,13 +31,20 @@ std::array<u8, Size> HexStringToArray(std::string_view str) {
return out;
}
-std::string HexVectorToString(const std::vector<u8>& vector, bool upper = true);
+template <typename ContiguousContainer>
+std::string HexToString(const ContiguousContainer& data, bool upper = true) {
+ static_assert(std::is_same_v<typename ContiguousContainer::value_type, u8>,
+ "Underlying type within the contiguous container must be u8.");
+
+ constexpr std::size_t pad_width = 2;
-template <std::size_t Size>
-std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) {
std::string out;
- for (u8 c : array)
+ out.reserve(std::size(data) * pad_width);
+
+ for (const u8 c : data) {
out += fmt::format(upper ? "{:02X}" : "{:02x}", c);
+ }
+
return out;
}
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 4462ff3fb..a03179520 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -46,12 +46,12 @@ public:
}
void AddBackend(std::unique_ptr<Backend> backend) {
- std::lock_guard<std::mutex> lock(writing_mutex);
+ std::lock_guard lock{writing_mutex};
backends.push_back(std::move(backend));
}
void RemoveBackend(std::string_view backend_name) {
- std::lock_guard<std::mutex> lock(writing_mutex);
+ std::lock_guard lock{writing_mutex};
const auto it =
std::remove_if(backends.begin(), backends.end(),
[&backend_name](const auto& i) { return backend_name == i->GetName(); });
@@ -80,7 +80,7 @@ private:
backend_thread = std::thread([&] {
Entry entry;
auto write_logs = [&](Entry& e) {
- std::lock_guard<std::mutex> lock(writing_mutex);
+ std::lock_guard lock{writing_mutex};
for (const auto& backend : backends) {
backend->Write(e);
}
diff --git a/src/common/lz4_compression.cpp b/src/common/lz4_compression.cpp
new file mode 100644
index 000000000..ade6759bb
--- /dev/null
+++ b/src/common/lz4_compression.cpp
@@ -0,0 +1,76 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <lz4hc.h>
+
+#include "common/assert.h"
+#include "common/lz4_compression.h"
+
+namespace Common::Compression {
+
+std::vector<u8> CompressDataLZ4(const u8* source, std::size_t source_size) {
+ ASSERT_MSG(source_size <= LZ4_MAX_INPUT_SIZE, "Source size exceeds LZ4 maximum input size");
+
+ const auto source_size_int = static_cast<int>(source_size);
+ const int max_compressed_size = LZ4_compressBound(source_size_int);
+ std::vector<u8> compressed(max_compressed_size);
+
+ const int compressed_size = LZ4_compress_default(reinterpret_cast<const char*>(source),
+ reinterpret_cast<char*>(compressed.data()),
+ source_size_int, max_compressed_size);
+
+ if (compressed_size <= 0) {
+ // Compression failed
+ return {};
+ }
+
+ compressed.resize(compressed_size);
+
+ return compressed;
+}
+
+std::vector<u8> CompressDataLZ4HC(const u8* source, std::size_t source_size,
+ s32 compression_level) {
+ ASSERT_MSG(source_size <= LZ4_MAX_INPUT_SIZE, "Source size exceeds LZ4 maximum input size");
+
+ compression_level = std::clamp(compression_level, LZ4HC_CLEVEL_MIN, LZ4HC_CLEVEL_MAX);
+
+ const auto source_size_int = static_cast<int>(source_size);
+ const int max_compressed_size = LZ4_compressBound(source_size_int);
+ std::vector<u8> compressed(max_compressed_size);
+
+ const int compressed_size = LZ4_compress_HC(
+ reinterpret_cast<const char*>(source), reinterpret_cast<char*>(compressed.data()),
+ source_size_int, max_compressed_size, compression_level);
+
+ if (compressed_size <= 0) {
+ // Compression failed
+ return {};
+ }
+
+ compressed.resize(compressed_size);
+
+ return compressed;
+}
+
+std::vector<u8> CompressDataLZ4HCMax(const u8* source, std::size_t source_size) {
+ return CompressDataLZ4HC(source, source_size, LZ4HC_CLEVEL_MAX);
+}
+
+std::vector<u8> DecompressDataLZ4(const std::vector<u8>& compressed,
+ std::size_t uncompressed_size) {
+ std::vector<u8> uncompressed(uncompressed_size);
+ const int size_check = LZ4_decompress_safe(reinterpret_cast<const char*>(compressed.data()),
+ reinterpret_cast<char*>(uncompressed.data()),
+ static_cast<int>(compressed.size()),
+ static_cast<int>(uncompressed.size()));
+ if (static_cast<int>(uncompressed_size) != size_check) {
+ // Decompression failed
+ return {};
+ }
+ return uncompressed;
+}
+
+} // namespace Common::Compression
diff --git a/src/common/lz4_compression.h b/src/common/lz4_compression.h
new file mode 100644
index 000000000..4c16f6e03
--- /dev/null
+++ b/src/common/lz4_compression.h
@@ -0,0 +1,57 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace Common::Compression {
+
+/**
+ * Compresses a source memory region with LZ4 and returns the compressed data in a vector.
+ *
+ * @param source the uncompressed source memory region.
+ * @param source_size the size in bytes of the uncompressed source memory region.
+ *
+ * @return the compressed data.
+ */
+std::vector<u8> CompressDataLZ4(const u8* source, std::size_t source_size);
+
+/**
+ * Utilizes the LZ4 subalgorithm LZ4HC with the specified compression level. Higher compression
+ * levels result in a smaller compressed size, but require more CPU time for compression. The
+ * compression level has almost no impact on decompression speed. Data compressed with LZ4HC can
+ * also be decompressed with the default LZ4 decompression.
+ *
+ * @param source the uncompressed source memory region.
+ * @param source_size the size in bytes of the uncompressed source memory region.
+ * @param compression_level the used compression level. Should be between 3 and 12.
+ *
+ * @return the compressed data.
+ */
+std::vector<u8> CompressDataLZ4HC(const u8* source, std::size_t source_size, s32 compression_level);
+
+/**
+ * Utilizes the LZ4 subalgorithm LZ4HC with the highest possible compression level.
+ *
+ * @param source the uncompressed source memory region.
+ * @param source_size the size in bytes of the uncompressed source memory region.
+ *
+ * @return the compressed data.
+ */
+std::vector<u8> CompressDataLZ4HCMax(const u8* source, std::size_t source_size);
+
+/**
+ * Decompresses a source memory region with LZ4 and returns the uncompressed data in a vector.
+ *
+ * @param compressed the compressed source memory region.
+ * @param uncompressed_size the size in bytes of the uncompressed data.
+ *
+ * @return the decompressed data.
+ */
+std::vector<u8> DecompressDataLZ4(const std::vector<u8>& compressed, std::size_t uncompressed_size);
+
+} // namespace Common::Compression \ No newline at end of file
diff --git a/src/common/math_util.h b/src/common/math_util.h
index cff3d48c5..d6c35ee89 100644
--- a/src/common/math_util.h
+++ b/src/common/math_util.h
@@ -41,4 +41,7 @@ struct Rectangle {
}
};
+template <typename T>
+Rectangle(T, T, T, T)->Rectangle<T>;
+
} // namespace Common
diff --git a/src/common/memory_hook.cpp b/src/common/memory_hook.cpp
new file mode 100644
index 000000000..3986986d6
--- /dev/null
+++ b/src/common/memory_hook.cpp
@@ -0,0 +1,11 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/memory_hook.h"
+
+namespace Common {
+
+MemoryHook::~MemoryHook() = default;
+
+} // namespace Common
diff --git a/src/common/memory_hook.h b/src/common/memory_hook.h
new file mode 100644
index 000000000..adaa4c2c5
--- /dev/null
+++ b/src/common/memory_hook.h
@@ -0,0 +1,47 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <optional>
+
+#include "common/common_types.h"
+
+namespace Common {
+
+/**
+ * Memory hooks have two purposes:
+ * 1. To allow reads and writes to a region of memory to be intercepted. This is used to implement
+ * texture forwarding and memory breakpoints for debugging.
+ * 2. To allow for the implementation of MMIO devices.
+ *
+ * A hook may be mapped to multiple regions of memory.
+ *
+ * If a std::nullopt or false is returned from a function, the read/write request is passed through
+ * to the underlying memory region.
+ */
+class MemoryHook {
+public:
+ virtual ~MemoryHook();
+
+ virtual std::optional<bool> IsValidAddress(VAddr addr) = 0;
+
+ virtual std::optional<u8> Read8(VAddr addr) = 0;
+ virtual std::optional<u16> Read16(VAddr addr) = 0;
+ virtual std::optional<u32> Read32(VAddr addr) = 0;
+ virtual std::optional<u64> Read64(VAddr addr) = 0;
+
+ virtual bool ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) = 0;
+
+ virtual bool Write8(VAddr addr, u8 data) = 0;
+ virtual bool Write16(VAddr addr, u16 data) = 0;
+ virtual bool Write32(VAddr addr, u32 data) = 0;
+ virtual bool Write64(VAddr addr, u64 data) = 0;
+
+ virtual bool WriteBlock(VAddr dest_addr, const void* src_buffer, std::size_t size) = 0;
+};
+
+using MemoryHookPointer = std::shared_ptr<MemoryHook>;
+} // namespace Common
diff --git a/src/common/multi_level_queue.h b/src/common/multi_level_queue.h
new file mode 100644
index 000000000..9cb448f56
--- /dev/null
+++ b/src/common/multi_level_queue.h
@@ -0,0 +1,337 @@
+// Copyright 2019 TuxSH
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <iterator>
+#include <list>
+#include <utility>
+
+#include "common/bit_util.h"
+#include "common/common_types.h"
+
+namespace Common {
+
+/**
+ * A MultiLevelQueue is a type of priority queue which has the following characteristics:
+ * - iteratable through each of its elements.
+ * - back can be obtained.
+ * - O(1) add, lookup (both front and back)
+ * - discrete priorities and a max of 64 priorities (limited domain)
+ * This type of priority queue is normaly used for managing threads within an scheduler
+ */
+template <typename T, std::size_t Depth>
+class MultiLevelQueue {
+public:
+ using value_type = T;
+ using reference = value_type&;
+ using const_reference = const value_type&;
+ using pointer = value_type*;
+ using const_pointer = const value_type*;
+
+ using difference_type = typename std::pointer_traits<pointer>::difference_type;
+ using size_type = std::size_t;
+
+ template <bool is_constant>
+ class iterator_impl {
+ public:
+ using iterator_category = std::bidirectional_iterator_tag;
+ using value_type = T;
+ using pointer = std::conditional_t<is_constant, T*, const T*>;
+ using reference = std::conditional_t<is_constant, const T&, T&>;
+ using difference_type = typename std::pointer_traits<pointer>::difference_type;
+
+ friend bool operator==(const iterator_impl& lhs, const iterator_impl& rhs) {
+ if (lhs.IsEnd() && rhs.IsEnd())
+ return true;
+ return std::tie(lhs.current_priority, lhs.it) == std::tie(rhs.current_priority, rhs.it);
+ }
+
+ friend bool operator!=(const iterator_impl& lhs, const iterator_impl& rhs) {
+ return !operator==(lhs, rhs);
+ }
+
+ reference operator*() const {
+ return *it;
+ }
+
+ pointer operator->() const {
+ return it.operator->();
+ }
+
+ iterator_impl& operator++() {
+ if (IsEnd()) {
+ return *this;
+ }
+
+ ++it;
+
+ if (it == GetEndItForPrio()) {
+ u64 prios = mlq.used_priorities;
+ prios &= ~((1ULL << (current_priority + 1)) - 1);
+ if (prios == 0) {
+ current_priority = static_cast<u32>(mlq.depth());
+ } else {
+ current_priority = CountTrailingZeroes64(prios);
+ it = GetBeginItForPrio();
+ }
+ }
+ return *this;
+ }
+
+ iterator_impl& operator--() {
+ if (IsEnd()) {
+ if (mlq.used_priorities != 0) {
+ current_priority = 63 - CountLeadingZeroes64(mlq.used_priorities);
+ it = GetEndItForPrio();
+ --it;
+ }
+ } else if (it == GetBeginItForPrio()) {
+ u64 prios = mlq.used_priorities;
+ prios &= (1ULL << current_priority) - 1;
+ if (prios != 0) {
+ current_priority = CountTrailingZeroes64(prios);
+ it = GetEndItForPrio();
+ --it;
+ }
+ } else {
+ --it;
+ }
+ return *this;
+ }
+
+ iterator_impl operator++(int) {
+ const iterator_impl v{*this};
+ ++(*this);
+ return v;
+ }
+
+ iterator_impl operator--(int) {
+ const iterator_impl v{*this};
+ --(*this);
+ return v;
+ }
+
+ // allow implicit const->non-const
+ iterator_impl(const iterator_impl<false>& other)
+ : mlq(other.mlq), it(other.it), current_priority(other.current_priority) {}
+
+ iterator_impl(const iterator_impl<true>& other)
+ : mlq(other.mlq), it(other.it), current_priority(other.current_priority) {}
+
+ iterator_impl& operator=(const iterator_impl<false>& other) {
+ mlq = other.mlq;
+ it = other.it;
+ current_priority = other.current_priority;
+ return *this;
+ }
+
+ friend class iterator_impl<true>;
+ iterator_impl() = default;
+
+ private:
+ friend class MultiLevelQueue;
+ using container_ref =
+ std::conditional_t<is_constant, const MultiLevelQueue&, MultiLevelQueue&>;
+ using list_iterator = std::conditional_t<is_constant, typename std::list<T>::const_iterator,
+ typename std::list<T>::iterator>;
+
+ explicit iterator_impl(container_ref mlq, list_iterator it, u32 current_priority)
+ : mlq(mlq), it(it), current_priority(current_priority) {}
+ explicit iterator_impl(container_ref mlq, u32 current_priority)
+ : mlq(mlq), it(), current_priority(current_priority) {}
+
+ bool IsEnd() const {
+ return current_priority == mlq.depth();
+ }
+
+ list_iterator GetBeginItForPrio() const {
+ return mlq.levels[current_priority].begin();
+ }
+
+ list_iterator GetEndItForPrio() const {
+ return mlq.levels[current_priority].end();
+ }
+
+ container_ref mlq;
+ list_iterator it;
+ u32 current_priority;
+ };
+
+ using iterator = iterator_impl<false>;
+ using const_iterator = iterator_impl<true>;
+
+ void add(const T& element, u32 priority, bool send_back = true) {
+ if (send_back)
+ levels[priority].push_back(element);
+ else
+ levels[priority].push_front(element);
+ used_priorities |= 1ULL << priority;
+ }
+
+ void remove(const T& element, u32 priority) {
+ auto it = ListIterateTo(levels[priority], element);
+ if (it == levels[priority].end())
+ return;
+ levels[priority].erase(it);
+ if (levels[priority].empty()) {
+ used_priorities &= ~(1ULL << priority);
+ }
+ }
+
+ void adjust(const T& element, u32 old_priority, u32 new_priority, bool adjust_front = false) {
+ remove(element, old_priority);
+ add(element, new_priority, !adjust_front);
+ }
+ void adjust(const_iterator it, u32 old_priority, u32 new_priority, bool adjust_front = false) {
+ adjust(*it, old_priority, new_priority, adjust_front);
+ }
+
+ void transfer_to_front(const T& element, u32 priority, MultiLevelQueue& other) {
+ ListSplice(other.levels[priority], other.levels[priority].begin(), levels[priority],
+ ListIterateTo(levels[priority], element));
+
+ other.used_priorities |= 1ULL << priority;
+
+ if (levels[priority].empty()) {
+ used_priorities &= ~(1ULL << priority);
+ }
+ }
+
+ void transfer_to_front(const_iterator it, u32 priority, MultiLevelQueue& other) {
+ transfer_to_front(*it, priority, other);
+ }
+
+ void transfer_to_back(const T& element, u32 priority, MultiLevelQueue& other) {
+ ListSplice(other.levels[priority], other.levels[priority].end(), levels[priority],
+ ListIterateTo(levels[priority], element));
+
+ other.used_priorities |= 1ULL << priority;
+
+ if (levels[priority].empty()) {
+ used_priorities &= ~(1ULL << priority);
+ }
+ }
+
+ void transfer_to_back(const_iterator it, u32 priority, MultiLevelQueue& other) {
+ transfer_to_back(*it, priority, other);
+ }
+
+ void yield(u32 priority, std::size_t n = 1) {
+ ListShiftForward(levels[priority], n);
+ }
+
+ std::size_t depth() const {
+ return Depth;
+ }
+
+ std::size_t size(u32 priority) const {
+ return levels[priority].size();
+ }
+
+ std::size_t size() const {
+ u64 priorities = used_priorities;
+ std::size_t size = 0;
+ while (priorities != 0) {
+ const u64 current_priority = CountTrailingZeroes64(priorities);
+ size += levels[current_priority].size();
+ priorities &= ~(1ULL << current_priority);
+ }
+ return size;
+ }
+
+ bool empty() const {
+ return used_priorities == 0;
+ }
+
+ bool empty(u32 priority) const {
+ return (used_priorities & (1ULL << priority)) == 0;
+ }
+
+ u32 highest_priority_set(u32 max_priority = 0) const {
+ const u64 priorities =
+ max_priority == 0 ? used_priorities : (used_priorities & ~((1ULL << max_priority) - 1));
+ return priorities == 0 ? Depth : static_cast<u32>(CountTrailingZeroes64(priorities));
+ }
+
+ u32 lowest_priority_set(u32 min_priority = Depth - 1) const {
+ const u64 priorities = min_priority >= Depth - 1
+ ? used_priorities
+ : (used_priorities & ((1ULL << (min_priority + 1)) - 1));
+ return priorities == 0 ? Depth : 63 - CountLeadingZeroes64(priorities);
+ }
+
+ const_iterator cbegin(u32 max_prio = 0) const {
+ const u32 priority = highest_priority_set(max_prio);
+ return priority == Depth ? cend()
+ : const_iterator{*this, levels[priority].cbegin(), priority};
+ }
+ const_iterator begin(u32 max_prio = 0) const {
+ return cbegin(max_prio);
+ }
+ iterator begin(u32 max_prio = 0) {
+ const u32 priority = highest_priority_set(max_prio);
+ return priority == Depth ? end() : iterator{*this, levels[priority].begin(), priority};
+ }
+
+ const_iterator cend(u32 min_prio = Depth - 1) const {
+ return min_prio == Depth - 1 ? const_iterator{*this, Depth} : cbegin(min_prio + 1);
+ }
+ const_iterator end(u32 min_prio = Depth - 1) const {
+ return cend(min_prio);
+ }
+ iterator end(u32 min_prio = Depth - 1) {
+ return min_prio == Depth - 1 ? iterator{*this, Depth} : begin(min_prio + 1);
+ }
+
+ T& front(u32 max_priority = 0) {
+ const u32 priority = highest_priority_set(max_priority);
+ return levels[priority == Depth ? 0 : priority].front();
+ }
+ const T& front(u32 max_priority = 0) const {
+ const u32 priority = highest_priority_set(max_priority);
+ return levels[priority == Depth ? 0 : priority].front();
+ }
+
+ T back(u32 min_priority = Depth - 1) {
+ const u32 priority = lowest_priority_set(min_priority); // intended
+ return levels[priority == Depth ? 63 : priority].back();
+ }
+ const T& back(u32 min_priority = Depth - 1) const {
+ const u32 priority = lowest_priority_set(min_priority); // intended
+ return levels[priority == Depth ? 63 : priority].back();
+ }
+
+private:
+ using const_list_iterator = typename std::list<T>::const_iterator;
+
+ static void ListShiftForward(std::list<T>& list, const std::size_t shift = 1) {
+ if (shift >= list.size()) {
+ return;
+ }
+
+ const auto begin_range = list.begin();
+ const auto end_range = std::next(begin_range, shift);
+ list.splice(list.end(), list, begin_range, end_range);
+ }
+
+ static void ListSplice(std::list<T>& in_list, const_list_iterator position,
+ std::list<T>& out_list, const_list_iterator element) {
+ in_list.splice(position, out_list, element);
+ }
+
+ static const_list_iterator ListIterateTo(const std::list<T>& list, const T& element) {
+ auto it = list.cbegin();
+ while (it != list.cend() && *it != element) {
+ ++it;
+ }
+ return it;
+ }
+
+ std::array<std::list<T>, Depth> levels;
+ u64 used_priorities = 0;
+};
+
+} // namespace Common
diff --git a/src/common/page_table.cpp b/src/common/page_table.cpp
new file mode 100644
index 000000000..69b7abc54
--- /dev/null
+++ b/src/common/page_table.cpp
@@ -0,0 +1,31 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/page_table.h"
+
+namespace Common {
+
+PageTable::PageTable(std::size_t page_size_in_bits) : page_size_in_bits{page_size_in_bits} {}
+
+PageTable::~PageTable() = default;
+
+void PageTable::Resize(std::size_t address_space_width_in_bits) {
+ const std::size_t num_page_table_entries = 1ULL
+ << (address_space_width_in_bits - page_size_in_bits);
+
+ pointers.resize(num_page_table_entries);
+ attributes.resize(num_page_table_entries);
+ backing_addr.resize(num_page_table_entries);
+
+ // The default is a 39-bit address space, which causes an initial 1GB allocation size. If the
+ // vector size is subsequently decreased (via resize), the vector might not automatically
+ // actually reallocate/resize its underlying allocation, which wastes up to ~800 MB for
+ // 36-bit titles. Call shrink_to_fit to reduce capacity to what's actually in use.
+
+ pointers.shrink_to_fit();
+ attributes.shrink_to_fit();
+ backing_addr.shrink_to_fit();
+}
+
+} // namespace Common
diff --git a/src/common/page_table.h b/src/common/page_table.h
new file mode 100644
index 000000000..8b8ff0bb8
--- /dev/null
+++ b/src/common/page_table.h
@@ -0,0 +1,84 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include <boost/icl/interval_map.hpp>
+#include "common/common_types.h"
+#include "common/memory_hook.h"
+
+namespace Common {
+
+enum class PageType : u8 {
+ /// Page is unmapped and should cause an access error.
+ Unmapped,
+ /// Page is mapped to regular memory. This is the only type you can get pointers to.
+ Memory,
+ /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
+ /// invalidation
+ RasterizerCachedMemory,
+ /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions.
+ Special,
+ /// Page is allocated for use.
+ Allocated,
+};
+
+struct SpecialRegion {
+ enum class Type {
+ DebugHook,
+ IODevice,
+ } type;
+
+ MemoryHookPointer handler;
+
+ bool operator<(const SpecialRegion& other) const {
+ return std::tie(type, handler) < std::tie(other.type, other.handler);
+ }
+
+ bool operator==(const SpecialRegion& other) const {
+ return std::tie(type, handler) == std::tie(other.type, other.handler);
+ }
+};
+
+/**
+ * A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely
+ * mimics the way a real CPU page table works.
+ */
+struct PageTable {
+ explicit PageTable(std::size_t page_size_in_bits);
+ ~PageTable();
+
+ /**
+ * Resizes the page table to be able to accomodate enough pages within
+ * a given address space.
+ *
+ * @param address_space_width_in_bits The address size width in bits.
+ */
+ void Resize(std::size_t address_space_width_in_bits);
+
+ /**
+ * Vector of memory pointers backing each page. An entry can only be non-null if the
+ * corresponding entry in the `attributes` vector is of type `Memory`.
+ */
+ std::vector<u8*> pointers;
+
+ /**
+ * Contains MMIO handlers that back memory regions whose entries in the `attribute` vector is
+ * of type `Special`.
+ */
+ boost::icl::interval_map<u64, std::set<SpecialRegion>> special_regions;
+
+ /**
+ * Vector of fine grained page attributes. If it is set to any value other than `Memory`, then
+ * the corresponding entry in `pointers` MUST be set to null.
+ */
+ std::vector<PageType> attributes;
+
+ std::vector<u64> backing_addr;
+
+ const std::size_t page_size_in_bits{};
+};
+
+} // namespace Common
diff --git a/src/common/scope_exit.h b/src/common/scope_exit.h
index baf1f1c9e..1176a72b1 100644
--- a/src/common/scope_exit.h
+++ b/src/common/scope_exit.h
@@ -20,7 +20,7 @@ struct ScopeExitHelper {
template <typename Func>
ScopeExitHelper<Func> ScopeExit(Func&& func) {
- return ScopeExitHelper<Func>(std::move(func));
+ return ScopeExitHelper<Func>(std::forward<Func>(func));
}
} // namespace detail
diff --git a/src/common/swap.h b/src/common/swap.h
index 0e219747f..71932c2bb 100644
--- a/src/common/swap.h
+++ b/src/common/swap.h
@@ -17,13 +17,10 @@
#pragma once
+#include <type_traits>
+
#if defined(_MSC_VER)
#include <cstdlib>
-#elif defined(__linux__)
-#include <byteswap.h>
-#elif defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || \
- defined(__NetBSD__) || defined(__OpenBSD__)
-#include <sys/endian.h>
#endif
#include <cstring>
#include "common/common_types.h"
@@ -60,86 +57,49 @@
namespace Common {
#ifdef _MSC_VER
-inline u16 swap16(u16 _data) {
- return _byteswap_ushort(_data);
-}
-inline u32 swap32(u32 _data) {
- return _byteswap_ulong(_data);
-}
-inline u64 swap64(u64 _data) {
- return _byteswap_uint64(_data);
-}
-#elif defined(ARCHITECTURE_ARM) && (__ARM_ARCH >= 6)
-inline u16 swap16(u16 _data) {
- u32 data = _data;
- __asm__("rev16 %0, %1\n" : "=l"(data) : "l"(data));
- return (u16)data;
-}
-inline u32 swap32(u32 _data) {
- __asm__("rev %0, %1\n" : "=l"(_data) : "l"(_data));
- return _data;
-}
-inline u64 swap64(u64 _data) {
- return ((u64)swap32(_data) << 32) | swap32(_data >> 32);
-}
-#elif __linux__
-inline u16 swap16(u16 _data) {
- return bswap_16(_data);
-}
-inline u32 swap32(u32 _data) {
- return bswap_32(_data);
-}
-inline u64 swap64(u64 _data) {
- return bswap_64(_data);
+[[nodiscard]] inline u16 swap16(u16 data) noexcept {
+ return _byteswap_ushort(data);
}
-#elif __APPLE__
-inline __attribute__((always_inline)) u16 swap16(u16 _data) {
- return (_data >> 8) | (_data << 8);
+[[nodiscard]] inline u32 swap32(u32 data) noexcept {
+ return _byteswap_ulong(data);
}
-inline __attribute__((always_inline)) u32 swap32(u32 _data) {
- return __builtin_bswap32(_data);
+[[nodiscard]] inline u64 swap64(u64 data) noexcept {
+ return _byteswap_uint64(data);
}
-inline __attribute__((always_inline)) u64 swap64(u64 _data) {
- return __builtin_bswap64(_data);
-}
-#elif defined(__Bitrig__) || defined(__OpenBSD__)
+#elif defined(__clang__) || defined(__GNUC__)
+#if defined(__Bitrig__) || defined(__OpenBSD__)
// redefine swap16, swap32, swap64 as inline functions
#undef swap16
#undef swap32
#undef swap64
-inline u16 swap16(u16 _data) {
- return __swap16(_data);
-}
-inline u32 swap32(u32 _data) {
- return __swap32(_data);
-}
-inline u64 swap64(u64 _data) {
- return __swap64(_data);
-}
-#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__)
-inline u16 swap16(u16 _data) {
- return bswap16(_data);
+#endif
+[[nodiscard]] inline u16 swap16(u16 data) noexcept {
+ return __builtin_bswap16(data);
}
-inline u32 swap32(u32 _data) {
- return bswap32(_data);
+[[nodiscard]] inline u32 swap32(u32 data) noexcept {
+ return __builtin_bswap32(data);
}
-inline u64 swap64(u64 _data) {
- return bswap64(_data);
+[[nodiscard]] inline u64 swap64(u64 data) noexcept {
+ return __builtin_bswap64(data);
}
#else
-// Slow generic implementation.
-inline u16 swap16(u16 data) {
+// Generic implementation.
+[[nodiscard]] inline u16 swap16(u16 data) noexcept {
return (data >> 8) | (data << 8);
}
-inline u32 swap32(u32 data) {
- return (swap16(data) << 16) | swap16(data >> 16);
+[[nodiscard]] inline u32 swap32(u32 data) noexcept {
+ return ((data & 0xFF000000U) >> 24) | ((data & 0x00FF0000U) >> 8) |
+ ((data & 0x0000FF00U) << 8) | ((data & 0x000000FFU) << 24);
}
-inline u64 swap64(u64 data) {
- return ((u64)swap32(data) << 32) | swap32(data >> 32);
+[[nodiscard]] inline u64 swap64(u64 data) noexcept {
+ return ((data & 0xFF00000000000000ULL) >> 56) | ((data & 0x00FF000000000000ULL) >> 40) |
+ ((data & 0x0000FF0000000000ULL) >> 24) | ((data & 0x000000FF00000000ULL) >> 8) |
+ ((data & 0x00000000FF000000ULL) << 8) | ((data & 0x0000000000FF0000ULL) << 24) |
+ ((data & 0x000000000000FF00ULL) << 40) | ((data & 0x00000000000000FFULL) << 56);
}
#endif
-inline float swapf(float f) {
+[[nodiscard]] inline float swapf(float f) noexcept {
static_assert(sizeof(u32) == sizeof(float), "float must be the same size as uint32_t.");
u32 value;
@@ -151,7 +111,7 @@ inline float swapf(float f) {
return f;
}
-inline double swapd(double f) {
+[[nodiscard]] inline double swapd(double f) noexcept {
static_assert(sizeof(u64) == sizeof(double), "double must be the same size as uint64_t.");
u64 value;
@@ -170,7 +130,7 @@ struct swap_struct_t {
using swapped_t = swap_struct_t;
protected:
- T value = T();
+ T value;
static T swap(T v) {
return F::swap(v);
@@ -605,52 +565,154 @@ struct swap_double_t {
}
};
-#if COMMON_LITTLE_ENDIAN
-using u16_le = u16;
-using u32_le = u32;
-using u64_le = u64;
+template <typename T>
+struct swap_enum_t {
+ static_assert(std::is_enum_v<T>);
+ using base = std::underlying_type_t<T>;
-using s16_le = s16;
-using s32_le = s32;
-using s64_le = s64;
+public:
+ swap_enum_t() = default;
+ swap_enum_t(const T& v) : value(swap(v)) {}
-using float_le = float;
-using double_le = double;
+ swap_enum_t& operator=(const T& v) {
+ value = swap(v);
+ return *this;
+ }
-using u64_be = swap_struct_t<u64, swap_64_t<u64>>;
-using s64_be = swap_struct_t<s64, swap_64_t<s64>>;
+ operator T() const {
+ return swap(value);
+ }
-using u32_be = swap_struct_t<u32, swap_32_t<u32>>;
-using s32_be = swap_struct_t<s32, swap_32_t<s32>>;
+ explicit operator base() const {
+ return static_cast<base>(swap(value));
+ }
-using u16_be = swap_struct_t<u16, swap_16_t<u16>>;
-using s16_be = swap_struct_t<s16, swap_16_t<s16>>;
+protected:
+ T value{};
+ // clang-format off
+ using swap_t = std::conditional_t<
+ std::is_same_v<base, u16>, swap_16_t<u16>, std::conditional_t<
+ std::is_same_v<base, s16>, swap_16_t<s16>, std::conditional_t<
+ std::is_same_v<base, u32>, swap_32_t<u32>, std::conditional_t<
+ std::is_same_v<base, s32>, swap_32_t<s32>, std::conditional_t<
+ std::is_same_v<base, u64>, swap_64_t<u64>, std::conditional_t<
+ std::is_same_v<base, s64>, swap_64_t<s64>, void>>>>>>;
+ // clang-format on
+ static T swap(T x) {
+ return static_cast<T>(swap_t::swap(static_cast<base>(x)));
+ }
+};
-using float_be = swap_struct_t<float, swap_float_t<float>>;
-using double_be = swap_struct_t<double, swap_double_t<double>>;
-#else
+struct SwapTag {}; // Use the different endianness from the system
+struct KeepTag {}; // Use the same endianness as the system
-using u64_le = swap_struct_t<u64, swap_64_t<u64>>;
-using s64_le = swap_struct_t<s64, swap_64_t<s64>>;
+template <typename T, typename Tag>
+struct AddEndian;
-using u32_le = swap_struct_t<u32, swap_32_t<u32>>;
-using s32_le = swap_struct_t<s32, swap_32_t<s32>>;
+// KeepTag specializations
-using u16_le = swap_struct_t<u16, swap_16_t<u16>>;
-using s16_le = swap_struct_t<s16, swap_16_t<s16>>;
+template <typename T>
+struct AddEndian<T, KeepTag> {
+ using type = T;
+};
-using float_le = swap_struct_t<float, swap_float_t<float>>;
-using double_le = swap_struct_t<double, swap_double_t<double>>;
+// SwapTag specializations
-using u16_be = u16;
-using u32_be = u32;
-using u64_be = u64;
+template <>
+struct AddEndian<u8, SwapTag> {
+ using type = u8;
+};
-using s16_be = s16;
-using s32_be = s32;
-using s64_be = s64;
+template <>
+struct AddEndian<u16, SwapTag> {
+ using type = swap_struct_t<u16, swap_16_t<u16>>;
+};
-using float_be = float;
-using double_be = double;
+template <>
+struct AddEndian<u32, SwapTag> {
+ using type = swap_struct_t<u32, swap_32_t<u32>>;
+};
+
+template <>
+struct AddEndian<u64, SwapTag> {
+ using type = swap_struct_t<u64, swap_64_t<u64>>;
+};
+
+template <>
+struct AddEndian<s8, SwapTag> {
+ using type = s8;
+};
+
+template <>
+struct AddEndian<s16, SwapTag> {
+ using type = swap_struct_t<s16, swap_16_t<s16>>;
+};
+
+template <>
+struct AddEndian<s32, SwapTag> {
+ using type = swap_struct_t<s32, swap_32_t<s32>>;
+};
+
+template <>
+struct AddEndian<s64, SwapTag> {
+ using type = swap_struct_t<s64, swap_64_t<s64>>;
+};
+
+template <>
+struct AddEndian<float, SwapTag> {
+ using type = swap_struct_t<float, swap_float_t<float>>;
+};
+
+template <>
+struct AddEndian<double, SwapTag> {
+ using type = swap_struct_t<double, swap_double_t<double>>;
+};
+
+template <typename T>
+struct AddEndian<T, SwapTag> {
+ static_assert(std::is_enum_v<T>);
+ using type = swap_enum_t<T>;
+};
+
+// Alias LETag/BETag as KeepTag/SwapTag depending on the system
+#if COMMON_LITTLE_ENDIAN
+
+using LETag = KeepTag;
+using BETag = SwapTag;
+
+#else
+
+using BETag = KeepTag;
+using LETag = SwapTag;
#endif
+
+// Aliases for LE types
+using u16_le = AddEndian<u16, LETag>::type;
+using u32_le = AddEndian<u32, LETag>::type;
+using u64_le = AddEndian<u64, LETag>::type;
+
+using s16_le = AddEndian<s16, LETag>::type;
+using s32_le = AddEndian<s32, LETag>::type;
+using s64_le = AddEndian<s64, LETag>::type;
+
+template <typename T>
+using enum_le = std::enable_if_t<std::is_enum_v<T>, typename AddEndian<T, LETag>::type>;
+
+using float_le = AddEndian<float, LETag>::type;
+using double_le = AddEndian<double, LETag>::type;
+
+// Aliases for BE types
+using u16_be = AddEndian<u16, BETag>::type;
+using u32_be = AddEndian<u32, BETag>::type;
+using u64_be = AddEndian<u64, BETag>::type;
+
+using s16_be = AddEndian<s16, BETag>::type;
+using s32_be = AddEndian<s32, BETag>::type;
+using s64_be = AddEndian<s64, BETag>::type;
+
+template <typename T>
+using enum_be = std::enable_if_t<std::is_enum_v<T>, typename AddEndian<T, BETag>::type>;
+
+using float_be = AddEndian<float, BETag>::type;
+using double_be = AddEndian<double, BETag>::type;
diff --git a/src/common/thread.cpp b/src/common/thread.cpp
index 5144c0d9f..fe7a420cc 100644
--- a/src/common/thread.cpp
+++ b/src/common/thread.cpp
@@ -27,18 +27,6 @@ namespace Common {
#ifdef _MSC_VER
-void SetThreadAffinity(std::thread::native_handle_type thread, u32 mask) {
- SetThreadAffinityMask(thread, mask);
-}
-
-void SetCurrentThreadAffinity(u32 mask) {
- SetThreadAffinityMask(GetCurrentThread(), mask);
-}
-
-void SwitchCurrentThread() {
- SwitchToThread();
-}
-
// Sets the debugger-visible name of the current thread.
// Uses undocumented (actually, it is now documented) trick.
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vsdebug/html/vxtsksettingthreadname.asp
@@ -70,31 +58,6 @@ void SetCurrentThreadName(const char* name) {
#else // !MSVC_VER, so must be POSIX threads
-void SetThreadAffinity(std::thread::native_handle_type thread, u32 mask) {
-#ifdef __APPLE__
- thread_policy_set(pthread_mach_thread_np(thread), THREAD_AFFINITY_POLICY, (integer_t*)&mask, 1);
-#elif (defined __linux__ || defined __FreeBSD__) && !(defined ANDROID)
- cpu_set_t cpu_set;
- CPU_ZERO(&cpu_set);
-
- for (int i = 0; i != sizeof(mask) * 8; ++i)
- if ((mask >> i) & 1)
- CPU_SET(i, &cpu_set);
-
- pthread_setaffinity_np(thread, sizeof(cpu_set), &cpu_set);
-#endif
-}
-
-void SetCurrentThreadAffinity(u32 mask) {
- SetThreadAffinity(pthread_self(), mask);
-}
-
-#ifndef _WIN32
-void SwitchCurrentThread() {
- usleep(1000 * 1);
-}
-#endif
-
// MinGW with the POSIX threading model does not support pthread_setname_np
#if !defined(_WIN32) || defined(_MSC_VER)
void SetCurrentThreadName(const char* name) {
diff --git a/src/common/thread.h b/src/common/thread.h
index 2cf74452d..0cfd98be6 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -9,14 +9,13 @@
#include <cstddef>
#include <mutex>
#include <thread>
-#include "common/common_types.h"
namespace Common {
class Event {
public:
void Set() {
- std::lock_guard<std::mutex> lk(mutex);
+ std::lock_guard lk{mutex};
if (!is_set) {
is_set = true;
condvar.notify_one();
@@ -24,14 +23,14 @@ public:
}
void Wait() {
- std::unique_lock<std::mutex> lk(mutex);
+ std::unique_lock lk{mutex};
condvar.wait(lk, [&] { return is_set; });
is_set = false;
}
template <class Clock, class Duration>
bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {
- std::unique_lock<std::mutex> lk(mutex);
+ std::unique_lock lk{mutex};
if (!condvar.wait_until(lk, time, [this] { return is_set; }))
return false;
is_set = false;
@@ -39,7 +38,7 @@ public:
}
void Reset() {
- std::unique_lock<std::mutex> lk(mutex);
+ std::unique_lock lk{mutex};
// no other action required, since wait loops on the predicate and any lingering signal will
// get cleared on the first iteration
is_set = false;
@@ -57,7 +56,7 @@ public:
/// Blocks until all "count" threads have called Sync()
void Sync() {
- std::unique_lock<std::mutex> lk(mutex);
+ std::unique_lock lk{mutex};
const std::size_t current_generation = generation;
if (++waiting == count) {
@@ -78,9 +77,6 @@ private:
std::size_t generation = 0; // Incremented once each time the barrier is used
};
-void SetThreadAffinity(std::thread::native_handle_type thread, u32 mask);
-void SetCurrentThreadAffinity(u32 mask);
-void SwitchCurrentThread(); // On Linux, this is equal to sleep 1ms
void SetCurrentThreadName(const char* name);
} // namespace Common
diff --git a/src/common/thread_queue_list.h b/src/common/thread_queue_list.h
index e7594db68..791f99a8c 100644
--- a/src/common/thread_queue_list.h
+++ b/src/common/thread_queue_list.h
@@ -6,7 +6,6 @@
#include <array>
#include <deque>
-#include <boost/range/algorithm_ext/erase.hpp>
namespace Common {
@@ -111,8 +110,9 @@ struct ThreadQueueList {
}
void remove(Priority priority, const T& thread_id) {
- Queue* cur = &queues[priority];
- boost::remove_erase(cur->data, thread_id);
+ Queue* const cur = &queues[priority];
+ const auto iter = std::remove(cur->data.begin(), cur->data.end(), thread_id);
+ cur->data.erase(iter, cur->data.end());
}
void rotate(Priority priority) {
diff --git a/src/common/threadsafe_queue.h b/src/common/threadsafe_queue.h
index 821e8536a..e714ba5b3 100644
--- a/src/common/threadsafe_queue.h
+++ b/src/common/threadsafe_queue.h
@@ -78,7 +78,7 @@ public:
T PopWait() {
if (Empty()) {
- std::unique_lock<std::mutex> lock(cv_mutex);
+ std::unique_lock lock{cv_mutex};
cv.wait(lock, [this]() { return !Empty(); });
}
T t;
@@ -137,7 +137,7 @@ public:
template <typename Arg>
void Push(Arg&& t) {
- std::lock_guard<std::mutex> lock(write_lock);
+ std::lock_guard lock{write_lock};
spsc_queue.Push(t);
}
diff --git a/src/common/uint128.cpp b/src/common/uint128.cpp
new file mode 100644
index 000000000..32bf56730
--- /dev/null
+++ b/src/common/uint128.cpp
@@ -0,0 +1,45 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#ifdef _MSC_VER
+#include <intrin.h>
+
+#pragma intrinsic(_umul128)
+#endif
+#include <cstring>
+#include "common/uint128.h"
+
+namespace Common {
+
+u128 Multiply64Into128(u64 a, u64 b) {
+ u128 result;
+#ifdef _MSC_VER
+ result[0] = _umul128(a, b, &result[1]);
+#else
+ unsigned __int128 tmp = a;
+ tmp *= b;
+ std::memcpy(&result, &tmp, sizeof(u128));
+#endif
+ return result;
+}
+
+std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor) {
+ u64 remainder = dividend[0] % divisor;
+ u64 accum = dividend[0] / divisor;
+ if (dividend[1] == 0)
+ return {accum, remainder};
+ // We ignore dividend[1] / divisor as that overflows
+ const u64 first_segment = (dividend[1] % divisor) << 32;
+ accum += (first_segment / divisor) << 32;
+ const u64 second_segment = (first_segment % divisor) << 32;
+ accum += (second_segment / divisor);
+ remainder += second_segment % divisor;
+ if (remainder >= divisor) {
+ accum++;
+ remainder -= divisor;
+ }
+ return {accum, remainder};
+}
+
+} // namespace Common
diff --git a/src/common/uint128.h b/src/common/uint128.h
new file mode 100644
index 000000000..a3be2a2cb
--- /dev/null
+++ b/src/common/uint128.h
@@ -0,0 +1,19 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <utility>
+#include "common/common_types.h"
+
+namespace Common {
+
+// This function multiplies 2 u64 values and produces a u128 value;
+u128 Multiply64Into128(u64 a, u64 b);
+
+// This function divides a u128 by a u32 value and produces two u64 values:
+// the result of division and the remainder
+std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor);
+
+} // namespace Common
diff --git a/src/common/uuid.cpp b/src/common/uuid.cpp
new file mode 100644
index 000000000..26db03fba
--- /dev/null
+++ b/src/common/uuid.cpp
@@ -0,0 +1,33 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <random>
+
+#include <fmt/format.h>
+
+#include "common/uuid.h"
+
+namespace Common {
+
+UUID UUID::Generate() {
+ std::random_device device;
+ std::mt19937 gen(device());
+ std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max());
+ return UUID{distribution(gen), distribution(gen)};
+}
+
+std::string UUID::Format() const {
+ return fmt::format("0x{:016X}{:016X}", uuid[1], uuid[0]);
+}
+
+std::string UUID::FormatSwitch() const {
+ std::array<u8, 16> s{};
+ std::memcpy(s.data(), uuid.data(), sizeof(u128));
+ return fmt::format("{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{"
+ ":02x}{:02x}{:02x}{:02x}{:02x}",
+ s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11],
+ s[12], s[13], s[14], s[15]);
+}
+
+} // namespace Common
diff --git a/src/common/uuid.h b/src/common/uuid.h
new file mode 100644
index 000000000..f6ad064fb
--- /dev/null
+++ b/src/common/uuid.h
@@ -0,0 +1,48 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+
+#include "common/common_types.h"
+
+namespace Common {
+
+constexpr u128 INVALID_UUID{{0, 0}};
+
+struct UUID {
+ // UUIDs which are 0 are considered invalid!
+ u128 uuid = INVALID_UUID;
+ constexpr UUID() = default;
+ constexpr explicit UUID(const u128& id) : uuid{id} {}
+ constexpr explicit UUID(const u64 lo, const u64 hi) : uuid{{lo, hi}} {}
+
+ constexpr explicit operator bool() const {
+ return uuid[0] != INVALID_UUID[0] && uuid[1] != INVALID_UUID[1];
+ }
+
+ constexpr bool operator==(const UUID& rhs) const {
+ // TODO(DarkLordZach): Replace with uuid == rhs.uuid with C++20
+ return uuid[0] == rhs.uuid[0] && uuid[1] == rhs.uuid[1];
+ }
+
+ constexpr bool operator!=(const UUID& rhs) const {
+ return !operator==(rhs);
+ }
+
+ // TODO(ogniK): Properly generate uuids based on RFC-4122
+ static UUID Generate();
+
+ // Set the UUID to {0,0} to be considered an invalid user
+ constexpr void Invalidate() {
+ uuid = INVALID_UUID;
+ }
+
+ std::string Format() const;
+ std::string FormatSwitch() const;
+};
+static_assert(sizeof(UUID) == 16, "UUID is an invalid size!");
+
+} // namespace Common
diff --git a/src/common/zstd_compression.cpp b/src/common/zstd_compression.cpp
new file mode 100644
index 000000000..978526492
--- /dev/null
+++ b/src/common/zstd_compression.cpp
@@ -0,0 +1,51 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <zstd.h>
+
+#include "common/assert.h"
+#include "common/zstd_compression.h"
+
+namespace Common::Compression {
+
+std::vector<u8> CompressDataZSTD(const u8* source, std::size_t source_size, s32 compression_level) {
+ compression_level = std::clamp(compression_level, 1, ZSTD_maxCLevel());
+
+ const std::size_t max_compressed_size = ZSTD_compressBound(source_size);
+ std::vector<u8> compressed(max_compressed_size);
+
+ const std::size_t compressed_size =
+ ZSTD_compress(compressed.data(), compressed.size(), source, source_size, compression_level);
+
+ if (ZSTD_isError(compressed_size)) {
+ // Compression failed
+ return {};
+ }
+
+ compressed.resize(compressed_size);
+
+ return compressed;
+}
+
+std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_size) {
+ return CompressDataZSTD(source, source_size, ZSTD_CLEVEL_DEFAULT);
+}
+
+std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed) {
+ const std::size_t decompressed_size =
+ ZSTD_getDecompressedSize(compressed.data(), compressed.size());
+ std::vector<u8> decompressed(decompressed_size);
+
+ const std::size_t uncompressed_result_size = ZSTD_decompress(
+ decompressed.data(), decompressed.size(), compressed.data(), compressed.size());
+
+ if (decompressed_size != uncompressed_result_size || ZSTD_isError(uncompressed_result_size)) {
+ // Decompression failed
+ return {};
+ }
+ return decompressed;
+}
+
+} // namespace Common::Compression
diff --git a/src/common/zstd_compression.h b/src/common/zstd_compression.h
new file mode 100644
index 000000000..e9de941c8
--- /dev/null
+++ b/src/common/zstd_compression.h
@@ -0,0 +1,44 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace Common::Compression {
+
+/**
+ * Compresses a source memory region with Zstandard and returns the compressed data in a vector.
+ *
+ * @param source the uncompressed source memory region.
+ * @param source_size the size in bytes of the uncompressed source memory region.
+ * @param compression_level the used compression level. Should be between 1 and 22.
+ *
+ * @return the compressed data.
+ */
+std::vector<u8> CompressDataZSTD(const u8* source, std::size_t source_size, s32 compression_level);
+
+/**
+ * Compresses a source memory region with Zstandard with the default compression level and returns
+ * the compressed data in a vector.
+ *
+ * @param source the uncompressed source memory region.
+ * @param source_size the size in bytes of the uncompressed source memory region.
+ *
+ * @return the compressed data.
+ */
+std::vector<u8> CompressDataZSTDDefault(const u8* source, std::size_t source_size);
+
+/**
+ * Decompresses a source memory region with Zstandard and returns the uncompressed data in a vector.
+ *
+ * @param compressed the compressed source memory region.
+ *
+ * @return the decompressed data.
+ */
+std::vector<u8> DecompressDataZSTD(const std::vector<u8>& compressed);
+
+} // namespace Common::Compression \ No newline at end of file