Compare commits
102 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3297d8cd1 | ||
|
|
611f4666fd | ||
|
|
1cf90f4570 | ||
|
|
90e27ea003 | ||
|
|
ee49e1fcb6 | ||
|
|
7b6d516faa | ||
|
|
b78e7b3454 | ||
|
|
4d95adcac5 | ||
|
|
e703772c83 | ||
|
|
639f0c524d | ||
|
|
76f27d1f44 | ||
|
|
18918f5f2f | ||
|
|
56c80a2a21 | ||
|
|
c221308a66 | ||
|
|
839c0f829b | ||
|
|
109b78a6d6 | ||
|
|
3e930304fe | ||
|
|
52f36ea1c7 | ||
|
|
b72664abfd | ||
|
|
f2c41ba256 | ||
|
|
b0d70096a1 | ||
|
|
e76f442a0e | ||
|
|
43b83d6b6a | ||
|
|
1b6adb5308 | ||
|
|
872a7bee72 | ||
|
|
e8ff8a66b0 | ||
|
|
723ad4512f | ||
|
|
c1409602da | ||
|
|
8f454a5c68 | ||
|
|
1b6bd9d6df | ||
|
|
9d11303a36 | ||
|
|
adf07cbe17 | ||
|
|
8a320a6ee2 | ||
|
|
1342c53e27 | ||
|
|
ab00552118 | ||
|
|
ec74a4fd4a | ||
|
|
b8c7072206 | ||
|
|
746167f11a | ||
|
|
eb335f51ca | ||
|
|
874826b6dd | ||
|
|
8dc2f01eae | ||
|
|
e05136f70b | ||
|
|
9f092554c2 | ||
|
|
26b809549b | ||
|
|
c07ebeac19 | ||
|
|
ecd3afdc8e | ||
|
|
30f228a8c9 | ||
|
|
c57d8eb66c | ||
|
|
51f609fee7 | ||
|
|
57ca1e3e69 | ||
|
|
114060fd87 | ||
|
|
d74aa13bd3 | ||
|
|
834d3fe336 | ||
|
|
41566c615b | ||
|
|
9ad3b01d30 | ||
|
|
fd0533ef4c | ||
|
|
ed7a1e1443 | ||
|
|
93da8e0abf | ||
|
|
032e4c4ca3 | ||
|
|
2392e146b0 | ||
|
|
bf41132aa9 | ||
|
|
059465d496 | ||
|
|
64444ff481 | ||
|
|
f71c598907 | ||
|
|
6abc56672c | ||
|
|
99f982dce2 | ||
|
|
29c242721a | ||
|
|
bdf2da4ee8 | ||
|
|
10118c71e0 | ||
|
|
574e89d924 | ||
|
|
47b622825c | ||
|
|
51d7f6bffc | ||
|
|
c892cf01fa | ||
|
|
db47d7e471 | ||
|
|
e0d1f11968 | ||
|
|
39483b92b7 | ||
|
|
0b78cfcc53 | ||
|
|
06ac6460d3 | ||
|
|
e5b004e903 | ||
|
|
59f16f2e02 | ||
|
|
d71cad6ed0 | ||
|
|
555cd26ec2 | ||
|
|
7ad3d4e49c | ||
|
|
39e895c5ff | ||
|
|
52ac6419da | ||
|
|
7053546687 | ||
|
|
769b346682 | ||
|
|
c100a4b8d4 | ||
|
|
b952a30555 | ||
|
|
4495bf5706 | ||
|
|
c5091bfe00 | ||
|
|
9d1ab766a0 | ||
|
|
a8d4927e29 | ||
|
|
ecccfe0337 | ||
|
|
3ea48e8ebe | ||
|
|
5b7ec71fb7 | ||
|
|
efd83570bd | ||
|
|
89abef3518 | ||
|
|
6b81ceb060 | ||
|
|
71530781f3 | ||
|
|
6734c64976 | ||
|
|
94bc48dd78 |
@@ -163,12 +163,6 @@ else()
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
# Fix GCC C++17 and Boost.ICL incompatibility (needed to build dynarmic)
|
||||
# See https://bugzilla.redhat.com/show_bug.cgi?id=1485641#c1
|
||||
if (CMAKE_COMPILER_IS_GNUCC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-new-ttp-matching")
|
||||
endif()
|
||||
|
||||
# Set file offset size to 64 bits.
|
||||
#
|
||||
# On modern Unixes, this is typically already the case. The lone exception is
|
||||
@@ -185,9 +179,9 @@ set_property(DIRECTORY APPEND PROPERTY
|
||||
# System imported libraries
|
||||
# ======================
|
||||
|
||||
find_package(Boost 1.63.0 QUIET)
|
||||
find_package(Boost 1.66.0 QUIET)
|
||||
if (NOT Boost_FOUND)
|
||||
message(STATUS "Boost 1.63.0 or newer not found, falling back to externals")
|
||||
message(STATUS "Boost 1.66.0 or newer not found, falling back to externals")
|
||||
|
||||
set(BOOST_ROOT "${PROJECT_SOURCE_DIR}/externals/boost")
|
||||
set(Boost_NO_SYSTEM_PATHS OFF)
|
||||
|
||||
2
externals/opus
vendored
2
externals/opus
vendored
Submodule externals/opus updated: b2871922a1...562f8ba555
@@ -92,10 +92,14 @@ add_library(common STATIC
|
||||
logging/text_formatter.cpp
|
||||
logging/text_formatter.h
|
||||
math_util.h
|
||||
memory_hook.cpp
|
||||
memory_hook.h
|
||||
microprofile.cpp
|
||||
microprofile.h
|
||||
microprofileui.h
|
||||
misc.cpp
|
||||
page_table.cpp
|
||||
page_table.h
|
||||
param_package.cpp
|
||||
param_package.h
|
||||
quaternion.h
|
||||
@@ -114,6 +118,8 @@ add_library(common STATIC
|
||||
threadsafe_queue.h
|
||||
timer.cpp
|
||||
timer.h
|
||||
uint128.cpp
|
||||
uint128.h
|
||||
vector_math.h
|
||||
web_result.h
|
||||
)
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/swap.h"
|
||||
|
||||
/*
|
||||
* Abstract bitfield class
|
||||
@@ -108,7 +109,7 @@
|
||||
* 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:
|
||||
// UnderlyingType is T for non-enum types and the underlying type of T if
|
||||
@@ -121,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;
|
||||
@@ -170,7 +173,7 @@ public:
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -182,7 +185,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
StorageType storage;
|
||||
StorageTypeWithEndian storage;
|
||||
|
||||
static_assert(bits + position <= 8 * sizeof(T), "Bitfield out of range");
|
||||
|
||||
@@ -193,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>;
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/memory_hook.h"
|
||||
#include "common/memory_hook.h"
|
||||
|
||||
namespace Memory {
|
||||
namespace Common {
|
||||
|
||||
MemoryHook::~MemoryHook() = default;
|
||||
|
||||
} // namespace Memory
|
||||
} // namespace Common
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Memory {
|
||||
namespace Common {
|
||||
|
||||
/**
|
||||
* Memory hooks have two purposes:
|
||||
@@ -44,4 +44,4 @@ public:
|
||||
};
|
||||
|
||||
using MemoryHookPointer = std::shared_ptr<MemoryHook>;
|
||||
} // namespace Memory
|
||||
} // namespace Common
|
||||
29
src/common/page_table.cpp
Normal file
29
src/common/page_table.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
// 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);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
80
src/common/page_table.h
Normal file
80
src/common/page_table.h
Normal file
@@ -0,0 +1,80 @@
|
||||
// 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,
|
||||
};
|
||||
|
||||
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<VAddr, 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;
|
||||
|
||||
const std::size_t page_size_in_bits{};
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <cstdlib>
|
||||
#elif defined(__linux__)
|
||||
@@ -170,7 +172,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 +607,154 @@ struct swap_double_t {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct swap_enum_t {
|
||||
static_assert(std::is_enum_v<T>);
|
||||
using base = std::underlying_type_t<T>;
|
||||
|
||||
public:
|
||||
swap_enum_t() = default;
|
||||
swap_enum_t(const T& v) : value(swap(v)) {}
|
||||
|
||||
swap_enum_t& operator=(const T& v) {
|
||||
value = swap(v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator T() const {
|
||||
return swap(value);
|
||||
}
|
||||
|
||||
explicit operator base() const {
|
||||
return static_cast<base>(swap(value));
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
};
|
||||
|
||||
struct SwapTag {}; // Use the different endianness from the system
|
||||
struct KeepTag {}; // Use the same endianness as the system
|
||||
|
||||
template <typename T, typename Tag>
|
||||
struct AddEndian;
|
||||
|
||||
// KeepTag specializations
|
||||
|
||||
template <typename T>
|
||||
struct AddEndian<T, KeepTag> {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
// SwapTag specializations
|
||||
|
||||
template <>
|
||||
struct AddEndian<u8, SwapTag> {
|
||||
using type = u8;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<u16, SwapTag> {
|
||||
using type = swap_struct_t<u16, swap_16_t<u16>>;
|
||||
};
|
||||
|
||||
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 u16_le = u16;
|
||||
using u32_le = u32;
|
||||
using u64_le = u64;
|
||||
|
||||
using s16_le = s16;
|
||||
using s32_le = s32;
|
||||
using s64_le = s64;
|
||||
using LETag = KeepTag;
|
||||
using BETag = SwapTag;
|
||||
|
||||
using float_le = float;
|
||||
using double_le = double;
|
||||
|
||||
using u64_be = swap_struct_t<u64, swap_64_t<u64>>;
|
||||
using s64_be = swap_struct_t<s64, swap_64_t<s64>>;
|
||||
|
||||
using u32_be = swap_struct_t<u32, swap_32_t<u32>>;
|
||||
using s32_be = swap_struct_t<s32, swap_32_t<s32>>;
|
||||
|
||||
using u16_be = swap_struct_t<u16, swap_16_t<u16>>;
|
||||
using s16_be = swap_struct_t<s16, swap_16_t<s16>>;
|
||||
|
||||
using float_be = swap_struct_t<float, swap_float_t<float>>;
|
||||
using double_be = swap_struct_t<double, swap_double_t<double>>;
|
||||
#else
|
||||
|
||||
using u64_le = swap_struct_t<u64, swap_64_t<u64>>;
|
||||
using s64_le = swap_struct_t<s64, swap_64_t<s64>>;
|
||||
|
||||
using u32_le = swap_struct_t<u32, swap_32_t<u32>>;
|
||||
using s32_le = swap_struct_t<s32, swap_32_t<s32>>;
|
||||
|
||||
using u16_le = swap_struct_t<u16, swap_16_t<u16>>;
|
||||
using s16_le = swap_struct_t<s16, swap_16_t<s16>>;
|
||||
|
||||
using float_le = swap_struct_t<float, swap_float_t<float>>;
|
||||
using double_le = swap_struct_t<double, swap_double_t<double>>;
|
||||
|
||||
using u16_be = u16;
|
||||
using u32_be = u32;
|
||||
using u64_be = u64;
|
||||
|
||||
using s16_be = s16;
|
||||
using s32_be = s32;
|
||||
using s64_be = s64;
|
||||
|
||||
using float_be = float;
|
||||
using double_be = double;
|
||||
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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
45
src/common/uint128.cpp
Normal file
45
src/common/uint128.cpp
Normal file
@@ -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
|
||||
19
src/common/uint128.h
Normal file
19
src/common/uint128.h
Normal file
@@ -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
|
||||
@@ -31,6 +31,8 @@ add_library(core STATIC
|
||||
file_sys/bis_factory.h
|
||||
file_sys/card_image.cpp
|
||||
file_sys/card_image.h
|
||||
file_sys/cheat_engine.cpp
|
||||
file_sys/cheat_engine.h
|
||||
file_sys/content_archive.cpp
|
||||
file_sys/content_archive.h
|
||||
file_sys/control_metadata.cpp
|
||||
@@ -107,6 +109,8 @@ add_library(core STATIC
|
||||
hle/kernel/client_port.h
|
||||
hle/kernel/client_session.cpp
|
||||
hle/kernel/client_session.h
|
||||
hle/kernel/code_set.cpp
|
||||
hle/kernel/code_set.h
|
||||
hle/kernel/errors.h
|
||||
hle/kernel/handle_table.cpp
|
||||
hle/kernel/handle_table.h
|
||||
@@ -419,8 +423,6 @@ add_library(core STATIC
|
||||
loader/deconstructed_rom_directory.h
|
||||
loader/elf.cpp
|
||||
loader/elf.h
|
||||
loader/linker.cpp
|
||||
loader/linker.h
|
||||
loader/loader.cpp
|
||||
loader/loader.h
|
||||
loader/nax.cpp
|
||||
@@ -437,8 +439,6 @@ add_library(core STATIC
|
||||
loader/xci.h
|
||||
memory.cpp
|
||||
memory.h
|
||||
memory_hook.cpp
|
||||
memory_hook.h
|
||||
memory_setup.h
|
||||
perf_stats.cpp
|
||||
perf_stats.h
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "core/core.h"
|
||||
#include "core/core_cpu.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
@@ -119,7 +120,7 @@ public:
|
||||
return std::max(parent.core_timing.GetDowncount(), 0);
|
||||
}
|
||||
u64 GetCNTPCT() override {
|
||||
return parent.core_timing.GetTicks();
|
||||
return Timing::CpuCyclesToClockCycles(parent.core_timing.GetTicks());
|
||||
}
|
||||
|
||||
ARM_Dynarmic& parent;
|
||||
@@ -151,7 +152,7 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
|
||||
config.tpidr_el0 = &cb->tpidr_el0;
|
||||
config.dczid_el0 = 4;
|
||||
config.ctr_el0 = 0x8444c004;
|
||||
config.cntfrq_el0 = 19200000; // Value from fusee.
|
||||
config.cntfrq_el0 = Timing::CNTFREQ;
|
||||
|
||||
// Unpredictable instructions
|
||||
config.define_unpredictable_behaviour = true;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/arm/unicorn/arm_unicorn.h"
|
||||
|
||||
namespace Memory {
|
||||
namespace Common {
|
||||
struct PageTable;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ private:
|
||||
Timing::CoreTiming& core_timing;
|
||||
DynarmicExclusiveMonitor& exclusive_monitor;
|
||||
|
||||
Memory::PageTable* current_page_table = nullptr;
|
||||
Common::PageTable* current_page_table = nullptr;
|
||||
};
|
||||
|
||||
class DynarmicExclusiveMonitor final : public ExclusiveMonitor {
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "file_sys/cheat_engine.h"
|
||||
#include "frontend/applets/profile_select.h"
|
||||
#include "frontend/applets/software_keyboard.h"
|
||||
#include "frontend/applets/web_browser.h"
|
||||
@@ -205,6 +206,7 @@ struct System::Impl {
|
||||
GDBStub::Shutdown();
|
||||
Service::Shutdown();
|
||||
service_manager.reset();
|
||||
cheat_engine.reset();
|
||||
telemetry_session.reset();
|
||||
gpu_core.reset();
|
||||
|
||||
@@ -255,6 +257,8 @@ struct System::Impl {
|
||||
CpuCoreManager cpu_core_manager;
|
||||
bool is_powered_on = false;
|
||||
|
||||
std::unique_ptr<FileSys::CheatEngine> cheat_engine;
|
||||
|
||||
/// Frontend applets
|
||||
std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector;
|
||||
std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard;
|
||||
@@ -453,6 +457,13 @@ Tegra::DebugContext* System::GetGPUDebugContext() const {
|
||||
return impl->debug_context.get();
|
||||
}
|
||||
|
||||
void System::RegisterCheatList(const std::vector<FileSys::CheatList>& list,
|
||||
const std::string& build_id, VAddr code_region_start,
|
||||
VAddr code_region_end) {
|
||||
impl->cheat_engine =
|
||||
std::make_unique<FileSys::CheatEngine>(list, build_id, code_region_start, code_region_end);
|
||||
}
|
||||
|
||||
void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) {
|
||||
impl->virtual_filesystem = std::move(vfs);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ class WebBrowserApplet;
|
||||
} // namespace Core::Frontend
|
||||
|
||||
namespace FileSys {
|
||||
class CheatList;
|
||||
class VfsFilesystem;
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -253,6 +254,9 @@ public:
|
||||
|
||||
std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const;
|
||||
|
||||
void RegisterCheatList(const std::vector<FileSys::CheatList>& list, const std::string& build_id,
|
||||
VAddr code_region_start, VAddr code_region_end);
|
||||
|
||||
void SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet);
|
||||
|
||||
const Frontend::ProfileSelectApplet& GetProfileSelector() const;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <cinttypes>
|
||||
#include <limits>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/uint128.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
|
||||
@@ -60,4 +61,9 @@ s64 nsToCycles(u64 ns) {
|
||||
return (BASE_CLOCK_RATE * static_cast<s64>(ns)) / 1000000000;
|
||||
}
|
||||
|
||||
u64 CpuCyclesToClockCycles(u64 ticks) {
|
||||
const u128 temporal = Common::Multiply64Into128(ticks, CNTFREQ);
|
||||
return Common::Divide128On32(temporal, static_cast<u32>(BASE_CLOCK_RATE)).first;
|
||||
}
|
||||
|
||||
} // namespace Core::Timing
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Core::Timing {
|
||||
// The below clock rate is based on Switch's clockspeed being widely known as 1.020GHz
|
||||
// The exact value used is of course unverified.
|
||||
constexpr u64 BASE_CLOCK_RATE = 1019215872; // Switch clock speed is 1020MHz un/docked
|
||||
constexpr u64 CNTFREQ = 19200000; // Value from fusee.
|
||||
|
||||
inline s64 msToCycles(int ms) {
|
||||
// since ms is int there is no way to overflow
|
||||
@@ -61,4 +62,6 @@ inline u64 cyclesToMs(s64 cycles) {
|
||||
return cycles * 1000 / BASE_CLOCK_RATE;
|
||||
}
|
||||
|
||||
u64 CpuCyclesToClockCycles(u64 ticks);
|
||||
|
||||
} // namespace Core::Timing
|
||||
|
||||
493
src/core/file_sys/cheat_engine.cpp
Normal file
493
src/core/file_sys/cheat_engine.cpp
Normal file
@@ -0,0 +1,493 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <locale>
|
||||
#include "common/hex_util.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/file_sys/cheat_engine.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/hid/controllers/controller_base.h"
|
||||
#include "core/hle/service/hid/controllers/npad.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr u64 CHEAT_ENGINE_TICKS = Core::Timing::BASE_CLOCK_RATE / 60;
|
||||
constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
|
||||
|
||||
u64 Cheat::Address() const {
|
||||
u64 out;
|
||||
std::memcpy(&out, raw.data(), sizeof(u64));
|
||||
return Common::swap64(out) & 0xFFFFFFFFFF;
|
||||
}
|
||||
|
||||
u64 Cheat::ValueWidth(u64 offset) const {
|
||||
return Value(offset, width);
|
||||
}
|
||||
|
||||
u64 Cheat::Value(u64 offset, u64 width) const {
|
||||
u64 out;
|
||||
std::memcpy(&out, raw.data() + offset, sizeof(u64));
|
||||
out = Common::swap64(out);
|
||||
if (width == 8)
|
||||
return out;
|
||||
return out & ((1ull << (width * CHAR_BIT)) - 1);
|
||||
}
|
||||
|
||||
u32 Cheat::KeypadValue() const {
|
||||
u32 out;
|
||||
std::memcpy(&out, raw.data(), sizeof(u32));
|
||||
return Common::swap32(out) & 0x0FFFFFFF;
|
||||
}
|
||||
|
||||
void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end,
|
||||
VAddr heap_end, MemoryWriter writer, MemoryReader reader) {
|
||||
this->main_region_begin = main_begin;
|
||||
this->main_region_end = main_end;
|
||||
this->heap_region_begin = heap_begin;
|
||||
this->heap_region_end = heap_end;
|
||||
this->writer = writer;
|
||||
this->reader = reader;
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
|
||||
|
||||
void CheatList::Execute() {
|
||||
MICROPROFILE_SCOPE(Cheat_Engine);
|
||||
|
||||
std::fill(scratch.begin(), scratch.end(), 0);
|
||||
in_standard = false;
|
||||
for (std::size_t i = 0; i < master_list.size(); ++i) {
|
||||
LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first);
|
||||
current_block = i;
|
||||
ExecuteBlock(master_list[i].second);
|
||||
}
|
||||
|
||||
in_standard = true;
|
||||
for (std::size_t i = 0; i < standard_list.size(); ++i) {
|
||||
LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first);
|
||||
current_block = i;
|
||||
ExecuteBlock(standard_list[i].second);
|
||||
}
|
||||
}
|
||||
|
||||
CheatList::CheatList(ProgramSegment master, ProgramSegment standard)
|
||||
: master_list(master), standard_list(standard) {}
|
||||
|
||||
bool CheatList::EvaluateConditional(const Cheat& cheat) const {
|
||||
using ComparisonFunction = bool (*)(u64, u64);
|
||||
constexpr std::array<ComparisonFunction, 6> comparison_functions{
|
||||
[](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; },
|
||||
[](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; },
|
||||
[](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; },
|
||||
};
|
||||
|
||||
if (cheat.type == CodeType::ConditionalInput) {
|
||||
const auto applet_resource = Core::System::GetInstance()
|
||||
.ServiceManager()
|
||||
.GetService<Service::HID::Hid>("hid")
|
||||
->GetAppletResource();
|
||||
if (applet_resource == nullptr) {
|
||||
LOG_WARNING(
|
||||
Common_Filesystem,
|
||||
"Attempted to evaluate input conditional, but applet resource is not initialized!");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto press_state =
|
||||
applet_resource
|
||||
->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)
|
||||
.GetAndResetPressState();
|
||||
return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0;
|
||||
}
|
||||
|
||||
ASSERT(cheat.type == CodeType::Conditional);
|
||||
|
||||
const auto offset =
|
||||
cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
|
||||
ASSERT(static_cast<u8>(cheat.comparison_op.Value()) < 6);
|
||||
auto* function = comparison_functions[static_cast<u8>(cheat.comparison_op.Value())];
|
||||
const auto addr = cheat.Address() + offset;
|
||||
|
||||
return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8));
|
||||
}
|
||||
|
||||
void CheatList::ProcessBlockPairs(const Block& block) {
|
||||
block_pairs.clear();
|
||||
|
||||
u64 scope = 0;
|
||||
std::map<u64, u64> pairs;
|
||||
|
||||
for (std::size_t i = 0; i < block.size(); ++i) {
|
||||
const auto& cheat = block[i];
|
||||
|
||||
switch (cheat.type) {
|
||||
case CodeType::Conditional:
|
||||
case CodeType::ConditionalInput:
|
||||
pairs.insert_or_assign(scope, i);
|
||||
++scope;
|
||||
break;
|
||||
case CodeType::EndConditional: {
|
||||
--scope;
|
||||
const auto idx = pairs.at(scope);
|
||||
block_pairs.insert_or_assign(idx, i);
|
||||
break;
|
||||
}
|
||||
case CodeType::Loop: {
|
||||
if (cheat.end_of_loop) {
|
||||
--scope;
|
||||
const auto idx = pairs.at(scope);
|
||||
block_pairs.insert_or_assign(idx, i);
|
||||
} else {
|
||||
pairs.insert_or_assign(scope, i);
|
||||
++scope;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheatList::WriteImmediate(const Cheat& cheat) {
|
||||
const auto offset =
|
||||
cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
|
||||
const auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
const auto addr = cheat.Address() + offset + register_3;
|
||||
LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr,
|
||||
cheat.Value(8, cheat.width));
|
||||
writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8));
|
||||
}
|
||||
|
||||
void CheatList::BeginConditional(const Cheat& cheat) {
|
||||
if (EvaluateConditional(cheat)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto iter = block_pairs.find(current_index);
|
||||
ASSERT(iter != block_pairs.end());
|
||||
current_index = iter->second - 1;
|
||||
}
|
||||
|
||||
void CheatList::EndConditional(const Cheat& cheat) {
|
||||
LOG_DEBUG(Common_Filesystem, "Ending conditional block.");
|
||||
}
|
||||
|
||||
void CheatList::Loop(const Cheat& cheat) {
|
||||
if (cheat.end_of_loop.Value())
|
||||
ASSERT(!cheat.end_of_loop.Value());
|
||||
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
const auto iter = block_pairs.find(current_index);
|
||||
ASSERT(iter != block_pairs.end());
|
||||
ASSERT(iter->first < iter->second);
|
||||
|
||||
for (int i = cheat.Value(4, 4); i >= 0; --i) {
|
||||
register_3 = i;
|
||||
for (std::size_t c = iter->first + 1; c < iter->second; ++c) {
|
||||
current_index = c;
|
||||
ExecuteSingleCheat(
|
||||
(in_standard ? standard_list : master_list)[current_block].second[c]);
|
||||
}
|
||||
}
|
||||
|
||||
current_index = iter->second;
|
||||
}
|
||||
|
||||
void CheatList::LoadImmediate(const Cheat& cheat) {
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3,
|
||||
cheat.Value(4, 8));
|
||||
register_3 = cheat.Value(4, 8);
|
||||
}
|
||||
|
||||
void CheatList::LoadIndexed(const Cheat& cheat) {
|
||||
const auto offset =
|
||||
cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address();
|
||||
LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}",
|
||||
cheat.register_3, addr);
|
||||
register_3 = reader(cheat.width, SanitizeAddress(addr));
|
||||
}
|
||||
|
||||
void CheatList::StoreIndexed(const Cheat& cheat) {
|
||||
const auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
const auto addr =
|
||||
register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0);
|
||||
LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}",
|
||||
cheat.Value(4, cheat.width), addr);
|
||||
writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4));
|
||||
}
|
||||
|
||||
void CheatList::RegisterArithmetic(const Cheat& cheat) {
|
||||
using ArithmeticFunction = u64 (*)(u64, u64);
|
||||
constexpr std::array<ArithmeticFunction, 5> arithmetic_functions{
|
||||
[](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; },
|
||||
[](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; },
|
||||
[](u64 a, u64 b) { return a >> b; },
|
||||
};
|
||||
|
||||
using ArithmeticOverflowCheck = bool (*)(u64, u64);
|
||||
constexpr std::array<ArithmeticOverflowCheck, 5> arithmetic_overflow_checks{
|
||||
[](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() - b); }, // a + b
|
||||
[](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() + b); }, // a - b
|
||||
[](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() / b); }, // a * b
|
||||
[](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b
|
||||
[](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; }, // a >> b
|
||||
};
|
||||
|
||||
static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks),
|
||||
"Missing or have extra arithmetic overflow checks compared to functions!");
|
||||
|
||||
auto& register_3 = scratch.at(cheat.register_3);
|
||||
|
||||
ASSERT(static_cast<u8>(cheat.arithmetic_op.Value()) < 5);
|
||||
auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())];
|
||||
auto* overflow_function =
|
||||
arithmetic_overflow_checks[static_cast<u8>(cheat.arithmetic_op.Value())];
|
||||
LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}",
|
||||
cheat.register_3, cheat.ValueWidth(4));
|
||||
|
||||
if (overflow_function(register_3, cheat.ValueWidth(4))) {
|
||||
LOG_WARNING(Common_Filesystem,
|
||||
"overflow will occur when performing arithmetic operation={:02X} with operands "
|
||||
"a={:016X}, b={:016X}!",
|
||||
static_cast<u8>(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4));
|
||||
}
|
||||
|
||||
register_3 = function(register_3, cheat.ValueWidth(4));
|
||||
}
|
||||
|
||||
void CheatList::BeginConditionalInput(const Cheat& cheat) {
|
||||
if (EvaluateConditional(cheat))
|
||||
return;
|
||||
|
||||
const auto iter = block_pairs.find(current_index);
|
||||
ASSERT(iter != block_pairs.end());
|
||||
current_index = iter->second - 1;
|
||||
}
|
||||
|
||||
VAddr CheatList::SanitizeAddress(VAddr in) const {
|
||||
if ((in < main_region_begin || in >= main_region_end) &&
|
||||
(in < heap_region_begin || in >= heap_region_end)) {
|
||||
LOG_ERROR(Common_Filesystem,
|
||||
"Cheat attempting to access memory at invalid address={:016X}, if this persists, "
|
||||
"the cheat may be incorrect. However, this may be normal early in execution if "
|
||||
"the game has not properly set up yet.",
|
||||
in);
|
||||
return 0; ///< Invalid addresses will hard crash
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
void CheatList::ExecuteSingleCheat(const Cheat& cheat) {
|
||||
using CheatOperationFunction = void (CheatList::*)(const Cheat&);
|
||||
constexpr std::array<CheatOperationFunction, 9> cheat_operation_functions{
|
||||
&CheatList::WriteImmediate, &CheatList::BeginConditional,
|
||||
&CheatList::EndConditional, &CheatList::Loop,
|
||||
&CheatList::LoadImmediate, &CheatList::LoadIndexed,
|
||||
&CheatList::StoreIndexed, &CheatList::RegisterArithmetic,
|
||||
&CheatList::BeginConditionalInput,
|
||||
};
|
||||
|
||||
const auto index = static_cast<u8>(cheat.type.Value());
|
||||
ASSERT(index < sizeof(cheat_operation_functions));
|
||||
const auto op = cheat_operation_functions[index];
|
||||
(this->*op)(cheat);
|
||||
}
|
||||
|
||||
void CheatList::ExecuteBlock(const Block& block) {
|
||||
encountered_loops.clear();
|
||||
|
||||
ProcessBlockPairs(block);
|
||||
for (std::size_t i = 0; i < block.size(); ++i) {
|
||||
current_index = i;
|
||||
ExecuteSingleCheat(block[i]);
|
||||
i = current_index;
|
||||
}
|
||||
}
|
||||
|
||||
CheatParser::~CheatParser() = default;
|
||||
|
||||
CheatList CheatParser::MakeCheatList(CheatList::ProgramSegment master,
|
||||
CheatList::ProgramSegment standard) const {
|
||||
return {master, standard};
|
||||
}
|
||||
|
||||
TextCheatParser::~TextCheatParser() = default;
|
||||
|
||||
CheatList TextCheatParser::Parse(const std::vector<u8>& data) const {
|
||||
std::stringstream ss;
|
||||
ss.write(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
|
||||
std::vector<std::string> lines;
|
||||
std::string stream_line;
|
||||
while (std::getline(ss, stream_line)) {
|
||||
// Remove a trailing \r
|
||||
if (!stream_line.empty() && stream_line.back() == '\r')
|
||||
stream_line.pop_back();
|
||||
lines.push_back(std::move(stream_line));
|
||||
}
|
||||
|
||||
CheatList::ProgramSegment master_list;
|
||||
CheatList::ProgramSegment standard_list;
|
||||
|
||||
for (std::size_t i = 0; i < lines.size(); ++i) {
|
||||
auto line = lines[i];
|
||||
|
||||
if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
|
||||
const auto master = line[0] == '{';
|
||||
const auto begin = master ? line.find('{') : line.find('[');
|
||||
const auto end = master ? line.rfind('}') : line.rfind(']');
|
||||
|
||||
ASSERT(begin != std::string::npos && end != std::string::npos);
|
||||
|
||||
const std::string patch_name{line.begin() + begin + 1, line.begin() + end};
|
||||
CheatList::Block block{};
|
||||
|
||||
while (i < lines.size() - 1) {
|
||||
line = lines[++i];
|
||||
if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
|
||||
--i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.size() < 8)
|
||||
continue;
|
||||
|
||||
Cheat out{};
|
||||
out.raw = ParseSingleLineCheat(line);
|
||||
block.push_back(out);
|
||||
}
|
||||
|
||||
(master ? master_list : standard_list).emplace_back(patch_name, block);
|
||||
}
|
||||
}
|
||||
|
||||
return MakeCheatList(master_list, standard_list);
|
||||
}
|
||||
|
||||
std::array<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const {
|
||||
std::array<u8, 16> out{};
|
||||
|
||||
if (line.size() < 8)
|
||||
return out;
|
||||
|
||||
const auto word1 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data(), 8});
|
||||
std::memcpy(out.data(), word1.data(), sizeof(u32));
|
||||
|
||||
if (line.size() < 17 || line[8] != ' ')
|
||||
return out;
|
||||
|
||||
const auto word2 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 9, 8});
|
||||
std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32));
|
||||
|
||||
if (line.size() < 26 || line[17] != ' ') {
|
||||
// Perform shifting in case value is truncated early.
|
||||
const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
|
||||
if (type == CodeType::Loop || type == CodeType::LoadImmediate ||
|
||||
type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) {
|
||||
std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32));
|
||||
std::memset(out.data() + 4, 0, sizeof(u32));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
const auto word3 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 18, 8});
|
||||
std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32));
|
||||
|
||||
if (line.size() < 35 || line[26] != ' ') {
|
||||
// Perform shifting in case value is truncated early.
|
||||
const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
|
||||
if (type == CodeType::WriteImmediate || type == CodeType::Conditional) {
|
||||
std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32));
|
||||
std::memset(out.data() + 8, 0, sizeof(u32));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
const auto word4 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 27, 8});
|
||||
std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
u64 MemoryReadImpl(u32 width, VAddr addr) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
return Memory::Read8(addr);
|
||||
case 2:
|
||||
return Memory::Read16(addr);
|
||||
case 4:
|
||||
return Memory::Read32(addr);
|
||||
case 8:
|
||||
return Memory::Read64(addr);
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryWriteImpl(u32 width, VAddr addr, u64 value) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
Memory::Write8(addr, static_cast<u8>(value));
|
||||
break;
|
||||
case 2:
|
||||
Memory::Write16(addr, static_cast<u16>(value));
|
||||
break;
|
||||
case 4:
|
||||
Memory::Write32(addr, static_cast<u32>(value));
|
||||
break;
|
||||
case 8:
|
||||
Memory::Write64(addr, value);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
CheatEngine::CheatEngine(std::vector<CheatList> cheats, const std::string& build_id,
|
||||
VAddr code_region_start, VAddr code_region_end)
|
||||
: cheats(std::move(cheats)) {
|
||||
auto& core_timing{Core::System::GetInstance().CoreTiming()};
|
||||
event = core_timing.RegisterEvent(
|
||||
"CheatEngine::FrameCallback::" + build_id,
|
||||
[this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
|
||||
core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event);
|
||||
|
||||
const auto& vm_manager = Core::System::GetInstance().CurrentProcess()->VMManager();
|
||||
for (auto& list : this->cheats) {
|
||||
list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(),
|
||||
code_region_end, vm_manager.GetHeapRegionEndAddress(),
|
||||
&MemoryWriteImpl, &MemoryReadImpl);
|
||||
}
|
||||
}
|
||||
|
||||
CheatEngine::~CheatEngine() {
|
||||
auto& core_timing{Core::System::GetInstance().CoreTiming()};
|
||||
core_timing.UnscheduleEvent(event, 0);
|
||||
}
|
||||
|
||||
void CheatEngine::FrameCallback(u64 userdata, int cycles_late) {
|
||||
for (auto& list : cheats)
|
||||
list.Execute();
|
||||
|
||||
auto& core_timing{Core::System::GetInstance().CoreTiming()};
|
||||
core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
227
src/core/file_sys/cheat_engine.h
Normal file
227
src/core/file_sys/cheat_engine.h
Normal file
@@ -0,0 +1,227 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
struct EventType;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
enum class CodeType : u32 {
|
||||
// 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY
|
||||
// Writes a T sized value Y to the address A added to the value of register R in memory domain M
|
||||
WriteImmediate = 0,
|
||||
|
||||
// 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY
|
||||
// Compares the T sized value Y to the value at address A in memory domain M using the
|
||||
// conditional function C. If success, continues execution. If failure, jumps to the matching
|
||||
// EndConditional statement.
|
||||
Conditional = 1,
|
||||
|
||||
// 20000000
|
||||
// Terminates a Conditional or ConditionalInput block.
|
||||
EndConditional = 2,
|
||||
|
||||
// 300R0000 VVVVVVVV
|
||||
// Starts looping V times, storing the current count in register R.
|
||||
// Loop block is terminated with a matching 310R0000.
|
||||
Loop = 3,
|
||||
|
||||
// 400R0000 VVVVVVVV VVVVVVVV
|
||||
// Sets the value of register R to the value V.
|
||||
LoadImmediate = 4,
|
||||
|
||||
// 5TMRI0AA AAAAAAAA
|
||||
// Sets the value of register R to the value of width T at address A in memory domain M, with
|
||||
// the current value of R added to the address if I == 1.
|
||||
LoadIndexed = 5,
|
||||
|
||||
// 6T0RIFG0 VVVVVVVV VVVVVVVV
|
||||
// Writes the value V of width T to the memory address stored in register R. Adds the value of
|
||||
// register G to the final calculation if F is nonzero. Increments the value of register R by T
|
||||
// after operation if I is nonzero.
|
||||
StoreIndexed = 6,
|
||||
|
||||
// 7T0RA000 VVVVVVVV
|
||||
// Performs the arithmetic operation A on the value in register R and the value V of width T,
|
||||
// storing the result in register R.
|
||||
RegisterArithmetic = 7,
|
||||
|
||||
// 8KKKKKKK
|
||||
// Checks to see if any of the buttons defined by the bitmask K are pressed. If any are,
|
||||
// execution continues. If none are, execution skips to the next EndConditional command.
|
||||
ConditionalInput = 8,
|
||||
};
|
||||
|
||||
enum class MemoryType : u32 {
|
||||
// Addressed relative to start of main NSO
|
||||
MainNSO = 0,
|
||||
|
||||
// Addressed relative to start of heap
|
||||
Heap = 1,
|
||||
};
|
||||
|
||||
enum class ArithmeticOp : u32 {
|
||||
Add = 0,
|
||||
Sub = 1,
|
||||
Mult = 2,
|
||||
LShift = 3,
|
||||
RShift = 4,
|
||||
};
|
||||
|
||||
enum class ComparisonOp : u32 {
|
||||
GreaterThan = 1,
|
||||
GreaterThanEqual = 2,
|
||||
LessThan = 3,
|
||||
LessThanEqual = 4,
|
||||
Equal = 5,
|
||||
Inequal = 6,
|
||||
};
|
||||
|
||||
union Cheat {
|
||||
std::array<u8, 16> raw;
|
||||
|
||||
BitField<4, 4, CodeType> type;
|
||||
BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes.
|
||||
BitField<0, 4, u32> end_of_loop;
|
||||
BitField<12, 4, MemoryType> memory_type;
|
||||
BitField<8, 4, u32> register_3;
|
||||
BitField<8, 4, ComparisonOp> comparison_op;
|
||||
BitField<20, 4, u32> load_from_register;
|
||||
BitField<20, 4, u32> increment_register;
|
||||
BitField<20, 4, ArithmeticOp> arithmetic_op;
|
||||
BitField<16, 4, u32> add_additional_register;
|
||||
BitField<28, 4, u32> register_6;
|
||||
|
||||
u64 Address() const;
|
||||
u64 ValueWidth(u64 offset) const;
|
||||
u64 Value(u64 offset, u64 width) const;
|
||||
u32 KeypadValue() const;
|
||||
};
|
||||
|
||||
class CheatParser;
|
||||
|
||||
// Represents a full collection of cheats for a game. The Execute function should be called every
|
||||
// interval that all cheats should be executed. Clients should not directly instantiate this class
|
||||
// (hence private constructor), they should instead receive an instance from CheatParser, which
|
||||
// guarantees the list is always in an acceptable state.
|
||||
class CheatList {
|
||||
public:
|
||||
friend class CheatParser;
|
||||
|
||||
using Block = std::vector<Cheat>;
|
||||
using ProgramSegment = std::vector<std::pair<std::string, Block>>;
|
||||
|
||||
// (width in bytes, address, value)
|
||||
using MemoryWriter = void (*)(u32, VAddr, u64);
|
||||
// (width in bytes, address) -> value
|
||||
using MemoryReader = u64 (*)(u32, VAddr);
|
||||
|
||||
void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end,
|
||||
MemoryWriter writer, MemoryReader reader);
|
||||
|
||||
void Execute();
|
||||
|
||||
private:
|
||||
CheatList(ProgramSegment master, ProgramSegment standard);
|
||||
|
||||
void ProcessBlockPairs(const Block& block);
|
||||
void ExecuteSingleCheat(const Cheat& cheat);
|
||||
|
||||
void ExecuteBlock(const Block& block);
|
||||
|
||||
bool EvaluateConditional(const Cheat& cheat) const;
|
||||
|
||||
// Individual cheat operations
|
||||
void WriteImmediate(const Cheat& cheat);
|
||||
void BeginConditional(const Cheat& cheat);
|
||||
void EndConditional(const Cheat& cheat);
|
||||
void Loop(const Cheat& cheat);
|
||||
void LoadImmediate(const Cheat& cheat);
|
||||
void LoadIndexed(const Cheat& cheat);
|
||||
void StoreIndexed(const Cheat& cheat);
|
||||
void RegisterArithmetic(const Cheat& cheat);
|
||||
void BeginConditionalInput(const Cheat& cheat);
|
||||
|
||||
VAddr SanitizeAddress(VAddr in) const;
|
||||
|
||||
// Master Codes are defined as codes that cannot be disabled and are run prior to all
|
||||
// others.
|
||||
ProgramSegment master_list;
|
||||
// All other codes
|
||||
ProgramSegment standard_list;
|
||||
|
||||
bool in_standard = false;
|
||||
|
||||
// 16 (0x0-0xF) scratch registers that can be used by cheats
|
||||
std::array<u64, 16> scratch{};
|
||||
|
||||
MemoryWriter writer = nullptr;
|
||||
MemoryReader reader = nullptr;
|
||||
|
||||
u64 main_region_begin{};
|
||||
u64 heap_region_begin{};
|
||||
u64 main_region_end{};
|
||||
u64 heap_region_end{};
|
||||
|
||||
u64 current_block{};
|
||||
// The current index of the cheat within the current Block
|
||||
u64 current_index{};
|
||||
|
||||
// The 'stack' of the program. When a conditional or loop statement is encountered, its index is
|
||||
// pushed onto this queue. When a end block is encountered, the condition is checked.
|
||||
std::map<u64, u64> block_pairs;
|
||||
|
||||
std::set<u64> encountered_loops;
|
||||
};
|
||||
|
||||
// Intermediary class that parses a text file or other disk format for storing cheats into a
|
||||
// CheatList object, that can be used for execution.
|
||||
class CheatParser {
|
||||
public:
|
||||
virtual ~CheatParser();
|
||||
|
||||
virtual CheatList Parse(const std::vector<u8>& data) const = 0;
|
||||
|
||||
protected:
|
||||
CheatList MakeCheatList(CheatList::ProgramSegment master,
|
||||
CheatList::ProgramSegment standard) const;
|
||||
};
|
||||
|
||||
// CheatParser implementation that parses text files
|
||||
class TextCheatParser final : public CheatParser {
|
||||
public:
|
||||
~TextCheatParser() override;
|
||||
|
||||
CheatList Parse(const std::vector<u8>& data) const override;
|
||||
|
||||
private:
|
||||
std::array<u8, 16> ParseSingleLineCheat(const std::string& line) const;
|
||||
};
|
||||
|
||||
// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
|
||||
class CheatEngine final {
|
||||
public:
|
||||
CheatEngine(std::vector<CheatList> cheats, const std::string& build_id, VAddr code_region_start,
|
||||
VAddr code_region_end);
|
||||
~CheatEngine();
|
||||
|
||||
private:
|
||||
void FrameCallback(u64 userdata, int cycles_late);
|
||||
|
||||
Core::Timing::EventType* event;
|
||||
|
||||
std::vector<CheatList> cheats;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -24,13 +24,26 @@ namespace FileSys {
|
||||
|
||||
union NCASectionHeader;
|
||||
|
||||
/// Describes the type of content within an NCA archive.
|
||||
enum class NCAContentType : u8 {
|
||||
/// Executable-related data
|
||||
Program = 0,
|
||||
|
||||
/// Metadata.
|
||||
Meta = 1,
|
||||
|
||||
/// Access control data.
|
||||
Control = 2,
|
||||
|
||||
/// Information related to the game manual
|
||||
/// e.g. Legal information, etc.
|
||||
Manual = 3,
|
||||
|
||||
/// System data.
|
||||
Data = 4,
|
||||
Data_Unknown5 = 5, ///< Seems to be used on some system archives
|
||||
|
||||
/// Data that can be accessed by applications.
|
||||
PublicData = 5,
|
||||
};
|
||||
|
||||
enum class NCASectionCryptoType : u8 {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "common/file_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
@@ -19,6 +20,7 @@
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/loader/nso.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace FileSys {
|
||||
@@ -31,14 +33,6 @@ constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{
|
||||
"subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9",
|
||||
};
|
||||
|
||||
struct NSOBuildHeader {
|
||||
u32_le magic;
|
||||
INSERT_PADDING_BYTES(0x3C);
|
||||
std::array<u8, 0x20> build_id;
|
||||
INSERT_PADDING_BYTES(0xA0);
|
||||
};
|
||||
static_assert(sizeof(NSOBuildHeader) == 0x100, "NSOBuildHeader has incorrect size.");
|
||||
|
||||
std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
|
||||
std::array<u8, sizeof(u32)> bytes{};
|
||||
bytes[0] = version % SINGLE_BYTE_MODULUS;
|
||||
@@ -162,14 +156,16 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
|
||||
}
|
||||
|
||||
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
|
||||
if (nso.size() < 0x100)
|
||||
if (nso.size() < sizeof(Loader::NSOHeader)) {
|
||||
return nso;
|
||||
}
|
||||
|
||||
NSOBuildHeader header;
|
||||
std::memcpy(&header, nso.data(), sizeof(NSOBuildHeader));
|
||||
Loader::NSOHeader header;
|
||||
std::memcpy(&header, nso.data(), sizeof(header));
|
||||
|
||||
if (header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
|
||||
if (header.magic != Common::MakeMagic('N', 'S', 'O', '0')) {
|
||||
return nso;
|
||||
}
|
||||
|
||||
const auto build_id_raw = Common::HexArrayToString(header.build_id);
|
||||
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
|
||||
@@ -212,9 +208,11 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
|
||||
}
|
||||
}
|
||||
|
||||
if (out.size() < 0x100)
|
||||
if (out.size() < sizeof(Loader::NSOHeader)) {
|
||||
return nso;
|
||||
std::memcpy(out.data(), &header, sizeof(NSOBuildHeader));
|
||||
}
|
||||
|
||||
std::memcpy(out.data(), &header, sizeof(header));
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -232,6 +230,57 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
|
||||
return !CollectPatches(patch_dirs, build_id).empty();
|
||||
}
|
||||
|
||||
static std::optional<CheatList> ReadCheatFileFromFolder(u64 title_id,
|
||||
const std::array<u8, 0x20>& build_id_,
|
||||
const VirtualDir& base_path, bool upper) {
|
||||
const auto build_id_raw = Common::HexArrayToString(build_id_, upper);
|
||||
const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
|
||||
const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
|
||||
|
||||
if (file == nullptr) {
|
||||
LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
|
||||
title_id, build_id);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<u8> data(file->GetSize());
|
||||
if (file->Read(data.data(), data.size()) != data.size()) {
|
||||
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
|
||||
title_id, build_id);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TextCheatParser parser;
|
||||
return parser.Parse(data);
|
||||
}
|
||||
|
||||
std::vector<CheatList> PatchManager::CreateCheatList(const std::array<u8, 32>& build_id_) const {
|
||||
std::vector<CheatList> out;
|
||||
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
out.reserve(patch_dirs.size());
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
auto cheats_dir = subdir->GetSubdirectory("cheats");
|
||||
if (cheats_dir != nullptr) {
|
||||
auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true);
|
||||
if (res.has_value()) {
|
||||
out.push_back(std::move(*res));
|
||||
continue;
|
||||
}
|
||||
|
||||
res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false);
|
||||
if (res.has_value())
|
||||
out.push_back(std::move(*res));
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
|
||||
@@ -403,6 +452,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
||||
}
|
||||
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
|
||||
AppendCommaIfNotEmpty(types, "LayeredFS");
|
||||
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("cheats")))
|
||||
AppendCommaIfNotEmpty(types, "Cheats");
|
||||
|
||||
if (types.empty())
|
||||
continue;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/cheat_engine.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
@@ -45,6 +46,9 @@ public:
|
||||
// Used to prevent expensive copies in NSO loader.
|
||||
bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
|
||||
|
||||
// Creates a CheatList object with all
|
||||
std::vector<CheatList> CreateCheatList(const std::array<u8, 0x20>& build_id) const;
|
||||
|
||||
// Currently tracked RomFS patches:
|
||||
// - Game Updates
|
||||
// - LayeredFS
|
||||
|
||||
@@ -94,7 +94,7 @@ static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
|
||||
case NCAContentType::Control:
|
||||
return ContentRecordType::Control;
|
||||
case NCAContentType::Data:
|
||||
case NCAContentType::Data_Unknown5:
|
||||
case NCAContentType::PublicData:
|
||||
return ContentRecordType::Data;
|
||||
case NCAContentType::Manual:
|
||||
// TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal.
|
||||
|
||||
@@ -39,10 +39,10 @@ struct CommandHeader {
|
||||
union {
|
||||
u32_le raw_low;
|
||||
BitField<0, 16, CommandType> type;
|
||||
BitField<16, 4, u32_le> num_buf_x_descriptors;
|
||||
BitField<20, 4, u32_le> num_buf_a_descriptors;
|
||||
BitField<24, 4, u32_le> num_buf_b_descriptors;
|
||||
BitField<28, 4, u32_le> num_buf_w_descriptors;
|
||||
BitField<16, 4, u32> num_buf_x_descriptors;
|
||||
BitField<20, 4, u32> num_buf_a_descriptors;
|
||||
BitField<24, 4, u32> num_buf_b_descriptors;
|
||||
BitField<28, 4, u32> num_buf_w_descriptors;
|
||||
};
|
||||
|
||||
enum class BufferDescriptorCFlag : u32 {
|
||||
@@ -53,28 +53,28 @@ struct CommandHeader {
|
||||
|
||||
union {
|
||||
u32_le raw_high;
|
||||
BitField<0, 10, u32_le> data_size;
|
||||
BitField<0, 10, u32> data_size;
|
||||
BitField<10, 4, BufferDescriptorCFlag> buf_c_descriptor_flags;
|
||||
BitField<31, 1, u32_le> enable_handle_descriptor;
|
||||
BitField<31, 1, u32> enable_handle_descriptor;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(CommandHeader) == 8, "CommandHeader size is incorrect");
|
||||
|
||||
union HandleDescriptorHeader {
|
||||
u32_le raw_high;
|
||||
BitField<0, 1, u32_le> send_current_pid;
|
||||
BitField<1, 4, u32_le> num_handles_to_copy;
|
||||
BitField<5, 4, u32_le> num_handles_to_move;
|
||||
BitField<0, 1, u32> send_current_pid;
|
||||
BitField<1, 4, u32> num_handles_to_copy;
|
||||
BitField<5, 4, u32> num_handles_to_move;
|
||||
};
|
||||
static_assert(sizeof(HandleDescriptorHeader) == 4, "HandleDescriptorHeader size is incorrect");
|
||||
|
||||
struct BufferDescriptorX {
|
||||
union {
|
||||
BitField<0, 6, u32_le> counter_bits_0_5;
|
||||
BitField<6, 3, u32_le> address_bits_36_38;
|
||||
BitField<9, 3, u32_le> counter_bits_9_11;
|
||||
BitField<12, 4, u32_le> address_bits_32_35;
|
||||
BitField<16, 16, u32_le> size;
|
||||
BitField<0, 6, u32> counter_bits_0_5;
|
||||
BitField<6, 3, u32> address_bits_36_38;
|
||||
BitField<9, 3, u32> counter_bits_9_11;
|
||||
BitField<12, 4, u32> address_bits_32_35;
|
||||
BitField<16, 16, u32> size;
|
||||
};
|
||||
|
||||
u32_le address_bits_0_31;
|
||||
@@ -103,10 +103,10 @@ struct BufferDescriptorABW {
|
||||
u32_le address_bits_0_31;
|
||||
|
||||
union {
|
||||
BitField<0, 2, u32_le> flags;
|
||||
BitField<2, 3, u32_le> address_bits_36_38;
|
||||
BitField<24, 4, u32_le> size_bits_32_35;
|
||||
BitField<28, 4, u32_le> address_bits_32_35;
|
||||
BitField<0, 2, u32> flags;
|
||||
BitField<2, 3, u32> address_bits_36_38;
|
||||
BitField<24, 4, u32> size_bits_32_35;
|
||||
BitField<28, 4, u32> address_bits_32_35;
|
||||
};
|
||||
|
||||
VAddr Address() const {
|
||||
@@ -128,8 +128,8 @@ struct BufferDescriptorC {
|
||||
u32_le address_bits_0_31;
|
||||
|
||||
union {
|
||||
BitField<0, 16, u32_le> address_bits_32_47;
|
||||
BitField<16, 16, u32_le> size;
|
||||
BitField<0, 16, u32> address_bits_32_47;
|
||||
BitField<16, 16, u32> size;
|
||||
};
|
||||
|
||||
VAddr Address() const {
|
||||
@@ -167,8 +167,8 @@ struct DomainMessageHeader {
|
||||
struct {
|
||||
union {
|
||||
BitField<0, 8, CommandType> command;
|
||||
BitField<8, 8, u32_le> input_object_count;
|
||||
BitField<16, 16, u32_le> size;
|
||||
BitField<8, 8, u32> input_object_count;
|
||||
BitField<16, 16, u32> size;
|
||||
};
|
||||
u32_le object_id;
|
||||
INSERT_PADDING_WORDS(2);
|
||||
|
||||
@@ -274,6 +274,20 @@ inline void ResponseBuilder::Push(u64 value) {
|
||||
Push(static_cast<u32>(value >> 32));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void ResponseBuilder::Push(float value) {
|
||||
u32 integral;
|
||||
std::memcpy(&integral, &value, sizeof(u32));
|
||||
Push(integral);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void ResponseBuilder::Push(double value) {
|
||||
u64 integral;
|
||||
std::memcpy(&integral, &value, sizeof(u64));
|
||||
Push(integral);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void ResponseBuilder::Push(bool value) {
|
||||
Push(static_cast<u8>(value));
|
||||
@@ -415,6 +429,22 @@ inline s64 RequestParser::Pop() {
|
||||
return static_cast<s64>(Pop<u64>());
|
||||
}
|
||||
|
||||
template <>
|
||||
inline float RequestParser::Pop() {
|
||||
const u32 value = Pop<u32>();
|
||||
float real;
|
||||
std::memcpy(&real, &value, sizeof(real));
|
||||
return real;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline double RequestParser::Pop() {
|
||||
const u64 value = Pop<u64>();
|
||||
float real;
|
||||
std::memcpy(&real, &value, sizeof(real));
|
||||
return real;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool RequestParser::Pop() {
|
||||
return Pop<u8>() != 0;
|
||||
|
||||
12
src/core/hle/kernel/code_set.cpp
Normal file
12
src/core/hle/kernel/code_set.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/hle/kernel/code_set.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
CodeSet::CodeSet() = default;
|
||||
CodeSet::~CodeSet() = default;
|
||||
|
||||
} // namespace Kernel
|
||||
90
src/core/hle/kernel/code_set.h
Normal file
90
src/core/hle/kernel/code_set.h
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
/**
|
||||
* Represents executable data that may be loaded into a kernel process.
|
||||
*
|
||||
* A code set consists of three basic segments:
|
||||
* - A code (AKA text) segment,
|
||||
* - A read-only data segment (rodata)
|
||||
* - A data segment
|
||||
*
|
||||
* The code segment is the portion of the object file that contains
|
||||
* executable instructions.
|
||||
*
|
||||
* The read-only data segment in the portion of the object file that
|
||||
* contains (as one would expect) read-only data, such as fixed constant
|
||||
* values and data structures.
|
||||
*
|
||||
* The data segment is similar to the read-only data segment -- it contains
|
||||
* variables and data structures that have predefined values, however,
|
||||
* entities within this segment can be modified.
|
||||
*/
|
||||
struct CodeSet final {
|
||||
/// A single segment within a code set.
|
||||
struct Segment final {
|
||||
/// The byte offset that this segment is located at.
|
||||
std::size_t offset = 0;
|
||||
|
||||
/// The address to map this segment to.
|
||||
VAddr addr = 0;
|
||||
|
||||
/// The size of this segment in bytes.
|
||||
u32 size = 0;
|
||||
};
|
||||
|
||||
explicit CodeSet();
|
||||
~CodeSet();
|
||||
|
||||
CodeSet(const CodeSet&) = delete;
|
||||
CodeSet& operator=(const CodeSet&) = delete;
|
||||
|
||||
CodeSet(CodeSet&&) = default;
|
||||
CodeSet& operator=(CodeSet&&) = default;
|
||||
|
||||
Segment& CodeSegment() {
|
||||
return segments[0];
|
||||
}
|
||||
|
||||
const Segment& CodeSegment() const {
|
||||
return segments[0];
|
||||
}
|
||||
|
||||
Segment& RODataSegment() {
|
||||
return segments[1];
|
||||
}
|
||||
|
||||
const Segment& RODataSegment() const {
|
||||
return segments[1];
|
||||
}
|
||||
|
||||
Segment& DataSegment() {
|
||||
return segments[2];
|
||||
}
|
||||
|
||||
const Segment& DataSegment() const {
|
||||
return segments[2];
|
||||
}
|
||||
|
||||
/// The overall data that backs this code set.
|
||||
std::shared_ptr<std::vector<u8>> memory;
|
||||
|
||||
/// The segments that comprise this code set.
|
||||
std::array<Segment, 3> segments;
|
||||
|
||||
/// The entry point address for this code set.
|
||||
VAddr entrypoint = 0;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -10,8 +9,11 @@
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/mutex.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/memory.h"
|
||||
@@ -57,41 +59,47 @@ static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_t
|
||||
}
|
||||
}
|
||||
|
||||
ResultCode Mutex::TryAcquire(HandleTable& handle_table, VAddr address, Handle holding_thread_handle,
|
||||
Mutex::Mutex(Core::System& system) : system{system} {}
|
||||
Mutex::~Mutex() = default;
|
||||
|
||||
ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
|
||||
Handle requesting_thread_handle) {
|
||||
// The mutex address must be 4-byte aligned
|
||||
if ((address % sizeof(u32)) != 0) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
||||
Thread* const current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
SharedPtr<Thread> holding_thread = handle_table.Get<Thread>(holding_thread_handle);
|
||||
SharedPtr<Thread> requesting_thread = handle_table.Get<Thread>(requesting_thread_handle);
|
||||
|
||||
// TODO(Subv): It is currently unknown if it is possible to lock a mutex in behalf of another
|
||||
// thread.
|
||||
ASSERT(requesting_thread == GetCurrentThread());
|
||||
ASSERT(requesting_thread == current_thread);
|
||||
|
||||
u32 addr_value = Memory::Read32(address);
|
||||
const u32 addr_value = Memory::Read32(address);
|
||||
|
||||
// If the mutex isn't being held, just return success.
|
||||
if (addr_value != (holding_thread_handle | Mutex::MutexHasWaitersFlag)) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
if (holding_thread == nullptr)
|
||||
if (holding_thread == nullptr) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
// Wait until the mutex is released
|
||||
GetCurrentThread()->SetMutexWaitAddress(address);
|
||||
GetCurrentThread()->SetWaitHandle(requesting_thread_handle);
|
||||
current_thread->SetMutexWaitAddress(address);
|
||||
current_thread->SetWaitHandle(requesting_thread_handle);
|
||||
|
||||
GetCurrentThread()->SetStatus(ThreadStatus::WaitMutex);
|
||||
GetCurrentThread()->InvalidateWakeupCallback();
|
||||
current_thread->SetStatus(ThreadStatus::WaitMutex);
|
||||
current_thread->InvalidateWakeupCallback();
|
||||
|
||||
// Update the lock holder thread's priority to prevent priority inversion.
|
||||
holding_thread->AddMutexWaiter(GetCurrentThread());
|
||||
holding_thread->AddMutexWaiter(current_thread);
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
system.PrepareReschedule();
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
@@ -102,7 +110,8 @@ ResultCode Mutex::Release(VAddr address) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(GetCurrentThread(), address);
|
||||
auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(current_thread, address);
|
||||
|
||||
// There are no more threads waiting for the mutex, release it completely.
|
||||
if (thread == nullptr) {
|
||||
@@ -111,7 +120,7 @@ ResultCode Mutex::Release(VAddr address) {
|
||||
}
|
||||
|
||||
// Transfer the ownership of the mutex from the previous owner to the new one.
|
||||
TransferMutexOwnership(address, GetCurrentThread(), thread);
|
||||
TransferMutexOwnership(address, current_thread, thread);
|
||||
|
||||
u32 mutex_value = thread->GetWaitHandle();
|
||||
|
||||
|
||||
@@ -5,32 +5,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
|
||||
union ResultCode;
|
||||
|
||||
namespace Kernel {
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
class HandleTable;
|
||||
class Thread;
|
||||
namespace Kernel {
|
||||
|
||||
class Mutex final {
|
||||
public:
|
||||
explicit Mutex(Core::System& system);
|
||||
~Mutex();
|
||||
|
||||
/// Flag that indicates that a mutex still has threads waiting for it.
|
||||
static constexpr u32 MutexHasWaitersFlag = 0x40000000;
|
||||
/// Mask of the bits in a mutex address value that contain the mutex owner.
|
||||
static constexpr u32 MutexOwnerMask = 0xBFFFFFFF;
|
||||
|
||||
/// Attempts to acquire a mutex at the specified address.
|
||||
static ResultCode TryAcquire(HandleTable& handle_table, VAddr address,
|
||||
Handle holding_thread_handle, Handle requesting_thread_handle);
|
||||
ResultCode TryAcquire(VAddr address, Handle holding_thread_handle,
|
||||
Handle requesting_thread_handle);
|
||||
|
||||
/// Releases the mutex at the specified address.
|
||||
static ResultCode Release(VAddr address);
|
||||
ResultCode Release(VAddr address);
|
||||
|
||||
private:
|
||||
Mutex() = default;
|
||||
~Mutex() = default;
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
#include "core/hle/kernel/code_set.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
@@ -31,7 +32,7 @@ namespace {
|
||||
*/
|
||||
void SetupMainThread(Process& owner_process, KernelCore& kernel, VAddr entry_point, u32 priority) {
|
||||
// Setup page table so we can write to memory
|
||||
SetCurrentPageTable(&owner_process.VMManager().page_table);
|
||||
Memory::SetCurrentPageTable(&owner_process.VMManager().page_table);
|
||||
|
||||
// Initialize new "main" thread
|
||||
const VAddr stack_top = owner_process.VMManager().GetTLSIORegionEndAddress();
|
||||
@@ -50,9 +51,6 @@ void SetupMainThread(Process& owner_process, KernelCore& kernel, VAddr entry_poi
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
CodeSet::CodeSet() = default;
|
||||
CodeSet::~CodeSet() = default;
|
||||
|
||||
SharedPtr<Process> Process::Create(Core::System& system, std::string&& name) {
|
||||
auto& kernel = system.Kernel();
|
||||
|
||||
@@ -212,7 +210,7 @@ void Process::FreeTLSSlot(VAddr tls_address) {
|
||||
}
|
||||
|
||||
void Process::LoadModule(CodeSet module_, VAddr base_addr) {
|
||||
const auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions,
|
||||
const auto MapSegment = [&](const CodeSet::Segment& segment, VMAPermission permissions,
|
||||
MemoryState memory_state) {
|
||||
const auto vma = vm_manager
|
||||
.MapMemoryBlock(segment.addr + base_addr, module_.memory,
|
||||
@@ -222,16 +220,17 @@ void Process::LoadModule(CodeSet module_, VAddr base_addr) {
|
||||
};
|
||||
|
||||
// Map CodeSet segments
|
||||
MapSegment(module_.CodeSegment(), VMAPermission::ReadExecute, MemoryState::CodeStatic);
|
||||
MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeMutable);
|
||||
MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeMutable);
|
||||
MapSegment(module_.CodeSegment(), VMAPermission::ReadExecute, MemoryState::Code);
|
||||
MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeData);
|
||||
MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeData);
|
||||
|
||||
// Clear instruction cache in CPU JIT
|
||||
system.InvalidateCpuInstructionCaches();
|
||||
}
|
||||
|
||||
Process::Process(Core::System& system)
|
||||
: WaitObject{system.Kernel()}, address_arbiter{system}, system{system} {}
|
||||
: WaitObject{system.Kernel()}, address_arbiter{system}, mutex{system}, system{system} {}
|
||||
|
||||
Process::~Process() = default;
|
||||
|
||||
void Process::Acquire(Thread* thread) {
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/address_arbiter.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/mutex.h"
|
||||
#include "core/hle/kernel/process_capability.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/hle/kernel/wait_object.h"
|
||||
@@ -33,6 +33,8 @@ class KernelCore;
|
||||
class ResourceLimit;
|
||||
class Thread;
|
||||
|
||||
struct CodeSet;
|
||||
|
||||
struct AddressMapping {
|
||||
// Address and size must be page-aligned
|
||||
VAddr address;
|
||||
@@ -65,46 +67,6 @@ enum class ProcessStatus {
|
||||
DebugBreak,
|
||||
};
|
||||
|
||||
struct CodeSet final {
|
||||
struct Segment {
|
||||
std::size_t offset = 0;
|
||||
VAddr addr = 0;
|
||||
u32 size = 0;
|
||||
};
|
||||
|
||||
explicit CodeSet();
|
||||
~CodeSet();
|
||||
|
||||
Segment& CodeSegment() {
|
||||
return segments[0];
|
||||
}
|
||||
|
||||
const Segment& CodeSegment() const {
|
||||
return segments[0];
|
||||
}
|
||||
|
||||
Segment& RODataSegment() {
|
||||
return segments[1];
|
||||
}
|
||||
|
||||
const Segment& RODataSegment() const {
|
||||
return segments[1];
|
||||
}
|
||||
|
||||
Segment& DataSegment() {
|
||||
return segments[2];
|
||||
}
|
||||
|
||||
const Segment& DataSegment() const {
|
||||
return segments[2];
|
||||
}
|
||||
|
||||
std::shared_ptr<std::vector<u8>> memory;
|
||||
|
||||
std::array<Segment, 3> segments;
|
||||
VAddr entrypoint = 0;
|
||||
};
|
||||
|
||||
class Process final : public WaitObject {
|
||||
public:
|
||||
enum : u64 {
|
||||
@@ -165,6 +127,16 @@ public:
|
||||
return address_arbiter;
|
||||
}
|
||||
|
||||
/// Gets a reference to the process' mutex lock.
|
||||
Mutex& GetMutex() {
|
||||
return mutex;
|
||||
}
|
||||
|
||||
/// Gets a const reference to the process' mutex lock
|
||||
const Mutex& GetMutex() const {
|
||||
return mutex;
|
||||
}
|
||||
|
||||
/// Gets the current status of the process
|
||||
ProcessStatus GetStatus() const {
|
||||
return status;
|
||||
@@ -327,6 +299,11 @@ private:
|
||||
/// Per-process address arbiter.
|
||||
AddressArbiter address_arbiter;
|
||||
|
||||
/// The per-process mutex lock instance used for handling various
|
||||
/// forms of services, such as lock arbitration, and condition
|
||||
/// variable related facilities.
|
||||
Mutex mutex;
|
||||
|
||||
/// Random values for svcGetInfo RandomEntropy
|
||||
std::array<u64, RANDOM_ENTROPY_SIZE> random_entropy;
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
auto* const thread_owner_process = current_thread->GetOwnerProcess();
|
||||
if (previous_process != thread_owner_process) {
|
||||
system.Kernel().MakeCurrentProcess(thread_owner_process);
|
||||
SetCurrentPageTable(&thread_owner_process->VMManager().page_table);
|
||||
Memory::SetCurrentPageTable(&thread_owner_process->VMManager().page_table);
|
||||
}
|
||||
|
||||
cpu_core.LoadContext(new_thread->GetContext());
|
||||
@@ -199,8 +199,7 @@ void Scheduler::YieldWithoutLoadBalancing(Thread* thread) {
|
||||
ASSERT(thread->GetPriority() < THREADPRIO_COUNT);
|
||||
|
||||
// Yield this thread -- sleep for zero time and force reschedule to different thread
|
||||
WaitCurrentThread_Sleep();
|
||||
GetCurrentThread()->WakeAfterDelay(0);
|
||||
GetCurrentThread()->Sleep(0);
|
||||
}
|
||||
|
||||
void Scheduler::YieldWithLoadBalancing(Thread* thread) {
|
||||
@@ -215,8 +214,7 @@ void Scheduler::YieldWithLoadBalancing(Thread* thread) {
|
||||
ASSERT(priority < THREADPRIO_COUNT);
|
||||
|
||||
// Sleep for zero time to be able to force reschedule to different thread
|
||||
WaitCurrentThread_Sleep();
|
||||
GetCurrentThread()->WakeAfterDelay(0);
|
||||
GetCurrentThread()->Sleep(0);
|
||||
|
||||
Thread* suggested_thread = nullptr;
|
||||
|
||||
|
||||
@@ -551,9 +551,9 @@ static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr,
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
return Mutex::TryAcquire(handle_table, mutex_addr, holding_thread_handle,
|
||||
requesting_thread_handle);
|
||||
auto* const current_process = Core::System::GetInstance().Kernel().CurrentProcess();
|
||||
return current_process->GetMutex().TryAcquire(mutex_addr, holding_thread_handle,
|
||||
requesting_thread_handle);
|
||||
}
|
||||
|
||||
/// Unlock a mutex
|
||||
@@ -571,7 +571,8 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
return Mutex::Release(mutex_addr);
|
||||
auto* const current_process = Core::System::GetInstance().Kernel().CurrentProcess();
|
||||
return current_process->GetMutex().Release(mutex_addr);
|
||||
}
|
||||
|
||||
enum class BreakType : u32 {
|
||||
@@ -1284,10 +1285,14 @@ static ResultCode StartThread(Handle thread_handle) {
|
||||
|
||||
/// Called when a thread exits
|
||||
static void ExitThread() {
|
||||
LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", Core::CurrentArmInterface().GetPC());
|
||||
auto& system = Core::System::GetInstance();
|
||||
|
||||
ExitCurrentThread();
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC());
|
||||
|
||||
auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
|
||||
current_thread->Stop();
|
||||
system.CurrentScheduler().RemoveThread(current_thread);
|
||||
system.PrepareReschedule();
|
||||
}
|
||||
|
||||
/// Sleep the current thread
|
||||
@@ -1300,32 +1305,32 @@ static void SleepThread(s64 nanoseconds) {
|
||||
YieldAndWaitForLoadBalancing = -2,
|
||||
};
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
auto& scheduler = system.CurrentScheduler();
|
||||
auto* const current_thread = scheduler.GetCurrentThread();
|
||||
|
||||
if (nanoseconds <= 0) {
|
||||
auto& scheduler{Core::System::GetInstance().CurrentScheduler()};
|
||||
switch (static_cast<SleepType>(nanoseconds)) {
|
||||
case SleepType::YieldWithoutLoadBalancing:
|
||||
scheduler.YieldWithoutLoadBalancing(GetCurrentThread());
|
||||
scheduler.YieldWithoutLoadBalancing(current_thread);
|
||||
break;
|
||||
case SleepType::YieldWithLoadBalancing:
|
||||
scheduler.YieldWithLoadBalancing(GetCurrentThread());
|
||||
scheduler.YieldWithLoadBalancing(current_thread);
|
||||
break;
|
||||
case SleepType::YieldAndWaitForLoadBalancing:
|
||||
scheduler.YieldAndWaitForLoadBalancing(GetCurrentThread());
|
||||
scheduler.YieldAndWaitForLoadBalancing(current_thread);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds);
|
||||
}
|
||||
} else {
|
||||
// Sleep current thread and check for next thread to schedule
|
||||
WaitCurrentThread_Sleep();
|
||||
|
||||
// Create an event to wake the thread up after the specified nanosecond delay has passed
|
||||
GetCurrentThread()->WakeAfterDelay(nanoseconds);
|
||||
current_thread->Sleep(nanoseconds);
|
||||
}
|
||||
|
||||
// Reschedule all CPU cores
|
||||
for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i)
|
||||
Core::System::GetInstance().CpuCore(i).PrepareReschedule();
|
||||
for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i) {
|
||||
system.CpuCore(i).PrepareReschedule();
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait process wide key atomic
|
||||
@@ -1336,11 +1341,15 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var
|
||||
"called mutex_addr={:X}, condition_variable_addr={:X}, thread_handle=0x{:08X}, timeout={}",
|
||||
mutex_addr, condition_variable_addr, thread_handle, nano_seconds);
|
||||
|
||||
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
|
||||
auto* const current_process = Core::System::GetInstance().Kernel().CurrentProcess();
|
||||
const auto& handle_table = current_process->GetHandleTable();
|
||||
SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
|
||||
ASSERT(thread);
|
||||
|
||||
CASCADE_CODE(Mutex::Release(mutex_addr));
|
||||
const auto release_result = current_process->GetMutex().Release(mutex_addr);
|
||||
if (release_result.IsError()) {
|
||||
return release_result;
|
||||
}
|
||||
|
||||
SharedPtr<Thread> current_thread = GetCurrentThread();
|
||||
current_thread->SetCondVarWaitAddress(condition_variable_addr);
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/range/algorithm_ext/erase.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -68,17 +66,6 @@ void Thread::Stop() {
|
||||
owner_process->FreeTLSSlot(tls_address);
|
||||
}
|
||||
|
||||
void WaitCurrentThread_Sleep() {
|
||||
Thread* thread = GetCurrentThread();
|
||||
thread->SetStatus(ThreadStatus::WaitSleep);
|
||||
}
|
||||
|
||||
void ExitCurrentThread() {
|
||||
Thread* thread = GetCurrentThread();
|
||||
thread->Stop();
|
||||
Core::System::GetInstance().CurrentScheduler().RemoveThread(thread);
|
||||
}
|
||||
|
||||
void Thread::WakeAfterDelay(s64 nanoseconds) {
|
||||
// Don't schedule a wakeup if the thread wants to wait forever
|
||||
if (nanoseconds == -1)
|
||||
@@ -269,8 +256,8 @@ void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
|
||||
if (thread->lock_owner == this) {
|
||||
// If the thread is already waiting for this thread to release the mutex, ensure that the
|
||||
// waiters list is consistent and return without doing anything.
|
||||
auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(itr != wait_mutex_threads.end());
|
||||
const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(iter != wait_mutex_threads.end());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -278,11 +265,16 @@ void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
|
||||
ASSERT(thread->lock_owner == nullptr);
|
||||
|
||||
// Ensure that the thread is not already in the list of mutex waiters
|
||||
auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(itr == wait_mutex_threads.end());
|
||||
const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(iter == wait_mutex_threads.end());
|
||||
|
||||
// Keep the list in an ordered fashion
|
||||
const auto insertion_point = std::find_if(
|
||||
wait_mutex_threads.begin(), wait_mutex_threads.end(),
|
||||
[&thread](const auto& entry) { return entry->GetPriority() > thread->GetPriority(); });
|
||||
wait_mutex_threads.insert(insertion_point, thread);
|
||||
thread->lock_owner = this;
|
||||
wait_mutex_threads.emplace_back(std::move(thread));
|
||||
|
||||
UpdatePriority();
|
||||
}
|
||||
|
||||
@@ -290,32 +282,44 @@ void Thread::RemoveMutexWaiter(SharedPtr<Thread> thread) {
|
||||
ASSERT(thread->lock_owner == this);
|
||||
|
||||
// Ensure that the thread is in the list of mutex waiters
|
||||
auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(itr != wait_mutex_threads.end());
|
||||
const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(iter != wait_mutex_threads.end());
|
||||
|
||||
wait_mutex_threads.erase(iter);
|
||||
|
||||
boost::remove_erase(wait_mutex_threads, thread);
|
||||
thread->lock_owner = nullptr;
|
||||
UpdatePriority();
|
||||
}
|
||||
|
||||
void Thread::UpdatePriority() {
|
||||
// Find the highest priority among all the threads that are waiting for this thread's lock
|
||||
// If any of the threads waiting on the mutex have a higher priority
|
||||
// (taking into account priority inheritance), then this thread inherits
|
||||
// that thread's priority.
|
||||
u32 new_priority = nominal_priority;
|
||||
for (const auto& thread : wait_mutex_threads) {
|
||||
if (thread->nominal_priority < new_priority)
|
||||
new_priority = thread->nominal_priority;
|
||||
if (!wait_mutex_threads.empty()) {
|
||||
if (wait_mutex_threads.front()->current_priority < new_priority) {
|
||||
new_priority = wait_mutex_threads.front()->current_priority;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_priority == current_priority)
|
||||
if (new_priority == current_priority) {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler->SetThreadPriority(this, new_priority);
|
||||
|
||||
current_priority = new_priority;
|
||||
|
||||
if (!lock_owner) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure that the thread is within the correct location in the waiting list.
|
||||
auto old_owner = lock_owner;
|
||||
lock_owner->RemoveMutexWaiter(this);
|
||||
old_owner->AddMutexWaiter(this);
|
||||
|
||||
// Recursively update the priority of the thread that depends on the priority of this one.
|
||||
if (lock_owner)
|
||||
lock_owner->UpdatePriority();
|
||||
lock_owner->UpdatePriority();
|
||||
}
|
||||
|
||||
void Thread::ChangeCore(u32 core, u64 mask) {
|
||||
@@ -391,6 +395,14 @@ void Thread::SetActivity(ThreadActivity value) {
|
||||
}
|
||||
}
|
||||
|
||||
void Thread::Sleep(s64 nanoseconds) {
|
||||
// Sleep current thread and check for next thread to schedule
|
||||
SetStatus(ThreadStatus::WaitSleep);
|
||||
|
||||
// Create an event to wake the thread up after the specified nanosecond delay has passed
|
||||
WakeAfterDelay(nanoseconds);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
|
||||
@@ -383,6 +383,9 @@ public:
|
||||
|
||||
void SetActivity(ThreadActivity value);
|
||||
|
||||
/// Sleeps this thread for the given amount of nanoseconds.
|
||||
void Sleep(s64 nanoseconds);
|
||||
|
||||
private:
|
||||
explicit Thread(KernelCore& kernel);
|
||||
~Thread() override;
|
||||
@@ -398,8 +401,14 @@ private:
|
||||
VAddr entry_point = 0;
|
||||
VAddr stack_top = 0;
|
||||
|
||||
u32 nominal_priority = 0; ///< Nominal thread priority, as set by the emulated application
|
||||
u32 current_priority = 0; ///< Current thread priority, can be temporarily changed
|
||||
/// Nominal thread priority, as set by the emulated application.
|
||||
/// The nominal priority is the thread priority without priority
|
||||
/// inheritance taken into account.
|
||||
u32 nominal_priority = 0;
|
||||
|
||||
/// Current thread priority. This may change over the course of the
|
||||
/// thread's lifetime in order to facilitate priority inheritance.
|
||||
u32 current_priority = 0;
|
||||
|
||||
u64 total_cpu_time_ticks = 0; ///< Total CPU running ticks.
|
||||
u64 last_running_ticks = 0; ///< CPU tick when thread was last running
|
||||
@@ -460,14 +469,4 @@ private:
|
||||
*/
|
||||
Thread* GetCurrentThread();
|
||||
|
||||
/**
|
||||
* Waits the current thread on a sleep
|
||||
*/
|
||||
void WaitCurrentThread_Sleep();
|
||||
|
||||
/**
|
||||
* Stops the current thread and removes it from the thread_list
|
||||
*/
|
||||
void ExitCurrentThread();
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -7,29 +7,29 @@
|
||||
#include <utility>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/memory_hook.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory_hook.h"
|
||||
#include "core/memory_setup.h"
|
||||
|
||||
namespace Kernel {
|
||||
namespace {
|
||||
const char* GetMemoryStateName(MemoryState state) {
|
||||
static constexpr const char* names[] = {
|
||||
"Unmapped", "Io",
|
||||
"Normal", "CodeStatic",
|
||||
"CodeMutable", "Heap",
|
||||
"Shared", "Unknown1",
|
||||
"ModuleCodeStatic", "ModuleCodeMutable",
|
||||
"IpcBuffer0", "Stack",
|
||||
"ThreadLocal", "TransferMemoryIsolated",
|
||||
"TransferMemory", "ProcessMemory",
|
||||
"Inaccessible", "IpcBuffer1",
|
||||
"IpcBuffer3", "KernelStack",
|
||||
"Unmapped", "Io",
|
||||
"Normal", "Code",
|
||||
"CodeData", "Heap",
|
||||
"Shared", "Unknown1",
|
||||
"ModuleCode", "ModuleCodeData",
|
||||
"IpcBuffer0", "Stack",
|
||||
"ThreadLocal", "TransferMemoryIsolated",
|
||||
"TransferMemory", "ProcessMemory",
|
||||
"Inaccessible", "IpcBuffer1",
|
||||
"IpcBuffer3", "KernelStack",
|
||||
};
|
||||
|
||||
return names[ToSvcMemoryState(state)];
|
||||
@@ -177,7 +177,7 @@ ResultVal<VAddr> VMManager::FindFreeRegion(u64 size) const {
|
||||
|
||||
ResultVal<VMManager::VMAHandle> VMManager::MapMMIO(VAddr target, PAddr paddr, u64 size,
|
||||
MemoryState state,
|
||||
Memory::MemoryHookPointer mmio_handler) {
|
||||
Common::MemoryHookPointer mmio_handler) {
|
||||
// This is the appropriately sized VMA that will turn into our allocation.
|
||||
CASCADE_RESULT(VMAIter vma_handle, CarveVMA(target, size));
|
||||
VirtualMemoryArea& final_vma = vma_handle->second;
|
||||
@@ -624,7 +624,7 @@ void VMManager::ClearPageTable() {
|
||||
std::fill(page_table.pointers.begin(), page_table.pointers.end(), nullptr);
|
||||
page_table.special_regions.clear();
|
||||
std::fill(page_table.attributes.begin(), page_table.attributes.end(),
|
||||
Memory::PageType::Unmapped);
|
||||
Common::PageType::Unmapped);
|
||||
}
|
||||
|
||||
VMManager::CheckResults VMManager::CheckRangeState(VAddr address, u64 size, MemoryState state_mask,
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/memory_hook.h"
|
||||
#include "common/page_table.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory_hook.h"
|
||||
|
||||
namespace FileSys {
|
||||
enum class ProgramAddressSpaceType : u8;
|
||||
@@ -164,12 +165,12 @@ enum class MemoryState : u32 {
|
||||
Unmapped = 0x00,
|
||||
Io = 0x01 | FlagMapped,
|
||||
Normal = 0x02 | FlagMapped | FlagQueryPhysicalAddressAllowed,
|
||||
CodeStatic = 0x03 | CodeFlags | FlagMapProcess,
|
||||
CodeMutable = 0x04 | CodeFlags | FlagMapProcess | FlagCodeMemory,
|
||||
Code = 0x03 | CodeFlags | FlagMapProcess,
|
||||
CodeData = 0x04 | DataFlags | FlagMapProcess | FlagCodeMemory,
|
||||
Heap = 0x05 | DataFlags | FlagCodeMemory,
|
||||
Shared = 0x06 | FlagMapped | FlagMemoryPoolAllocated,
|
||||
ModuleCodeStatic = 0x08 | CodeFlags | FlagModule | FlagMapProcess,
|
||||
ModuleCodeMutable = 0x09 | DataFlags | FlagModule | FlagMapProcess | FlagCodeMemory,
|
||||
ModuleCode = 0x08 | CodeFlags | FlagModule | FlagMapProcess,
|
||||
ModuleCodeData = 0x09 | DataFlags | FlagModule | FlagMapProcess | FlagCodeMemory,
|
||||
|
||||
IpcBuffer0 = 0x0A | FlagMapped | FlagQueryPhysicalAddressAllowed | FlagMemoryPoolAllocated |
|
||||
IPCFlags | FlagSharedDevice | FlagSharedDeviceAligned,
|
||||
@@ -290,7 +291,7 @@ struct VirtualMemoryArea {
|
||||
// Settings for type = MMIO
|
||||
/// Physical address of the register area this VMA maps to.
|
||||
PAddr paddr = 0;
|
||||
Memory::MemoryHookPointer mmio_handler = nullptr;
|
||||
Common::MemoryHookPointer mmio_handler = nullptr;
|
||||
|
||||
/// Tests if this area can be merged to the right with `next`.
|
||||
bool CanBeMergedWith(const VirtualMemoryArea& next) const;
|
||||
@@ -368,7 +369,7 @@ public:
|
||||
* @param mmio_handler The handler that will implement read and write for this MMIO region.
|
||||
*/
|
||||
ResultVal<VMAHandle> MapMMIO(VAddr target, PAddr paddr, u64 size, MemoryState state,
|
||||
Memory::MemoryHookPointer mmio_handler);
|
||||
Common::MemoryHookPointer mmio_handler);
|
||||
|
||||
/// Unmaps a range of addresses, splitting VMAs as necessary.
|
||||
ResultCode UnmapRange(VAddr target, u64 size);
|
||||
@@ -509,7 +510,7 @@ public:
|
||||
|
||||
/// Each VMManager has its own page table, which is set as the main one when the owning process
|
||||
/// is scheduled.
|
||||
Memory::PageTable page_table;
|
||||
Common::PageTable page_table{Memory::PAGE_BITS};
|
||||
|
||||
private:
|
||||
using VMAIter = VMAMap::iterator;
|
||||
@@ -616,6 +617,9 @@ private:
|
||||
VAddr new_map_region_base = 0;
|
||||
VAddr new_map_region_end = 0;
|
||||
|
||||
VAddr main_code_region_base = 0;
|
||||
VAddr main_code_region_end = 0;
|
||||
|
||||
VAddr tls_io_region_base = 0;
|
||||
VAddr tls_io_region_end = 0;
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include <stack>
|
||||
#include "audio_core/audio_renderer.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
@@ -93,38 +93,84 @@ void IWindowController::AcquireForegroundRights(Kernel::HLERequestContext& ctx)
|
||||
}
|
||||
|
||||
IAudioController::IAudioController() : ServiceFramework("IAudioController") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IAudioController::SetExpectedMasterVolume, "SetExpectedMasterVolume"},
|
||||
{1, &IAudioController::GetMainAppletExpectedMasterVolume,
|
||||
"GetMainAppletExpectedMasterVolume"},
|
||||
{2, &IAudioController::GetLibraryAppletExpectedMasterVolume,
|
||||
"GetLibraryAppletExpectedMasterVolume"},
|
||||
{3, nullptr, "ChangeMainAppletMasterVolume"},
|
||||
{4, nullptr, "SetTransparentVolumeRate"},
|
||||
{1, &IAudioController::GetMainAppletExpectedMasterVolume, "GetMainAppletExpectedMasterVolume"},
|
||||
{2, &IAudioController::GetLibraryAppletExpectedMasterVolume, "GetLibraryAppletExpectedMasterVolume"},
|
||||
{3, &IAudioController::ChangeMainAppletMasterVolume, "ChangeMainAppletMasterVolume"},
|
||||
{4, &IAudioController::SetTransparentAudioRate, "SetTransparentVolumeRate"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IAudioController::~IAudioController() = default;
|
||||
|
||||
void IAudioController::SetExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
const float main_applet_volume_tmp = rp.Pop<float>();
|
||||
const float library_applet_volume_tmp = rp.Pop<float>();
|
||||
|
||||
LOG_DEBUG(Service_AM, "called. main_applet_volume={}, library_applet_volume={}",
|
||||
main_applet_volume_tmp, library_applet_volume_tmp);
|
||||
|
||||
// Ensure the volume values remain within the 0-100% range
|
||||
main_applet_volume = std::clamp(main_applet_volume_tmp, min_allowed_volume, max_allowed_volume);
|
||||
library_applet_volume =
|
||||
std::clamp(library_applet_volume_tmp, min_allowed_volume, max_allowed_volume);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void IAudioController::GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_AM, "called. main_applet_volume={}", main_applet_volume);
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(volume);
|
||||
rb.Push(main_applet_volume);
|
||||
}
|
||||
|
||||
void IAudioController::GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_AM, "called. library_applet_volume={}", library_applet_volume);
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(volume);
|
||||
rb.Push(library_applet_volume);
|
||||
}
|
||||
|
||||
void IAudioController::ChangeMainAppletMasterVolume(Kernel::HLERequestContext& ctx) {
|
||||
struct Parameters {
|
||||
float volume;
|
||||
s64 fade_time_ns;
|
||||
};
|
||||
static_assert(sizeof(Parameters) == 16);
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto parameters = rp.PopRaw<Parameters>();
|
||||
|
||||
LOG_DEBUG(Service_AM, "called. volume={}, fade_time_ns={}", parameters.volume,
|
||||
parameters.fade_time_ns);
|
||||
|
||||
main_applet_volume = std::clamp(parameters.volume, min_allowed_volume, max_allowed_volume);
|
||||
fade_time_ns = std::chrono::nanoseconds{parameters.fade_time_ns};
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void IAudioController::SetTransparentAudioRate(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const float transparent_volume_rate_tmp = rp.Pop<float>();
|
||||
|
||||
LOG_DEBUG(Service_AM, "called. transparent_volume_rate={}", transparent_volume_rate_tmp);
|
||||
|
||||
// Clamp volume range to 0-100%.
|
||||
transparent_volume_rate =
|
||||
std::clamp(transparent_volume_rate_tmp, min_allowed_volume, max_allowed_volume);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") {
|
||||
@@ -169,7 +215,21 @@ IDisplayController::IDisplayController() : ServiceFramework("IDisplayController"
|
||||
|
||||
IDisplayController::~IDisplayController() = default;
|
||||
|
||||
IDebugFunctions::IDebugFunctions() : ServiceFramework("IDebugFunctions") {}
|
||||
IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "NotifyMessageToHomeMenuForDebug"},
|
||||
{1, nullptr, "OpenMainApplication"},
|
||||
{10, nullptr, "EmulateButtonEvent"},
|
||||
{20, nullptr, "InvalidateTransitionLayer"},
|
||||
{30, nullptr, "RequestLaunchApplicationWithUserAndArgumentForDebug"},
|
||||
{40, nullptr, "GetAppletResourceUsageInfo"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IDebugFunctions::~IDebugFunctions() = default;
|
||||
|
||||
ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include "core/hle/kernel/writable_event.h"
|
||||
@@ -81,8 +82,21 @@ private:
|
||||
void SetExpectedMasterVolume(Kernel::HLERequestContext& ctx);
|
||||
void GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx);
|
||||
void GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx);
|
||||
void ChangeMainAppletMasterVolume(Kernel::HLERequestContext& ctx);
|
||||
void SetTransparentAudioRate(Kernel::HLERequestContext& ctx);
|
||||
|
||||
u32 volume{100};
|
||||
static constexpr float min_allowed_volume = 0.0f;
|
||||
static constexpr float max_allowed_volume = 1.0f;
|
||||
|
||||
float main_applet_volume{0.25f};
|
||||
float library_applet_volume{max_allowed_volume};
|
||||
float transparent_volume_rate{min_allowed_volume};
|
||||
|
||||
// Volume transition fade time in nanoseconds.
|
||||
// e.g. If the main applet volume was 0% and was changed to 50%
|
||||
// with a fade of 50ns, then over the course of 50ns,
|
||||
// the volume will gradually fade up to 50%
|
||||
std::chrono::nanoseconds fade_time_ns{0};
|
||||
};
|
||||
|
||||
class IDisplayController final : public ServiceFramework<IDisplayController> {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include <opus.h>
|
||||
#include <opus_multistream.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -18,12 +19,12 @@
|
||||
namespace Service::Audio {
|
||||
namespace {
|
||||
struct OpusDeleter {
|
||||
void operator()(void* ptr) const {
|
||||
operator delete(ptr);
|
||||
void operator()(OpusMSDecoder* ptr) const {
|
||||
opus_multistream_decoder_destroy(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
using OpusDecoderPtr = std::unique_ptr<OpusDecoder, OpusDeleter>;
|
||||
using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>;
|
||||
|
||||
struct OpusPacketHeader {
|
||||
// Packet size in bytes.
|
||||
@@ -33,7 +34,7 @@ struct OpusPacketHeader {
|
||||
};
|
||||
static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size");
|
||||
|
||||
class OpusDecoderStateBase {
|
||||
class OpusDecoderState {
|
||||
public:
|
||||
/// Describes extra behavior that may be asked of the decoding context.
|
||||
enum class ExtraBehavior {
|
||||
@@ -49,22 +50,13 @@ public:
|
||||
Enabled,
|
||||
};
|
||||
|
||||
virtual ~OpusDecoderStateBase() = default;
|
||||
|
||||
// Decodes interleaved Opus packets. Optionally allows reporting time taken to
|
||||
// perform the decoding, as well as any relevant extra behavior.
|
||||
virtual void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time,
|
||||
ExtraBehavior extra_behavior) = 0;
|
||||
};
|
||||
|
||||
// Represents the decoder state for a non-multistream decoder.
|
||||
class OpusDecoderState final : public OpusDecoderStateBase {
|
||||
public:
|
||||
explicit OpusDecoderState(OpusDecoderPtr decoder, u32 sample_rate, u32 channel_count)
|
||||
: decoder{std::move(decoder)}, sample_rate{sample_rate}, channel_count{channel_count} {}
|
||||
|
||||
// Decodes interleaved Opus packets. Optionally allows reporting time taken to
|
||||
// perform the decoding, as well as any relevant extra behavior.
|
||||
void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time,
|
||||
ExtraBehavior extra_behavior) override {
|
||||
ExtraBehavior extra_behavior) {
|
||||
if (perf_time == PerfTime::Disabled) {
|
||||
DecodeInterleavedHelper(ctx, nullptr, extra_behavior);
|
||||
} else {
|
||||
@@ -135,7 +127,7 @@ private:
|
||||
|
||||
const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count));
|
||||
const auto out_sample_count =
|
||||
opus_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0);
|
||||
opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0);
|
||||
if (out_sample_count < 0) {
|
||||
LOG_ERROR(Audio,
|
||||
"Incorrect sample count received from opus_decode, "
|
||||
@@ -158,7 +150,7 @@ private:
|
||||
void ResetDecoderContext() {
|
||||
ASSERT(decoder != nullptr);
|
||||
|
||||
opus_decoder_ctl(decoder.get(), OPUS_RESET_STATE);
|
||||
opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE);
|
||||
}
|
||||
|
||||
OpusDecoderPtr decoder;
|
||||
@@ -168,7 +160,7 @@ private:
|
||||
|
||||
class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> {
|
||||
public:
|
||||
explicit IHardwareOpusDecoderManager(std::unique_ptr<OpusDecoderStateBase> decoder_state)
|
||||
explicit IHardwareOpusDecoderManager(OpusDecoderState decoder_state)
|
||||
: ServiceFramework("IHardwareOpusDecoderManager"), decoder_state{std::move(decoder_state)} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
@@ -190,35 +182,51 @@ private:
|
||||
void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Disabled,
|
||||
OpusDecoderStateBase::ExtraBehavior::None);
|
||||
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled,
|
||||
OpusDecoderState::ExtraBehavior::None);
|
||||
}
|
||||
|
||||
void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Enabled,
|
||||
OpusDecoderStateBase::ExtraBehavior::None);
|
||||
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled,
|
||||
OpusDecoderState::ExtraBehavior::None);
|
||||
}
|
||||
|
||||
void DecodeInterleaved(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto extra_behavior = rp.Pop<bool>()
|
||||
? OpusDecoderStateBase::ExtraBehavior::ResetContext
|
||||
: OpusDecoderStateBase::ExtraBehavior::None;
|
||||
const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext
|
||||
: OpusDecoderState::ExtraBehavior::None;
|
||||
|
||||
decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Enabled,
|
||||
extra_behavior);
|
||||
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior);
|
||||
}
|
||||
|
||||
std::unique_ptr<OpusDecoderStateBase> decoder_state;
|
||||
OpusDecoderState decoder_state;
|
||||
};
|
||||
|
||||
std::size_t WorkerBufferSize(u32 channel_count) {
|
||||
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
|
||||
return opus_decoder_get_size(static_cast<int>(channel_count));
|
||||
constexpr int num_streams = 1;
|
||||
const int num_stereo_streams = channel_count == 2 ? 1 : 0;
|
||||
return opus_multistream_decoder_get_size(num_streams, num_stereo_streams);
|
||||
}
|
||||
|
||||
// Creates the mapping table that maps the input channels to the particular
|
||||
// output channels. In the stereo case, we map the left and right input channels
|
||||
// to the left and right output channels respectively.
|
||||
//
|
||||
// However, in the monophonic case, we only map the one available channel
|
||||
// to the sole output channel. We specify 255 for the would-be right channel
|
||||
// as this is a special value defined by Opus to indicate to the decoder to
|
||||
// ignore that channel.
|
||||
std::array<u8, 2> CreateMappingTable(u32 channel_count) {
|
||||
if (channel_count == 2) {
|
||||
return {{0, 1}};
|
||||
}
|
||||
|
||||
return {{0, 255}};
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
@@ -259,9 +267,15 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
|
||||
const std::size_t worker_sz = WorkerBufferSize(channel_count);
|
||||
ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large");
|
||||
|
||||
OpusDecoderPtr decoder{static_cast<OpusDecoder*>(operator new(worker_sz))};
|
||||
if (const int err = opus_decoder_init(decoder.get(), sample_rate, channel_count)) {
|
||||
LOG_ERROR(Audio, "Failed to init opus decoder with error={}", err);
|
||||
const int num_stereo_streams = channel_count == 2 ? 1 : 0;
|
||||
const auto mapping_table = CreateMappingTable(channel_count);
|
||||
|
||||
int error = 0;
|
||||
OpusDecoderPtr decoder{
|
||||
opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1,
|
||||
num_stereo_streams, mapping_table.data(), &error)};
|
||||
if (error != OPUS_OK || decoder == nullptr) {
|
||||
LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(ogniK): Use correct error code
|
||||
rb.Push(ResultCode(-1));
|
||||
@@ -271,7 +285,7 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IHardwareOpusDecoderManager>(
|
||||
std::make_unique<OpusDecoderState>(std::move(decoder), sample_rate, channel_count));
|
||||
OpusDecoderState{std::move(decoder), sample_rate, channel_count});
|
||||
}
|
||||
|
||||
HwOpus::HwOpus() : ServiceFramework("hwopus") {
|
||||
|
||||
@@ -733,7 +733,10 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
|
||||
FSP_SRV::~FSP_SRV() = default;
|
||||
|
||||
void FSP_SRV::SetCurrentProcess(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
current_process_id = rp.Pop<u64>();
|
||||
|
||||
LOG_DEBUG(Service_FS, "called. current_process_id=0x{:016X}", current_process_id);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
@@ -32,6 +32,7 @@ private:
|
||||
void OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
|
||||
|
||||
FileSys::VirtualFile romfs;
|
||||
u64 current_process_id = 0;
|
||||
};
|
||||
|
||||
} // namespace Service::FileSystem
|
||||
|
||||
@@ -41,20 +41,20 @@ private:
|
||||
struct PadState {
|
||||
union {
|
||||
u32_le raw{};
|
||||
BitField<0, 1, u32_le> a;
|
||||
BitField<1, 1, u32_le> b;
|
||||
BitField<2, 1, u32_le> x;
|
||||
BitField<3, 1, u32_le> y;
|
||||
BitField<4, 1, u32_le> l;
|
||||
BitField<5, 1, u32_le> r;
|
||||
BitField<6, 1, u32_le> zl;
|
||||
BitField<7, 1, u32_le> zr;
|
||||
BitField<8, 1, u32_le> plus;
|
||||
BitField<9, 1, u32_le> minus;
|
||||
BitField<10, 1, u32_le> d_left;
|
||||
BitField<11, 1, u32_le> d_up;
|
||||
BitField<12, 1, u32_le> d_right;
|
||||
BitField<13, 1, u32_le> d_down;
|
||||
BitField<0, 1, u32> a;
|
||||
BitField<1, 1, u32> b;
|
||||
BitField<2, 1, u32> x;
|
||||
BitField<3, 1, u32> y;
|
||||
BitField<4, 1, u32> l;
|
||||
BitField<5, 1, u32> r;
|
||||
BitField<6, 1, u32> zl;
|
||||
BitField<7, 1, u32> zr;
|
||||
BitField<8, 1, u32> plus;
|
||||
BitField<9, 1, u32> minus;
|
||||
BitField<10, 1, u32> d_left;
|
||||
BitField<11, 1, u32> d_up;
|
||||
BitField<12, 1, u32> d_right;
|
||||
BitField<13, 1, u32> d_down;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(PadState) == 0x4, "PadState is an invalid size");
|
||||
@@ -62,7 +62,7 @@ private:
|
||||
struct Attributes {
|
||||
union {
|
||||
u32_le raw{};
|
||||
BitField<0, 1, u32_le> connected;
|
||||
BitField<0, 1, u32> connected;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size");
|
||||
|
||||
@@ -39,13 +39,13 @@ public:
|
||||
union {
|
||||
u32_le raw{};
|
||||
|
||||
BitField<0, 1, u32_le> pro_controller;
|
||||
BitField<1, 1, u32_le> handheld;
|
||||
BitField<2, 1, u32_le> joycon_dual;
|
||||
BitField<3, 1, u32_le> joycon_left;
|
||||
BitField<4, 1, u32_le> joycon_right;
|
||||
BitField<0, 1, u32> pro_controller;
|
||||
BitField<1, 1, u32> handheld;
|
||||
BitField<2, 1, u32> joycon_dual;
|
||||
BitField<3, 1, u32> joycon_left;
|
||||
BitField<4, 1, u32> joycon_right;
|
||||
|
||||
BitField<6, 1, u32_le> pokeball; // TODO(ogniK): Confirm when possible
|
||||
BitField<6, 1, u32> pokeball; // TODO(ogniK): Confirm when possible
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(NPadType) == 4, "NPadType is an invalid size");
|
||||
@@ -150,43 +150,43 @@ private:
|
||||
union {
|
||||
u64_le raw{};
|
||||
// Button states
|
||||
BitField<0, 1, u64_le> a;
|
||||
BitField<1, 1, u64_le> b;
|
||||
BitField<2, 1, u64_le> x;
|
||||
BitField<3, 1, u64_le> y;
|
||||
BitField<4, 1, u64_le> l_stick;
|
||||
BitField<5, 1, u64_le> r_stick;
|
||||
BitField<6, 1, u64_le> l;
|
||||
BitField<7, 1, u64_le> r;
|
||||
BitField<8, 1, u64_le> zl;
|
||||
BitField<9, 1, u64_le> zr;
|
||||
BitField<10, 1, u64_le> plus;
|
||||
BitField<11, 1, u64_le> minus;
|
||||
BitField<0, 1, u64> a;
|
||||
BitField<1, 1, u64> b;
|
||||
BitField<2, 1, u64> x;
|
||||
BitField<3, 1, u64> y;
|
||||
BitField<4, 1, u64> l_stick;
|
||||
BitField<5, 1, u64> r_stick;
|
||||
BitField<6, 1, u64> l;
|
||||
BitField<7, 1, u64> r;
|
||||
BitField<8, 1, u64> zl;
|
||||
BitField<9, 1, u64> zr;
|
||||
BitField<10, 1, u64> plus;
|
||||
BitField<11, 1, u64> minus;
|
||||
|
||||
// D-Pad
|
||||
BitField<12, 1, u64_le> d_left;
|
||||
BitField<13, 1, u64_le> d_up;
|
||||
BitField<14, 1, u64_le> d_right;
|
||||
BitField<15, 1, u64_le> d_down;
|
||||
BitField<12, 1, u64> d_left;
|
||||
BitField<13, 1, u64> d_up;
|
||||
BitField<14, 1, u64> d_right;
|
||||
BitField<15, 1, u64> d_down;
|
||||
|
||||
// Left JoyStick
|
||||
BitField<16, 1, u64_le> l_stick_left;
|
||||
BitField<17, 1, u64_le> l_stick_up;
|
||||
BitField<18, 1, u64_le> l_stick_right;
|
||||
BitField<19, 1, u64_le> l_stick_down;
|
||||
BitField<16, 1, u64> l_stick_left;
|
||||
BitField<17, 1, u64> l_stick_up;
|
||||
BitField<18, 1, u64> l_stick_right;
|
||||
BitField<19, 1, u64> l_stick_down;
|
||||
|
||||
// Right JoyStick
|
||||
BitField<20, 1, u64_le> r_stick_left;
|
||||
BitField<21, 1, u64_le> r_stick_up;
|
||||
BitField<22, 1, u64_le> r_stick_right;
|
||||
BitField<23, 1, u64_le> r_stick_down;
|
||||
BitField<20, 1, u64> r_stick_left;
|
||||
BitField<21, 1, u64> r_stick_up;
|
||||
BitField<22, 1, u64> r_stick_right;
|
||||
BitField<23, 1, u64> r_stick_down;
|
||||
|
||||
// Not always active?
|
||||
BitField<24, 1, u64_le> left_sl;
|
||||
BitField<25, 1, u64_le> left_sr;
|
||||
BitField<24, 1, u64> left_sl;
|
||||
BitField<25, 1, u64> left_sr;
|
||||
|
||||
BitField<26, 1, u64_le> right_sl;
|
||||
BitField<27, 1, u64_le> right_sr;
|
||||
BitField<26, 1, u64> right_sl;
|
||||
BitField<27, 1, u64> right_sr;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(ControllerPadState) == 8, "ControllerPadState is an invalid size");
|
||||
@@ -200,12 +200,12 @@ private:
|
||||
struct ConnectionState {
|
||||
union {
|
||||
u32_le raw{};
|
||||
BitField<0, 1, u32_le> IsConnected;
|
||||
BitField<1, 1, u32_le> IsWired;
|
||||
BitField<2, 1, u32_le> IsLeftJoyConnected;
|
||||
BitField<3, 1, u32_le> IsLeftJoyWired;
|
||||
BitField<4, 1, u32_le> IsRightJoyConnected;
|
||||
BitField<5, 1, u32_le> IsRightJoyWired;
|
||||
BitField<0, 1, u32> IsConnected;
|
||||
BitField<1, 1, u32> IsWired;
|
||||
BitField<2, 1, u32> IsLeftJoyConnected;
|
||||
BitField<3, 1, u32> IsLeftJoyWired;
|
||||
BitField<4, 1, u32> IsRightJoyConnected;
|
||||
BitField<5, 1, u32> IsRightJoyWired;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(ConnectionState) == 4, "ConnectionState is an invalid size");
|
||||
@@ -240,23 +240,23 @@ private:
|
||||
struct NPadProperties {
|
||||
union {
|
||||
s64_le raw{};
|
||||
BitField<11, 1, s64_le> is_vertical;
|
||||
BitField<12, 1, s64_le> is_horizontal;
|
||||
BitField<13, 1, s64_le> use_plus;
|
||||
BitField<14, 1, s64_le> use_minus;
|
||||
BitField<11, 1, s64> is_vertical;
|
||||
BitField<12, 1, s64> is_horizontal;
|
||||
BitField<13, 1, s64> use_plus;
|
||||
BitField<14, 1, s64> use_minus;
|
||||
};
|
||||
};
|
||||
|
||||
struct NPadDevice {
|
||||
union {
|
||||
u32_le raw{};
|
||||
BitField<0, 1, s32_le> pro_controller;
|
||||
BitField<1, 1, s32_le> handheld;
|
||||
BitField<2, 1, s32_le> handheld_left;
|
||||
BitField<3, 1, s32_le> handheld_right;
|
||||
BitField<4, 1, s32_le> joycon_left;
|
||||
BitField<5, 1, s32_le> joycon_right;
|
||||
BitField<6, 1, s32_le> pokeball;
|
||||
BitField<0, 1, s32> pro_controller;
|
||||
BitField<1, 1, s32> handheld;
|
||||
BitField<2, 1, s32> handheld_left;
|
||||
BitField<3, 1, s32> handheld_right;
|
||||
BitField<4, 1, s32> joycon_left;
|
||||
BitField<5, 1, s32> joycon_right;
|
||||
BitField<6, 1, s32> pokeball;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ private:
|
||||
struct Attributes {
|
||||
union {
|
||||
u32 raw{};
|
||||
BitField<0, 1, u32_le> start_touch;
|
||||
BitField<1, 1, u32_le> end_touch;
|
||||
BitField<0, 1, u32> start_touch;
|
||||
BitField<1, 1, u32> end_touch;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size");
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/hid/controllers/controller_base.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
#include "controllers/controller_base.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
|
||||
@@ -319,15 +319,14 @@ public:
|
||||
}
|
||||
|
||||
ASSERT(vm_manager
|
||||
.MirrorMemory(*map_address, nro_addr, nro_size,
|
||||
Kernel::MemoryState::ModuleCodeStatic)
|
||||
.MirrorMemory(*map_address, nro_addr, nro_size, Kernel::MemoryState::ModuleCode)
|
||||
.IsSuccess());
|
||||
ASSERT(vm_manager.UnmapRange(nro_addr, nro_size).IsSuccess());
|
||||
|
||||
if (bss_size > 0) {
|
||||
ASSERT(vm_manager
|
||||
.MirrorMemory(*map_address + nro_size, bss_addr, bss_size,
|
||||
Kernel::MemoryState::ModuleCodeStatic)
|
||||
Kernel::MemoryState::ModuleCode)
|
||||
.IsSuccess());
|
||||
ASSERT(vm_manager.UnmapRange(bss_addr, bss_size).IsSuccess());
|
||||
}
|
||||
@@ -388,8 +387,7 @@ public:
|
||||
const auto& nro_size = iter->second.size;
|
||||
|
||||
ASSERT(vm_manager
|
||||
.MirrorMemory(heap_addr, mapped_addr, nro_size,
|
||||
Kernel::MemoryState::ModuleCodeStatic)
|
||||
.MirrorMemory(heap_addr, mapped_addr, nro_size, Kernel::MemoryState::ModuleCode)
|
||||
.IsSuccess());
|
||||
ASSERT(vm_manager.UnmapRange(mapped_addr, nro_size).IsSuccess());
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ private:
|
||||
union {
|
||||
BitField<0, 16, Flags> flags;
|
||||
BitField<16, 8, Severity> severity;
|
||||
BitField<24, 8, u32_le> verbosity;
|
||||
BitField<24, 8, u32> verbosity;
|
||||
};
|
||||
u32_le payload_size;
|
||||
|
||||
|
||||
@@ -19,11 +19,11 @@ public:
|
||||
virtual ~nvdevice() = default;
|
||||
union Ioctl {
|
||||
u32_le raw;
|
||||
BitField<0, 8, u32_le> cmd;
|
||||
BitField<8, 8, u32_le> group;
|
||||
BitField<16, 14, u32_le> length;
|
||||
BitField<30, 1, u32_le> is_in;
|
||||
BitField<31, 1, u32_le> is_out;
|
||||
BitField<0, 8, u32> cmd;
|
||||
BitField<8, 8, u32> group;
|
||||
BitField<16, 14, u32> length;
|
||||
BitField<30, 1, u32> is_in;
|
||||
BitField<31, 1, u32> is_out;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/kernel/code_set.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/loader/elf.h"
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/loader/linker.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Loader {
|
||||
|
||||
enum class RelocationType : u32 { ABS64 = 257, GLOB_DAT = 1025, JUMP_SLOT = 1026, RELATIVE = 1027 };
|
||||
|
||||
enum DynamicType : u32 {
|
||||
DT_NULL = 0,
|
||||
DT_PLTRELSZ = 2,
|
||||
DT_STRTAB = 5,
|
||||
DT_SYMTAB = 6,
|
||||
DT_RELA = 7,
|
||||
DT_RELASZ = 8,
|
||||
DT_STRSZ = 10,
|
||||
DT_JMPREL = 23,
|
||||
};
|
||||
|
||||
struct Elf64_Rela {
|
||||
u64_le offset;
|
||||
RelocationType type;
|
||||
u32_le symbol;
|
||||
s64_le addend;
|
||||
};
|
||||
static_assert(sizeof(Elf64_Rela) == 0x18, "Elf64_Rela has incorrect size.");
|
||||
|
||||
struct Elf64_Dyn {
|
||||
u64_le tag;
|
||||
u64_le value;
|
||||
};
|
||||
static_assert(sizeof(Elf64_Dyn) == 0x10, "Elf64_Dyn has incorrect size.");
|
||||
|
||||
struct Elf64_Sym {
|
||||
u32_le name;
|
||||
INSERT_PADDING_BYTES(0x2);
|
||||
u16_le shndx;
|
||||
u64_le value;
|
||||
u64_le size;
|
||||
};
|
||||
static_assert(sizeof(Elf64_Sym) == 0x18, "Elf64_Sym has incorrect size.");
|
||||
|
||||
void Linker::WriteRelocations(std::vector<u8>& program_image, const std::vector<Symbol>& symbols,
|
||||
u64 relocation_offset, u64 size, VAddr load_base) {
|
||||
for (u64 i = 0; i < size; i += sizeof(Elf64_Rela)) {
|
||||
Elf64_Rela rela;
|
||||
std::memcpy(&rela, &program_image[relocation_offset + i], sizeof(Elf64_Rela));
|
||||
|
||||
const Symbol& symbol = symbols[rela.symbol];
|
||||
switch (rela.type) {
|
||||
case RelocationType::RELATIVE: {
|
||||
const u64 value = load_base + rela.addend;
|
||||
if (!symbol.name.empty()) {
|
||||
exports[symbol.name] = value;
|
||||
}
|
||||
std::memcpy(&program_image[rela.offset], &value, sizeof(u64));
|
||||
break;
|
||||
}
|
||||
case RelocationType::JUMP_SLOT:
|
||||
case RelocationType::GLOB_DAT:
|
||||
if (!symbol.value) {
|
||||
imports[symbol.name] = {rela.offset + load_base, 0};
|
||||
} else {
|
||||
exports[symbol.name] = symbol.value;
|
||||
std::memcpy(&program_image[rela.offset], &symbol.value, sizeof(u64));
|
||||
}
|
||||
break;
|
||||
case RelocationType::ABS64:
|
||||
if (!symbol.value) {
|
||||
imports[symbol.name] = {rela.offset + load_base, rela.addend};
|
||||
} else {
|
||||
const u64 value = symbol.value + rela.addend;
|
||||
exports[symbol.name] = value;
|
||||
std::memcpy(&program_image[rela.offset], &value, sizeof(u64));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(Loader, "Unknown relocation type: {}", static_cast<int>(rela.type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::Relocate(std::vector<u8>& program_image, u32 dynamic_section_offset, VAddr load_base) {
|
||||
std::map<u64, u64> dynamic;
|
||||
while (dynamic_section_offset < program_image.size()) {
|
||||
Elf64_Dyn dyn;
|
||||
std::memcpy(&dyn, &program_image[dynamic_section_offset], sizeof(Elf64_Dyn));
|
||||
dynamic_section_offset += sizeof(Elf64_Dyn);
|
||||
|
||||
if (dyn.tag == DT_NULL) {
|
||||
break;
|
||||
}
|
||||
dynamic[dyn.tag] = dyn.value;
|
||||
}
|
||||
|
||||
u64 offset = dynamic[DT_SYMTAB];
|
||||
std::vector<Symbol> symbols;
|
||||
while (offset < program_image.size()) {
|
||||
Elf64_Sym sym;
|
||||
std::memcpy(&sym, &program_image[offset], sizeof(Elf64_Sym));
|
||||
offset += sizeof(Elf64_Sym);
|
||||
|
||||
if (sym.name >= dynamic[DT_STRSZ]) {
|
||||
break;
|
||||
}
|
||||
|
||||
std::string name = reinterpret_cast<char*>(&program_image[dynamic[DT_STRTAB] + sym.name]);
|
||||
if (sym.value) {
|
||||
exports[name] = load_base + sym.value;
|
||||
symbols.emplace_back(std::move(name), load_base + sym.value);
|
||||
} else {
|
||||
symbols.emplace_back(std::move(name), 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (dynamic.find(DT_RELA) != dynamic.end()) {
|
||||
WriteRelocations(program_image, symbols, dynamic[DT_RELA], dynamic[DT_RELASZ], load_base);
|
||||
}
|
||||
|
||||
if (dynamic.find(DT_JMPREL) != dynamic.end()) {
|
||||
WriteRelocations(program_image, symbols, dynamic[DT_JMPREL], dynamic[DT_PLTRELSZ],
|
||||
load_base);
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::ResolveImports() {
|
||||
// Resolve imports
|
||||
for (const auto& import : imports) {
|
||||
const auto& search = exports.find(import.first);
|
||||
if (search != exports.end()) {
|
||||
Memory::Write64(import.second.ea, search->second + import.second.addend);
|
||||
} else {
|
||||
LOG_ERROR(Loader, "Unresolved import: {}", import.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Loader
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Loader {
|
||||
|
||||
class Linker {
|
||||
protected:
|
||||
struct Symbol {
|
||||
Symbol(std::string&& name, u64 value) : name(std::move(name)), value(value) {}
|
||||
std::string name;
|
||||
u64 value;
|
||||
};
|
||||
|
||||
struct Import {
|
||||
VAddr ea;
|
||||
s64 addend;
|
||||
};
|
||||
|
||||
void WriteRelocations(std::vector<u8>& program_image, const std::vector<Symbol>& symbols,
|
||||
u64 relocation_offset, u64 size, VAddr load_base);
|
||||
void Relocate(std::vector<u8>& program_image, u32 dynamic_section_offset, VAddr load_base);
|
||||
|
||||
void ResolveImports();
|
||||
|
||||
std::map<std::string, Import> imports;
|
||||
std::map<std::string, VAddr> exports;
|
||||
};
|
||||
|
||||
} // namespace Loader
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/code_set.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "core/loader/linker.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
@@ -21,7 +21,7 @@ class Process;
|
||||
namespace Loader {
|
||||
|
||||
/// Loads an NRO file
|
||||
class AppLoader_NRO final : public AppLoader, Linker {
|
||||
class AppLoader_NRO final : public AppLoader {
|
||||
public:
|
||||
explicit AppLoader_NRO(FileSys::VirtualFile file);
|
||||
~AppLoader_NRO() override;
|
||||
|
||||
@@ -7,10 +7,13 @@
|
||||
#include <lz4.h>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/code_set.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/loader/nso.h"
|
||||
@@ -18,36 +21,8 @@
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Loader {
|
||||
|
||||
struct NsoSegmentHeader {
|
||||
u32_le offset;
|
||||
u32_le location;
|
||||
u32_le size;
|
||||
union {
|
||||
u32_le alignment;
|
||||
u32_le bss_size;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(NsoSegmentHeader) == 0x10, "NsoSegmentHeader has incorrect size.");
|
||||
|
||||
struct NsoHeader {
|
||||
u32_le magic;
|
||||
u32_le version;
|
||||
INSERT_PADDING_WORDS(1);
|
||||
u8 flags;
|
||||
std::array<NsoSegmentHeader, 3> segments; // Text, RoData, Data (in that order)
|
||||
std::array<u8, 0x20> build_id;
|
||||
std::array<u32_le, 3> segments_compressed_size;
|
||||
|
||||
bool IsSegmentCompressed(size_t segment_num) const {
|
||||
ASSERT_MSG(segment_num < 3, "Invalid segment {}", segment_num);
|
||||
return ((flags >> segment_num) & 1);
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(NsoHeader) == 0x6c, "NsoHeader has incorrect size.");
|
||||
static_assert(std::is_trivially_copyable_v<NsoHeader>, "NsoHeader isn't trivially copyable.");
|
||||
|
||||
struct ModHeader {
|
||||
namespace {
|
||||
struct MODHeader {
|
||||
u32_le magic;
|
||||
u32_le dynamic_offset;
|
||||
u32_le bss_start_offset;
|
||||
@@ -56,7 +31,32 @@ struct ModHeader {
|
||||
u32_le eh_frame_hdr_end_offset;
|
||||
u32_le module_offset; // Offset to runtime-generated module object. typically equal to .bss base
|
||||
};
|
||||
static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size.");
|
||||
static_assert(sizeof(MODHeader) == 0x1c, "MODHeader has incorrect size.");
|
||||
|
||||
std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data,
|
||||
const NSOSegmentHeader& header) {
|
||||
std::vector<u8> uncompressed_data(header.size);
|
||||
const int bytes_uncompressed =
|
||||
LZ4_decompress_safe(reinterpret_cast<const char*>(compressed_data.data()),
|
||||
reinterpret_cast<char*>(uncompressed_data.data()),
|
||||
static_cast<int>(compressed_data.size()), header.size);
|
||||
|
||||
ASSERT_MSG(bytes_uncompressed == static_cast<int>(header.size) &&
|
||||
bytes_uncompressed == static_cast<int>(uncompressed_data.size()),
|
||||
"{} != {} != {}", bytes_uncompressed, header.size, uncompressed_data.size());
|
||||
|
||||
return uncompressed_data;
|
||||
}
|
||||
|
||||
constexpr u32 PageAlignSize(u32 size) {
|
||||
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
bool NSOHeader::IsSegmentCompressed(size_t segment_num) const {
|
||||
ASSERT_MSG(segment_num < 3, "Invalid segment {}", segment_num);
|
||||
return ((flags >> segment_num) & 1) != 0;
|
||||
}
|
||||
|
||||
AppLoader_NSO::AppLoader_NSO(FileSys::VirtualFile file) : AppLoader(std::move(file)) {}
|
||||
|
||||
@@ -73,38 +73,22 @@ FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) {
|
||||
return FileType::NSO;
|
||||
}
|
||||
|
||||
static std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data,
|
||||
const NsoSegmentHeader& header) {
|
||||
std::vector<u8> uncompressed_data(header.size);
|
||||
const int bytes_uncompressed =
|
||||
LZ4_decompress_safe(reinterpret_cast<const char*>(compressed_data.data()),
|
||||
reinterpret_cast<char*>(uncompressed_data.data()),
|
||||
static_cast<int>(compressed_data.size()), header.size);
|
||||
|
||||
ASSERT_MSG(bytes_uncompressed == static_cast<int>(header.size) &&
|
||||
bytes_uncompressed == static_cast<int>(uncompressed_data.size()),
|
||||
"{} != {} != {}", bytes_uncompressed, header.size, uncompressed_data.size());
|
||||
|
||||
return uncompressed_data;
|
||||
}
|
||||
|
||||
static constexpr u32 PageAlignSize(u32 size) {
|
||||
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
|
||||
}
|
||||
|
||||
std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
|
||||
const FileSys::VfsFile& file, VAddr load_base,
|
||||
bool should_pass_arguments,
|
||||
std::optional<FileSys::PatchManager> pm) {
|
||||
if (file.GetSize() < sizeof(NsoHeader))
|
||||
if (file.GetSize() < sizeof(NSOHeader)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
NsoHeader nso_header{};
|
||||
if (sizeof(NsoHeader) != file.ReadObject(&nso_header))
|
||||
NSOHeader nso_header{};
|
||||
if (sizeof(NSOHeader) != file.ReadObject(&nso_header)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
|
||||
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Build program image
|
||||
Kernel::CodeSet codeset;
|
||||
@@ -140,10 +124,10 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
|
||||
std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32));
|
||||
|
||||
// Read MOD header
|
||||
ModHeader mod_header{};
|
||||
MODHeader mod_header{};
|
||||
// Default .bss to size in segment header if MOD0 section doesn't exist
|
||||
u32 bss_size{PageAlignSize(nso_header.segments[2].bss_size)};
|
||||
std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(ModHeader));
|
||||
std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(MODHeader));
|
||||
const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')};
|
||||
if (has_mod_header) {
|
||||
// Resize program image to include .bss section and page align each section
|
||||
@@ -155,13 +139,25 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
|
||||
|
||||
// Apply patches if necessary
|
||||
if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) {
|
||||
std::vector<u8> pi_header(program_image.size() + 0x100);
|
||||
std::memcpy(pi_header.data(), &nso_header, sizeof(NsoHeader));
|
||||
std::memcpy(pi_header.data() + 0x100, program_image.data(), program_image.size());
|
||||
std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size());
|
||||
pi_header.insert(pi_header.begin(), reinterpret_cast<u8*>(&nso_header),
|
||||
reinterpret_cast<u8*>(&nso_header) + sizeof(NSOHeader));
|
||||
pi_header.insert(pi_header.begin() + sizeof(NSOHeader), program_image.begin(),
|
||||
program_image.end());
|
||||
|
||||
pi_header = pm->PatchNSO(pi_header);
|
||||
|
||||
std::memcpy(program_image.data(), pi_header.data() + 0x100, program_image.size());
|
||||
std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.begin());
|
||||
}
|
||||
|
||||
// Apply cheats if they exist and the program has a valid title ID
|
||||
if (pm) {
|
||||
const auto cheats = pm->CreateCheatList(nso_header.build_id);
|
||||
if (!cheats.empty()) {
|
||||
Core::System::GetInstance().RegisterCheatList(
|
||||
cheats, Common::HexArrayToString(nso_header.build_id), load_base,
|
||||
load_base + program_image.size());
|
||||
}
|
||||
}
|
||||
|
||||
// Load codeset for current process
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/loader/linker.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace Kernel {
|
||||
@@ -16,6 +18,43 @@ class Process;
|
||||
|
||||
namespace Loader {
|
||||
|
||||
struct NSOSegmentHeader {
|
||||
u32_le offset;
|
||||
u32_le location;
|
||||
u32_le size;
|
||||
union {
|
||||
u32_le alignment;
|
||||
u32_le bss_size;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(NSOSegmentHeader) == 0x10, "NsoSegmentHeader has incorrect size.");
|
||||
|
||||
struct NSOHeader {
|
||||
using SHA256Hash = std::array<u8, 0x20>;
|
||||
|
||||
struct RODataRelativeExtent {
|
||||
u32_le data_offset;
|
||||
u32_le size;
|
||||
};
|
||||
|
||||
u32_le magic;
|
||||
u32_le version;
|
||||
u32 reserved;
|
||||
u32_le flags;
|
||||
std::array<NSOSegmentHeader, 3> segments; // Text, RoData, Data (in that order)
|
||||
std::array<u8, 0x20> build_id;
|
||||
std::array<u32_le, 3> segments_compressed_size;
|
||||
std::array<u8, 0x1C> padding;
|
||||
RODataRelativeExtent api_info_extent;
|
||||
RODataRelativeExtent dynstr_extent;
|
||||
RODataRelativeExtent dynsyn_extent;
|
||||
std::array<SHA256Hash, 3> segment_hashes;
|
||||
|
||||
bool IsSegmentCompressed(size_t segment_num) const;
|
||||
};
|
||||
static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size.");
|
||||
static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable.");
|
||||
|
||||
constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000;
|
||||
|
||||
struct NSOArgumentHeader {
|
||||
@@ -26,7 +65,7 @@ struct NSOArgumentHeader {
|
||||
static_assert(sizeof(NSOArgumentHeader) == 0x20, "NSOArgumentHeader has incorrect size.");
|
||||
|
||||
/// Loads an NSO file
|
||||
class AppLoader_NSO final : public AppLoader, Linker {
|
||||
class AppLoader_NSO final : public AppLoader {
|
||||
public:
|
||||
explicit AppLoader_NSO(FileSys::VirtualFile file);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/page_table.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
@@ -18,13 +19,14 @@
|
||||
#include "core/hle/lock.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory_setup.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
namespace Memory {
|
||||
|
||||
static PageTable* current_page_table = nullptr;
|
||||
static Common::PageTable* current_page_table = nullptr;
|
||||
|
||||
void SetCurrentPageTable(PageTable* page_table) {
|
||||
void SetCurrentPageTable(Common::PageTable* page_table) {
|
||||
current_page_table = page_table;
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
@@ -36,41 +38,19 @@ void SetCurrentPageTable(PageTable* page_table) {
|
||||
}
|
||||
}
|
||||
|
||||
PageTable* GetCurrentPageTable() {
|
||||
Common::PageTable* GetCurrentPageTable() {
|
||||
return current_page_table;
|
||||
}
|
||||
|
||||
PageTable::PageTable() = default;
|
||||
|
||||
PageTable::PageTable(std::size_t address_space_width_in_bits) {
|
||||
Resize(address_space_width_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_BITS);
|
||||
|
||||
pointers.resize(num_page_table_entries);
|
||||
attributes.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();
|
||||
}
|
||||
|
||||
static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, PageType type) {
|
||||
static void MapPages(Common::PageTable& page_table, VAddr base, u64 size, u8* memory,
|
||||
Common::PageType type) {
|
||||
LOG_DEBUG(HW_Memory, "Mapping {} onto {:016X}-{:016X}", fmt::ptr(memory), base * PAGE_SIZE,
|
||||
(base + size) * PAGE_SIZE);
|
||||
|
||||
// During boot, current_page_table might not be set yet, in which case we need not flush
|
||||
if (current_page_table) {
|
||||
RasterizerFlushVirtualRegion(base << PAGE_BITS, size * PAGE_SIZE,
|
||||
FlushMode::FlushAndInvalidate);
|
||||
Core::System::GetInstance().GPU().FlushAndInvalidateRegion(base << PAGE_BITS,
|
||||
size * PAGE_SIZE);
|
||||
}
|
||||
|
||||
VAddr end = base + size;
|
||||
@@ -91,41 +71,47 @@ static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, Pa
|
||||
}
|
||||
}
|
||||
|
||||
void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target) {
|
||||
void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target) {
|
||||
ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size);
|
||||
ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base);
|
||||
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, PageType::Memory);
|
||||
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, Common::PageType::Memory);
|
||||
}
|
||||
|
||||
void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer mmio_handler) {
|
||||
void MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size,
|
||||
Common::MemoryHookPointer mmio_handler) {
|
||||
ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size);
|
||||
ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base);
|
||||
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Special);
|
||||
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, Common::PageType::Special);
|
||||
|
||||
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
|
||||
SpecialRegion region{SpecialRegion::Type::IODevice, std::move(mmio_handler)};
|
||||
page_table.special_regions.add(std::make_pair(interval, std::set<SpecialRegion>{region}));
|
||||
Common::SpecialRegion region{Common::SpecialRegion::Type::IODevice, std::move(mmio_handler)};
|
||||
page_table.special_regions.add(
|
||||
std::make_pair(interval, std::set<Common::SpecialRegion>{region}));
|
||||
}
|
||||
|
||||
void UnmapRegion(PageTable& page_table, VAddr base, u64 size) {
|
||||
void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) {
|
||||
ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size);
|
||||
ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base);
|
||||
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped);
|
||||
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, Common::PageType::Unmapped);
|
||||
|
||||
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
|
||||
page_table.special_regions.erase(interval);
|
||||
}
|
||||
|
||||
void AddDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook) {
|
||||
void AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
|
||||
Common::MemoryHookPointer hook) {
|
||||
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
|
||||
SpecialRegion region{SpecialRegion::Type::DebugHook, std::move(hook)};
|
||||
page_table.special_regions.add(std::make_pair(interval, std::set<SpecialRegion>{region}));
|
||||
Common::SpecialRegion region{Common::SpecialRegion::Type::DebugHook, std::move(hook)};
|
||||
page_table.special_regions.add(
|
||||
std::make_pair(interval, std::set<Common::SpecialRegion>{region}));
|
||||
}
|
||||
|
||||
void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook) {
|
||||
void RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
|
||||
Common::MemoryHookPointer hook) {
|
||||
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
|
||||
SpecialRegion region{SpecialRegion::Type::DebugHook, std::move(hook)};
|
||||
page_table.special_regions.subtract(std::make_pair(interval, std::set<SpecialRegion>{region}));
|
||||
Common::SpecialRegion region{Common::SpecialRegion::Type::DebugHook, std::move(hook)};
|
||||
page_table.special_regions.subtract(
|
||||
std::make_pair(interval, std::set<Common::SpecialRegion>{region}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,19 +160,19 @@ T Read(const VAddr vaddr) {
|
||||
return value;
|
||||
}
|
||||
|
||||
PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
switch (type) {
|
||||
case PageType::Unmapped:
|
||||
case Common::PageType::Unmapped:
|
||||
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:08X}", sizeof(T) * 8, vaddr);
|
||||
return 0;
|
||||
case PageType::Memory:
|
||||
case Common::PageType::Memory:
|
||||
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
|
||||
break;
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush);
|
||||
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
auto host_ptr{GetPointerFromVMA(vaddr)};
|
||||
Core::System::GetInstance().GPU().FlushRegion(ToCacheAddr(host_ptr), sizeof(T));
|
||||
T value;
|
||||
std::memcpy(&value, GetPointerFromVMA(vaddr), sizeof(T));
|
||||
std::memcpy(&value, host_ptr, sizeof(T));
|
||||
return value;
|
||||
}
|
||||
default:
|
||||
@@ -204,18 +190,19 @@ void Write(const VAddr vaddr, const T data) {
|
||||
return;
|
||||
}
|
||||
|
||||
PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
switch (type) {
|
||||
case PageType::Unmapped:
|
||||
case Common::PageType::Unmapped:
|
||||
LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8,
|
||||
static_cast<u32>(data), vaddr);
|
||||
return;
|
||||
case PageType::Memory:
|
||||
case Common::PageType::Memory:
|
||||
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
|
||||
break;
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Invalidate);
|
||||
std::memcpy(GetPointerFromVMA(vaddr), &data, sizeof(T));
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
auto host_ptr{GetPointerFromVMA(vaddr)};
|
||||
Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), sizeof(T));
|
||||
std::memcpy(host_ptr, &data, sizeof(T));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -230,10 +217,10 @@ bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) {
|
||||
if (page_pointer)
|
||||
return true;
|
||||
|
||||
if (page_table.attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory)
|
||||
if (page_table.attributes[vaddr >> PAGE_BITS] == Common::PageType::RasterizerCachedMemory)
|
||||
return true;
|
||||
|
||||
if (page_table.attributes[vaddr >> PAGE_BITS] != PageType::Special)
|
||||
if (page_table.attributes[vaddr >> PAGE_BITS] != Common::PageType::Special)
|
||||
return false;
|
||||
|
||||
return false;
|
||||
@@ -253,7 +240,8 @@ u8* GetPointer(const VAddr vaddr) {
|
||||
return page_pointer + (vaddr & PAGE_MASK);
|
||||
}
|
||||
|
||||
if (current_page_table->attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory) {
|
||||
if (current_page_table->attributes[vaddr >> PAGE_BITS] ==
|
||||
Common::PageType::RasterizerCachedMemory) {
|
||||
return GetPointerFromVMA(vaddr);
|
||||
}
|
||||
|
||||
@@ -287,20 +275,20 @@ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
|
||||
|
||||
u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1;
|
||||
for (unsigned i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) {
|
||||
PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
Common::PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
|
||||
if (cached) {
|
||||
// Switch page type to cached if now cached
|
||||
switch (page_type) {
|
||||
case PageType::Unmapped:
|
||||
case Common::PageType::Unmapped:
|
||||
// It is not necessary for a process to have this region mapped into its address
|
||||
// space, for example, a system module need not have a VRAM mapping.
|
||||
break;
|
||||
case PageType::Memory:
|
||||
page_type = PageType::RasterizerCachedMemory;
|
||||
case Common::PageType::Memory:
|
||||
page_type = Common::PageType::RasterizerCachedMemory;
|
||||
current_page_table->pointers[vaddr >> PAGE_BITS] = nullptr;
|
||||
break;
|
||||
case PageType::RasterizerCachedMemory:
|
||||
case Common::PageType::RasterizerCachedMemory:
|
||||
// There can be more than one GPU region mapped per CPU region, so it's common that
|
||||
// this area is already marked as cached.
|
||||
break;
|
||||
@@ -310,23 +298,23 @@ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
|
||||
} else {
|
||||
// Switch page type to uncached if now uncached
|
||||
switch (page_type) {
|
||||
case PageType::Unmapped:
|
||||
case Common::PageType::Unmapped:
|
||||
// It is not necessary for a process to have this region mapped into its address
|
||||
// space, for example, a system module need not have a VRAM mapping.
|
||||
break;
|
||||
case PageType::Memory:
|
||||
case Common::PageType::Memory:
|
||||
// There can be more than one GPU region mapped per CPU region, so it's common that
|
||||
// this area is already unmarked as cached.
|
||||
break;
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
u8* pointer = GetPointerFromVMA(vaddr & ~PAGE_MASK);
|
||||
if (pointer == nullptr) {
|
||||
// It's possible that this function has been called while updating the pagetable
|
||||
// after unmapping a VMA. In that case the underlying VMA will no longer exist,
|
||||
// and we should just leave the pagetable entry blank.
|
||||
page_type = PageType::Unmapped;
|
||||
page_type = Common::PageType::Unmapped;
|
||||
} else {
|
||||
page_type = PageType::Memory;
|
||||
page_type = Common::PageType::Memory;
|
||||
current_page_table->pointers[vaddr >> PAGE_BITS] = pointer;
|
||||
}
|
||||
break;
|
||||
@@ -338,47 +326,6 @@ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode) {
|
||||
auto& system_instance = Core::System::GetInstance();
|
||||
|
||||
// Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
|
||||
// null here
|
||||
if (!system_instance.IsPoweredOn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const VAddr end = start + size;
|
||||
|
||||
const auto CheckRegion = [&](VAddr region_start, VAddr region_end) {
|
||||
if (start >= region_end || end <= region_start) {
|
||||
// No overlap with region
|
||||
return;
|
||||
}
|
||||
|
||||
const VAddr overlap_start = std::max(start, region_start);
|
||||
const VAddr overlap_end = std::min(end, region_end);
|
||||
const VAddr overlap_size = overlap_end - overlap_start;
|
||||
|
||||
auto& gpu = system_instance.GPU();
|
||||
switch (mode) {
|
||||
case FlushMode::Flush:
|
||||
gpu.FlushRegion(ToCacheAddr(GetPointer(overlap_start)), overlap_size);
|
||||
break;
|
||||
case FlushMode::Invalidate:
|
||||
gpu.InvalidateRegion(ToCacheAddr(GetPointer(overlap_start)), overlap_size);
|
||||
break;
|
||||
case FlushMode::FlushAndInvalidate:
|
||||
gpu.FlushAndInvalidateRegion(ToCacheAddr(GetPointer(overlap_start)), overlap_size);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const auto& vm_manager = Core::CurrentProcess()->VMManager();
|
||||
|
||||
CheckRegion(vm_manager.GetCodeRegionBaseAddress(), vm_manager.GetCodeRegionEndAddress());
|
||||
CheckRegion(vm_manager.GetHeapRegionBaseAddress(), vm_manager.GetHeapRegionEndAddress());
|
||||
}
|
||||
|
||||
u8 Read8(const VAddr addr) {
|
||||
return Read<u8>(addr);
|
||||
}
|
||||
@@ -409,24 +356,24 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_
|
||||
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
|
||||
|
||||
switch (page_table.attributes[page_index]) {
|
||||
case PageType::Unmapped: {
|
||||
case Common::PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory,
|
||||
"Unmapped ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
current_vaddr, src_addr, size);
|
||||
std::memset(dest_buffer, 0, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::Memory: {
|
||||
case Common::PageType::Memory: {
|
||||
DEBUG_ASSERT(page_table.pointers[page_index]);
|
||||
|
||||
const u8* src_ptr = page_table.pointers[page_index] + page_offset;
|
||||
std::memcpy(dest_buffer, src_ptr, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||
FlushMode::Flush);
|
||||
std::memcpy(dest_buffer, GetPointerFromVMA(process, current_vaddr), copy_amount);
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)};
|
||||
Core::System::GetInstance().GPU().FlushRegion(ToCacheAddr(host_ptr), copy_amount);
|
||||
std::memcpy(dest_buffer, host_ptr, copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -473,23 +420,23 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi
|
||||
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
|
||||
|
||||
switch (page_table.attributes[page_index]) {
|
||||
case PageType::Unmapped: {
|
||||
case Common::PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory,
|
||||
"Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
current_vaddr, dest_addr, size);
|
||||
break;
|
||||
}
|
||||
case PageType::Memory: {
|
||||
case Common::PageType::Memory: {
|
||||
DEBUG_ASSERT(page_table.pointers[page_index]);
|
||||
|
||||
u8* dest_ptr = page_table.pointers[page_index] + page_offset;
|
||||
std::memcpy(dest_ptr, src_buffer, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||
FlushMode::Invalidate);
|
||||
std::memcpy(GetPointerFromVMA(process, current_vaddr), src_buffer, copy_amount);
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)};
|
||||
Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), copy_amount);
|
||||
std::memcpy(host_ptr, src_buffer, copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -519,23 +466,23 @@ void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const std:
|
||||
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
|
||||
|
||||
switch (page_table.attributes[page_index]) {
|
||||
case PageType::Unmapped: {
|
||||
case Common::PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory,
|
||||
"Unmapped ZeroBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
current_vaddr, dest_addr, size);
|
||||
break;
|
||||
}
|
||||
case PageType::Memory: {
|
||||
case Common::PageType::Memory: {
|
||||
DEBUG_ASSERT(page_table.pointers[page_index]);
|
||||
|
||||
u8* dest_ptr = page_table.pointers[page_index] + page_offset;
|
||||
std::memset(dest_ptr, 0, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||
FlushMode::Invalidate);
|
||||
std::memset(GetPointerFromVMA(process, current_vaddr), 0, copy_amount);
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)};
|
||||
Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), copy_amount);
|
||||
std::memset(host_ptr, 0, copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -561,23 +508,23 @@ void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr,
|
||||
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
|
||||
|
||||
switch (page_table.attributes[page_index]) {
|
||||
case PageType::Unmapped: {
|
||||
case Common::PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory,
|
||||
"Unmapped CopyBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
current_vaddr, src_addr, size);
|
||||
ZeroBlock(process, dest_addr, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::Memory: {
|
||||
case Common::PageType::Memory: {
|
||||
DEBUG_ASSERT(page_table.pointers[page_index]);
|
||||
const u8* src_ptr = page_table.pointers[page_index] + page_offset;
|
||||
WriteBlock(process, dest_addr, src_ptr, copy_amount);
|
||||
break;
|
||||
}
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||
FlushMode::Flush);
|
||||
WriteBlock(process, dest_addr, GetPointerFromVMA(process, current_vaddr), copy_amount);
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)};
|
||||
Core::System::GetInstance().GPU().FlushRegion(ToCacheAddr(host_ptr), copy_amount);
|
||||
WriteBlock(process, dest_addr, host_ptr, copy_amount);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/memory_hook.h"
|
||||
|
||||
namespace Common {
|
||||
struct PageTable;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
class Process;
|
||||
@@ -26,71 +26,6 @@ constexpr std::size_t PAGE_BITS = 12;
|
||||
constexpr u64 PAGE_SIZE = 1ULL << PAGE_BITS;
|
||||
constexpr u64 PAGE_MASK = PAGE_SIZE - 1;
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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();
|
||||
explicit PageTable(std::size_t address_space_width_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<VAddr, 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;
|
||||
};
|
||||
|
||||
/// Virtual user-space memory regions
|
||||
enum : VAddr {
|
||||
/// Read-only page containing kernel and system configuration values.
|
||||
@@ -116,8 +51,8 @@ enum : VAddr {
|
||||
};
|
||||
|
||||
/// Currently active page table
|
||||
void SetCurrentPageTable(PageTable* page_table);
|
||||
PageTable* GetCurrentPageTable();
|
||||
void SetCurrentPageTable(Common::PageTable* page_table);
|
||||
Common::PageTable* GetCurrentPageTable();
|
||||
|
||||
/// Determines if the given VAddr is valid for the specified process.
|
||||
bool IsValidVirtualAddress(const Kernel::Process& process, VAddr vaddr);
|
||||
@@ -161,10 +96,4 @@ enum class FlushMode {
|
||||
*/
|
||||
void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached);
|
||||
|
||||
/**
|
||||
* Flushes and invalidates any externally cached rasterizer resources touching the given virtual
|
||||
* address region.
|
||||
*/
|
||||
void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode);
|
||||
|
||||
} // namespace Memory
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/memory_hook.h"
|
||||
#include "common/memory_hook.h"
|
||||
|
||||
namespace Common {
|
||||
struct PageTable;
|
||||
}
|
||||
|
||||
namespace Memory {
|
||||
|
||||
@@ -17,7 +21,7 @@ namespace Memory {
|
||||
* @param size The amount of bytes to map. Must be page-aligned.
|
||||
* @param target Buffer with the memory backing the mapping. Must be of length at least `size`.
|
||||
*/
|
||||
void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target);
|
||||
void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target);
|
||||
|
||||
/**
|
||||
* Maps a region of the emulated process address space as a IO region.
|
||||
@@ -26,11 +30,14 @@ void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target);
|
||||
* @param size The amount of bytes to map. Must be page-aligned.
|
||||
* @param mmio_handler The handler that backs the mapping.
|
||||
*/
|
||||
void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer mmio_handler);
|
||||
void MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size,
|
||||
Common::MemoryHookPointer mmio_handler);
|
||||
|
||||
void UnmapRegion(PageTable& page_table, VAddr base, u64 size);
|
||||
void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size);
|
||||
|
||||
void AddDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook);
|
||||
void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook);
|
||||
void AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
|
||||
Common::MemoryHookPointer hook);
|
||||
void RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
|
||||
Common::MemoryHookPointer hook);
|
||||
|
||||
} // namespace Memory
|
||||
|
||||
@@ -24,17 +24,19 @@ namespace InputCommon::SDL {
|
||||
|
||||
class State {
|
||||
public:
|
||||
/// Unresisters SDL device factories and shut them down.
|
||||
using Pollers = std::vector<std::unique_ptr<Polling::DevicePoller>>;
|
||||
|
||||
/// Unregisters SDL device factories and shut them down.
|
||||
virtual ~State() = default;
|
||||
|
||||
virtual std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
|
||||
InputCommon::Polling::DeviceType type) = 0;
|
||||
virtual Pollers GetPollers(Polling::DeviceType type) = 0;
|
||||
};
|
||||
|
||||
class NullState : public State {
|
||||
public:
|
||||
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
|
||||
InputCommon::Polling::DeviceType type) override {}
|
||||
Pollers GetPollers(Polling::DeviceType type) override {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<State> Init();
|
||||
|
||||
@@ -475,12 +475,11 @@ SDLState::SDLState() {
|
||||
|
||||
initialized = true;
|
||||
if (start_thread) {
|
||||
poll_thread = std::thread([&] {
|
||||
poll_thread = std::thread([this] {
|
||||
using namespace std::chrono_literals;
|
||||
SDL_Event event;
|
||||
while (initialized) {
|
||||
SDL_PumpEvents();
|
||||
std::this_thread::sleep_for(std::chrono::duration(10ms));
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -651,9 +650,9 @@ private:
|
||||
};
|
||||
} // namespace Polling
|
||||
|
||||
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> SDLState::GetPollers(
|
||||
InputCommon::Polling::DeviceType type) {
|
||||
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> pollers;
|
||||
SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) {
|
||||
Pollers pollers;
|
||||
|
||||
switch (type) {
|
||||
case InputCommon::Polling::DeviceType::Analog:
|
||||
pollers.emplace_back(std::make_unique<Polling::SDLAnalogPoller>(*this));
|
||||
@@ -661,8 +660,9 @@ std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> SDLState::GetPo
|
||||
case InputCommon::Polling::DeviceType::Button:
|
||||
pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this));
|
||||
break;
|
||||
return pollers;
|
||||
}
|
||||
|
||||
return pollers;
|
||||
}
|
||||
|
||||
} // namespace SDL
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
/// Initializes and registers SDL device factories
|
||||
SDLState();
|
||||
|
||||
/// Unresisters SDL device factories and shut them down.
|
||||
/// Unregisters SDL device factories and shut them down.
|
||||
~SDLState() override;
|
||||
|
||||
/// Handle SDL_Events for joysticks from SDL_PollEvent
|
||||
@@ -35,8 +35,7 @@ public:
|
||||
std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port);
|
||||
|
||||
/// Get all DevicePoller that use the SDL backend for a specific device type
|
||||
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
|
||||
InputCommon::Polling::DeviceType type) override;
|
||||
Pollers GetPollers(Polling::DeviceType type) override;
|
||||
|
||||
/// Used by the Pollers during config
|
||||
std::atomic<bool> polling = false;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
add_executable(tests
|
||||
common/bit_field.cpp
|
||||
common/param_package.cpp
|
||||
common/ring_buffer.cpp
|
||||
core/arm/arm_test_common.cpp
|
||||
|
||||
90
src/tests/common/bit_field.cpp
Normal file
90
src/tests/common/bit_field.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <catch2/catch.hpp>
|
||||
#include "common/bit_field.h"
|
||||
|
||||
TEST_CASE("BitField", "[common]") {
|
||||
enum class TestEnum : u32 {
|
||||
A = 0b10111101,
|
||||
B = 0b10101110,
|
||||
C = 0b00001111,
|
||||
};
|
||||
|
||||
union LEBitField {
|
||||
u32_le raw;
|
||||
BitField<0, 6, u32> a;
|
||||
BitField<6, 4, s32> b;
|
||||
BitField<10, 8, TestEnum> c;
|
||||
BitField<18, 14, u32> d;
|
||||
} le_bitfield;
|
||||
|
||||
union BEBitField {
|
||||
u32_be raw;
|
||||
BitFieldBE<0, 6, u32> a;
|
||||
BitFieldBE<6, 4, s32> b;
|
||||
BitFieldBE<10, 8, TestEnum> c;
|
||||
BitFieldBE<18, 14, u32> d;
|
||||
} be_bitfield;
|
||||
|
||||
static_assert(sizeof(LEBitField) == sizeof(u32));
|
||||
static_assert(sizeof(BEBitField) == sizeof(u32));
|
||||
static_assert(std::is_trivially_copyable_v<LEBitField>);
|
||||
static_assert(std::is_trivially_copyable_v<BEBitField>);
|
||||
|
||||
std::array<u8, 4> raw{{
|
||||
0b01101100,
|
||||
0b11110110,
|
||||
0b10111010,
|
||||
0b11101100,
|
||||
}};
|
||||
|
||||
std::memcpy(&le_bitfield, &raw, sizeof(raw));
|
||||
std::memcpy(&be_bitfield, &raw, sizeof(raw));
|
||||
|
||||
// bit fields: 11101100101110'10111101'1001'101100
|
||||
REQUIRE(le_bitfield.raw == 0b11101100'10111010'11110110'01101100);
|
||||
REQUIRE(le_bitfield.a == 0b101100);
|
||||
REQUIRE(le_bitfield.b == -7); // 1001 as two's complement
|
||||
REQUIRE(le_bitfield.c == TestEnum::A);
|
||||
REQUIRE(le_bitfield.d == 0b11101100101110);
|
||||
|
||||
le_bitfield.a.Assign(0b000111);
|
||||
le_bitfield.b.Assign(-1);
|
||||
le_bitfield.c.Assign(TestEnum::C);
|
||||
le_bitfield.d.Assign(0b01010101010101);
|
||||
std::memcpy(&raw, &le_bitfield, sizeof(raw));
|
||||
// bit fields: 01010101010101'00001111'1111'000111
|
||||
REQUIRE(le_bitfield.raw == 0b01010101'01010100'00111111'11000111);
|
||||
REQUIRE(raw == std::array<u8, 4>{{
|
||||
0b11000111,
|
||||
0b00111111,
|
||||
0b01010100,
|
||||
0b01010101,
|
||||
}});
|
||||
|
||||
// bit fields: 01101100111101'10101110'1011'101100
|
||||
REQUIRE(be_bitfield.raw == 0b01101100'11110110'10111010'11101100);
|
||||
REQUIRE(be_bitfield.a == 0b101100);
|
||||
REQUIRE(be_bitfield.b == -5); // 1011 as two's complement
|
||||
REQUIRE(be_bitfield.c == TestEnum::B);
|
||||
REQUIRE(be_bitfield.d == 0b01101100111101);
|
||||
|
||||
be_bitfield.a.Assign(0b000111);
|
||||
be_bitfield.b.Assign(-1);
|
||||
be_bitfield.c.Assign(TestEnum::C);
|
||||
be_bitfield.d.Assign(0b01010101010101);
|
||||
std::memcpy(&raw, &be_bitfield, sizeof(raw));
|
||||
// bit fields: 01010101010101'00001111'1111'000111
|
||||
REQUIRE(be_bitfield.raw == 0b01010101'01010100'00111111'11000111);
|
||||
REQUIRE(raw == std::array<u8, 4>{{
|
||||
0b01010101,
|
||||
0b01010100,
|
||||
0b00111111,
|
||||
0b11000111,
|
||||
}});
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "common/page_table.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/memory.h"
|
||||
@@ -22,7 +23,7 @@ TestEnvironment::TestEnvironment(bool mutable_memory_)
|
||||
std::fill(page_table->pointers.begin(), page_table->pointers.end(), nullptr);
|
||||
page_table->special_regions.clear();
|
||||
std::fill(page_table->attributes.begin(), page_table->attributes.end(),
|
||||
Memory::PageType::Unmapped);
|
||||
Common::PageType::Unmapped);
|
||||
|
||||
Memory::MapIoRegion(*page_table, 0x00000000, 0x80000000, test_memory);
|
||||
Memory::MapIoRegion(*page_table, 0x80000000, 0x80000000, test_memory);
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/memory_hook.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/memory_hook.h"
|
||||
|
||||
namespace Memory {
|
||||
namespace Common {
|
||||
struct PageTable;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ public:
|
||||
|
||||
private:
|
||||
friend struct TestMemory;
|
||||
struct TestMemory final : Memory::MemoryHook {
|
||||
struct TestMemory final : Common::MemoryHook {
|
||||
explicit TestMemory(TestEnvironment* env_) : env(env_) {}
|
||||
TestEnvironment* env;
|
||||
|
||||
@@ -86,7 +86,7 @@ private:
|
||||
bool mutable_memory;
|
||||
std::shared_ptr<TestMemory> test_memory;
|
||||
std::vector<WriteRecord> write_records;
|
||||
Memory::PageTable* page_table = nullptr;
|
||||
Common::PageTable* page_table = nullptr;
|
||||
Kernel::KernelCore kernel;
|
||||
};
|
||||
|
||||
|
||||
@@ -55,12 +55,9 @@ bool DmaPusher::Step() {
|
||||
}
|
||||
|
||||
// Push buffer non-empty, read a word
|
||||
const auto address = gpu.MemoryManager().GpuToCpuAddress(dma_get);
|
||||
ASSERT_MSG(address, "Invalid GPU address");
|
||||
|
||||
command_headers.resize(command_list_header.size);
|
||||
|
||||
Memory::ReadBlock(*address, command_headers.data(), command_list_header.size * sizeof(u32));
|
||||
gpu.MemoryManager().ReadBlock(dma_get, command_headers.data(),
|
||||
command_list_header.size * sizeof(u32));
|
||||
|
||||
for (const CommandHeader& command_header : command_headers) {
|
||||
|
||||
|
||||
@@ -41,18 +41,13 @@ void KeplerMemory::ProcessData(u32 data) {
|
||||
ASSERT_MSG(regs.exec.linear, "Non-linear uploads are not supported");
|
||||
ASSERT(regs.dest.x == 0 && regs.dest.y == 0 && regs.dest.z == 0);
|
||||
|
||||
const GPUVAddr address = regs.dest.Address();
|
||||
const auto dest_address =
|
||||
memory_manager.GpuToCpuAddress(address + state.write_offset * sizeof(u32));
|
||||
ASSERT_MSG(dest_address, "Invalid GPU address");
|
||||
|
||||
// We have to invalidate the destination region to evict any outdated surfaces from the cache.
|
||||
// We do this before actually writing the new data because the destination address might contain
|
||||
// a dirty surface that will have to be written back to memory.
|
||||
system.Renderer().Rasterizer().InvalidateRegion(ToCacheAddr(Memory::GetPointer(*dest_address)),
|
||||
sizeof(u32));
|
||||
// We do this before actually writing the new data because the destination address might
|
||||
// contain a dirty surface that will have to be written back to memory.
|
||||
const GPUVAddr address{regs.dest.Address() + state.write_offset * sizeof(u32)};
|
||||
rasterizer.InvalidateRegion(ToCacheAddr(memory_manager.GetPointer(address)), sizeof(u32));
|
||||
memory_manager.Write32(address, data);
|
||||
|
||||
Memory::Write32(*dest_address, data);
|
||||
system.GPU().Maxwell3D().dirty_flags.OnMemoryWrite();
|
||||
|
||||
state.write_offset++;
|
||||
|
||||
@@ -270,11 +270,9 @@ void Maxwell3D::ProcessMacroBind(u32 data) {
|
||||
}
|
||||
|
||||
void Maxwell3D::ProcessQueryGet() {
|
||||
GPUVAddr sequence_address = regs.query.QueryAddress();
|
||||
const GPUVAddr sequence_address{regs.query.QueryAddress()};
|
||||
// Since the sequence address is given as a GPU VAddr, we have to convert it to an application
|
||||
// VAddr before writing.
|
||||
const auto address = memory_manager.GpuToCpuAddress(sequence_address);
|
||||
ASSERT_MSG(address, "Invalid GPU address");
|
||||
|
||||
// TODO(Subv): Support the other query units.
|
||||
ASSERT_MSG(regs.query.query_get.unit == Regs::QueryUnit::Crop,
|
||||
@@ -309,7 +307,7 @@ void Maxwell3D::ProcessQueryGet() {
|
||||
// Write the current query sequence to the sequence address.
|
||||
// TODO(Subv): Find out what happens if you use a long query type but mark it as a short
|
||||
// query.
|
||||
Memory::Write32(*address, sequence);
|
||||
memory_manager.Write32(sequence_address, sequence);
|
||||
} else {
|
||||
// Write the 128-bit result structure in long mode. Note: We emulate an infinitely fast
|
||||
// GPU, this command may actually take a while to complete in real hardware due to GPU
|
||||
@@ -318,7 +316,7 @@ void Maxwell3D::ProcessQueryGet() {
|
||||
query_result.value = result;
|
||||
// TODO(Subv): Generate a real GPU timestamp and write it here instead of CoreTiming
|
||||
query_result.timestamp = system.CoreTiming().GetTicks();
|
||||
Memory::WriteBlock(*address, &query_result, sizeof(query_result));
|
||||
memory_manager.WriteBlock(sequence_address, &query_result, sizeof(query_result));
|
||||
}
|
||||
dirty_flags.OnMemoryWrite();
|
||||
break;
|
||||
@@ -393,12 +391,11 @@ void Maxwell3D::ProcessCBData(u32 value) {
|
||||
// Don't allow writing past the end of the buffer.
|
||||
ASSERT(regs.const_buffer.cb_pos + sizeof(u32) <= regs.const_buffer.cb_size);
|
||||
|
||||
const auto address = memory_manager.GpuToCpuAddress(buffer_address + regs.const_buffer.cb_pos);
|
||||
ASSERT_MSG(address, "Invalid GPU address");
|
||||
const GPUVAddr address{buffer_address + regs.const_buffer.cb_pos};
|
||||
|
||||
u8* ptr{Memory::GetPointer(*address)};
|
||||
u8* ptr{memory_manager.GetPointer(address)};
|
||||
rasterizer.InvalidateRegion(ToCacheAddr(ptr), sizeof(u32));
|
||||
std::memcpy(ptr, &value, sizeof(u32));
|
||||
memory_manager.Write32(address, value);
|
||||
|
||||
dirty_flags.OnMemoryWrite();
|
||||
|
||||
@@ -407,14 +404,10 @@ void Maxwell3D::ProcessCBData(u32 value) {
|
||||
}
|
||||
|
||||
Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
|
||||
const GPUVAddr tic_base_address = regs.tic.TICAddress();
|
||||
|
||||
const GPUVAddr tic_address_gpu = tic_base_address + tic_index * sizeof(Texture::TICEntry);
|
||||
const auto tic_address_cpu = memory_manager.GpuToCpuAddress(tic_address_gpu);
|
||||
ASSERT_MSG(tic_address_cpu, "Invalid GPU address");
|
||||
const GPUVAddr tic_address_gpu{regs.tic.TICAddress() + tic_index * sizeof(Texture::TICEntry)};
|
||||
|
||||
Texture::TICEntry tic_entry;
|
||||
Memory::ReadBlock(*tic_address_cpu, &tic_entry, sizeof(Texture::TICEntry));
|
||||
memory_manager.ReadBlock(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry));
|
||||
|
||||
ASSERT_MSG(tic_entry.header_version == Texture::TICHeaderVersion::BlockLinear ||
|
||||
tic_entry.header_version == Texture::TICHeaderVersion::Pitch,
|
||||
@@ -432,14 +425,10 @@ Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
|
||||
}
|
||||
|
||||
Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const {
|
||||
const GPUVAddr tsc_base_address = regs.tsc.TSCAddress();
|
||||
|
||||
const GPUVAddr tsc_address_gpu = tsc_base_address + tsc_index * sizeof(Texture::TSCEntry);
|
||||
const auto tsc_address_cpu = memory_manager.GpuToCpuAddress(tsc_address_gpu);
|
||||
ASSERT_MSG(tsc_address_cpu, "Invalid GPU address");
|
||||
const GPUVAddr tsc_address_gpu{regs.tsc.TSCAddress() + tsc_index * sizeof(Texture::TSCEntry)};
|
||||
|
||||
Texture::TSCEntry tsc_entry;
|
||||
Memory::ReadBlock(*tsc_address_cpu, &tsc_entry, sizeof(Texture::TSCEntry));
|
||||
memory_manager.ReadBlock(tsc_address_gpu, &tsc_entry, sizeof(Texture::TSCEntry));
|
||||
return tsc_entry;
|
||||
}
|
||||
|
||||
@@ -458,10 +447,7 @@ std::vector<Texture::FullTextureInfo> Maxwell3D::GetStageTextures(Regs::ShaderSt
|
||||
for (GPUVAddr current_texture = tex_info_buffer.address + TextureInfoOffset;
|
||||
current_texture < tex_info_buffer_end; current_texture += sizeof(Texture::TextureHandle)) {
|
||||
|
||||
const auto address = memory_manager.GpuToCpuAddress(current_texture);
|
||||
ASSERT_MSG(address, "Invalid GPU address");
|
||||
|
||||
const Texture::TextureHandle tex_handle{Memory::Read32(*address)};
|
||||
const Texture::TextureHandle tex_handle{memory_manager.Read32(current_texture)};
|
||||
|
||||
Texture::FullTextureInfo tex_info{};
|
||||
// TODO(Subv): Use the shader to determine which textures are actually accessed.
|
||||
@@ -496,10 +482,7 @@ Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage,
|
||||
|
||||
ASSERT(tex_info_address < tex_info_buffer.address + tex_info_buffer.size);
|
||||
|
||||
const auto tex_address_cpu = memory_manager.GpuToCpuAddress(tex_info_address);
|
||||
ASSERT_MSG(tex_address_cpu, "Invalid GPU address");
|
||||
|
||||
const Texture::TextureHandle tex_handle{Memory::Read32(*tex_address_cpu)};
|
||||
const Texture::TextureHandle tex_handle{memory_manager.Read32(tex_info_address)};
|
||||
|
||||
Texture::FullTextureInfo tex_info{};
|
||||
tex_info.index = static_cast<u32>(offset);
|
||||
|
||||
@@ -43,11 +43,6 @@ void MaxwellDMA::HandleCopy() {
|
||||
const GPUVAddr source = regs.src_address.Address();
|
||||
const GPUVAddr dest = regs.dst_address.Address();
|
||||
|
||||
const auto source_cpu = memory_manager.GpuToCpuAddress(source);
|
||||
const auto dest_cpu = memory_manager.GpuToCpuAddress(dest);
|
||||
ASSERT_MSG(source_cpu, "Invalid source GPU address");
|
||||
ASSERT_MSG(dest_cpu, "Invalid destination GPU address");
|
||||
|
||||
// TODO(Subv): Perform more research and implement all features of this engine.
|
||||
ASSERT(regs.exec.enable_swizzle == 0);
|
||||
ASSERT(regs.exec.query_mode == Regs::QueryMode::None);
|
||||
@@ -70,7 +65,7 @@ void MaxwellDMA::HandleCopy() {
|
||||
// buffer of length `x_count`, otherwise we copy a 2D image of dimensions (x_count,
|
||||
// y_count).
|
||||
if (!regs.exec.enable_2d) {
|
||||
Memory::CopyBlock(*dest_cpu, *source_cpu, regs.x_count);
|
||||
memory_manager.CopyBlock(dest, source, regs.x_count);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -79,9 +74,9 @@ void MaxwellDMA::HandleCopy() {
|
||||
// rectangle. There is no need to manually flush/invalidate the regions because
|
||||
// CopyBlock does that for us.
|
||||
for (u32 line = 0; line < regs.y_count; ++line) {
|
||||
const VAddr source_line = *source_cpu + line * regs.src_pitch;
|
||||
const VAddr dest_line = *dest_cpu + line * regs.dst_pitch;
|
||||
Memory::CopyBlock(dest_line, source_line, regs.x_count);
|
||||
const GPUVAddr source_line = source + line * regs.src_pitch;
|
||||
const GPUVAddr dest_line = dest + line * regs.dst_pitch;
|
||||
memory_manager.CopyBlock(dest_line, source_line, regs.x_count);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -90,17 +85,18 @@ void MaxwellDMA::HandleCopy() {
|
||||
|
||||
const std::size_t copy_size = regs.x_count * regs.y_count;
|
||||
|
||||
auto source_ptr{memory_manager.GetPointer(source)};
|
||||
auto dst_ptr{memory_manager.GetPointer(dest)};
|
||||
|
||||
const auto FlushAndInvalidate = [&](u32 src_size, u64 dst_size) {
|
||||
// TODO(Subv): For now, manually flush the regions until we implement GPU-accelerated
|
||||
// copying.
|
||||
Core::System::GetInstance().Renderer().Rasterizer().FlushRegion(
|
||||
ToCacheAddr(Memory::GetPointer(*source_cpu)), src_size);
|
||||
rasterizer.FlushRegion(ToCacheAddr(source_ptr), src_size);
|
||||
|
||||
// We have to invalidate the destination region to evict any outdated surfaces from the
|
||||
// cache. We do this before actually writing the new data because the destination address
|
||||
// might contain a dirty surface that will have to be written back to memory.
|
||||
Core::System::GetInstance().Renderer().Rasterizer().InvalidateRegion(
|
||||
ToCacheAddr(Memory::GetPointer(*dest_cpu)), dst_size);
|
||||
rasterizer.InvalidateRegion(ToCacheAddr(dst_ptr), dst_size);
|
||||
};
|
||||
|
||||
if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) {
|
||||
@@ -113,8 +109,8 @@ void MaxwellDMA::HandleCopy() {
|
||||
copy_size * src_bytes_per_pixel);
|
||||
|
||||
Texture::UnswizzleSubrect(regs.x_count, regs.y_count, regs.dst_pitch,
|
||||
regs.src_params.size_x, src_bytes_per_pixel, *source_cpu,
|
||||
*dest_cpu, regs.src_params.BlockHeight(), regs.src_params.pos_x,
|
||||
regs.src_params.size_x, src_bytes_per_pixel, source_ptr, dst_ptr,
|
||||
regs.src_params.BlockHeight(), regs.src_params.pos_x,
|
||||
regs.src_params.pos_y);
|
||||
} else {
|
||||
ASSERT(regs.dst_params.size_z == 1);
|
||||
@@ -127,7 +123,7 @@ void MaxwellDMA::HandleCopy() {
|
||||
|
||||
// If the input is linear and the output is tiled, swizzle the input and copy it over.
|
||||
Texture::SwizzleSubrect(regs.x_count, regs.y_count, regs.src_pitch, regs.dst_params.size_x,
|
||||
src_bpp, *dest_cpu, *source_cpu, regs.dst_params.BlockHeight());
|
||||
src_bpp, dst_ptr, source_ptr, regs.dst_params.BlockHeight());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -274,7 +274,6 @@ void GPU::ProcessSemaphoreTriggerMethod() {
|
||||
const auto op =
|
||||
static_cast<GpuSemaphoreOperation>(regs.semaphore_trigger & semaphoreOperationMask);
|
||||
if (op == GpuSemaphoreOperation::WriteLong) {
|
||||
auto address = memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress());
|
||||
struct Block {
|
||||
u32 sequence;
|
||||
u32 zeros = 0;
|
||||
@@ -286,11 +285,9 @@ void GPU::ProcessSemaphoreTriggerMethod() {
|
||||
// TODO(Kmather73): Generate a real GPU timestamp and write it here instead of
|
||||
// CoreTiming
|
||||
block.timestamp = Core::System::GetInstance().CoreTiming().GetTicks();
|
||||
Memory::WriteBlock(*address, &block, sizeof(block));
|
||||
memory_manager->WriteBlock(regs.smaphore_address.SmaphoreAddress(), &block, sizeof(block));
|
||||
} else {
|
||||
const auto address =
|
||||
memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress());
|
||||
const u32 word = Memory::Read32(*address);
|
||||
const u32 word{memory_manager->Read32(regs.smaphore_address.SmaphoreAddress())};
|
||||
if ((op == GpuSemaphoreOperation::AcquireEqual && word == regs.semaphore_sequence) ||
|
||||
(op == GpuSemaphoreOperation::AcquireGequal &&
|
||||
static_cast<s32>(word - regs.semaphore_sequence) > 0) ||
|
||||
@@ -317,13 +314,11 @@ void GPU::ProcessSemaphoreTriggerMethod() {
|
||||
}
|
||||
|
||||
void GPU::ProcessSemaphoreRelease() {
|
||||
const auto address = memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress());
|
||||
Memory::Write32(*address, regs.semaphore_release);
|
||||
memory_manager->Write32(regs.smaphore_address.SmaphoreAddress(), regs.semaphore_release);
|
||||
}
|
||||
|
||||
void GPU::ProcessSemaphoreAcquire() {
|
||||
const auto address = memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress());
|
||||
const u32 word = Memory::Read32(*address);
|
||||
const u32 word = memory_manager->Read32(regs.smaphore_address.SmaphoreAddress());
|
||||
const auto value = regs.semaphore_acquire;
|
||||
if (word != value) {
|
||||
regs.acquire_active = true;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
namespace Tegra {
|
||||
@@ -162,15 +163,51 @@ std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) {
|
||||
return base_addr + (gpu_addr & PAGE_MASK);
|
||||
}
|
||||
|
||||
std::vector<GPUVAddr> MemoryManager::CpuToGpuAddress(VAddr cpu_addr) const {
|
||||
std::vector<GPUVAddr> results;
|
||||
for (const auto& region : mapped_regions) {
|
||||
if (cpu_addr >= region.cpu_addr && cpu_addr < (region.cpu_addr + region.size)) {
|
||||
const u64 offset{cpu_addr - region.cpu_addr};
|
||||
results.push_back(region.gpu_addr + offset);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
u8 MemoryManager::Read8(GPUVAddr addr) {
|
||||
return Memory::Read8(*GpuToCpuAddress(addr));
|
||||
}
|
||||
|
||||
u16 MemoryManager::Read16(GPUVAddr addr) {
|
||||
return Memory::Read16(*GpuToCpuAddress(addr));
|
||||
}
|
||||
|
||||
u32 MemoryManager::Read32(GPUVAddr addr) {
|
||||
return Memory::Read32(*GpuToCpuAddress(addr));
|
||||
}
|
||||
|
||||
u64 MemoryManager::Read64(GPUVAddr addr) {
|
||||
return Memory::Read64(*GpuToCpuAddress(addr));
|
||||
}
|
||||
|
||||
void MemoryManager::Write8(GPUVAddr addr, u8 data) {
|
||||
Memory::Write8(*GpuToCpuAddress(addr), data);
|
||||
}
|
||||
|
||||
void MemoryManager::Write16(GPUVAddr addr, u16 data) {
|
||||
Memory::Write16(*GpuToCpuAddress(addr), data);
|
||||
}
|
||||
|
||||
void MemoryManager::Write32(GPUVAddr addr, u32 data) {
|
||||
Memory::Write32(*GpuToCpuAddress(addr), data);
|
||||
}
|
||||
|
||||
void MemoryManager::Write64(GPUVAddr addr, u64 data) {
|
||||
Memory::Write64(*GpuToCpuAddress(addr), data);
|
||||
}
|
||||
|
||||
u8* MemoryManager::GetPointer(GPUVAddr addr) {
|
||||
return Memory::GetPointer(*GpuToCpuAddress(addr));
|
||||
}
|
||||
|
||||
void MemoryManager::ReadBlock(GPUVAddr src_addr, void* dest_buffer, std::size_t size) {
|
||||
std::memcpy(dest_buffer, GetPointer(src_addr), size);
|
||||
}
|
||||
void MemoryManager::WriteBlock(GPUVAddr dest_addr, const void* src_buffer, std::size_t size) {
|
||||
std::memcpy(GetPointer(dest_addr), src_buffer, size);
|
||||
}
|
||||
|
||||
void MemoryManager::CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, std::size_t size) {
|
||||
std::memcpy(GetPointer(dest_addr), GetPointer(src_addr), size);
|
||||
}
|
||||
|
||||
VAddr& MemoryManager::PageSlot(GPUVAddr gpu_addr) {
|
||||
|
||||
@@ -27,12 +27,27 @@ public:
|
||||
GPUVAddr UnmapBuffer(GPUVAddr gpu_addr, u64 size);
|
||||
GPUVAddr GetRegionEnd(GPUVAddr region_start) const;
|
||||
std::optional<VAddr> GpuToCpuAddress(GPUVAddr gpu_addr);
|
||||
std::vector<GPUVAddr> CpuToGpuAddress(VAddr cpu_addr) const;
|
||||
|
||||
static constexpr u64 PAGE_BITS = 16;
|
||||
static constexpr u64 PAGE_SIZE = 1 << PAGE_BITS;
|
||||
static constexpr u64 PAGE_MASK = PAGE_SIZE - 1;
|
||||
|
||||
u8 Read8(GPUVAddr addr);
|
||||
u16 Read16(GPUVAddr addr);
|
||||
u32 Read32(GPUVAddr addr);
|
||||
u64 Read64(GPUVAddr addr);
|
||||
|
||||
void Write8(GPUVAddr addr, u8 data);
|
||||
void Write16(GPUVAddr addr, u16 data);
|
||||
void Write32(GPUVAddr addr, u32 data);
|
||||
void Write64(GPUVAddr addr, u64 data);
|
||||
|
||||
u8* GetPointer(GPUVAddr vaddr);
|
||||
|
||||
void ReadBlock(GPUVAddr src_addr, void* dest_buffer, std::size_t size);
|
||||
void WriteBlock(GPUVAddr dest_addr, const void* src_buffer, std::size_t size);
|
||||
void CopyBlock(VAddr dest_addr, VAddr src_addr, std::size_t size);
|
||||
|
||||
private:
|
||||
enum class PageStatus : u64 {
|
||||
Unmapped = 0xFFFFFFFFFFFFFFFFULL,
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <cstring>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/morton.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
@@ -16,12 +15,12 @@ namespace VideoCore {
|
||||
using Surface::GetBytesPerPixel;
|
||||
using Surface::PixelFormat;
|
||||
|
||||
using MortonCopyFn = void (*)(u32, u32, u32, u32, u32, u32, u8*, VAddr);
|
||||
using MortonCopyFn = void (*)(u32, u32, u32, u32, u32, u32, u8*, u8*);
|
||||
using ConversionArray = std::array<MortonCopyFn, Surface::MaxPixelFormat>;
|
||||
|
||||
template <bool morton_to_linear, PixelFormat format>
|
||||
static void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 depth,
|
||||
u32 tile_width_spacing, u8* buffer, VAddr addr) {
|
||||
u32 tile_width_spacing, u8* buffer, u8* addr) {
|
||||
constexpr u32 bytes_per_pixel = GetBytesPerPixel(format);
|
||||
|
||||
// With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
|
||||
@@ -34,10 +33,10 @@ static void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth
|
||||
stride, height, depth, block_height, block_depth,
|
||||
tile_width_spacing);
|
||||
} else {
|
||||
Tegra::Texture::CopySwizzledData(
|
||||
(stride + tile_size_x - 1) / tile_size_x, (height + tile_size_y - 1) / tile_size_y,
|
||||
depth, bytes_per_pixel, bytes_per_pixel, Memory::GetPointer(addr), buffer, false,
|
||||
block_height, block_depth, tile_width_spacing);
|
||||
Tegra::Texture::CopySwizzledData((stride + tile_size_x - 1) / tile_size_x,
|
||||
(height + tile_size_y - 1) / tile_size_y, depth,
|
||||
bytes_per_pixel, bytes_per_pixel, addr, buffer, false,
|
||||
block_height, block_depth, tile_width_spacing);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +281,7 @@ static u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) {
|
||||
|
||||
void MortonSwizzle(MortonSwizzleMode mode, Surface::PixelFormat format, u32 stride,
|
||||
u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing,
|
||||
u8* buffer, VAddr addr) {
|
||||
u8* buffer, u8* addr) {
|
||||
GetSwizzleFunction(mode, format)(stride, block_height, height, block_depth, depth,
|
||||
tile_width_spacing, buffer, addr);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ enum class MortonSwizzleMode { MortonToLinear, LinearToMorton };
|
||||
|
||||
void MortonSwizzle(MortonSwizzleMode mode, VideoCore::Surface::PixelFormat format, u32 stride,
|
||||
u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing,
|
||||
u8* buffer, VAddr addr);
|
||||
u8* buffer, u8* addr);
|
||||
|
||||
void MortonCopyPixels128(MortonSwizzleMode mode, u32 width, u32 height, u32 bytes_per_pixel,
|
||||
u32 linear_bytes_per_pixel, u8* morton_data, u8* linear_data);
|
||||
|
||||
@@ -24,14 +24,12 @@ OGLBufferCache::OGLBufferCache(RasterizerOpenGL& rasterizer, std::size_t size)
|
||||
GLintptr OGLBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size,
|
||||
std::size_t alignment, bool cache) {
|
||||
auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
|
||||
const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
|
||||
ASSERT_MSG(cpu_addr, "Invalid GPU address");
|
||||
|
||||
// Cache management is a big overhead, so only cache entries with a given size.
|
||||
// TODO: Figure out which size is the best for given games.
|
||||
cache &= size >= 2048;
|
||||
|
||||
const auto& host_ptr{Memory::GetPointer(*cpu_addr)};
|
||||
const auto& host_ptr{memory_manager.GetPointer(gpu_addr)};
|
||||
if (cache) {
|
||||
auto entry = TryGet(host_ptr);
|
||||
if (entry) {
|
||||
@@ -54,8 +52,8 @@ GLintptr OGLBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size
|
||||
buffer_offset += size;
|
||||
|
||||
if (cache) {
|
||||
auto entry = std::make_shared<CachedBufferEntry>(*cpu_addr, size, uploaded_offset,
|
||||
alignment, host_ptr);
|
||||
auto entry = std::make_shared<CachedBufferEntry>(
|
||||
*memory_manager.GpuToCpuAddress(gpu_addr), size, uploaded_offset, alignment, host_ptr);
|
||||
Register(entry);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/renderer_opengl/gl_global_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
|
||||
@@ -39,7 +38,7 @@ void CachedGlobalRegion::Reload(u32 size_) {
|
||||
glBufferData(GL_SHADER_STORAGE_BUFFER, size, GetHostPtr(), GL_DYNAMIC_DRAW);
|
||||
}
|
||||
|
||||
GlobalRegion GlobalRegionCacheOpenGL::TryGetReservedGlobalRegion(VAddr addr, u32 size) const {
|
||||
GlobalRegion GlobalRegionCacheOpenGL::TryGetReservedGlobalRegion(CacheAddr addr, u32 size) const {
|
||||
const auto search{reserve.find(addr)};
|
||||
if (search == reserve.end()) {
|
||||
return {};
|
||||
@@ -47,11 +46,14 @@ GlobalRegion GlobalRegionCacheOpenGL::TryGetReservedGlobalRegion(VAddr addr, u32
|
||||
return search->second;
|
||||
}
|
||||
|
||||
GlobalRegion GlobalRegionCacheOpenGL::GetUncachedGlobalRegion(VAddr addr, u32 size, u8* host_ptr) {
|
||||
GlobalRegion region{TryGetReservedGlobalRegion(addr, size)};
|
||||
GlobalRegion GlobalRegionCacheOpenGL::GetUncachedGlobalRegion(Tegra::GPUVAddr addr, u32 size,
|
||||
u8* host_ptr) {
|
||||
GlobalRegion region{TryGetReservedGlobalRegion(ToCacheAddr(host_ptr), size)};
|
||||
if (!region) {
|
||||
// No reserved surface available, create a new one and reserve it
|
||||
region = std::make_shared<CachedGlobalRegion>(addr, size, host_ptr);
|
||||
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
|
||||
const auto cpu_addr = *memory_manager.GpuToCpuAddress(addr);
|
||||
region = std::make_shared<CachedGlobalRegion>(cpu_addr, size, host_ptr);
|
||||
ReserveGlobalRegion(region);
|
||||
}
|
||||
region->Reload(size);
|
||||
@@ -59,7 +61,7 @@ GlobalRegion GlobalRegionCacheOpenGL::GetUncachedGlobalRegion(VAddr addr, u32 si
|
||||
}
|
||||
|
||||
void GlobalRegionCacheOpenGL::ReserveGlobalRegion(GlobalRegion region) {
|
||||
reserve.insert_or_assign(region->GetCpuAddr(), std::move(region));
|
||||
reserve.insert_or_assign(region->GetCacheAddr(), std::move(region));
|
||||
}
|
||||
|
||||
GlobalRegionCacheOpenGL::GlobalRegionCacheOpenGL(RasterizerOpenGL& rasterizer)
|
||||
@@ -70,23 +72,20 @@ GlobalRegion GlobalRegionCacheOpenGL::GetGlobalRegion(
|
||||
Tegra::Engines::Maxwell3D::Regs::ShaderStage stage) {
|
||||
|
||||
auto& gpu{Core::System::GetInstance().GPU()};
|
||||
const auto cbufs = gpu.Maxwell3D().state.shader_stages[static_cast<u64>(stage)];
|
||||
const auto cbuf_addr = gpu.MemoryManager().GpuToCpuAddress(
|
||||
cbufs.const_buffers[global_region.GetCbufIndex()].address + global_region.GetCbufOffset());
|
||||
ASSERT(cbuf_addr);
|
||||
|
||||
const auto actual_addr_gpu = Memory::Read64(*cbuf_addr);
|
||||
const auto size = Memory::Read32(*cbuf_addr + 8);
|
||||
const auto actual_addr = gpu.MemoryManager().GpuToCpuAddress(actual_addr_gpu);
|
||||
ASSERT(actual_addr);
|
||||
auto& memory_manager{gpu.MemoryManager()};
|
||||
const auto cbufs{gpu.Maxwell3D().state.shader_stages[static_cast<u64>(stage)]};
|
||||
const auto addr{cbufs.const_buffers[global_region.GetCbufIndex()].address +
|
||||
global_region.GetCbufOffset()};
|
||||
const auto actual_addr{memory_manager.Read64(addr)};
|
||||
const auto size{memory_manager.Read32(addr + 8)};
|
||||
|
||||
// Look up global region in the cache based on address
|
||||
const auto& host_ptr{Memory::GetPointer(*actual_addr)};
|
||||
const auto& host_ptr{memory_manager.GetPointer(actual_addr)};
|
||||
GlobalRegion region{TryGet(host_ptr)};
|
||||
|
||||
if (!region) {
|
||||
// No global region found - create a new one
|
||||
region = GetUncachedGlobalRegion(*actual_addr, size, host_ptr);
|
||||
region = GetUncachedGlobalRegion(actual_addr, size, host_ptr);
|
||||
Register(region);
|
||||
}
|
||||
|
||||
|
||||
@@ -65,11 +65,11 @@ public:
|
||||
Tegra::Engines::Maxwell3D::Regs::ShaderStage stage);
|
||||
|
||||
private:
|
||||
GlobalRegion TryGetReservedGlobalRegion(VAddr addr, u32 size) const;
|
||||
GlobalRegion GetUncachedGlobalRegion(VAddr addr, u32 size, u8* host_ptr);
|
||||
GlobalRegion TryGetReservedGlobalRegion(CacheAddr addr, u32 size) const;
|
||||
GlobalRegion GetUncachedGlobalRegion(Tegra::GPUVAddr addr, u32 size, u8* host_ptr);
|
||||
void ReserveGlobalRegion(GlobalRegion region);
|
||||
|
||||
std::unordered_map<VAddr, GlobalRegion> reserve;
|
||||
std::unordered_map<CacheAddr, GlobalRegion> reserve;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -46,10 +46,7 @@ GLintptr PrimitiveAssembler::MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size
|
||||
auto [dst_pointer, index_offset] = buffer_cache.ReserveMemory(map_size);
|
||||
|
||||
auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
|
||||
const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
|
||||
ASSERT_MSG(cpu_addr, "Invalid GPU address");
|
||||
|
||||
const u8* source{Memory::GetPointer(*cpu_addr)};
|
||||
const u8* source{memory_manager.GetPointer(gpu_addr)};
|
||||
|
||||
for (u32 primitive = 0; primitive < count / 4; ++primitive) {
|
||||
for (std::size_t i = 0; i < TRIANGLES_PER_QUAD; ++i) {
|
||||
@@ -64,4 +61,4 @@ GLintptr PrimitiveAssembler::MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size
|
||||
return index_offset;
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -749,11 +749,17 @@ void RasterizerOpenGL::FlushAll() {}
|
||||
|
||||
void RasterizerOpenGL::FlushRegion(CacheAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
if (!addr || !size) {
|
||||
return;
|
||||
}
|
||||
res_cache.FlushRegion(addr, size);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::InvalidateRegion(CacheAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
if (!addr || !size) {
|
||||
return;
|
||||
}
|
||||
res_cache.InvalidateRegion(addr, size);
|
||||
shader_cache.InvalidateRegion(addr, size);
|
||||
global_cache.InvalidateRegion(addr, size);
|
||||
|
||||
@@ -57,11 +57,9 @@ static void ApplyTextureDefaults(GLuint texture, u32 max_mip_level) {
|
||||
|
||||
void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) {
|
||||
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
|
||||
const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr_)};
|
||||
|
||||
addr = cpu_addr ? *cpu_addr : 0;
|
||||
gpu_addr = gpu_addr_;
|
||||
host_ptr = Memory::GetPointer(addr);
|
||||
host_ptr = memory_manager.GetPointer(gpu_addr_);
|
||||
size_in_bytes = SizeInBytesRaw();
|
||||
|
||||
if (IsPixelFormatASTC(pixel_format)) {
|
||||
@@ -447,7 +445,7 @@ void SwizzleFunc(const MortonSwizzleMode& mode, const SurfaceParams& params,
|
||||
MortonSwizzle(mode, params.pixel_format, params.MipWidth(mip_level),
|
||||
params.MipBlockHeight(mip_level), params.MipHeight(mip_level),
|
||||
params.MipBlockDepth(mip_level), 1, params.tile_width_spacing,
|
||||
gl_buffer.data() + offset_gl, params.addr + offset);
|
||||
gl_buffer.data() + offset_gl, params.host_ptr + offset);
|
||||
offset += layer_size;
|
||||
offset_gl += gl_size;
|
||||
}
|
||||
@@ -456,7 +454,7 @@ void SwizzleFunc(const MortonSwizzleMode& mode, const SurfaceParams& params,
|
||||
MortonSwizzle(mode, params.pixel_format, params.MipWidth(mip_level),
|
||||
params.MipBlockHeight(mip_level), params.MipHeight(mip_level),
|
||||
params.MipBlockDepth(mip_level), depth, params.tile_width_spacing,
|
||||
gl_buffer.data(), params.addr + offset);
|
||||
gl_buffer.data(), params.host_ptr + offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,9 +512,9 @@ void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surfac
|
||||
"reinterpretation but the texture is tiled.");
|
||||
}
|
||||
const std::size_t remaining_size = dst_params.size_in_bytes - src_params.size_in_bytes;
|
||||
|
||||
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
|
||||
glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes, remaining_size,
|
||||
Memory::GetPointer(dst_params.addr + src_params.size_in_bytes));
|
||||
memory_manager.GetPointer(dst_params.gpu_addr + src_params.size_in_bytes));
|
||||
}
|
||||
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
@@ -604,7 +602,7 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
|
||||
|
||||
ApplyTextureDefaults(texture.handle, params.max_mip_level);
|
||||
|
||||
OpenGL::LabelGLObject(GL_TEXTURE, texture.handle, params.addr, params.IdentityString());
|
||||
OpenGL::LabelGLObject(GL_TEXTURE, texture.handle, params.gpu_addr, params.IdentityString());
|
||||
|
||||
// Clamp size to mapped GPU memory region
|
||||
// TODO(bunnei): Super Mario Odyssey maps a 0x40000 byte region and then uses it for a 0x80000
|
||||
@@ -617,6 +615,8 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
|
||||
LOG_ERROR(HW_GPU, "Surface size {} exceeds region size {}", params.size_in_bytes, max_size);
|
||||
cached_size_in_bytes = max_size;
|
||||
}
|
||||
|
||||
cpu_addr = *memory_manager.GpuToCpuAddress(params.gpu_addr);
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 192, 64));
|
||||
@@ -925,7 +925,7 @@ void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) {
|
||||
}
|
||||
|
||||
Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool preserve_contents) {
|
||||
if (params.addr == 0 || params.height * params.width == 0) {
|
||||
if (params.gpu_addr == 0 || params.height * params.width == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -979,14 +979,16 @@ void RasterizerCacheOpenGL::FastLayeredCopySurface(const Surface& src_surface,
|
||||
const Surface& dst_surface) {
|
||||
const auto& init_params{src_surface->GetSurfaceParams()};
|
||||
const auto& dst_params{dst_surface->GetSurfaceParams()};
|
||||
VAddr address = init_params.addr;
|
||||
const std::size_t layer_size = dst_params.LayerMemorySize();
|
||||
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
|
||||
Tegra::GPUVAddr address{init_params.gpu_addr};
|
||||
const std::size_t layer_size{dst_params.LayerMemorySize()};
|
||||
for (u32 layer = 0; layer < dst_params.depth; layer++) {
|
||||
for (u32 mipmap = 0; mipmap < dst_params.max_mip_level; mipmap++) {
|
||||
const VAddr sub_address = address + dst_params.GetMipmapLevelOffset(mipmap);
|
||||
const Surface& copy = TryGet(Memory::GetPointer(sub_address));
|
||||
if (!copy)
|
||||
const Tegra::GPUVAddr sub_address{address + dst_params.GetMipmapLevelOffset(mipmap)};
|
||||
const Surface& copy{TryGet(memory_manager.GetPointer(sub_address))};
|
||||
if (!copy) {
|
||||
continue;
|
||||
}
|
||||
const auto& src_params{copy->GetSurfaceParams()};
|
||||
const u32 width{std::min(src_params.width, dst_params.MipWidth(mipmap))};
|
||||
const u32 height{std::min(src_params.height, dst_params.MipHeight(mipmap))};
|
||||
@@ -1242,9 +1244,10 @@ static std::optional<u32> TryFindBestMipMap(std::size_t memory, const SurfacePar
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::optional<u32> TryFindBestLayer(VAddr addr, const SurfaceParams params, u32 mipmap) {
|
||||
const std::size_t size = params.LayerMemorySize();
|
||||
VAddr start = params.addr + params.GetMipmapLevelOffset(mipmap);
|
||||
static std::optional<u32> TryFindBestLayer(Tegra::GPUVAddr addr, const SurfaceParams params,
|
||||
u32 mipmap) {
|
||||
const std::size_t size{params.LayerMemorySize()};
|
||||
Tegra::GPUVAddr start{params.gpu_addr + params.GetMipmapLevelOffset(mipmap)};
|
||||
for (u32 i = 0; i < params.depth; i++) {
|
||||
if (start == addr) {
|
||||
return {i};
|
||||
@@ -1266,7 +1269,7 @@ static bool LayerFitReinterpretSurface(RasterizerCacheOpenGL& cache, const Surfa
|
||||
src_params.height == dst_params.MipHeight(*level) &&
|
||||
src_params.block_height >= dst_params.MipBlockHeight(*level)) {
|
||||
const std::optional<u32> slot =
|
||||
TryFindBestLayer(render_surface->GetCpuAddr(), dst_params, *level);
|
||||
TryFindBestLayer(render_surface->GetSurfaceParams().gpu_addr, dst_params, *level);
|
||||
if (slot.has_value()) {
|
||||
glCopyImageSubData(render_surface->Texture().handle,
|
||||
SurfaceTargetToGL(src_params.target), 0, 0, 0, 0,
|
||||
|
||||
@@ -296,7 +296,6 @@ struct SurfaceParams {
|
||||
bool is_array;
|
||||
bool srgb_conversion;
|
||||
// Parameters used for caching
|
||||
VAddr addr;
|
||||
u8* host_ptr;
|
||||
Tegra::GPUVAddr gpu_addr;
|
||||
std::size_t size_in_bytes;
|
||||
@@ -349,7 +348,7 @@ public:
|
||||
explicit CachedSurface(const SurfaceParams& params);
|
||||
|
||||
VAddr GetCpuAddr() const override {
|
||||
return params.addr;
|
||||
return cpu_addr;
|
||||
}
|
||||
|
||||
std::size_t GetSizeInBytes() const override {
|
||||
@@ -433,6 +432,7 @@ private:
|
||||
std::size_t memory_size;
|
||||
bool reinterpreted = false;
|
||||
bool must_reload = false;
|
||||
VAddr cpu_addr{};
|
||||
};
|
||||
|
||||
class RasterizerCacheOpenGL final : public RasterizerCache<Surface> {
|
||||
|
||||
@@ -32,13 +32,10 @@ struct UnspecializedShader {
|
||||
namespace {
|
||||
|
||||
/// Gets the address for the specified shader stage program
|
||||
VAddr GetShaderAddress(Maxwell::ShaderProgram program) {
|
||||
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
const auto& shader_config = gpu.regs.shader_config[static_cast<std::size_t>(program)];
|
||||
const auto address = gpu.memory_manager.GpuToCpuAddress(gpu.regs.code_address.CodeAddress() +
|
||||
shader_config.offset);
|
||||
ASSERT_MSG(address, "Invalid GPU address");
|
||||
return *address;
|
||||
Tegra::GPUVAddr GetShaderAddress(Maxwell::ShaderProgram program) {
|
||||
const auto& gpu{Core::System::GetInstance().GPU().Maxwell3D()};
|
||||
const auto& shader_config{gpu.regs.shader_config[static_cast<std::size_t>(program)]};
|
||||
return gpu.regs.code_address.CodeAddress() + shader_config.offset;
|
||||
}
|
||||
|
||||
/// Gets the shader program code from memory for the specified address
|
||||
@@ -214,11 +211,11 @@ std::set<GLenum> GetSupportedFormats() {
|
||||
|
||||
} // namespace
|
||||
|
||||
CachedShader::CachedShader(VAddr guest_addr, u64 unique_identifier,
|
||||
CachedShader::CachedShader(VAddr cpu_addr, u64 unique_identifier,
|
||||
Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
|
||||
const PrecompiledPrograms& precompiled_programs,
|
||||
ProgramCode&& program_code, ProgramCode&& program_code_b, u8* host_ptr)
|
||||
: host_ptr{host_ptr}, guest_addr{guest_addr}, unique_identifier{unique_identifier},
|
||||
: host_ptr{host_ptr}, cpu_addr{cpu_addr}, unique_identifier{unique_identifier},
|
||||
program_type{program_type}, disk_cache{disk_cache},
|
||||
precompiled_programs{precompiled_programs}, RasterizerCacheObject{host_ptr} {
|
||||
|
||||
@@ -244,11 +241,11 @@ CachedShader::CachedShader(VAddr guest_addr, u64 unique_identifier,
|
||||
disk_cache.SaveRaw(raw);
|
||||
}
|
||||
|
||||
CachedShader::CachedShader(VAddr guest_addr, u64 unique_identifier,
|
||||
CachedShader::CachedShader(VAddr cpu_addr, u64 unique_identifier,
|
||||
Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
|
||||
const PrecompiledPrograms& precompiled_programs,
|
||||
GLShader::ProgramResult result, u8* host_ptr)
|
||||
: guest_addr{guest_addr}, unique_identifier{unique_identifier}, program_type{program_type},
|
||||
: cpu_addr{cpu_addr}, unique_identifier{unique_identifier}, program_type{program_type},
|
||||
disk_cache{disk_cache}, precompiled_programs{precompiled_programs}, RasterizerCacheObject{
|
||||
host_ptr} {
|
||||
|
||||
@@ -273,7 +270,7 @@ std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(GLenum primitive
|
||||
disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings));
|
||||
}
|
||||
|
||||
LabelGLObject(GL_PROGRAM, program->handle, guest_addr);
|
||||
LabelGLObject(GL_PROGRAM, program->handle, cpu_addr);
|
||||
}
|
||||
|
||||
handle = program->handle;
|
||||
@@ -325,7 +322,7 @@ GLuint CachedShader::LazyGeometryProgram(CachedProgram& target_program, BaseBind
|
||||
disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings));
|
||||
}
|
||||
|
||||
LabelGLObject(GL_PROGRAM, target_program->handle, guest_addr, debug_name);
|
||||
LabelGLObject(GL_PROGRAM, target_program->handle, cpu_addr, debug_name);
|
||||
|
||||
return target_program->handle;
|
||||
};
|
||||
@@ -488,31 +485,31 @@ Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
|
||||
return last_shaders[static_cast<u32>(program)];
|
||||
}
|
||||
|
||||
const VAddr program_addr{GetShaderAddress(program)};
|
||||
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
|
||||
const Tegra::GPUVAddr program_addr{GetShaderAddress(program)};
|
||||
|
||||
// Look up shader in the cache based on address
|
||||
const auto& host_ptr{Memory::GetPointer(program_addr)};
|
||||
const auto& host_ptr{memory_manager.GetPointer(program_addr)};
|
||||
Shader shader{TryGet(host_ptr)};
|
||||
|
||||
if (!shader) {
|
||||
// No shader found - create a new one
|
||||
const auto& host_ptr{Memory::GetPointer(program_addr)};
|
||||
ProgramCode program_code{GetShaderCode(host_ptr)};
|
||||
ProgramCode program_code_b;
|
||||
if (program == Maxwell::ShaderProgram::VertexA) {
|
||||
program_code_b = GetShaderCode(
|
||||
Memory::GetPointer(GetShaderAddress(Maxwell::ShaderProgram::VertexB)));
|
||||
memory_manager.GetPointer(GetShaderAddress(Maxwell::ShaderProgram::VertexB)));
|
||||
}
|
||||
const u64 unique_identifier = GetUniqueIdentifier(program, program_code, program_code_b);
|
||||
|
||||
const VAddr cpu_addr{*memory_manager.GpuToCpuAddress(program_addr)};
|
||||
const auto found = precompiled_shaders.find(unique_identifier);
|
||||
if (found != precompiled_shaders.end()) {
|
||||
shader =
|
||||
std::make_shared<CachedShader>(program_addr, unique_identifier, program, disk_cache,
|
||||
std::make_shared<CachedShader>(cpu_addr, unique_identifier, program, disk_cache,
|
||||
precompiled_programs, found->second, host_ptr);
|
||||
} else {
|
||||
shader = std::make_shared<CachedShader>(
|
||||
program_addr, unique_identifier, program, disk_cache, precompiled_programs,
|
||||
cpu_addr, unique_identifier, program, disk_cache, precompiled_programs,
|
||||
std::move(program_code), std::move(program_code_b), host_ptr);
|
||||
}
|
||||
Register(shader);
|
||||
|
||||
@@ -39,18 +39,18 @@ using PrecompiledShaders = std::unordered_map<u64, GLShader::ProgramResult>;
|
||||
|
||||
class CachedShader final : public RasterizerCacheObject {
|
||||
public:
|
||||
explicit CachedShader(VAddr guest_addr, u64 unique_identifier,
|
||||
explicit CachedShader(VAddr cpu_addr, u64 unique_identifier,
|
||||
Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
|
||||
const PrecompiledPrograms& precompiled_programs,
|
||||
ProgramCode&& program_code, ProgramCode&& program_code_b, u8* host_ptr);
|
||||
|
||||
explicit CachedShader(VAddr guest_addr, u64 unique_identifier,
|
||||
explicit CachedShader(VAddr cpu_addr, u64 unique_identifier,
|
||||
Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
|
||||
const PrecompiledPrograms& precompiled_programs,
|
||||
GLShader::ProgramResult result, u8* host_ptr);
|
||||
|
||||
VAddr GetCpuAddr() const override {
|
||||
return guest_addr;
|
||||
return cpu_addr;
|
||||
}
|
||||
|
||||
std::size_t GetSizeInBytes() const override {
|
||||
@@ -92,7 +92,7 @@ private:
|
||||
ShaderDiskCacheUsage GetUsage(GLenum primitive_mode, BaseBindings base_bindings) const;
|
||||
|
||||
u8* host_ptr{};
|
||||
VAddr guest_addr{};
|
||||
VAddr cpu_addr{};
|
||||
u64 unique_identifier{};
|
||||
Maxwell::ShaderProgram program_type{};
|
||||
ShaderDiskCacheOpenGL& disk_cache;
|
||||
|
||||
@@ -164,8 +164,7 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf
|
||||
// Reset the screen info's display texture to its own permanent texture
|
||||
screen_info.display_texture = screen_info.texture.resource.handle;
|
||||
|
||||
Memory::RasterizerFlushVirtualRegion(framebuffer_addr, size_in_bytes,
|
||||
Memory::FlushMode::Flush);
|
||||
rasterizer->FlushRegion(ToCacheAddr(Memory::GetPointer(framebuffer_addr)), size_in_bytes);
|
||||
|
||||
constexpr u32 linear_bpp = 4;
|
||||
VideoCore::MortonCopyPixels128(VideoCore::MortonSwizzleMode::MortonToLinear,
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <cstring>
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
@@ -230,18 +229,18 @@ u32 BytesPerPixel(TextureFormat format) {
|
||||
}
|
||||
}
|
||||
|
||||
void UnswizzleTexture(u8* const unswizzled_data, VAddr address, u32 tile_size_x, u32 tile_size_y,
|
||||
void UnswizzleTexture(u8* const unswizzled_data, u8* address, u32 tile_size_x, u32 tile_size_y,
|
||||
u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height,
|
||||
u32 block_depth, u32 width_spacing) {
|
||||
CopySwizzledData((width + tile_size_x - 1) / tile_size_x,
|
||||
(height + tile_size_y - 1) / tile_size_y, depth, bytes_per_pixel,
|
||||
bytes_per_pixel, Memory::GetPointer(address), unswizzled_data, true,
|
||||
block_height, block_depth, width_spacing);
|
||||
bytes_per_pixel, address, unswizzled_data, true, block_height, block_depth,
|
||||
width_spacing);
|
||||
}
|
||||
|
||||
std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y,
|
||||
u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
|
||||
u32 block_height, u32 block_depth, u32 width_spacing) {
|
||||
std::vector<u8> UnswizzleTexture(u8* address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel,
|
||||
u32 width, u32 height, u32 depth, u32 block_height,
|
||||
u32 block_depth, u32 width_spacing) {
|
||||
std::vector<u8> unswizzled_data(width * height * depth * bytes_per_pixel);
|
||||
UnswizzleTexture(unswizzled_data.data(), address, tile_size_x, tile_size_y, bytes_per_pixel,
|
||||
width, height, depth, block_height, block_depth, width_spacing);
|
||||
@@ -249,8 +248,7 @@ std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y
|
||||
}
|
||||
|
||||
void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width,
|
||||
u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
|
||||
u32 block_height) {
|
||||
u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height) {
|
||||
const u32 image_width_in_gobs{(swizzled_width * bytes_per_pixel + (gob_size_x - 1)) /
|
||||
gob_size_x};
|
||||
for (u32 line = 0; line < subrect_height; ++line) {
|
||||
@@ -262,17 +260,17 @@ void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32
|
||||
const u32 gob_address =
|
||||
gob_address_y + (x * bytes_per_pixel / gob_size_x) * gob_size * block_height;
|
||||
const u32 swizzled_offset = gob_address + table[(x * bytes_per_pixel) % gob_size_x];
|
||||
const VAddr source_line = unswizzled_data + line * source_pitch + x * bytes_per_pixel;
|
||||
const VAddr dest_addr = swizzled_data + swizzled_offset;
|
||||
u8* source_line = unswizzled_data + line * source_pitch + x * bytes_per_pixel;
|
||||
u8* dest_addr = swizzled_data + swizzled_offset;
|
||||
|
||||
Memory::CopyBlock(dest_addr, source_line, bytes_per_pixel);
|
||||
std::memcpy(dest_addr, source_line, bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width,
|
||||
u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
|
||||
u32 block_height, u32 offset_x, u32 offset_y) {
|
||||
u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height,
|
||||
u32 offset_x, u32 offset_y) {
|
||||
for (u32 line = 0; line < subrect_height; ++line) {
|
||||
const u32 y2 = line + offset_y;
|
||||
const u32 gob_address_y = (y2 / (gob_size_y * block_height)) * gob_size * block_height +
|
||||
@@ -282,10 +280,10 @@ void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32
|
||||
const u32 x2 = (x + offset_x) * bytes_per_pixel;
|
||||
const u32 gob_address = gob_address_y + (x2 / gob_size_x) * gob_size * block_height;
|
||||
const u32 swizzled_offset = gob_address + table[x2 % gob_size_x];
|
||||
const VAddr dest_line = unswizzled_data + line * dest_pitch + x * bytes_per_pixel;
|
||||
const VAddr source_addr = swizzled_data + swizzled_offset;
|
||||
u8* dest_line = unswizzled_data + line * dest_pitch + x * bytes_per_pixel;
|
||||
u8* source_addr = swizzled_data + swizzled_offset;
|
||||
|
||||
Memory::CopyBlock(dest_line, source_addr, bytes_per_pixel);
|
||||
std::memcpy(dest_line, source_addr, bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,14 @@ inline std::size_t GetGOBSize() {
|
||||
}
|
||||
|
||||
/// Unswizzles a swizzled texture without changing its format.
|
||||
void UnswizzleTexture(u8* unswizzled_data, VAddr address, u32 tile_size_x, u32 tile_size_y,
|
||||
void UnswizzleTexture(u8* unswizzled_data, u8* address, u32 tile_size_x, u32 tile_size_y,
|
||||
u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
|
||||
u32 block_height = TICEntry::DefaultBlockHeight,
|
||||
u32 block_depth = TICEntry::DefaultBlockHeight, u32 width_spacing = 0);
|
||||
|
||||
/// Unswizzles a swizzled texture without changing its format.
|
||||
std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y,
|
||||
u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
|
||||
std::vector<u8> UnswizzleTexture(u8* address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel,
|
||||
u32 width, u32 height, u32 depth,
|
||||
u32 block_height = TICEntry::DefaultBlockHeight,
|
||||
u32 block_depth = TICEntry::DefaultBlockHeight,
|
||||
u32 width_spacing = 0);
|
||||
@@ -44,12 +44,11 @@ std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height
|
||||
|
||||
/// Copies an untiled subrectangle into a tiled surface.
|
||||
void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width,
|
||||
u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
|
||||
u32 block_height);
|
||||
u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height);
|
||||
|
||||
/// Copies a tiled subrectangle into a linear surface.
|
||||
void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width,
|
||||
u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
|
||||
u32 block_height, u32 offset_x, u32 offset_y);
|
||||
u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height,
|
||||
u32 offset_x, u32 offset_y);
|
||||
|
||||
} // namespace Tegra::Texture
|
||||
|
||||
@@ -24,8 +24,6 @@ void EmuThread::run() {
|
||||
|
||||
MicroProfileOnThreadCreate("EmuThread");
|
||||
|
||||
stop_run = false;
|
||||
|
||||
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
|
||||
|
||||
Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources(
|
||||
@@ -40,7 +38,7 @@ void EmuThread::run() {
|
||||
render_window->DoneCurrent();
|
||||
}
|
||||
|
||||
// holds whether the cpu was running during the last iteration,
|
||||
// Holds whether the cpu was running during the last iteration,
|
||||
// so that the DebugModeLeft signal can be emitted before the
|
||||
// next execution step
|
||||
bool was_active = false;
|
||||
|
||||
@@ -209,7 +209,7 @@ void Config::ReadPlayerValues() {
|
||||
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
|
||||
auto& player = Settings::values.players[p];
|
||||
|
||||
player.connected = qt_config->value(QString("player_%1_connected").arg(p), false).toBool();
|
||||
player.connected = ReadSetting(QString("player_%1_connected").arg(p), false).toBool();
|
||||
|
||||
player.type = static_cast<Settings::ControllerType>(
|
||||
qt_config
|
||||
@@ -269,7 +269,7 @@ void Config::ReadPlayerValues() {
|
||||
}
|
||||
|
||||
void Config::ReadDebugValues() {
|
||||
Settings::values.debug_pad_enabled = qt_config->value("debug_pad_enabled", false).toBool();
|
||||
Settings::values.debug_pad_enabled = ReadSetting("debug_pad_enabled", false).toBool();
|
||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
||||
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
|
||||
Settings::values.debug_pad_buttons[i] =
|
||||
@@ -298,7 +298,7 @@ void Config::ReadDebugValues() {
|
||||
}
|
||||
|
||||
void Config::ReadKeyboardValues() {
|
||||
Settings::values.keyboard_enabled = qt_config->value("keyboard_enabled", false).toBool();
|
||||
Settings::values.keyboard_enabled = ReadSetting("keyboard_enabled", false).toBool();
|
||||
|
||||
std::transform(default_keyboard_keys.begin(), default_keyboard_keys.end(),
|
||||
Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam);
|
||||
@@ -311,7 +311,7 @@ void Config::ReadKeyboardValues() {
|
||||
}
|
||||
|
||||
void Config::ReadMouseValues() {
|
||||
Settings::values.mouse_enabled = qt_config->value("mouse_enabled", false).toBool();
|
||||
Settings::values.mouse_enabled = ReadSetting("mouse_enabled", false).toBool();
|
||||
|
||||
for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) {
|
||||
std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]);
|
||||
@@ -327,16 +327,14 @@ void Config::ReadMouseValues() {
|
||||
}
|
||||
|
||||
void Config::ReadTouchscreenValues() {
|
||||
Settings::values.touchscreen.enabled = qt_config->value("touchscreen_enabled", true).toBool();
|
||||
Settings::values.touchscreen.enabled = ReadSetting("touchscreen_enabled", true).toBool();
|
||||
Settings::values.touchscreen.device =
|
||||
qt_config->value("touchscreen_device", "engine:emu_window").toString().toStdString();
|
||||
ReadSetting("touchscreen_device", "engine:emu_window").toString().toStdString();
|
||||
|
||||
Settings::values.touchscreen.finger = qt_config->value("touchscreen_finger", 0).toUInt();
|
||||
Settings::values.touchscreen.rotation_angle = qt_config->value("touchscreen_angle", 0).toUInt();
|
||||
Settings::values.touchscreen.diameter_x =
|
||||
qt_config->value("touchscreen_diameter_x", 15).toUInt();
|
||||
Settings::values.touchscreen.diameter_y =
|
||||
qt_config->value("touchscreen_diameter_y", 15).toUInt();
|
||||
Settings::values.touchscreen.finger = ReadSetting("touchscreen_finger", 0).toUInt();
|
||||
Settings::values.touchscreen.rotation_angle = ReadSetting("touchscreen_angle", 0).toUInt();
|
||||
Settings::values.touchscreen.diameter_x = ReadSetting("touchscreen_diameter_x", 15).toUInt();
|
||||
Settings::values.touchscreen.diameter_y = ReadSetting("touchscreen_diameter_y", 15).toUInt();
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
@@ -357,42 +355,41 @@ void Config::ReadValues() {
|
||||
ReadTouchscreenValues();
|
||||
|
||||
Settings::values.motion_device =
|
||||
qt_config->value("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01")
|
||||
ReadSetting("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01")
|
||||
.toString()
|
||||
.toStdString();
|
||||
|
||||
qt_config->beginGroup("Core");
|
||||
Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool();
|
||||
Settings::values.use_multi_core = qt_config->value("use_multi_core", false).toBool();
|
||||
Settings::values.use_cpu_jit = ReadSetting("use_cpu_jit", true).toBool();
|
||||
Settings::values.use_multi_core = ReadSetting("use_multi_core", false).toBool();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Renderer");
|
||||
Settings::values.resolution_factor = qt_config->value("resolution_factor", 1.0).toFloat();
|
||||
Settings::values.use_frame_limit = qt_config->value("use_frame_limit", true).toBool();
|
||||
Settings::values.frame_limit = qt_config->value("frame_limit", 100).toInt();
|
||||
Settings::values.use_disk_shader_cache =
|
||||
qt_config->value("use_disk_shader_cache", false).toBool();
|
||||
Settings::values.resolution_factor = ReadSetting("resolution_factor", 1.0).toFloat();
|
||||
Settings::values.use_frame_limit = ReadSetting("use_frame_limit", true).toBool();
|
||||
Settings::values.frame_limit = ReadSetting("frame_limit", 100).toInt();
|
||||
Settings::values.use_disk_shader_cache = ReadSetting("use_disk_shader_cache", true).toBool();
|
||||
Settings::values.use_accurate_gpu_emulation =
|
||||
qt_config->value("use_accurate_gpu_emulation", false).toBool();
|
||||
ReadSetting("use_accurate_gpu_emulation", false).toBool();
|
||||
Settings::values.use_asynchronous_gpu_emulation =
|
||||
qt_config->value("use_asynchronous_gpu_emulation", false).toBool();
|
||||
ReadSetting("use_asynchronous_gpu_emulation", false).toBool();
|
||||
|
||||
Settings::values.bg_red = qt_config->value("bg_red", 0.0).toFloat();
|
||||
Settings::values.bg_green = qt_config->value("bg_green", 0.0).toFloat();
|
||||
Settings::values.bg_blue = qt_config->value("bg_blue", 0.0).toFloat();
|
||||
Settings::values.bg_red = ReadSetting("bg_red", 0.0).toFloat();
|
||||
Settings::values.bg_green = ReadSetting("bg_green", 0.0).toFloat();
|
||||
Settings::values.bg_blue = ReadSetting("bg_blue", 0.0).toFloat();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Audio");
|
||||
Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString();
|
||||
Settings::values.sink_id = ReadSetting("output_engine", "auto").toString().toStdString();
|
||||
Settings::values.enable_audio_stretching =
|
||||
qt_config->value("enable_audio_stretching", true).toBool();
|
||||
ReadSetting("enable_audio_stretching", true).toBool();
|
||||
Settings::values.audio_device_id =
|
||||
qt_config->value("output_device", "auto").toString().toStdString();
|
||||
Settings::values.volume = qt_config->value("volume", 1).toFloat();
|
||||
ReadSetting("output_device", "auto").toString().toStdString();
|
||||
Settings::values.volume = ReadSetting("volume", 1).toFloat();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Data Storage");
|
||||
Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool();
|
||||
Settings::values.use_virtual_sd = ReadSetting("use_virtual_sd", true).toBool();
|
||||
FileUtil::GetUserPath(
|
||||
FileUtil::UserPath::NANDDir,
|
||||
qt_config
|
||||
@@ -410,30 +407,30 @@ void Config::ReadValues() {
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Core");
|
||||
Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool();
|
||||
Settings::values.use_multi_core = qt_config->value("use_multi_core", false).toBool();
|
||||
Settings::values.use_cpu_jit = ReadSetting("use_cpu_jit", true).toBool();
|
||||
Settings::values.use_multi_core = ReadSetting("use_multi_core", false).toBool();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("System");
|
||||
Settings::values.use_docked_mode = qt_config->value("use_docked_mode", false).toBool();
|
||||
Settings::values.enable_nfc = qt_config->value("enable_nfc", true).toBool();
|
||||
Settings::values.use_docked_mode = ReadSetting("use_docked_mode", false).toBool();
|
||||
Settings::values.enable_nfc = ReadSetting("enable_nfc", true).toBool();
|
||||
|
||||
Settings::values.current_user = std::clamp<int>(qt_config->value("current_user", 0).toInt(), 0,
|
||||
Service::Account::MAX_USERS - 1);
|
||||
Settings::values.current_user =
|
||||
std::clamp<int>(ReadSetting("current_user", 0).toInt(), 0, Service::Account::MAX_USERS - 1);
|
||||
|
||||
Settings::values.language_index = qt_config->value("language_index", 1).toInt();
|
||||
Settings::values.language_index = ReadSetting("language_index", 1).toInt();
|
||||
|
||||
const auto rng_seed_enabled = qt_config->value("rng_seed_enabled", false).toBool();
|
||||
const auto rng_seed_enabled = ReadSetting("rng_seed_enabled", false).toBool();
|
||||
if (rng_seed_enabled) {
|
||||
Settings::values.rng_seed = qt_config->value("rng_seed", 0).toULongLong();
|
||||
Settings::values.rng_seed = ReadSetting("rng_seed", 0).toULongLong();
|
||||
} else {
|
||||
Settings::values.rng_seed = std::nullopt;
|
||||
}
|
||||
|
||||
const auto custom_rtc_enabled = qt_config->value("custom_rtc_enabled", false).toBool();
|
||||
const auto custom_rtc_enabled = ReadSetting("custom_rtc_enabled", false).toBool();
|
||||
if (custom_rtc_enabled) {
|
||||
Settings::values.custom_rtc =
|
||||
std::chrono::seconds(qt_config->value("custom_rtc", 0).toULongLong());
|
||||
std::chrono::seconds(ReadSetting("custom_rtc", 0).toULongLong());
|
||||
} else {
|
||||
Settings::values.custom_rtc = std::nullopt;
|
||||
}
|
||||
@@ -441,35 +438,35 @@ void Config::ReadValues() {
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Miscellaneous");
|
||||
Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString();
|
||||
Settings::values.use_dev_keys = qt_config->value("use_dev_keys", false).toBool();
|
||||
Settings::values.log_filter = ReadSetting("log_filter", "*:Info").toString().toStdString();
|
||||
Settings::values.use_dev_keys = ReadSetting("use_dev_keys", false).toBool();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Debugging");
|
||||
Settings::values.use_gdbstub = qt_config->value("use_gdbstub", false).toBool();
|
||||
Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt();
|
||||
Settings::values.program_args = qt_config->value("program_args", "").toString().toStdString();
|
||||
Settings::values.dump_exefs = qt_config->value("dump_exefs", false).toBool();
|
||||
Settings::values.dump_nso = qt_config->value("dump_nso", false).toBool();
|
||||
Settings::values.use_gdbstub = ReadSetting("use_gdbstub", false).toBool();
|
||||
Settings::values.gdbstub_port = ReadSetting("gdbstub_port", 24689).toInt();
|
||||
Settings::values.program_args = ReadSetting("program_args", "").toString().toStdString();
|
||||
Settings::values.dump_exefs = ReadSetting("dump_exefs", false).toBool();
|
||||
Settings::values.dump_nso = ReadSetting("dump_nso", false).toBool();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("WebService");
|
||||
Settings::values.enable_telemetry = qt_config->value("enable_telemetry", true).toBool();
|
||||
Settings::values.enable_telemetry = ReadSetting("enable_telemetry", true).toBool();
|
||||
Settings::values.web_api_url =
|
||||
qt_config->value("web_api_url", "https://api.yuzu-emu.org").toString().toStdString();
|
||||
Settings::values.yuzu_username = qt_config->value("yuzu_username").toString().toStdString();
|
||||
Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString();
|
||||
ReadSetting("web_api_url", "https://api.yuzu-emu.org").toString().toStdString();
|
||||
Settings::values.yuzu_username = ReadSetting("yuzu_username").toString().toStdString();
|
||||
Settings::values.yuzu_token = ReadSetting("yuzu_token").toString().toStdString();
|
||||
qt_config->endGroup();
|
||||
|
||||
const auto size = qt_config->beginReadArray("DisabledAddOns");
|
||||
for (int i = 0; i < size; ++i) {
|
||||
qt_config->setArrayIndex(i);
|
||||
const auto title_id = qt_config->value("title_id", 0).toULongLong();
|
||||
const auto title_id = ReadSetting("title_id", 0).toULongLong();
|
||||
std::vector<std::string> out;
|
||||
const auto d_size = qt_config->beginReadArray("disabled");
|
||||
for (int j = 0; j < d_size; ++j) {
|
||||
qt_config->setArrayIndex(j);
|
||||
out.push_back(qt_config->value("d", "").toString().toStdString());
|
||||
out.push_back(ReadSetting("d", "").toString().toStdString());
|
||||
}
|
||||
qt_config->endArray();
|
||||
Settings::values.disabled_addons.insert_or_assign(title_id, out);
|
||||
@@ -477,41 +474,38 @@ void Config::ReadValues() {
|
||||
qt_config->endArray();
|
||||
|
||||
qt_config->beginGroup("UI");
|
||||
UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
|
||||
UISettings::values.theme = ReadSetting("theme", UISettings::themes[0].second).toString();
|
||||
UISettings::values.enable_discord_presence =
|
||||
qt_config->value("enable_discord_presence", true).toBool();
|
||||
ReadSetting("enable_discord_presence", true).toBool();
|
||||
UISettings::values.screenshot_resolution_factor =
|
||||
static_cast<u16>(qt_config->value("screenshot_resolution_factor", 0).toUInt());
|
||||
UISettings::values.select_user_on_boot =
|
||||
qt_config->value("select_user_on_boot", false).toBool();
|
||||
static_cast<u16>(ReadSetting("screenshot_resolution_factor", 0).toUInt());
|
||||
UISettings::values.select_user_on_boot = ReadSetting("select_user_on_boot", false).toBool();
|
||||
|
||||
qt_config->beginGroup("UIGameList");
|
||||
UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool();
|
||||
UISettings::values.show_add_ons = qt_config->value("show_add_ons", true).toBool();
|
||||
UISettings::values.icon_size = qt_config->value("icon_size", 64).toUInt();
|
||||
UISettings::values.row_1_text_id = qt_config->value("row_1_text_id", 3).toUInt();
|
||||
UISettings::values.row_2_text_id = qt_config->value("row_2_text_id", 2).toUInt();
|
||||
UISettings::values.show_unknown = ReadSetting("show_unknown", true).toBool();
|
||||
UISettings::values.show_add_ons = ReadSetting("show_add_ons", true).toBool();
|
||||
UISettings::values.icon_size = ReadSetting("icon_size", 64).toUInt();
|
||||
UISettings::values.row_1_text_id = ReadSetting("row_1_text_id", 3).toUInt();
|
||||
UISettings::values.row_2_text_id = ReadSetting("row_2_text_id", 2).toUInt();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("UILayout");
|
||||
UISettings::values.geometry = qt_config->value("geometry").toByteArray();
|
||||
UISettings::values.state = qt_config->value("state").toByteArray();
|
||||
UISettings::values.renderwindow_geometry =
|
||||
qt_config->value("geometryRenderWindow").toByteArray();
|
||||
UISettings::values.gamelist_header_state =
|
||||
qt_config->value("gameListHeaderState").toByteArray();
|
||||
UISettings::values.geometry = ReadSetting("geometry").toByteArray();
|
||||
UISettings::values.state = ReadSetting("state").toByteArray();
|
||||
UISettings::values.renderwindow_geometry = ReadSetting("geometryRenderWindow").toByteArray();
|
||||
UISettings::values.gamelist_header_state = ReadSetting("gameListHeaderState").toByteArray();
|
||||
UISettings::values.microprofile_geometry =
|
||||
qt_config->value("microProfileDialogGeometry").toByteArray();
|
||||
ReadSetting("microProfileDialogGeometry").toByteArray();
|
||||
UISettings::values.microprofile_visible =
|
||||
qt_config->value("microProfileDialogVisible", false).toBool();
|
||||
ReadSetting("microProfileDialogVisible", false).toBool();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Paths");
|
||||
UISettings::values.roms_path = qt_config->value("romsPath").toString();
|
||||
UISettings::values.symbols_path = qt_config->value("symbolsPath").toString();
|
||||
UISettings::values.gamedir = qt_config->value("gameListRootDir", ".").toString();
|
||||
UISettings::values.gamedir_deepscan = qt_config->value("gameListDeepScan", false).toBool();
|
||||
UISettings::values.recent_files = qt_config->value("recentFiles").toStringList();
|
||||
UISettings::values.roms_path = ReadSetting("romsPath").toString();
|
||||
UISettings::values.symbols_path = ReadSetting("symbolsPath").toString();
|
||||
UISettings::values.gamedir = ReadSetting("gameListRootDir", ".").toString();
|
||||
UISettings::values.gamedir_deepscan = ReadSetting("gameListDeepScan", false).toBool();
|
||||
UISettings::values.recent_files = ReadSetting("recentFiles").toStringList();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Shortcuts");
|
||||
@@ -524,8 +518,8 @@ void Config::ReadValues() {
|
||||
qt_config->beginGroup(hotkey);
|
||||
UISettings::values.shortcuts.emplace_back(UISettings::Shortcut(
|
||||
group + "/" + hotkey,
|
||||
UISettings::ContextualShortcut(qt_config->value("KeySeq").toString(),
|
||||
qt_config->value("Context").toInt())));
|
||||
UISettings::ContextualShortcut(ReadSetting("KeySeq").toString(),
|
||||
ReadSetting("Context").toInt())));
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
@@ -533,16 +527,16 @@ void Config::ReadValues() {
|
||||
}
|
||||
qt_config->endGroup();
|
||||
|
||||
UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool();
|
||||
UISettings::values.fullscreen = qt_config->value("fullscreen", false).toBool();
|
||||
UISettings::values.display_titlebar = qt_config->value("displayTitleBars", true).toBool();
|
||||
UISettings::values.show_filter_bar = qt_config->value("showFilterBar", true).toBool();
|
||||
UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool();
|
||||
UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool();
|
||||
UISettings::values.first_start = qt_config->value("firstStart", true).toBool();
|
||||
UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt();
|
||||
UISettings::values.show_console = qt_config->value("showConsole", false).toBool();
|
||||
UISettings::values.profile_index = qt_config->value("profileIndex", 0).toUInt();
|
||||
UISettings::values.single_window_mode = ReadSetting("singleWindowMode", true).toBool();
|
||||
UISettings::values.fullscreen = ReadSetting("fullscreen", false).toBool();
|
||||
UISettings::values.display_titlebar = ReadSetting("displayTitleBars", true).toBool();
|
||||
UISettings::values.show_filter_bar = ReadSetting("showFilterBar", true).toBool();
|
||||
UISettings::values.show_status_bar = ReadSetting("showStatusBar", true).toBool();
|
||||
UISettings::values.confirm_before_closing = ReadSetting("confirmClose", true).toBool();
|
||||
UISettings::values.first_start = ReadSetting("firstStart", true).toBool();
|
||||
UISettings::values.callout_flags = ReadSetting("calloutFlags", 0).toUInt();
|
||||
UISettings::values.show_console = ReadSetting("showConsole", false).toBool();
|
||||
UISettings::values.profile_index = ReadSetting("profileIndex", 0).toUInt();
|
||||
|
||||
ApplyDefaultProfileIfInputInvalid();
|
||||
|
||||
@@ -553,62 +547,79 @@ void Config::SavePlayerValues() {
|
||||
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
|
||||
const auto& player = Settings::values.players[p];
|
||||
|
||||
qt_config->setValue(QString("player_%1_connected").arg(p), player.connected);
|
||||
qt_config->setValue(QString("player_%1_type").arg(p), static_cast<u8>(player.type));
|
||||
WriteSetting(QString("player_%1_connected").arg(p), player.connected, false);
|
||||
WriteSetting(QString("player_%1_type").arg(p), static_cast<u8>(player.type),
|
||||
static_cast<u8>(Settings::ControllerType::DualJoycon));
|
||||
|
||||
qt_config->setValue(QString("player_%1_body_color_left").arg(p), player.body_color_left);
|
||||
qt_config->setValue(QString("player_%1_body_color_right").arg(p), player.body_color_right);
|
||||
qt_config->setValue(QString("player_%1_button_color_left").arg(p),
|
||||
player.button_color_left);
|
||||
qt_config->setValue(QString("player_%1_button_color_right").arg(p),
|
||||
player.button_color_right);
|
||||
WriteSetting(QString("player_%1_body_color_left").arg(p), player.body_color_left,
|
||||
Settings::JOYCON_BODY_NEON_BLUE);
|
||||
WriteSetting(QString("player_%1_body_color_right").arg(p), player.body_color_right,
|
||||
Settings::JOYCON_BODY_NEON_RED);
|
||||
WriteSetting(QString("player_%1_button_color_left").arg(p), player.button_color_left,
|
||||
Settings::JOYCON_BUTTONS_NEON_BLUE);
|
||||
WriteSetting(QString("player_%1_button_color_right").arg(p), player.button_color_right,
|
||||
Settings::JOYCON_BUTTONS_NEON_RED);
|
||||
|
||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
||||
qt_config->setValue(QString("player_%1_").arg(p) +
|
||||
QString::fromStdString(Settings::NativeButton::mapping[i]),
|
||||
QString::fromStdString(player.buttons[i]));
|
||||
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
|
||||
WriteSetting(QString("player_%1_").arg(p) +
|
||||
QString::fromStdString(Settings::NativeButton::mapping[i]),
|
||||
QString::fromStdString(player.buttons[i]),
|
||||
QString::fromStdString(default_param));
|
||||
}
|
||||
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
|
||||
qt_config->setValue(QString("player_%1_").arg(p) +
|
||||
QString::fromStdString(Settings::NativeAnalog::mapping[i]),
|
||||
QString::fromStdString(player.analogs[i]));
|
||||
std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
|
||||
default_analogs[i][3], default_analogs[i][4], 0.5f);
|
||||
WriteSetting(QString("player_%1_").arg(p) +
|
||||
QString::fromStdString(Settings::NativeAnalog::mapping[i]),
|
||||
QString::fromStdString(player.analogs[i]),
|
||||
QString::fromStdString(default_param));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Config::SaveDebugValues() {
|
||||
qt_config->setValue("debug_pad_enabled", Settings::values.debug_pad_enabled);
|
||||
WriteSetting("debug_pad_enabled", Settings::values.debug_pad_enabled, false);
|
||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
||||
qt_config->setValue(QString("debug_pad_") +
|
||||
QString::fromStdString(Settings::NativeButton::mapping[i]),
|
||||
QString::fromStdString(Settings::values.debug_pad_buttons[i]));
|
||||
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
|
||||
WriteSetting(QString("debug_pad_") +
|
||||
QString::fromStdString(Settings::NativeButton::mapping[i]),
|
||||
QString::fromStdString(Settings::values.debug_pad_buttons[i]),
|
||||
QString::fromStdString(default_param));
|
||||
}
|
||||
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
|
||||
qt_config->setValue(QString("debug_pad_") +
|
||||
QString::fromStdString(Settings::NativeAnalog::mapping[i]),
|
||||
QString::fromStdString(Settings::values.debug_pad_analogs[i]));
|
||||
std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
|
||||
default_analogs[i][3], default_analogs[i][4], 0.5f);
|
||||
WriteSetting(QString("debug_pad_") +
|
||||
QString::fromStdString(Settings::NativeAnalog::mapping[i]),
|
||||
QString::fromStdString(Settings::values.debug_pad_analogs[i]),
|
||||
QString::fromStdString(default_param));
|
||||
}
|
||||
}
|
||||
|
||||
void Config::SaveMouseValues() {
|
||||
qt_config->setValue("mouse_enabled", Settings::values.mouse_enabled);
|
||||
WriteSetting("mouse_enabled", Settings::values.mouse_enabled, false);
|
||||
|
||||
for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) {
|
||||
qt_config->setValue(QString("mouse_") +
|
||||
QString::fromStdString(Settings::NativeMouseButton::mapping[i]),
|
||||
QString::fromStdString(Settings::values.mouse_buttons[i]));
|
||||
std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]);
|
||||
WriteSetting(QString("mouse_") +
|
||||
QString::fromStdString(Settings::NativeMouseButton::mapping[i]),
|
||||
QString::fromStdString(Settings::values.mouse_buttons[i]),
|
||||
QString::fromStdString(default_param));
|
||||
}
|
||||
}
|
||||
|
||||
void Config::SaveTouchscreenValues() {
|
||||
qt_config->setValue("touchscreen_enabled", Settings::values.touchscreen.enabled);
|
||||
qt_config->setValue("touchscreen_device",
|
||||
QString::fromStdString(Settings::values.touchscreen.device));
|
||||
WriteSetting("touchscreen_enabled", Settings::values.touchscreen.enabled, true);
|
||||
WriteSetting("touchscreen_device", QString::fromStdString(Settings::values.touchscreen.device),
|
||||
"engine:emu_window");
|
||||
|
||||
qt_config->setValue("touchscreen_finger", Settings::values.touchscreen.finger);
|
||||
qt_config->setValue("touchscreen_angle", Settings::values.touchscreen.rotation_angle);
|
||||
qt_config->setValue("touchscreen_diameter_x", Settings::values.touchscreen.diameter_x);
|
||||
qt_config->setValue("touchscreen_diameter_y", Settings::values.touchscreen.diameter_y);
|
||||
WriteSetting("touchscreen_finger", Settings::values.touchscreen.finger, 0);
|
||||
WriteSetting("touchscreen_angle", Settings::values.touchscreen.rotation_angle, 0);
|
||||
WriteSetting("touchscreen_diameter_x", Settings::values.touchscreen.diameter_x, 15);
|
||||
WriteSetting("touchscreen_diameter_y", Settings::values.touchscreen.diameter_y, 15);
|
||||
}
|
||||
|
||||
void Config::SaveValues() {
|
||||
@@ -619,91 +630,96 @@ void Config::SaveValues() {
|
||||
SaveMouseValues();
|
||||
SaveTouchscreenValues();
|
||||
|
||||
qt_config->setValue("motion_device", QString::fromStdString(Settings::values.motion_device));
|
||||
qt_config->setValue("keyboard_enabled", Settings::values.keyboard_enabled);
|
||||
WriteSetting("motion_device", QString::fromStdString(Settings::values.motion_device),
|
||||
"engine:motion_emu,update_period:100,sensitivity:0.01");
|
||||
WriteSetting("keyboard_enabled", Settings::values.keyboard_enabled, false);
|
||||
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Core");
|
||||
qt_config->setValue("use_cpu_jit", Settings::values.use_cpu_jit);
|
||||
qt_config->setValue("use_multi_core", Settings::values.use_multi_core);
|
||||
WriteSetting("use_cpu_jit", Settings::values.use_cpu_jit, true);
|
||||
WriteSetting("use_multi_core", Settings::values.use_multi_core, false);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Renderer");
|
||||
qt_config->setValue("resolution_factor", (double)Settings::values.resolution_factor);
|
||||
qt_config->setValue("use_frame_limit", Settings::values.use_frame_limit);
|
||||
qt_config->setValue("frame_limit", Settings::values.frame_limit);
|
||||
qt_config->setValue("use_disk_shader_cache", Settings::values.use_disk_shader_cache);
|
||||
qt_config->setValue("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation);
|
||||
qt_config->setValue("use_asynchronous_gpu_emulation",
|
||||
Settings::values.use_asynchronous_gpu_emulation);
|
||||
WriteSetting("resolution_factor", (double)Settings::values.resolution_factor, 1.0);
|
||||
WriteSetting("use_frame_limit", Settings::values.use_frame_limit, true);
|
||||
WriteSetting("frame_limit", Settings::values.frame_limit, 100);
|
||||
WriteSetting("use_disk_shader_cache", Settings::values.use_disk_shader_cache, true);
|
||||
WriteSetting("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation, false);
|
||||
WriteSetting("use_asynchronous_gpu_emulation", Settings::values.use_asynchronous_gpu_emulation,
|
||||
false);
|
||||
|
||||
// Cast to double because Qt's written float values are not human-readable
|
||||
qt_config->setValue("bg_red", (double)Settings::values.bg_red);
|
||||
qt_config->setValue("bg_green", (double)Settings::values.bg_green);
|
||||
qt_config->setValue("bg_blue", (double)Settings::values.bg_blue);
|
||||
WriteSetting("bg_red", (double)Settings::values.bg_red, 0.0);
|
||||
WriteSetting("bg_green", (double)Settings::values.bg_green, 0.0);
|
||||
WriteSetting("bg_blue", (double)Settings::values.bg_blue, 0.0);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Audio");
|
||||
qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id));
|
||||
qt_config->setValue("enable_audio_stretching", Settings::values.enable_audio_stretching);
|
||||
qt_config->setValue("output_device", QString::fromStdString(Settings::values.audio_device_id));
|
||||
qt_config->setValue("volume", Settings::values.volume);
|
||||
WriteSetting("output_engine", QString::fromStdString(Settings::values.sink_id), "auto");
|
||||
WriteSetting("enable_audio_stretching", Settings::values.enable_audio_stretching, true);
|
||||
WriteSetting("output_device", QString::fromStdString(Settings::values.audio_device_id), "auto");
|
||||
WriteSetting("volume", Settings::values.volume, 1.0f);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Data Storage");
|
||||
qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd);
|
||||
qt_config->setValue("nand_directory",
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
|
||||
qt_config->setValue("sdmc_directory",
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
|
||||
WriteSetting("use_virtual_sd", Settings::values.use_virtual_sd, true);
|
||||
WriteSetting("nand_directory",
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)),
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
|
||||
WriteSetting("sdmc_directory",
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)),
|
||||
QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("System");
|
||||
qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode);
|
||||
qt_config->setValue("enable_nfc", Settings::values.enable_nfc);
|
||||
qt_config->setValue("current_user", Settings::values.current_user);
|
||||
qt_config->setValue("language_index", Settings::values.language_index);
|
||||
WriteSetting("use_docked_mode", Settings::values.use_docked_mode, false);
|
||||
WriteSetting("enable_nfc", Settings::values.enable_nfc, true);
|
||||
WriteSetting("current_user", Settings::values.current_user, 0);
|
||||
WriteSetting("language_index", Settings::values.language_index, 1);
|
||||
|
||||
qt_config->setValue("rng_seed_enabled", Settings::values.rng_seed.has_value());
|
||||
qt_config->setValue("rng_seed", Settings::values.rng_seed.value_or(0));
|
||||
WriteSetting("rng_seed_enabled", Settings::values.rng_seed.has_value(), false);
|
||||
WriteSetting("rng_seed", Settings::values.rng_seed.value_or(0), 0);
|
||||
|
||||
qt_config->setValue("custom_rtc_enabled", Settings::values.custom_rtc.has_value());
|
||||
qt_config->setValue("custom_rtc",
|
||||
QVariant::fromValue<long long>(
|
||||
Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()));
|
||||
WriteSetting("custom_rtc_enabled", Settings::values.custom_rtc.has_value(), false);
|
||||
WriteSetting("custom_rtc",
|
||||
QVariant::fromValue<long long>(
|
||||
Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()),
|
||||
0);
|
||||
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Miscellaneous");
|
||||
qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter));
|
||||
qt_config->setValue("use_dev_keys", Settings::values.use_dev_keys);
|
||||
WriteSetting("log_filter", QString::fromStdString(Settings::values.log_filter), "*:Info");
|
||||
WriteSetting("use_dev_keys", Settings::values.use_dev_keys, false);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Debugging");
|
||||
qt_config->setValue("use_gdbstub", Settings::values.use_gdbstub);
|
||||
qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port);
|
||||
qt_config->setValue("program_args", QString::fromStdString(Settings::values.program_args));
|
||||
qt_config->setValue("dump_exefs", Settings::values.dump_exefs);
|
||||
qt_config->setValue("dump_nso", Settings::values.dump_nso);
|
||||
WriteSetting("use_gdbstub", Settings::values.use_gdbstub, false);
|
||||
WriteSetting("gdbstub_port", Settings::values.gdbstub_port, 24689);
|
||||
WriteSetting("program_args", QString::fromStdString(Settings::values.program_args), "");
|
||||
WriteSetting("dump_exefs", Settings::values.dump_exefs, false);
|
||||
WriteSetting("dump_nso", Settings::values.dump_nso, false);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("WebService");
|
||||
qt_config->setValue("enable_telemetry", Settings::values.enable_telemetry);
|
||||
qt_config->setValue("web_api_url", QString::fromStdString(Settings::values.web_api_url));
|
||||
qt_config->setValue("yuzu_username", QString::fromStdString(Settings::values.yuzu_username));
|
||||
qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));
|
||||
WriteSetting("enable_telemetry", Settings::values.enable_telemetry, true);
|
||||
WriteSetting("web_api_url", QString::fromStdString(Settings::values.web_api_url),
|
||||
"https://api.yuzu-emu.org");
|
||||
WriteSetting("yuzu_username", QString::fromStdString(Settings::values.yuzu_username));
|
||||
WriteSetting("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginWriteArray("DisabledAddOns");
|
||||
int i = 0;
|
||||
for (const auto& elem : Settings::values.disabled_addons) {
|
||||
qt_config->setArrayIndex(i);
|
||||
qt_config->setValue("title_id", QVariant::fromValue<u64>(elem.first));
|
||||
WriteSetting("title_id", QVariant::fromValue<u64>(elem.first), 0);
|
||||
qt_config->beginWriteArray("disabled");
|
||||
for (std::size_t j = 0; j < elem.second.size(); ++j) {
|
||||
qt_config->setArrayIndex(static_cast<int>(j));
|
||||
qt_config->setValue("d", QString::fromStdString(elem.second[j]));
|
||||
WriteSetting("d", QString::fromStdString(elem.second[j]), "");
|
||||
}
|
||||
qt_config->endArray();
|
||||
++i;
|
||||
@@ -711,60 +727,86 @@ void Config::SaveValues() {
|
||||
qt_config->endArray();
|
||||
|
||||
qt_config->beginGroup("UI");
|
||||
qt_config->setValue("theme", UISettings::values.theme);
|
||||
qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence);
|
||||
qt_config->setValue("screenshot_resolution_factor",
|
||||
UISettings::values.screenshot_resolution_factor);
|
||||
qt_config->setValue("select_user_on_boot", UISettings::values.select_user_on_boot);
|
||||
WriteSetting("theme", UISettings::values.theme, UISettings::themes[0].second);
|
||||
WriteSetting("enable_discord_presence", UISettings::values.enable_discord_presence, true);
|
||||
WriteSetting("screenshot_resolution_factor", UISettings::values.screenshot_resolution_factor,
|
||||
0);
|
||||
WriteSetting("select_user_on_boot", UISettings::values.select_user_on_boot, false);
|
||||
|
||||
qt_config->beginGroup("UIGameList");
|
||||
qt_config->setValue("show_unknown", UISettings::values.show_unknown);
|
||||
qt_config->setValue("show_add_ons", UISettings::values.show_add_ons);
|
||||
qt_config->setValue("icon_size", UISettings::values.icon_size);
|
||||
qt_config->setValue("row_1_text_id", UISettings::values.row_1_text_id);
|
||||
qt_config->setValue("row_2_text_id", UISettings::values.row_2_text_id);
|
||||
WriteSetting("show_unknown", UISettings::values.show_unknown, true);
|
||||
WriteSetting("show_add_ons", UISettings::values.show_add_ons, true);
|
||||
WriteSetting("icon_size", UISettings::values.icon_size, 64);
|
||||
WriteSetting("row_1_text_id", UISettings::values.row_1_text_id, 3);
|
||||
WriteSetting("row_2_text_id", UISettings::values.row_2_text_id, 2);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("UILayout");
|
||||
qt_config->setValue("geometry", UISettings::values.geometry);
|
||||
qt_config->setValue("state", UISettings::values.state);
|
||||
qt_config->setValue("geometryRenderWindow", UISettings::values.renderwindow_geometry);
|
||||
qt_config->setValue("gameListHeaderState", UISettings::values.gamelist_header_state);
|
||||
qt_config->setValue("microProfileDialogGeometry", UISettings::values.microprofile_geometry);
|
||||
qt_config->setValue("microProfileDialogVisible", UISettings::values.microprofile_visible);
|
||||
WriteSetting("geometry", UISettings::values.geometry);
|
||||
WriteSetting("state", UISettings::values.state);
|
||||
WriteSetting("geometryRenderWindow", UISettings::values.renderwindow_geometry);
|
||||
WriteSetting("gameListHeaderState", UISettings::values.gamelist_header_state);
|
||||
WriteSetting("microProfileDialogGeometry", UISettings::values.microprofile_geometry);
|
||||
WriteSetting("microProfileDialogVisible", UISettings::values.microprofile_visible, false);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Paths");
|
||||
qt_config->setValue("romsPath", UISettings::values.roms_path);
|
||||
qt_config->setValue("symbolsPath", UISettings::values.symbols_path);
|
||||
qt_config->setValue("screenshotPath", UISettings::values.screenshot_path);
|
||||
qt_config->setValue("gameListRootDir", UISettings::values.gamedir);
|
||||
qt_config->setValue("gameListDeepScan", UISettings::values.gamedir_deepscan);
|
||||
qt_config->setValue("recentFiles", UISettings::values.recent_files);
|
||||
WriteSetting("romsPath", UISettings::values.roms_path);
|
||||
WriteSetting("symbolsPath", UISettings::values.symbols_path);
|
||||
WriteSetting("screenshotPath", UISettings::values.screenshot_path);
|
||||
WriteSetting("gameListRootDir", UISettings::values.gamedir, ".");
|
||||
WriteSetting("gameListDeepScan", UISettings::values.gamedir_deepscan, false);
|
||||
WriteSetting("recentFiles", UISettings::values.recent_files);
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Shortcuts");
|
||||
for (auto shortcut : UISettings::values.shortcuts) {
|
||||
qt_config->setValue(shortcut.first + "/KeySeq", shortcut.second.first);
|
||||
qt_config->setValue(shortcut.first + "/Context", shortcut.second.second);
|
||||
WriteSetting(shortcut.first + "/KeySeq", shortcut.second.first);
|
||||
WriteSetting(shortcut.first + "/Context", shortcut.second.second);
|
||||
}
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->setValue("singleWindowMode", UISettings::values.single_window_mode);
|
||||
qt_config->setValue("fullscreen", UISettings::values.fullscreen);
|
||||
qt_config->setValue("displayTitleBars", UISettings::values.display_titlebar);
|
||||
qt_config->setValue("showFilterBar", UISettings::values.show_filter_bar);
|
||||
qt_config->setValue("showStatusBar", UISettings::values.show_status_bar);
|
||||
qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing);
|
||||
qt_config->setValue("firstStart", UISettings::values.first_start);
|
||||
qt_config->setValue("calloutFlags", UISettings::values.callout_flags);
|
||||
qt_config->setValue("showConsole", UISettings::values.show_console);
|
||||
qt_config->setValue("profileIndex", UISettings::values.profile_index);
|
||||
WriteSetting("singleWindowMode", UISettings::values.single_window_mode, true);
|
||||
WriteSetting("fullscreen", UISettings::values.fullscreen, false);
|
||||
WriteSetting("displayTitleBars", UISettings::values.display_titlebar, true);
|
||||
WriteSetting("showFilterBar", UISettings::values.show_filter_bar, true);
|
||||
WriteSetting("showStatusBar", UISettings::values.show_status_bar, true);
|
||||
WriteSetting("confirmClose", UISettings::values.confirm_before_closing, true);
|
||||
WriteSetting("firstStart", UISettings::values.first_start, true);
|
||||
WriteSetting("calloutFlags", UISettings::values.callout_flags, 0);
|
||||
WriteSetting("showConsole", UISettings::values.show_console, false);
|
||||
WriteSetting("profileIndex", UISettings::values.profile_index, 0);
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
QVariant Config::ReadSetting(const QString& name) const {
|
||||
return qt_config->value(name);
|
||||
}
|
||||
|
||||
QVariant Config::ReadSetting(const QString& name, const QVariant& default_value) const {
|
||||
QVariant result;
|
||||
if (qt_config->value(name + "/default", false).toBool()) {
|
||||
result = default_value;
|
||||
} else {
|
||||
result = qt_config->value(name, default_value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Config::WriteSetting(const QString& name, const QVariant& value) {
|
||||
qt_config->setValue(name, value);
|
||||
}
|
||||
|
||||
void Config::WriteSetting(const QString& name, const QVariant& value,
|
||||
const QVariant& default_value) {
|
||||
qt_config->setValue(name + "/default", value == default_value);
|
||||
qt_config->setValue(name, value);
|
||||
}
|
||||
|
||||
void Config::Reload() {
|
||||
ReadValues();
|
||||
// To apply default value changes
|
||||
SaveValues();
|
||||
Settings::Apply();
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,11 @@ private:
|
||||
void SaveMouseValues();
|
||||
void SaveTouchscreenValues();
|
||||
|
||||
QVariant ReadSetting(const QString& name) const;
|
||||
QVariant ReadSetting(const QString& name, const QVariant& default_value) const;
|
||||
void WriteSetting(const QString& name, const QVariant& value);
|
||||
void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value);
|
||||
|
||||
std::unique_ptr<QSettings> qt_config;
|
||||
std::string qt_config_loc;
|
||||
};
|
||||
|
||||
@@ -383,13 +383,12 @@ void GraphicsSurfaceWidget::OnUpdate() {
|
||||
// TODO: Implement a good way to visualize alpha components!
|
||||
|
||||
QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32);
|
||||
std::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);
|
||||
|
||||
// TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles.
|
||||
// Needs to be fixed if we plan to use this feature more, otherwise we may remove it.
|
||||
auto unswizzled_data = Tegra::Texture::UnswizzleTexture(
|
||||
*address, 1, 1, Tegra::Texture::BytesPerPixel(surface_format), surface_width,
|
||||
surface_height, 1U);
|
||||
gpu.MemoryManager().GetPointer(surface_address), 1, 1,
|
||||
Tegra::Texture::BytesPerPixel(surface_format), surface_width, surface_height, 1U);
|
||||
|
||||
auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format,
|
||||
surface_width, surface_height);
|
||||
|
||||
@@ -114,9 +114,9 @@ int main(int argc, char** argv) {
|
||||
};
|
||||
|
||||
while (optind < argc) {
|
||||
char arg = getopt_long(argc, argv, "g:fhvp::", long_options, &option_index);
|
||||
int arg = getopt_long(argc, argv, "g:fhvp::", long_options, &option_index);
|
||||
if (arg != -1) {
|
||||
switch (arg) {
|
||||
switch (static_cast<char>(arg)) {
|
||||
case 'g':
|
||||
errno = 0;
|
||||
gdb_port = strtoul(optarg, &endarg, 0);
|
||||
|
||||
Reference in New Issue
Block a user