Compare commits
170 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26c4cb1fdc | ||
|
|
f72b9e2c32 | ||
|
|
03feb29bce | ||
|
|
fdd82b754a | ||
|
|
7f152f2273 | ||
|
|
e5d428cf1e | ||
|
|
0291a86f60 | ||
|
|
4b5ae8dbaa | ||
|
|
541e9624eb | ||
|
|
d27f4a4928 | ||
|
|
f109615be0 | ||
|
|
d4ff4152ad | ||
|
|
6acd8d166a | ||
|
|
98c7a6d622 | ||
|
|
7dee60d7d2 | ||
|
|
77e2d68df7 | ||
|
|
caaa9914fd | ||
|
|
cb9fdc7a26 | ||
|
|
dbc34db6ce | ||
|
|
fd9e2d0073 | ||
|
|
f912a82a8e | ||
|
|
6e8752881c | ||
|
|
86dcf2942b | ||
|
|
afe22d8405 | ||
|
|
648b55c6b9 | ||
|
|
2a035a1f6f | ||
|
|
8144fa42bd | ||
|
|
43b9494a0f | ||
|
|
ee7c2dbf5a | ||
|
|
91602de7f2 | ||
|
|
871350ae35 | ||
|
|
441b5b97bd | ||
|
|
53e77ffbfe | ||
|
|
d6604fa765 | ||
|
|
4783ad54de | ||
|
|
73e1e929a2 | ||
|
|
0e59291310 | ||
|
|
949d7832fa | ||
|
|
5f79ba04bd | ||
|
|
58be4dff79 | ||
|
|
cf7b46c101 | ||
|
|
3afdfd7bfa | ||
|
|
b4e29ccb81 | ||
|
|
4e9683e9d5 | ||
|
|
37575eae65 | ||
|
|
0be7e82289 | ||
|
|
9b929e934b | ||
|
|
78f2a6a9e1 | ||
|
|
92d8ad3770 | ||
|
|
88b8383da2 | ||
|
|
59c1ca8b0c | ||
|
|
d6e390bc5c | ||
|
|
9d4e6176eb | ||
|
|
74890cf2da | ||
|
|
39ae73b356 | ||
|
|
41674d20ac | ||
|
|
548958bcaf | ||
|
|
870c18b078 | ||
|
|
89fe950d3c | ||
|
|
76fc8b59b2 | ||
|
|
9b21fbd1eb | ||
|
|
50e6205c21 | ||
|
|
7665411317 | ||
|
|
bed872ed38 | ||
|
|
123df8f7d7 | ||
|
|
936c36a514 | ||
|
|
b461342a84 | ||
|
|
27916764b1 | ||
|
|
5484742fda | ||
|
|
59f872a8e0 | ||
|
|
aeadbfa790 | ||
|
|
c34efbbd60 | ||
|
|
720d36ca71 | ||
|
|
bb9cf8a127 | ||
|
|
0732786ddc | ||
|
|
90f8474fc1 | ||
|
|
5737441374 | ||
|
|
b3cca34f50 | ||
|
|
3203193a67 | ||
|
|
14286f70f0 | ||
|
|
0d2ba0a320 | ||
|
|
b82bbfba77 | ||
|
|
2f8ca32020 | ||
|
|
b183ce4365 | ||
|
|
1d6559fbd3 | ||
|
|
92fae7e1ab | ||
|
|
7e2096db8a | ||
|
|
d880b77698 | ||
|
|
331ce2942c | ||
|
|
1c7a7ed79b | ||
|
|
1ff20d8538 | ||
|
|
e0ca938b22 | ||
|
|
d4ae43f9c1 | ||
|
|
4d959c6bdc | ||
|
|
736db284d2 | ||
|
|
0149162dba | ||
|
|
a4c57436fc | ||
|
|
53a0221484 | ||
|
|
cbf723896f | ||
|
|
6467b01de2 | ||
|
|
781fd7983c | ||
|
|
e0c76226ad | ||
|
|
3d9df49619 | ||
|
|
bc2196bb09 | ||
|
|
6da2ed4232 | ||
|
|
f56a8da46a | ||
|
|
d257a3b56c | ||
|
|
e96d69c328 | ||
|
|
aaca7543f0 | ||
|
|
06898263f6 | ||
|
|
e70c08b543 | ||
|
|
ef5639bfbb | ||
|
|
82ea1cf35a | ||
|
|
f61379f8d2 | ||
|
|
90c07e0d33 | ||
|
|
1584fb6b38 | ||
|
|
c2aa4293ec | ||
|
|
38b027aa81 | ||
|
|
ffcda6c08e | ||
|
|
97b6405a17 | ||
|
|
2946d4bdbe | ||
|
|
1abed2f4c4 | ||
|
|
0f7ab3e21a | ||
|
|
f9d03b1d41 | ||
|
|
dc328440c8 | ||
|
|
b492d43e63 | ||
|
|
4d2de6564f | ||
|
|
c55b5de0fb | ||
|
|
4ccf30dfaa | ||
|
|
98b760c645 | ||
|
|
9bf409f275 | ||
|
|
3fd26b7147 | ||
|
|
bc293e1751 | ||
|
|
83ac3e6395 | ||
|
|
85b0d9a7be | ||
|
|
c7763603ef | ||
|
|
5dd538cace | ||
|
|
17290a4416 | ||
|
|
bf795edac4 | ||
|
|
fa10905e1e | ||
|
|
6d82c4adf9 | ||
|
|
72e9cb523e | ||
|
|
9e924f2ef2 | ||
|
|
3d75c9cd7a | ||
|
|
46cdeb4549 | ||
|
|
5f4ee6f0c8 | ||
|
|
f43815af5d | ||
|
|
b79c294c02 | ||
|
|
5857aea94e | ||
|
|
56f35ab262 | ||
|
|
2db37ddea9 | ||
|
|
09b6dda8f0 | ||
|
|
a4412c8e22 | ||
|
|
af653906d0 | ||
|
|
3ec054643e | ||
|
|
8f958b89e7 | ||
|
|
3edafc6802 | ||
|
|
29dc6f4519 | ||
|
|
4aad010f7a | ||
|
|
d041d6231c | ||
|
|
a57aac5772 | ||
|
|
d7398283e3 | ||
|
|
d6a0d5d432 | ||
|
|
c79d2ca6cf | ||
|
|
e4602748d6 | ||
|
|
9e34303fb9 | ||
|
|
1fa6ee4723 | ||
|
|
ce05df0a6d | ||
|
|
721632fe66 | ||
|
|
89ad82ce5c |
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
if grep -nr '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .travis* dist/*.desktop \
|
||||
if grep -nrI '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .travis* dist/*.desktop \
|
||||
dist/*.svg dist/*.xml; then
|
||||
echo Trailing whitespace found, aborting
|
||||
exit 1
|
||||
|
||||
2
externals/mbedtls
vendored
2
externals/mbedtls
vendored
Submodule externals/mbedtls updated: d409b75a4c...a280e602f3
@@ -19,4 +19,16 @@ constexpr T AlignDown(T value, std::size_t size) {
|
||||
return static_cast<T>(value - value % size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr bool Is4KBAligned(T value) {
|
||||
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
|
||||
return (value & 0xFFF) == 0;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr bool IsWordAligned(T value) {
|
||||
static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
|
||||
return (value & 0b11) == 0;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -18,6 +18,8 @@ add_library(core STATIC
|
||||
crypto/encryption_layer.h
|
||||
crypto/key_manager.cpp
|
||||
crypto/key_manager.h
|
||||
crypto/partition_data_manager.cpp
|
||||
crypto/partition_data_manager.h
|
||||
crypto/ctr_encryption_layer.cpp
|
||||
crypto/ctr_encryption_layer.h
|
||||
crypto/xts_encryption_layer.cpp
|
||||
@@ -70,6 +72,7 @@ add_library(core STATIC
|
||||
file_sys/vfs_real.cpp
|
||||
file_sys/vfs_real.h
|
||||
file_sys/vfs_static.h
|
||||
file_sys/vfs_types.h
|
||||
file_sys/vfs_vector.cpp
|
||||
file_sys/vfs_vector.h
|
||||
file_sys/xts_archive.cpp
|
||||
@@ -233,6 +236,24 @@ add_library(core STATIC
|
||||
hle/service/hid/irs.h
|
||||
hle/service/hid/xcd.cpp
|
||||
hle/service/hid/xcd.h
|
||||
hle/service/hid/controllers/controller_base.cpp
|
||||
hle/service/hid/controllers/controller_base.h
|
||||
hle/service/hid/controllers/debug_pad.cpp
|
||||
hle/service/hid/controllers/debug_pad.h
|
||||
hle/service/hid/controllers/gesture.cpp
|
||||
hle/service/hid/controllers/gesture.h
|
||||
hle/service/hid/controllers/keyboard.cpp
|
||||
hle/service/hid/controllers/keyboard.h
|
||||
hle/service/hid/controllers/mouse.cpp
|
||||
hle/service/hid/controllers/mouse.h
|
||||
hle/service/hid/controllers/npad.cpp
|
||||
hle/service/hid/controllers/npad.h
|
||||
hle/service/hid/controllers/stubbed.cpp
|
||||
hle/service/hid/controllers/stubbed.h
|
||||
hle/service/hid/controllers/touchscreen.cpp
|
||||
hle/service/hid/controllers/touchscreen.h
|
||||
hle/service/hid/controllers/xpad.cpp
|
||||
hle/service/hid/controllers/xpad.h
|
||||
hle/service/lbl/lbl.cpp
|
||||
hle/service/lbl/lbl.h
|
||||
hle/service/ldn/ldn.cpp
|
||||
|
||||
@@ -144,7 +144,7 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
|
||||
|
||||
// Multi-process state
|
||||
config.processor_id = core_index;
|
||||
config.global_monitor = &exclusive_monitor->monitor;
|
||||
config.global_monitor = &exclusive_monitor.monitor;
|
||||
|
||||
// System registers
|
||||
config.tpidrro_el0 = &cb->tpidrro_el0;
|
||||
@@ -171,10 +171,9 @@ void ARM_Dynarmic::Step() {
|
||||
cb->InterpreterFallback(jit->GetPC(), 1);
|
||||
}
|
||||
|
||||
ARM_Dynarmic::ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
|
||||
std::size_t core_index)
|
||||
ARM_Dynarmic::ARM_Dynarmic(ExclusiveMonitor& exclusive_monitor, std::size_t core_index)
|
||||
: cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), core_index{core_index},
|
||||
exclusive_monitor{std::dynamic_pointer_cast<DynarmicExclusiveMonitor>(exclusive_monitor)} {
|
||||
exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {
|
||||
ThreadContext ctx{};
|
||||
inner_unicorn.SaveContext(ctx);
|
||||
PageTableChanged();
|
||||
|
||||
@@ -23,7 +23,7 @@ class DynarmicExclusiveMonitor;
|
||||
|
||||
class ARM_Dynarmic final : public ARM_Interface {
|
||||
public:
|
||||
ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, std::size_t core_index);
|
||||
ARM_Dynarmic(ExclusiveMonitor& exclusive_monitor, std::size_t core_index);
|
||||
~ARM_Dynarmic();
|
||||
|
||||
void MapBackingMemory(VAddr address, std::size_t size, u8* memory,
|
||||
@@ -62,7 +62,7 @@ private:
|
||||
ARM_Unicorn inner_unicorn;
|
||||
|
||||
std::size_t core_index;
|
||||
std::shared_ptr<DynarmicExclusiveMonitor> exclusive_monitor;
|
||||
DynarmicExclusiveMonitor& exclusive_monitor;
|
||||
|
||||
Memory::PageTable* current_page_table = nullptr;
|
||||
};
|
||||
|
||||
@@ -71,9 +71,9 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
||||
}
|
||||
|
||||
/// Runs a CPU core while the system is powered on
|
||||
void RunCpuCore(std::shared_ptr<Cpu> cpu_state) {
|
||||
void RunCpuCore(Cpu& cpu_state) {
|
||||
while (Core::System::GetInstance().IsPoweredOn()) {
|
||||
cpu_state->RunLoop(true);
|
||||
cpu_state.RunLoop(true);
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
@@ -95,7 +95,7 @@ struct System::Impl {
|
||||
status = ResultStatus::Success;
|
||||
|
||||
// Update thread_to_cpu in case Core 0 is run from a different host thread
|
||||
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0];
|
||||
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0].get();
|
||||
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
GDBStub::HandlePacket();
|
||||
@@ -139,16 +139,16 @@ struct System::Impl {
|
||||
auto main_process = Kernel::Process::Create(kernel, "main");
|
||||
kernel.MakeCurrentProcess(main_process.get());
|
||||
|
||||
cpu_barrier = std::make_shared<CpuBarrier>();
|
||||
cpu_barrier = std::make_unique<CpuBarrier>();
|
||||
cpu_exclusive_monitor = Cpu::MakeExclusiveMonitor(cpu_cores.size());
|
||||
for (std::size_t index = 0; index < cpu_cores.size(); ++index) {
|
||||
cpu_cores[index] = std::make_shared<Cpu>(cpu_exclusive_monitor, cpu_barrier, index);
|
||||
cpu_cores[index] = std::make_unique<Cpu>(*cpu_exclusive_monitor, *cpu_barrier, index);
|
||||
}
|
||||
|
||||
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
||||
service_manager = std::make_shared<Service::SM::ServiceManager>();
|
||||
|
||||
Service::Init(service_manager, virtual_filesystem);
|
||||
Service::Init(service_manager, *virtual_filesystem);
|
||||
GDBStub::Init();
|
||||
|
||||
renderer = VideoCore::CreateRenderer(emu_window);
|
||||
@@ -160,12 +160,12 @@ struct System::Impl {
|
||||
|
||||
// Create threads for CPU cores 1-3, and build thread_to_cpu map
|
||||
// CPU core 0 is run on the main thread
|
||||
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0];
|
||||
thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0].get();
|
||||
if (Settings::values.use_multi_core) {
|
||||
for (std::size_t index = 0; index < cpu_core_threads.size(); ++index) {
|
||||
cpu_core_threads[index] =
|
||||
std::make_unique<std::thread>(RunCpuCore, cpu_cores[index + 1]);
|
||||
thread_to_cpu[cpu_core_threads[index]->get_id()] = cpu_cores[index + 1];
|
||||
std::make_unique<std::thread>(RunCpuCore, std::ref(*cpu_cores[index + 1]));
|
||||
thread_to_cpu[cpu_core_threads[index]->get_id()] = cpu_cores[index + 1].get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,6 +245,7 @@ struct System::Impl {
|
||||
for (auto& cpu_core : cpu_cores) {
|
||||
cpu_core.reset();
|
||||
}
|
||||
cpu_exclusive_monitor.reset();
|
||||
cpu_barrier.reset();
|
||||
|
||||
// Shutdown kernel and core timing
|
||||
@@ -282,9 +283,9 @@ struct System::Impl {
|
||||
std::unique_ptr<VideoCore::RendererBase> renderer;
|
||||
std::unique_ptr<Tegra::GPU> gpu_core;
|
||||
std::shared_ptr<Tegra::DebugContext> debug_context;
|
||||
std::shared_ptr<ExclusiveMonitor> cpu_exclusive_monitor;
|
||||
std::shared_ptr<CpuBarrier> cpu_barrier;
|
||||
std::array<std::shared_ptr<Cpu>, NUM_CPU_CORES> cpu_cores;
|
||||
std::unique_ptr<ExclusiveMonitor> cpu_exclusive_monitor;
|
||||
std::unique_ptr<CpuBarrier> cpu_barrier;
|
||||
std::array<std::unique_ptr<Cpu>, NUM_CPU_CORES> cpu_cores;
|
||||
std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> cpu_core_threads;
|
||||
std::size_t active_core{}; ///< Active core, only used in single thread mode
|
||||
|
||||
@@ -298,7 +299,7 @@ struct System::Impl {
|
||||
std::string status_details = "";
|
||||
|
||||
/// Map of guest threads to CPU cores
|
||||
std::map<std::thread::id, std::shared_ptr<Cpu>> thread_to_cpu;
|
||||
std::map<std::thread::id, Cpu*> thread_to_cpu;
|
||||
|
||||
Core::PerfStats perf_stats;
|
||||
Core::FrameLimiter frame_limiter;
|
||||
@@ -354,12 +355,15 @@ std::size_t System::CurrentCoreIndex() {
|
||||
}
|
||||
|
||||
Kernel::Scheduler& System::CurrentScheduler() {
|
||||
return *CurrentCpuCore().Scheduler();
|
||||
return CurrentCpuCore().Scheduler();
|
||||
}
|
||||
|
||||
const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(std::size_t core_index) {
|
||||
ASSERT(core_index < NUM_CPU_CORES);
|
||||
return impl->cpu_cores[core_index]->Scheduler();
|
||||
Kernel::Scheduler& System::Scheduler(std::size_t core_index) {
|
||||
return CpuCore(core_index).Scheduler();
|
||||
}
|
||||
|
||||
const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const {
|
||||
return CpuCore(core_index).Scheduler();
|
||||
}
|
||||
|
||||
Kernel::Process* System::CurrentProcess() {
|
||||
@@ -371,8 +375,7 @@ const Kernel::Process* System::CurrentProcess() const {
|
||||
}
|
||||
|
||||
ARM_Interface& System::ArmInterface(std::size_t core_index) {
|
||||
ASSERT(core_index < NUM_CPU_CORES);
|
||||
return impl->cpu_cores[core_index]->ArmInterface();
|
||||
return CpuCore(core_index).ArmInterface();
|
||||
}
|
||||
|
||||
Cpu& System::CpuCore(std::size_t core_index) {
|
||||
@@ -380,6 +383,11 @@ Cpu& System::CpuCore(std::size_t core_index) {
|
||||
return *impl->cpu_cores[core_index];
|
||||
}
|
||||
|
||||
const Cpu& System::CpuCore(std::size_t core_index) const {
|
||||
ASSERT(core_index < NUM_CPU_CORES);
|
||||
return *impl->cpu_cores[core_index];
|
||||
}
|
||||
|
||||
ExclusiveMonitor& System::Monitor() {
|
||||
return *impl->cpu_exclusive_monitor;
|
||||
}
|
||||
|
||||
@@ -156,6 +156,9 @@ public:
|
||||
/// Gets a CPU interface to the CPU core with the specified index
|
||||
Cpu& CpuCore(std::size_t core_index);
|
||||
|
||||
/// Gets a CPU interface to the CPU core with the specified index
|
||||
const Cpu& CpuCore(std::size_t core_index) const;
|
||||
|
||||
/// Gets the exclusive monitor
|
||||
ExclusiveMonitor& Monitor();
|
||||
|
||||
@@ -172,7 +175,10 @@ public:
|
||||
const VideoCore::RendererBase& Renderer() const;
|
||||
|
||||
/// Gets the scheduler for the CPU core with the specified index
|
||||
const std::shared_ptr<Kernel::Scheduler>& Scheduler(std::size_t core_index);
|
||||
Kernel::Scheduler& Scheduler(std::size_t core_index);
|
||||
|
||||
/// Gets the scheduler for the CPU core with the specified index
|
||||
const Kernel::Scheduler& Scheduler(std::size_t core_index) const;
|
||||
|
||||
/// Provides a pointer to the current process
|
||||
Kernel::Process* CurrentProcess();
|
||||
|
||||
@@ -49,10 +49,8 @@ bool CpuBarrier::Rendezvous() {
|
||||
return false;
|
||||
}
|
||||
|
||||
Cpu::Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
|
||||
std::shared_ptr<CpuBarrier> cpu_barrier, std::size_t core_index)
|
||||
: cpu_barrier{std::move(cpu_barrier)}, core_index{core_index} {
|
||||
|
||||
Cpu::Cpu(ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier, std::size_t core_index)
|
||||
: cpu_barrier{cpu_barrier}, core_index{core_index} {
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
arm_interface = std::make_unique<ARM_Dynarmic>(exclusive_monitor, core_index);
|
||||
@@ -64,15 +62,15 @@ Cpu::Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
|
||||
arm_interface = std::make_unique<ARM_Unicorn>();
|
||||
}
|
||||
|
||||
scheduler = std::make_shared<Kernel::Scheduler>(*arm_interface);
|
||||
scheduler = std::make_unique<Kernel::Scheduler>(*arm_interface);
|
||||
}
|
||||
|
||||
Cpu::~Cpu() = default;
|
||||
|
||||
std::shared_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(std::size_t num_cores) {
|
||||
std::unique_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(std::size_t num_cores) {
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
return std::make_shared<DynarmicExclusiveMonitor>(num_cores);
|
||||
return std::make_unique<DynarmicExclusiveMonitor>(num_cores);
|
||||
#else
|
||||
return nullptr; // TODO(merry): Passthrough exclusive monitor
|
||||
#endif
|
||||
@@ -83,7 +81,7 @@ std::shared_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(std::size_t num_core
|
||||
|
||||
void Cpu::RunLoop(bool tight_loop) {
|
||||
// Wait for all other CPU cores to complete the previous slice, such that they run in lock-step
|
||||
if (!cpu_barrier->Rendezvous()) {
|
||||
if (!cpu_barrier.Rendezvous()) {
|
||||
// If rendezvous failed, session has been killed
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -41,8 +41,7 @@ private:
|
||||
|
||||
class Cpu {
|
||||
public:
|
||||
Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
|
||||
std::shared_ptr<CpuBarrier> cpu_barrier, std::size_t core_index);
|
||||
Cpu(ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier, std::size_t core_index);
|
||||
~Cpu();
|
||||
|
||||
void RunLoop(bool tight_loop = true);
|
||||
@@ -59,8 +58,12 @@ public:
|
||||
return *arm_interface;
|
||||
}
|
||||
|
||||
const std::shared_ptr<Kernel::Scheduler>& Scheduler() const {
|
||||
return scheduler;
|
||||
Kernel::Scheduler& Scheduler() {
|
||||
return *scheduler;
|
||||
}
|
||||
|
||||
const Kernel::Scheduler& Scheduler() const {
|
||||
return *scheduler;
|
||||
}
|
||||
|
||||
bool IsMainCore() const {
|
||||
@@ -71,14 +74,14 @@ public:
|
||||
return core_index;
|
||||
}
|
||||
|
||||
static std::shared_ptr<ExclusiveMonitor> MakeExclusiveMonitor(std::size_t num_cores);
|
||||
static std::unique_ptr<ExclusiveMonitor> MakeExclusiveMonitor(std::size_t num_cores);
|
||||
|
||||
private:
|
||||
void Reschedule();
|
||||
|
||||
std::unique_ptr<ARM_Interface> arm_interface;
|
||||
std::shared_ptr<CpuBarrier> cpu_barrier;
|
||||
std::shared_ptr<Kernel::Scheduler> scheduler;
|
||||
CpuBarrier& cpu_barrier;
|
||||
std::unique_ptr<Kernel::Scheduler> scheduler;
|
||||
|
||||
std::atomic<bool> reschedule_pending = false;
|
||||
std::size_t core_index;
|
||||
|
||||
@@ -4,23 +4,56 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
#include <locale>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include <mbedtls/bignum.h>
|
||||
#include <mbedtls/cipher.h>
|
||||
#include <mbedtls/cmac.h>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/crypto/partition_data_manager.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
|
||||
|
||||
using namespace Common;
|
||||
|
||||
const std::array<SHA256Hash, 2> eticket_source_hashes{
|
||||
"B71DB271DC338DF380AA2C4335EF8873B1AFD408E80B3582D8719FC81C5E511C"_array32, // eticket_rsa_kek_source
|
||||
"E8965A187D30E57869F562D04383C996DE487BBA5761363D2D4D32391866A85C"_array32, // eticket_rsa_kekek_source
|
||||
};
|
||||
|
||||
const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{
|
||||
{{S128KeyType::Master, 0}, "master_key_"},
|
||||
{{S128KeyType::Package1, 0}, "package1_key_"},
|
||||
{{S128KeyType::Package2, 0}, "package2_key_"},
|
||||
{{S128KeyType::Titlekek, 0}, "titlekek_"},
|
||||
{{S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob)}, "keyblob_key_source_"},
|
||||
{{S128KeyType::Keyblob, 0}, "keyblob_key_"},
|
||||
{{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"},
|
||||
};
|
||||
|
||||
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) {
|
||||
Key128 out{};
|
||||
|
||||
@@ -37,57 +70,136 @@ Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, K
|
||||
return out;
|
||||
}
|
||||
|
||||
Key128 DeriveKeyblobKey(const Key128& sbk, const Key128& tsec, Key128 source) {
|
||||
AESCipher<Key128> sbk_cipher(sbk, Mode::ECB);
|
||||
AESCipher<Key128> tsec_cipher(tsec, Mode::ECB);
|
||||
tsec_cipher.Transcode(source.data(), source.size(), source.data(), Op::Decrypt);
|
||||
sbk_cipher.Transcode(source.data(), source.size(), source.data(), Op::Decrypt);
|
||||
return source;
|
||||
}
|
||||
|
||||
Key128 DeriveMasterKey(const std::array<u8, 0x90>& keyblob, const Key128& master_source) {
|
||||
Key128 master_root;
|
||||
std::memcpy(master_root.data(), keyblob.data(), sizeof(Key128));
|
||||
|
||||
AESCipher<Key128> master_cipher(master_root, Mode::ECB);
|
||||
|
||||
Key128 master{};
|
||||
master_cipher.Transcode(master_source.data(), master_source.size(), master.data(), Op::Decrypt);
|
||||
return master;
|
||||
}
|
||||
|
||||
std::array<u8, 144> DecryptKeyblob(const std::array<u8, 176>& encrypted_keyblob,
|
||||
const Key128& key) {
|
||||
std::array<u8, 0x90> keyblob;
|
||||
AESCipher<Key128> cipher(key, Mode::CTR);
|
||||
cipher.SetIV(std::vector<u8>(encrypted_keyblob.data() + 0x10, encrypted_keyblob.data() + 0x20));
|
||||
cipher.Transcode(encrypted_keyblob.data() + 0x20, keyblob.size(), keyblob.data(), Op::Decrypt);
|
||||
return keyblob;
|
||||
}
|
||||
|
||||
void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) {
|
||||
const auto kek_generation_source =
|
||||
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration));
|
||||
const auto key_generation_source =
|
||||
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration));
|
||||
|
||||
if (HasKey(S128KeyType::Master, crypto_revision)) {
|
||||
for (auto kak_type :
|
||||
{KeyAreaKeyType::Application, KeyAreaKeyType::Ocean, KeyAreaKeyType::System}) {
|
||||
if (HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
|
||||
static_cast<u64>(kak_type))) {
|
||||
const auto source =
|
||||
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
|
||||
static_cast<u64>(kak_type));
|
||||
const auto kek =
|
||||
GenerateKeyEncryptionKey(source, GetKey(S128KeyType::Master, crypto_revision),
|
||||
kek_generation_source, key_generation_source);
|
||||
SetKey(S128KeyType::KeyArea, kek, crypto_revision, static_cast<u64>(kak_type));
|
||||
}
|
||||
}
|
||||
|
||||
AESCipher<Key128> master_cipher(GetKey(S128KeyType::Master, crypto_revision), Mode::ECB);
|
||||
for (auto key_type : {SourceKeyType::Titlekek, SourceKeyType::Package2}) {
|
||||
if (HasKey(S128KeyType::Source, static_cast<u64>(key_type))) {
|
||||
Key128 key{};
|
||||
master_cipher.Transcode(
|
||||
GetKey(S128KeyType::Source, static_cast<u64>(key_type)).data(), key.size(),
|
||||
key.data(), Op::Decrypt);
|
||||
SetKey(key_type == SourceKeyType::Titlekek ? S128KeyType::Titlekek
|
||||
: S128KeyType::Package2,
|
||||
key, crypto_revision);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) {
|
||||
AESCipher<Key128> mac_cipher(keyblob_key, Mode::ECB);
|
||||
Key128 mac_key{};
|
||||
mac_cipher.Transcode(mac_source.data(), mac_key.size(), mac_key.data(), Op::Decrypt);
|
||||
return mac_key;
|
||||
}
|
||||
|
||||
boost::optional<Key128> DeriveSDSeed() {
|
||||
const FileUtil::IOFile save_43(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||
"/system/save/8000000000000043",
|
||||
"rb+");
|
||||
if (!save_43.IsOpen())
|
||||
return boost::none;
|
||||
|
||||
const FileUtil::IOFile sd_private(
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "/Nintendo/Contents/private", "rb+");
|
||||
if (!sd_private.IsOpen())
|
||||
return boost::none;
|
||||
|
||||
sd_private.Seek(0, SEEK_SET);
|
||||
std::array<u8, 0x10> private_seed{};
|
||||
if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != 0x10)
|
||||
if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != private_seed.size()) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
std::array<u8, 0x10> buffer{};
|
||||
std::size_t offset = 0;
|
||||
for (; offset + 0x10 < save_43.GetSize(); ++offset) {
|
||||
save_43.Seek(offset, SEEK_SET);
|
||||
if (!save_43.Seek(offset, SEEK_SET)) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
save_43.ReadBytes(buffer.data(), buffer.size());
|
||||
if (buffer == private_seed)
|
||||
if (buffer == private_seed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (offset + 0x10 >= save_43.GetSize())
|
||||
if (!save_43.Seek(offset + 0x10, SEEK_SET)) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
Key128 seed{};
|
||||
save_43.Seek(offset + 0x10, SEEK_SET);
|
||||
save_43.ReadBytes(seed.data(), seed.size());
|
||||
if (save_43.ReadBytes(seed.data(), seed.size()) != seed.size()) {
|
||||
return boost::none;
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
|
||||
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys) {
|
||||
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK)))
|
||||
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys) {
|
||||
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek)))
|
||||
return Loader::ResultStatus::ErrorMissingSDKEKSource;
|
||||
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration)))
|
||||
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)))
|
||||
return Loader::ResultStatus::ErrorMissingAESKEKGenerationSource;
|
||||
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)))
|
||||
return Loader::ResultStatus::ErrorMissingAESKeyGenerationSource;
|
||||
|
||||
const auto sd_kek_source =
|
||||
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK));
|
||||
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek));
|
||||
const auto aes_kek_gen =
|
||||
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration));
|
||||
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration));
|
||||
const auto aes_key_gen =
|
||||
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration));
|
||||
const auto master_00 = keys.GetKey(S128KeyType::Master);
|
||||
const auto sd_kek =
|
||||
GenerateKeyEncryptionKey(sd_kek_source, master_00, aes_kek_gen, aes_key_gen);
|
||||
keys.SetKey(S128KeyType::SDKek, sd_kek);
|
||||
|
||||
if (!keys.HasKey(S128KeyType::SDSeed))
|
||||
return Loader::ResultStatus::ErrorMissingSDSeed;
|
||||
@@ -118,9 +230,147 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManag
|
||||
return source; ///< Return unaltered source to satisfy output requirement.
|
||||
});
|
||||
|
||||
keys.SetKey(S256KeyType::SDKey, sd_keys[0], static_cast<u64>(SDKeyType::Save));
|
||||
keys.SetKey(S256KeyType::SDKey, sd_keys[1], static_cast<u64>(SDKeyType::NCA));
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) {
|
||||
if (!ticket_save.IsOpen())
|
||||
return {};
|
||||
|
||||
std::vector<u8> buffer(ticket_save.GetSize());
|
||||
if (ticket_save.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<TicketRaw> out;
|
||||
u32 magic{};
|
||||
for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) {
|
||||
if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 &&
|
||||
buffer[offset + 3] == 0x0) {
|
||||
out.emplace_back();
|
||||
auto& next = out.back();
|
||||
std::memcpy(&next, buffer.data() + offset, sizeof(TicketRaw));
|
||||
offset += next.size();
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
template <size_t size>
|
||||
static std::array<u8, size> operator^(const std::array<u8, size>& lhs,
|
||||
const std::array<u8, size>& rhs) {
|
||||
std::array<u8, size> out{};
|
||||
std::transform(lhs.begin(), lhs.end(), rhs.begin(), out.begin(), std::bit_xor<>());
|
||||
return out;
|
||||
}
|
||||
|
||||
template <size_t target_size, size_t in_size>
|
||||
static std::array<u8, target_size> MGF1(const std::array<u8, in_size>& seed) {
|
||||
// Avoids truncation overflow within the loop below.
|
||||
static_assert(target_size <= 0xFF);
|
||||
|
||||
std::array<u8, in_size + 4> seed_exp{};
|
||||
std::memcpy(seed_exp.data(), seed.data(), in_size);
|
||||
|
||||
std::vector<u8> out;
|
||||
size_t i = 0;
|
||||
while (out.size() < target_size) {
|
||||
out.resize(out.size() + 0x20);
|
||||
seed_exp[in_size + 3] = static_cast<u8>(i);
|
||||
mbedtls_sha256(seed_exp.data(), seed_exp.size(), out.data() + out.size() - 0x20, 0);
|
||||
++i;
|
||||
}
|
||||
|
||||
std::array<u8, target_size> target;
|
||||
std::memcpy(target.data(), out.data(), target_size);
|
||||
return target;
|
||||
}
|
||||
|
||||
template <size_t size>
|
||||
static boost::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
|
||||
u64 offset = 0;
|
||||
for (size_t i = 0x20; i < data.size() - 0x10; ++i) {
|
||||
if (data[i] == 0x1) {
|
||||
offset = i + 1;
|
||||
break;
|
||||
} else if (data[i] != 0x0) {
|
||||
return boost::none;
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
boost::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
|
||||
const RSAKeyPair<2048>& key) {
|
||||
u32 cert_authority;
|
||||
std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority));
|
||||
if (cert_authority == 0)
|
||||
return boost::none;
|
||||
if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't')) {
|
||||
LOG_INFO(Crypto,
|
||||
"Attempting to parse ticket with non-standard certificate authority {:08X}.",
|
||||
cert_authority);
|
||||
}
|
||||
|
||||
Key128 rights_id;
|
||||
std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128));
|
||||
|
||||
if (rights_id == Key128{})
|
||||
return boost::none;
|
||||
|
||||
Key128 key_temp{};
|
||||
|
||||
if (!std::any_of(ticket.begin() + 0x190, ticket.begin() + 0x280, [](u8 b) { return b != 0; })) {
|
||||
std::memcpy(key_temp.data(), ticket.data() + 0x180, key_temp.size());
|
||||
return std::make_pair(rights_id, key_temp);
|
||||
}
|
||||
|
||||
mbedtls_mpi D; // RSA Private Exponent
|
||||
mbedtls_mpi N; // RSA Modulus
|
||||
mbedtls_mpi S; // Input
|
||||
mbedtls_mpi M; // Output
|
||||
|
||||
mbedtls_mpi_init(&D);
|
||||
mbedtls_mpi_init(&N);
|
||||
mbedtls_mpi_init(&S);
|
||||
mbedtls_mpi_init(&M);
|
||||
|
||||
mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size());
|
||||
mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size());
|
||||
mbedtls_mpi_read_binary(&S, ticket.data() + 0x180, 0x100);
|
||||
|
||||
mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr);
|
||||
|
||||
std::array<u8, 0x100> rsa_step;
|
||||
mbedtls_mpi_write_binary(&M, rsa_step.data(), rsa_step.size());
|
||||
|
||||
u8 m_0 = rsa_step[0];
|
||||
std::array<u8, 0x20> m_1;
|
||||
std::memcpy(m_1.data(), rsa_step.data() + 0x01, m_1.size());
|
||||
std::array<u8, 0xDF> m_2;
|
||||
std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size());
|
||||
|
||||
if (m_0 != 0)
|
||||
return boost::none;
|
||||
|
||||
m_1 = m_1 ^ MGF1<0x20>(m_2);
|
||||
m_2 = m_2 ^ MGF1<0xDF>(m_1);
|
||||
|
||||
const auto offset = FindTicketOffset(m_2);
|
||||
if (offset == boost::none)
|
||||
return boost::none;
|
||||
ASSERT(offset.get() > 0);
|
||||
|
||||
std::memcpy(key_temp.data(), m_2.data() + offset.get(), key_temp.size());
|
||||
|
||||
return std::make_pair(rights_id, key_temp);
|
||||
}
|
||||
|
||||
KeyManager::KeyManager() {
|
||||
// Initialize keys
|
||||
const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
|
||||
@@ -137,6 +387,15 @@ KeyManager::KeyManager() {
|
||||
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true);
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "title.keys_autogenerated", true);
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "console.keys", false);
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "console.keys_autogenerated", false);
|
||||
}
|
||||
|
||||
static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) {
|
||||
if (base.size() < begin + length)
|
||||
return false;
|
||||
return std::all_of(base.begin() + begin, base.begin() + begin + length,
|
||||
[](u8 c) { return std::isdigit(c); });
|
||||
}
|
||||
|
||||
void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
|
||||
@@ -158,6 +417,9 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
|
||||
out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end());
|
||||
out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end());
|
||||
|
||||
if (out[0].compare(0, 1, "#") == 0)
|
||||
continue;
|
||||
|
||||
if (is_title_keys) {
|
||||
auto rights_id_raw = Common::HexStringToArray<16>(out[0]);
|
||||
u128 rights_id{};
|
||||
@@ -174,6 +436,50 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
|
||||
const auto index = s256_file_id.at(out[0]);
|
||||
Key256 key = Common::HexStringToArray<32>(out[1]);
|
||||
s256_keys[{index.type, index.field1, index.field2}] = key;
|
||||
} else if (out[0].compare(0, 8, "keyblob_") == 0 &&
|
||||
out[0].compare(0, 9, "keyblob_k") != 0) {
|
||||
if (!ValidCryptoRevisionString(out[0], 8, 2))
|
||||
continue;
|
||||
|
||||
const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16);
|
||||
keyblobs[index] = Common::HexStringToArray<0x90>(out[1]);
|
||||
} else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) {
|
||||
if (!ValidCryptoRevisionString(out[0], 18, 2))
|
||||
continue;
|
||||
|
||||
const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16);
|
||||
encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
|
||||
} else {
|
||||
for (const auto& kv : KEYS_VARIABLE_LENGTH) {
|
||||
if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2))
|
||||
continue;
|
||||
if (out[0].compare(0, kv.second.size(), kv.second) == 0) {
|
||||
const auto index =
|
||||
std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16);
|
||||
const auto sub = kv.first.second;
|
||||
if (sub == 0) {
|
||||
s128_keys[{kv.first.first, index, 0}] =
|
||||
Common::HexStringToArray<16>(out[1]);
|
||||
} else {
|
||||
s128_keys[{kv.first.first, kv.first.second, index}] =
|
||||
Common::HexStringToArray<16>(out[1]);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr std::array<const char*, 3> kak_names = {
|
||||
"key_area_key_application_", "key_area_key_ocean_", "key_area_key_system_"};
|
||||
for (size_t j = 0; j < kak_names.size(); ++j) {
|
||||
const auto& match = kak_names[j];
|
||||
if (out[0].compare(0, std::strlen(match), match) == 0) {
|
||||
const auto index =
|
||||
std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16);
|
||||
s128_keys[{S128KeyType::KeyArea, index, j}] =
|
||||
Common::HexStringToArray<16>(out[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,6 +493,28 @@ void KeyManager::AttemptLoadKeyFile(const std::string& dir1, const std::string&
|
||||
LoadFromFile(dir2 + DIR_SEP + filename, title);
|
||||
}
|
||||
|
||||
bool KeyManager::BaseDeriveNecessary() const {
|
||||
const auto check_key_existence = [this](auto key_type, u64 index1 = 0, u64 index2 = 0) {
|
||||
return !HasKey(key_type, index1, index2);
|
||||
};
|
||||
|
||||
if (check_key_existence(S256KeyType::Header))
|
||||
return true;
|
||||
|
||||
for (size_t i = 0; i < CURRENT_CRYPTO_REVISION; ++i) {
|
||||
if (check_key_existence(S128KeyType::Master, i) ||
|
||||
check_key_existence(S128KeyType::KeyArea, i,
|
||||
static_cast<u64>(KeyAreaKeyType::Application)) ||
|
||||
check_key_existence(S128KeyType::KeyArea, i, static_cast<u64>(KeyAreaKeyType::Ocean)) ||
|
||||
check_key_existence(S128KeyType::KeyArea, i,
|
||||
static_cast<u64>(KeyAreaKeyType::System)) ||
|
||||
check_key_existence(S128KeyType::Titlekek, i))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) const {
|
||||
return s128_keys.find({id, field1, field2}) != s128_keys.end();
|
||||
}
|
||||
@@ -207,13 +535,30 @@ Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const {
|
||||
return s256_keys.at({id, field1, field2});
|
||||
}
|
||||
|
||||
template <std::size_t Size>
|
||||
void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname,
|
||||
Key256 KeyManager::GetBISKey(u8 partition_id) const {
|
||||
Key256 out{};
|
||||
|
||||
for (const auto& bis_type : {BISKeyType::Crypto, BISKeyType::Tweak}) {
|
||||
if (HasKey(S128KeyType::BIS, partition_id, static_cast<u64>(bis_type))) {
|
||||
std::memcpy(
|
||||
out.data() + sizeof(Key128) * static_cast<u64>(bis_type),
|
||||
s128_keys.at({S128KeyType::BIS, partition_id, static_cast<u64>(bis_type)}).data(),
|
||||
sizeof(Key128));
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
template <size_t Size>
|
||||
void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
|
||||
const std::array<u8, Size>& key) {
|
||||
const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
|
||||
std::string filename = "title.keys_autogenerated";
|
||||
if (!title_key)
|
||||
if (category == KeyCategory::Standard)
|
||||
filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated";
|
||||
else if (category == KeyCategory::Console)
|
||||
filename = "console.keys_autogenerated";
|
||||
const auto add_info_text = !FileUtil::Exists(yuzu_keys_dir + DIR_SEP + filename);
|
||||
FileUtil::CreateFullPath(yuzu_keys_dir + DIR_SEP + filename);
|
||||
std::ofstream file(yuzu_keys_dir + DIR_SEP + filename, std::ios::app);
|
||||
@@ -227,7 +572,7 @@ void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname,
|
||||
}
|
||||
|
||||
file << fmt::format("\n{} = {}", keyname, Common::HexArrayToString(key));
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, title_key);
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, category == KeyCategory::Title);
|
||||
}
|
||||
|
||||
void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
|
||||
@@ -237,8 +582,15 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
|
||||
Key128 rights_id;
|
||||
std::memcpy(rights_id.data(), &field2, sizeof(u64));
|
||||
std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64));
|
||||
WriteKeyToFile(true, Common::HexArrayToString(rights_id), key);
|
||||
WriteKeyToFile(KeyCategory::Title, Common::HexArrayToString(rights_id), key);
|
||||
}
|
||||
|
||||
auto category = KeyCategory::Standard;
|
||||
if (id == S128KeyType::Keyblob || id == S128KeyType::KeyblobMAC || id == S128KeyType::TSEC ||
|
||||
id == S128KeyType::SecureBoot || id == S128KeyType::SDSeed || id == S128KeyType::BIS) {
|
||||
category = KeyCategory::Console;
|
||||
}
|
||||
|
||||
const auto iter2 = std::find_if(
|
||||
s128_file_id.begin(), s128_file_id.end(),
|
||||
[&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) {
|
||||
@@ -246,7 +598,30 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
|
||||
std::tie(id, field1, field2);
|
||||
});
|
||||
if (iter2 != s128_file_id.end())
|
||||
WriteKeyToFile(false, iter2->first, key);
|
||||
WriteKeyToFile(category, iter2->first, key);
|
||||
|
||||
// Variable cases
|
||||
if (id == S128KeyType::KeyArea) {
|
||||
static constexpr std::array<const char*, 3> kak_names = {"key_area_key_application_{:02X}",
|
||||
"key_area_key_ocean_{:02X}",
|
||||
"key_area_key_system_{:02X}"};
|
||||
WriteKeyToFile(category, fmt::format(kak_names.at(field2), field1), key);
|
||||
} else if (id == S128KeyType::Master) {
|
||||
WriteKeyToFile(category, fmt::format("master_key_{:02X}", field1), key);
|
||||
} else if (id == S128KeyType::Package1) {
|
||||
WriteKeyToFile(category, fmt::format("package1_key_{:02X}", field1), key);
|
||||
} else if (id == S128KeyType::Package2) {
|
||||
WriteKeyToFile(category, fmt::format("package2_key_{:02X}", field1), key);
|
||||
} else if (id == S128KeyType::Titlekek) {
|
||||
WriteKeyToFile(category, fmt::format("titlekek_{:02X}", field1), key);
|
||||
} else if (id == S128KeyType::Keyblob) {
|
||||
WriteKeyToFile(category, fmt::format("keyblob_key_{:02X}", field1), key);
|
||||
} else if (id == S128KeyType::KeyblobMAC) {
|
||||
WriteKeyToFile(category, fmt::format("keyblob_mac_key_{:02X}", field1), key);
|
||||
} else if (id == S128KeyType::Source && field1 == static_cast<u64>(SourceKeyType::Keyblob)) {
|
||||
WriteKeyToFile(category, fmt::format("keyblob_key_source_{:02X}", field2), key);
|
||||
}
|
||||
|
||||
s128_keys[{id, field1, field2}] = key;
|
||||
}
|
||||
|
||||
@@ -260,7 +635,7 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
|
||||
std::tie(id, field1, field2);
|
||||
});
|
||||
if (iter != s256_file_id.end())
|
||||
WriteKeyToFile(false, iter->first, key);
|
||||
WriteKeyToFile(KeyCategory::Standard, iter->first, key);
|
||||
s256_keys[{id, field1, field2}] = key;
|
||||
}
|
||||
|
||||
@@ -290,59 +665,388 @@ void KeyManager::DeriveSDSeedLazy() {
|
||||
SetKey(S128KeyType::SDSeed, res.get());
|
||||
}
|
||||
|
||||
static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) {
|
||||
Key128 out{};
|
||||
|
||||
mbedtls_cipher_cmac(mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB), key.data(),
|
||||
key.size() * 8, source, size, out.data());
|
||||
return out;
|
||||
}
|
||||
|
||||
void KeyManager::DeriveBase() {
|
||||
if (!BaseDeriveNecessary())
|
||||
return;
|
||||
|
||||
if (!HasKey(S128KeyType::SecureBoot) || !HasKey(S128KeyType::TSEC))
|
||||
return;
|
||||
|
||||
const auto has_bis = [this](u64 id) {
|
||||
return HasKey(S128KeyType::BIS, id, static_cast<u64>(BISKeyType::Crypto)) &&
|
||||
HasKey(S128KeyType::BIS, id, static_cast<u64>(BISKeyType::Tweak));
|
||||
};
|
||||
|
||||
const auto copy_bis = [this](u64 id_from, u64 id_to) {
|
||||
SetKey(S128KeyType::BIS,
|
||||
GetKey(S128KeyType::BIS, id_from, static_cast<u64>(BISKeyType::Crypto)), id_to,
|
||||
static_cast<u64>(BISKeyType::Crypto));
|
||||
|
||||
SetKey(S128KeyType::BIS,
|
||||
GetKey(S128KeyType::BIS, id_from, static_cast<u64>(BISKeyType::Tweak)), id_to,
|
||||
static_cast<u64>(BISKeyType::Tweak));
|
||||
};
|
||||
|
||||
if (has_bis(2) && !has_bis(3))
|
||||
copy_bis(2, 3);
|
||||
else if (has_bis(3) && !has_bis(2))
|
||||
copy_bis(3, 2);
|
||||
|
||||
std::bitset<32> revisions(0xFFFFFFFF);
|
||||
for (size_t i = 0; i < revisions.size(); ++i) {
|
||||
if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob), i) ||
|
||||
encrypted_keyblobs[i] == std::array<u8, 0xB0>{}) {
|
||||
revisions.reset(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (!revisions.any())
|
||||
return;
|
||||
|
||||
const auto sbk = GetKey(S128KeyType::SecureBoot);
|
||||
const auto tsec = GetKey(S128KeyType::TSEC);
|
||||
const auto master_source = GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Master));
|
||||
|
||||
for (size_t i = 0; i < revisions.size(); ++i) {
|
||||
if (!revisions[i])
|
||||
continue;
|
||||
|
||||
// Derive keyblob key
|
||||
const auto key = DeriveKeyblobKey(
|
||||
sbk, tsec, GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob), i));
|
||||
|
||||
SetKey(S128KeyType::Keyblob, key, i);
|
||||
|
||||
// Derive keyblob MAC key
|
||||
if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)))
|
||||
continue;
|
||||
|
||||
const auto mac_key = DeriveKeyblobMACKey(
|
||||
key, GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)));
|
||||
SetKey(S128KeyType::KeyblobMAC, mac_key, i);
|
||||
|
||||
Key128 cmac = CalculateCMAC(encrypted_keyblobs[i].data() + 0x10, 0xA0, mac_key);
|
||||
if (std::memcmp(cmac.data(), encrypted_keyblobs[i].data(), cmac.size()) != 0)
|
||||
continue;
|
||||
|
||||
// Decrypt keyblob
|
||||
if (keyblobs[i] == std::array<u8, 0x90>{}) {
|
||||
keyblobs[i] = DecryptKeyblob(encrypted_keyblobs[i], key);
|
||||
WriteKeyToFile<0x90>(KeyCategory::Console, fmt::format("keyblob_{:02X}", i),
|
||||
keyblobs[i]);
|
||||
}
|
||||
|
||||
Key128 package1;
|
||||
std::memcpy(package1.data(), keyblobs[i].data() + 0x80, sizeof(Key128));
|
||||
SetKey(S128KeyType::Package1, package1, i);
|
||||
|
||||
// Derive master key
|
||||
if (HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Master))) {
|
||||
SetKey(S128KeyType::Master,
|
||||
DeriveMasterKey(keyblobs[i], GetKey(S128KeyType::Source,
|
||||
static_cast<u64>(SourceKeyType::Master))),
|
||||
i);
|
||||
}
|
||||
}
|
||||
|
||||
revisions.set();
|
||||
for (size_t i = 0; i < revisions.size(); ++i) {
|
||||
if (!HasKey(S128KeyType::Master, i))
|
||||
revisions.reset(i);
|
||||
}
|
||||
|
||||
if (!revisions.any())
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < revisions.size(); ++i) {
|
||||
if (!revisions[i])
|
||||
continue;
|
||||
|
||||
// Derive general purpose keys
|
||||
DeriveGeneralPurposeKeys(i);
|
||||
}
|
||||
|
||||
if (HasKey(S128KeyType::Master, 0) &&
|
||||
HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)) &&
|
||||
HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)) &&
|
||||
HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek)) &&
|
||||
HasKey(S256KeyType::HeaderSource)) {
|
||||
const auto header_kek = GenerateKeyEncryptionKey(
|
||||
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek)),
|
||||
GetKey(S128KeyType::Master, 0),
|
||||
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)),
|
||||
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)));
|
||||
SetKey(S128KeyType::HeaderKek, header_kek);
|
||||
|
||||
AESCipher<Key128> header_cipher(header_kek, Mode::ECB);
|
||||
Key256 out = GetKey(S256KeyType::HeaderSource);
|
||||
header_cipher.Transcode(out.data(), out.size(), out.data(), Op::Decrypt);
|
||||
SetKey(S256KeyType::Header, out);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyManager::DeriveETicket(PartitionDataManager& data) {
|
||||
// ETicket keys
|
||||
const auto es = Service::FileSystem::GetUnionContents()->GetEntry(
|
||||
0x0100000000000033, FileSys::ContentRecordType::Program);
|
||||
|
||||
if (es == nullptr)
|
||||
return;
|
||||
|
||||
const auto exefs = es->GetExeFS();
|
||||
if (exefs == nullptr)
|
||||
return;
|
||||
|
||||
const auto main = exefs->GetFile("main");
|
||||
if (main == nullptr)
|
||||
return;
|
||||
|
||||
const auto bytes = main->ReadAllBytes();
|
||||
|
||||
const auto eticket_kek = FindKeyFromHex16(bytes, eticket_source_hashes[0]);
|
||||
const auto eticket_kekek = FindKeyFromHex16(bytes, eticket_source_hashes[1]);
|
||||
|
||||
const auto seed3 = data.GetRSAKekSeed3();
|
||||
const auto mask0 = data.GetRSAKekMask0();
|
||||
|
||||
if (eticket_kek != Key128{})
|
||||
SetKey(S128KeyType::Source, eticket_kek, static_cast<size_t>(SourceKeyType::ETicketKek));
|
||||
if (eticket_kekek != Key128{}) {
|
||||
SetKey(S128KeyType::Source, eticket_kekek,
|
||||
static_cast<size_t>(SourceKeyType::ETicketKekek));
|
||||
}
|
||||
if (seed3 != Key128{})
|
||||
SetKey(S128KeyType::RSAKek, seed3, static_cast<size_t>(RSAKekType::Seed3));
|
||||
if (mask0 != Key128{})
|
||||
SetKey(S128KeyType::RSAKek, mask0, static_cast<size_t>(RSAKekType::Mask0));
|
||||
if (eticket_kek == Key128{} || eticket_kekek == Key128{} || seed3 == Key128{} ||
|
||||
mask0 == Key128{}) {
|
||||
return;
|
||||
}
|
||||
|
||||
Key128 rsa_oaep_kek{};
|
||||
std::transform(seed3.begin(), seed3.end(), mask0.begin(), rsa_oaep_kek.begin(),
|
||||
std::bit_xor<>());
|
||||
|
||||
if (rsa_oaep_kek == Key128{})
|
||||
return;
|
||||
|
||||
SetKey(S128KeyType::Source, rsa_oaep_kek,
|
||||
static_cast<u64>(SourceKeyType::RSAOaepKekGeneration));
|
||||
|
||||
Key128 temp_kek{};
|
||||
Key128 temp_kekek{};
|
||||
Key128 eticket_final{};
|
||||
|
||||
// Derive ETicket RSA Kek
|
||||
AESCipher<Key128> es_master(GetKey(S128KeyType::Master), Mode::ECB);
|
||||
es_master.Transcode(rsa_oaep_kek.data(), rsa_oaep_kek.size(), temp_kek.data(), Op::Decrypt);
|
||||
AESCipher<Key128> es_kekek(temp_kek, Mode::ECB);
|
||||
es_kekek.Transcode(eticket_kekek.data(), eticket_kekek.size(), temp_kekek.data(), Op::Decrypt);
|
||||
AESCipher<Key128> es_kek(temp_kekek, Mode::ECB);
|
||||
es_kek.Transcode(eticket_kek.data(), eticket_kek.size(), eticket_final.data(), Op::Decrypt);
|
||||
|
||||
if (eticket_final == Key128{})
|
||||
return;
|
||||
|
||||
SetKey(S128KeyType::ETicketRSAKek, eticket_final);
|
||||
|
||||
// Titlekeys
|
||||
data.DecryptProdInfo(GetBISKey(0));
|
||||
|
||||
const auto eticket_extended_kek = data.GetETicketExtendedKek();
|
||||
|
||||
std::vector<u8> extended_iv(0x10);
|
||||
std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size());
|
||||
std::array<u8, 0x230> extended_dec{};
|
||||
AESCipher<Key128> rsa_1(eticket_final, Mode::CTR);
|
||||
rsa_1.SetIV(extended_iv);
|
||||
rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
|
||||
extended_dec.data(), Op::Decrypt);
|
||||
|
||||
RSAKeyPair<2048> rsa_key{};
|
||||
std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size());
|
||||
std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size());
|
||||
std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size());
|
||||
|
||||
const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||
"/system/save/80000000000000e1",
|
||||
"rb+");
|
||||
const FileUtil::IOFile save2(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||
"/system/save/80000000000000e2",
|
||||
"rb+");
|
||||
|
||||
const auto blob2 = GetTicketblob(save2);
|
||||
auto res = GetTicketblob(save1);
|
||||
res.insert(res.end(), blob2.begin(), blob2.end());
|
||||
|
||||
for (const auto& raw : res) {
|
||||
const auto pair = ParseTicket(raw, rsa_key);
|
||||
if (pair == boost::none)
|
||||
continue;
|
||||
const auto& [rid, key] = pair.value();
|
||||
u128 rights_id;
|
||||
std::memcpy(rights_id.data(), rid.data(), rid.size());
|
||||
SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) {
|
||||
if (key == Key128{})
|
||||
return;
|
||||
SetKey(id, key, field1, field2);
|
||||
}
|
||||
|
||||
void KeyManager::SetKeyWrapped(S256KeyType id, Key256 key, u64 field1, u64 field2) {
|
||||
if (key == Key256{})
|
||||
return;
|
||||
SetKey(id, key, field1, field2);
|
||||
}
|
||||
|
||||
void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
|
||||
if (!BaseDeriveNecessary())
|
||||
return;
|
||||
|
||||
if (!data.HasBoot0())
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < encrypted_keyblobs.size(); ++i) {
|
||||
if (encrypted_keyblobs[i] != std::array<u8, 0xB0>{})
|
||||
continue;
|
||||
encrypted_keyblobs[i] = data.GetEncryptedKeyblob(i);
|
||||
WriteKeyToFile<0xB0>(KeyCategory::Console, fmt::format("encrypted_keyblob_{:02X}", i),
|
||||
encrypted_keyblobs[i]);
|
||||
}
|
||||
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetPackage2KeySource(),
|
||||
static_cast<u64>(SourceKeyType::Package2));
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetAESKekGenerationSource(),
|
||||
static_cast<u64>(SourceKeyType::AESKekGeneration));
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetTitlekekSource(),
|
||||
static_cast<u64>(SourceKeyType::Titlekek));
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetMasterKeySource(),
|
||||
static_cast<u64>(SourceKeyType::Master));
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetKeyblobMACKeySource(),
|
||||
static_cast<u64>(SourceKeyType::KeyblobMAC));
|
||||
|
||||
for (size_t i = 0; i < PartitionDataManager::MAX_KEYBLOB_SOURCE_HASH; ++i) {
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetKeyblobKeySource(i),
|
||||
static_cast<u64>(SourceKeyType::Keyblob), i);
|
||||
}
|
||||
|
||||
if (data.HasFuses())
|
||||
SetKeyWrapped(S128KeyType::SecureBoot, data.GetSecureBootKey());
|
||||
|
||||
DeriveBase();
|
||||
|
||||
Key128 latest_master{};
|
||||
for (s8 i = 0x1F; i >= 0; --i) {
|
||||
if (GetKey(S128KeyType::Master, static_cast<u8>(i)) != Key128{}) {
|
||||
latest_master = GetKey(S128KeyType::Master, static_cast<u8>(i));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const auto masters = data.GetTZMasterKeys(latest_master);
|
||||
for (size_t i = 0; i < masters.size(); ++i) {
|
||||
if (masters[i] != Key128{} && !HasKey(S128KeyType::Master, i))
|
||||
SetKey(S128KeyType::Master, masters[i], i);
|
||||
}
|
||||
|
||||
DeriveBase();
|
||||
|
||||
if (!data.HasPackage2())
|
||||
return;
|
||||
|
||||
std::array<Key128, 0x20> package2_keys{};
|
||||
for (size_t i = 0; i < package2_keys.size(); ++i) {
|
||||
if (HasKey(S128KeyType::Package2, i))
|
||||
package2_keys[i] = GetKey(S128KeyType::Package2, i);
|
||||
}
|
||||
data.DecryptPackage2(package2_keys, Package2Type::NormalMain);
|
||||
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeyApplicationSource(),
|
||||
static_cast<u64>(SourceKeyType::KeyAreaKey),
|
||||
static_cast<u64>(KeyAreaKeyType::Application));
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeyOceanSource(),
|
||||
static_cast<u64>(SourceKeyType::KeyAreaKey),
|
||||
static_cast<u64>(KeyAreaKeyType::Ocean));
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeySystemSource(),
|
||||
static_cast<u64>(SourceKeyType::KeyAreaKey),
|
||||
static_cast<u64>(KeyAreaKeyType::System));
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetSDKekSource(),
|
||||
static_cast<u64>(SourceKeyType::SDKek));
|
||||
SetKeyWrapped(S256KeyType::SDKeySource, data.GetSDSaveKeySource(),
|
||||
static_cast<u64>(SDKeyType::Save));
|
||||
SetKeyWrapped(S256KeyType::SDKeySource, data.GetSDNCAKeySource(),
|
||||
static_cast<u64>(SDKeyType::NCA));
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetHeaderKekSource(),
|
||||
static_cast<u64>(SourceKeyType::HeaderKek));
|
||||
SetKeyWrapped(S256KeyType::HeaderSource, data.GetHeaderKeySource());
|
||||
SetKeyWrapped(S128KeyType::Source, data.GetAESKeyGenerationSource(),
|
||||
static_cast<u64>(SourceKeyType::AESKeyGeneration));
|
||||
|
||||
DeriveBase();
|
||||
}
|
||||
|
||||
const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
|
||||
{"master_key_00", {S128KeyType::Master, 0, 0}},
|
||||
{"master_key_01", {S128KeyType::Master, 1, 0}},
|
||||
{"master_key_02", {S128KeyType::Master, 2, 0}},
|
||||
{"master_key_03", {S128KeyType::Master, 3, 0}},
|
||||
{"master_key_04", {S128KeyType::Master, 4, 0}},
|
||||
{"package1_key_00", {S128KeyType::Package1, 0, 0}},
|
||||
{"package1_key_01", {S128KeyType::Package1, 1, 0}},
|
||||
{"package1_key_02", {S128KeyType::Package1, 2, 0}},
|
||||
{"package1_key_03", {S128KeyType::Package1, 3, 0}},
|
||||
{"package1_key_04", {S128KeyType::Package1, 4, 0}},
|
||||
{"package2_key_00", {S128KeyType::Package2, 0, 0}},
|
||||
{"package2_key_01", {S128KeyType::Package2, 1, 0}},
|
||||
{"package2_key_02", {S128KeyType::Package2, 2, 0}},
|
||||
{"package2_key_03", {S128KeyType::Package2, 3, 0}},
|
||||
{"package2_key_04", {S128KeyType::Package2, 4, 0}},
|
||||
{"titlekek_00", {S128KeyType::Titlekek, 0, 0}},
|
||||
{"titlekek_01", {S128KeyType::Titlekek, 1, 0}},
|
||||
{"titlekek_02", {S128KeyType::Titlekek, 2, 0}},
|
||||
{"titlekek_03", {S128KeyType::Titlekek, 3, 0}},
|
||||
{"titlekek_04", {S128KeyType::Titlekek, 4, 0}},
|
||||
{"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
|
||||
{"key_area_key_application_00",
|
||||
{S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_application_01",
|
||||
{S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_application_02",
|
||||
{S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_application_03",
|
||||
{S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_application_04",
|
||||
{S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_ocean_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_ocean_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_ocean_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_ocean_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_ocean_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_system_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"key_area_key_system_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"key_area_key_system_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"key_area_key_system_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"key_area_key_system_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK), 0}},
|
||||
{"eticket_rsa_kek_source",
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKek), 0}},
|
||||
{"eticket_rsa_kekek_source",
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKekek), 0}},
|
||||
{"rsa_kek_mask_0", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Mask0), 0}},
|
||||
{"rsa_kek_seed_3", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Seed3), 0}},
|
||||
{"rsa_oaep_kek_generation_source",
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::RSAOaepKekGeneration), 0}},
|
||||
{"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek), 0}},
|
||||
{"aes_kek_generation_source",
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration), 0}},
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration), 0}},
|
||||
{"aes_key_generation_source",
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration), 0}},
|
||||
{"package2_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Package2), 0}},
|
||||
{"master_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Master), 0}},
|
||||
{"header_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek), 0}},
|
||||
{"key_area_key_application_source",
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
|
||||
static_cast<u64>(KeyAreaKeyType::Application)}},
|
||||
{"key_area_key_ocean_source",
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
|
||||
static_cast<u64>(KeyAreaKeyType::Ocean)}},
|
||||
{"key_area_key_system_source",
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
|
||||
static_cast<u64>(KeyAreaKeyType::System)}},
|
||||
{"titlekek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Titlekek), 0}},
|
||||
{"keyblob_mac_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)}},
|
||||
{"tsec_key", {S128KeyType::TSEC, 0, 0}},
|
||||
{"secure_boot_key", {S128KeyType::SecureBoot, 0, 0}},
|
||||
{"sd_seed", {S128KeyType::SDSeed, 0, 0}},
|
||||
{"bis_key_0_crypt", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Crypto)}},
|
||||
{"bis_key_0_tweak", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Tweak)}},
|
||||
{"bis_key_1_crypt", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Crypto)}},
|
||||
{"bis_key_1_tweak", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Tweak)}},
|
||||
{"bis_key_2_crypt", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Crypto)}},
|
||||
{"bis_key_2_tweak", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Tweak)}},
|
||||
{"bis_key_3_crypt", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Crypto)}},
|
||||
{"bis_key_3_tweak", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Tweak)}},
|
||||
{"header_kek", {S128KeyType::HeaderKek, 0, 0}},
|
||||
{"sd_card_kek", {S128KeyType::SDKek, 0, 0}},
|
||||
};
|
||||
|
||||
const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {
|
||||
{"header_key", {S256KeyType::Header, 0, 0}},
|
||||
{"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save), 0}},
|
||||
{"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA), 0}},
|
||||
{"header_key_source", {S256KeyType::HeaderSource, 0, 0}},
|
||||
{"sd_card_save_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::Save), 0}},
|
||||
{"sd_card_nca_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::NCA), 0}},
|
||||
};
|
||||
} // namespace Core::Crypto
|
||||
|
||||
@@ -5,11 +5,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <fmt/format.h>
|
||||
#include "common/common_types.h"
|
||||
#include "core/crypto/partition_data_manager.h"
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
|
||||
namespace FileUtil {
|
||||
class IOFile;
|
||||
}
|
||||
|
||||
namespace Loader {
|
||||
enum class ResultStatus : u16;
|
||||
@@ -22,13 +29,30 @@ constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
|
||||
using Key128 = std::array<u8, 0x10>;
|
||||
using Key256 = std::array<u8, 0x20>;
|
||||
using SHA256Hash = std::array<u8, 0x20>;
|
||||
using TicketRaw = std::array<u8, 0x400>;
|
||||
|
||||
static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
|
||||
static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big.");
|
||||
static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big.");
|
||||
|
||||
template <size_t bit_size, size_t byte_size = (bit_size >> 3)>
|
||||
struct RSAKeyPair {
|
||||
std::array<u8, byte_size> encryption_key;
|
||||
std::array<u8, byte_size> decryption_key;
|
||||
std::array<u8, byte_size> modulus;
|
||||
std::array<u8, 4> exponent;
|
||||
};
|
||||
|
||||
enum class KeyCategory : u8 {
|
||||
Standard,
|
||||
Title,
|
||||
Console,
|
||||
};
|
||||
|
||||
enum class S256KeyType : u64 {
|
||||
Header, //
|
||||
SDKeySource, // f1=SDKeyType
|
||||
SDKey, // f1=SDKeyType
|
||||
Header, //
|
||||
SDKeySource, // f1=SDKeyType
|
||||
HeaderSource, //
|
||||
};
|
||||
|
||||
enum class S128KeyType : u64 {
|
||||
@@ -41,6 +65,14 @@ enum class S128KeyType : u64 {
|
||||
SDSeed, //
|
||||
Titlekey, // f1=rights id LSB f2=rights id MSB
|
||||
Source, // f1=source type, f2= sub id
|
||||
Keyblob, // f1=crypto revision
|
||||
KeyblobMAC, // f1=crypto revision
|
||||
TSEC, //
|
||||
SecureBoot, //
|
||||
BIS, // f1=partition (0-3), f2=type {crypt, tweak}
|
||||
HeaderKek, //
|
||||
SDKek, //
|
||||
RSAKek, //
|
||||
};
|
||||
|
||||
enum class KeyAreaKeyType : u8 {
|
||||
@@ -50,9 +82,19 @@ enum class KeyAreaKeyType : u8 {
|
||||
};
|
||||
|
||||
enum class SourceKeyType : u8 {
|
||||
SDKEK,
|
||||
AESKEKGeneration,
|
||||
AESKeyGeneration,
|
||||
SDKek, //
|
||||
AESKekGeneration, //
|
||||
AESKeyGeneration, //
|
||||
RSAOaepKekGeneration, //
|
||||
Master, //
|
||||
Keyblob, // f2=crypto revision
|
||||
KeyAreaKey, // f2=KeyAreaKeyType
|
||||
Titlekek, //
|
||||
Package2, //
|
||||
HeaderKek, //
|
||||
KeyblobMAC, //
|
||||
ETicketKek, //
|
||||
ETicketKekek, //
|
||||
};
|
||||
|
||||
enum class SDKeyType : u8 {
|
||||
@@ -60,6 +102,16 @@ enum class SDKeyType : u8 {
|
||||
NCA,
|
||||
};
|
||||
|
||||
enum class BISKeyType : u8 {
|
||||
Crypto,
|
||||
Tweak,
|
||||
};
|
||||
|
||||
enum class RSAKekType : u8 {
|
||||
Mask0,
|
||||
Seed3,
|
||||
};
|
||||
|
||||
template <typename KeyType>
|
||||
struct KeyIndex {
|
||||
KeyType type;
|
||||
@@ -91,6 +143,8 @@ public:
|
||||
Key128 GetKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const;
|
||||
Key256 GetKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const;
|
||||
|
||||
Key256 GetBISKey(u8 partition_id) const;
|
||||
|
||||
void SetKey(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
|
||||
void SetKey(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
|
||||
|
||||
@@ -100,23 +154,51 @@ public:
|
||||
// 8*43 and the private file to exist.
|
||||
void DeriveSDSeedLazy();
|
||||
|
||||
bool BaseDeriveNecessary() const;
|
||||
void DeriveBase();
|
||||
void DeriveETicket(PartitionDataManager& data);
|
||||
|
||||
void PopulateFromPartitionData(PartitionDataManager& data);
|
||||
|
||||
private:
|
||||
boost::container::flat_map<KeyIndex<S128KeyType>, Key128> s128_keys;
|
||||
boost::container::flat_map<KeyIndex<S256KeyType>, Key256> s256_keys;
|
||||
std::map<KeyIndex<S128KeyType>, Key128> s128_keys;
|
||||
std::map<KeyIndex<S256KeyType>, Key256> s256_keys;
|
||||
|
||||
std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{};
|
||||
std::array<std::array<u8, 0x90>, 0x20> keyblobs{};
|
||||
|
||||
bool dev_mode;
|
||||
void LoadFromFile(const std::string& filename, bool is_title_keys);
|
||||
void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2,
|
||||
const std::string& filename, bool title);
|
||||
template <std::size_t Size>
|
||||
void WriteKeyToFile(bool title_key, std::string_view keyname, const std::array<u8, Size>& key);
|
||||
template <size_t Size>
|
||||
void WriteKeyToFile(KeyCategory category, std::string_view keyname,
|
||||
const std::array<u8, Size>& key);
|
||||
|
||||
void DeriveGeneralPurposeKeys(std::size_t crypto_revision);
|
||||
|
||||
void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
|
||||
void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
|
||||
|
||||
static const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
|
||||
static const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> s256_file_id;
|
||||
};
|
||||
|
||||
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed);
|
||||
Key128 DeriveKeyblobKey(const Key128& sbk, const Key128& tsec, Key128 source);
|
||||
Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source);
|
||||
Key128 DeriveMasterKey(const std::array<u8, 0x90>& keyblob, const Key128& master_source);
|
||||
std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblob,
|
||||
const Key128& key);
|
||||
|
||||
boost::optional<Key128> DeriveSDSeed();
|
||||
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys);
|
||||
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys);
|
||||
|
||||
std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save);
|
||||
|
||||
// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority (offset
|
||||
// 0x140-0x144 is zero)
|
||||
boost::optional<std::pair<Key128, Key128>> ParseTicket(
|
||||
const TicketRaw& ticket, const RSAKeyPair<2048>& eticket_extended_key);
|
||||
|
||||
} // namespace Core::Crypto
|
||||
|
||||
593
src/core/crypto/partition_data_manager.cpp
Normal file
593
src/core/crypto/partition_data_manager.cpp
Normal file
@@ -0,0 +1,593 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// NOTE TO FUTURE MAINTAINERS:
|
||||
// When a new version of switch cryptography is released,
|
||||
// hash the new keyblob source and master key and add the hashes to
|
||||
// the arrays below.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/crypto/partition_data_manager.h"
|
||||
#include "core/crypto/xts_encryption_layer.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
|
||||
using namespace Common;
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
struct Package2Header {
|
||||
std::array<u8, 0x100> signature;
|
||||
Key128 header_ctr;
|
||||
std::array<Key128, 4> section_ctr;
|
||||
u32_le magic;
|
||||
u32_le base_offset;
|
||||
INSERT_PADDING_BYTES(4);
|
||||
u8 version_max;
|
||||
u8 version_min;
|
||||
INSERT_PADDING_BYTES(2);
|
||||
std::array<u32_le, 4> section_size;
|
||||
std::array<u32_le, 4> section_offset;
|
||||
std::array<SHA256Hash, 4> section_hash;
|
||||
};
|
||||
static_assert(sizeof(Package2Header) == 0x200, "Package2Header has incorrect size.");
|
||||
|
||||
struct INIHeader {
|
||||
u32_le magic;
|
||||
u32_le size;
|
||||
u32_le process_count;
|
||||
INSERT_PADDING_BYTES(4);
|
||||
};
|
||||
static_assert(sizeof(INIHeader) == 0x10, "INIHeader has incorrect size.");
|
||||
|
||||
struct SectionHeader {
|
||||
u32_le offset;
|
||||
u32_le size_decompressed;
|
||||
u32_le size_compressed;
|
||||
u32_le attribute;
|
||||
};
|
||||
static_assert(sizeof(SectionHeader) == 0x10, "SectionHeader has incorrect size.");
|
||||
|
||||
struct KIPHeader {
|
||||
u32_le magic;
|
||||
std::array<char, 12> name;
|
||||
u64_le title_id;
|
||||
u32_le category;
|
||||
u8 priority;
|
||||
u8 core;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
u8 flags;
|
||||
std::array<SectionHeader, 6> sections;
|
||||
std::array<u32, 0x20> capabilities;
|
||||
};
|
||||
static_assert(sizeof(KIPHeader) == 0x100, "KIPHeader has incorrect size.");
|
||||
|
||||
const std::array<SHA256Hash, 0x10> source_hashes{
|
||||
"B24BD293259DBC7AC5D63F88E60C59792498E6FC5443402C7FFE87EE8B61A3F0"_array32, // keyblob_mac_key_source
|
||||
"7944862A3A5C31C6720595EFD302245ABD1B54CCDCF33000557681E65C5664A4"_array32, // master_key_source
|
||||
"21E2DF100FC9E094DB51B47B9B1D6E94ED379DB8B547955BEF8FE08D8DD35603"_array32, // package2_key_source
|
||||
"FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // aes_kek_generation_source
|
||||
"FBD10056999EDC7ACDB96098E47E2C3606230270D23281E671F0F389FC5BC585"_array32, // aes_key_generation_source
|
||||
"C48B619827986C7F4E3081D59DB2B460C84312650E9A8E6B458E53E8CBCA4E87"_array32, // titlekek_source
|
||||
"04AD66143C726B2A139FB6B21128B46F56C553B2B3887110304298D8D0092D9E"_array32, // key_area_key_application_source
|
||||
"FD434000C8FF2B26F8E9A9D2D2C12F6BE5773CBB9DC86300E1BD99F8EA33A417"_array32, // key_area_key_ocean_source
|
||||
"1F17B1FD51AD1C2379B58F152CA4912EC2106441E51722F38700D5937A1162F7"_array32, // key_area_key_system_source
|
||||
"6B2ED877C2C52334AC51E59ABFA7EC457F4A7D01E46291E9F2EAA45F011D24B7"_array32, // sd_card_kek_source
|
||||
"D482743563D3EA5DCDC3B74E97C9AC8A342164FA041A1DC80F17F6D31E4BC01C"_array32, // sd_card_save_key_source
|
||||
"2E751CECF7D93A2B957BD5FFCB082FD038CC2853219DD3092C6DAB9838F5A7CC"_array32, // sd_card_nca_key_source
|
||||
"1888CAED5551B3EDE01499E87CE0D86827F80820EFB275921055AA4E2ABDFFC2"_array32, // header_kek_source
|
||||
"8F783E46852DF6BE0BA4E19273C4ADBAEE16380043E1B8C418C4089A8BD64AA6"_array32, // header_key_source
|
||||
"D1757E52F1AE55FA882EC690BC6F954AC46A83DC22F277F8806BD55577C6EED7"_array32, // rsa_kek_seed3
|
||||
"FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // rsa_kek_mask0
|
||||
};
|
||||
|
||||
const std::array<SHA256Hash, 0x20> keyblob_source_hashes{
|
||||
"8A06FE274AC491436791FDB388BCDD3AB9943BD4DEF8094418CDAC150FD73786"_array32, // keyblob_key_source_00
|
||||
"2D5CAEB2521FEF70B47E17D6D0F11F8CE2C1E442A979AD8035832C4E9FBCCC4B"_array32, // keyblob_key_source_01
|
||||
"61C5005E713BAE780641683AF43E5F5C0E03671117F702F401282847D2FC6064"_array32, // keyblob_key_source_02
|
||||
"8E9795928E1C4428E1B78F0BE724D7294D6934689C11B190943923B9D5B85903"_array32, // keyblob_key_source_03
|
||||
"95FA33AF95AFF9D9B61D164655B32710ED8D615D46C7D6CC3CC70481B686B402"_array32, // keyblob_key_source_04
|
||||
"3F5BE7B3C8B1ABD8C10B4B703D44766BA08730562C172A4FE0D6B866B3E2DB3E"_array32, // keyblob_key_source_05
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_06
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_07
|
||||
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_08
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_09
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0A
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0B
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0C
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0D
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0E
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0F
|
||||
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_10
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_11
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_12
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_13
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_14
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_15
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_16
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_17
|
||||
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_18
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_19
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1A
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1B
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1C
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1D
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1E
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1F
|
||||
};
|
||||
|
||||
const std::array<SHA256Hash, 0x20> master_key_hashes{
|
||||
"0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA"_array32, // master_key_00
|
||||
"4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E"_array32, // master_key_01
|
||||
"79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09"_array32, // master_key_02
|
||||
"4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754"_array32, // master_key_03
|
||||
"75FF1D95D26113550EE6FCC20ACB58E97EDEB3A2FF52543ED5AEC63BDCC3DA50"_array32, // master_key_04
|
||||
"EBE2BCD6704673EC0F88A187BB2AD9F1CC82B718C389425941BDC194DC46B0DD"_array32, // master_key_05
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_06
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_07
|
||||
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_08
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_09
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0A
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0B
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0C
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0D
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0E
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0F
|
||||
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_10
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_11
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_12
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_13
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_14
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_15
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_16
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_17
|
||||
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_18
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_19
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1A
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1B
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1C
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1D
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1E
|
||||
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1F
|
||||
};
|
||||
|
||||
static std::vector<u8> DecompressBLZ(const std::vector<u8>& in) {
|
||||
const auto data_size = in.size() - 0xC;
|
||||
|
||||
u32 compressed_size{};
|
||||
u32 init_index{};
|
||||
u32 additional_size{};
|
||||
std::memcpy(&compressed_size, in.data() + data_size, sizeof(u32));
|
||||
std::memcpy(&init_index, in.data() + data_size + 0x4, sizeof(u32));
|
||||
std::memcpy(&additional_size, in.data() + data_size + 0x8, sizeof(u32));
|
||||
|
||||
std::vector<u8> out(in.size() + additional_size);
|
||||
|
||||
if (compressed_size == in.size())
|
||||
std::memcpy(out.data(), in.data() + in.size() - compressed_size, compressed_size);
|
||||
else
|
||||
std::memcpy(out.data(), in.data(), compressed_size);
|
||||
|
||||
auto index = in.size() - init_index;
|
||||
auto out_index = out.size();
|
||||
|
||||
while (out_index > 0) {
|
||||
--index;
|
||||
auto control = in[index];
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
if ((control & 0x80) > 0) {
|
||||
ASSERT(index >= 2);
|
||||
index -= 2;
|
||||
u64 segment_offset = in[index] | in[index + 1] << 8;
|
||||
u64 segment_size = ((segment_offset >> 12) & 0xF) + 3;
|
||||
segment_offset &= 0xFFF;
|
||||
segment_offset += 3;
|
||||
|
||||
if (out_index < segment_size)
|
||||
segment_size = out_index;
|
||||
|
||||
ASSERT(out_index >= segment_size);
|
||||
|
||||
out_index -= segment_size;
|
||||
|
||||
for (size_t j = 0; j < segment_size; ++j) {
|
||||
ASSERT(out_index + j + segment_offset < out.size());
|
||||
out[out_index + j] = out[out_index + j + segment_offset];
|
||||
}
|
||||
} else {
|
||||
ASSERT(out_index >= 1);
|
||||
--out_index;
|
||||
--index;
|
||||
out[out_index] = in[index];
|
||||
}
|
||||
|
||||
control <<= 1;
|
||||
if (out_index == 0)
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static u8 CalculateMaxKeyblobSourceHash() {
|
||||
for (s8 i = 0x1F; i >= 0; --i) {
|
||||
if (keyblob_source_hashes[i] != SHA256Hash{})
|
||||
return static_cast<u8>(i + 1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const u8 PartitionDataManager::MAX_KEYBLOB_SOURCE_HASH = CalculateMaxKeyblobSourceHash();
|
||||
|
||||
template <size_t key_size = 0x10>
|
||||
std::array<u8, key_size> FindKeyFromHex(const std::vector<u8>& binary,
|
||||
const std::array<u8, 0x20>& hash) {
|
||||
if (binary.size() < key_size)
|
||||
return {};
|
||||
|
||||
std::array<u8, 0x20> temp{};
|
||||
for (size_t i = 0; i < binary.size() - key_size; ++i) {
|
||||
mbedtls_sha256(binary.data() + i, key_size, temp.data(), 0);
|
||||
|
||||
if (temp != hash)
|
||||
continue;
|
||||
|
||||
std::array<u8, key_size> out{};
|
||||
std::memcpy(out.data(), binary.data() + i, key_size);
|
||||
return out;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::array<u8, 16> FindKeyFromHex16(const std::vector<u8>& binary, std::array<u8, 32> hash) {
|
||||
return FindKeyFromHex<0x10>(binary, hash);
|
||||
}
|
||||
|
||||
static std::array<Key128, 0x20> FindEncryptedMasterKeyFromHex(const std::vector<u8>& binary,
|
||||
const Key128& key) {
|
||||
if (binary.size() < 0x10)
|
||||
return {};
|
||||
|
||||
SHA256Hash temp{};
|
||||
Key128 dec_temp{};
|
||||
std::array<Key128, 0x20> out{};
|
||||
AESCipher<Key128> cipher(key, Mode::ECB);
|
||||
for (size_t i = 0; i < binary.size() - 0x10; ++i) {
|
||||
cipher.Transcode(binary.data() + i, dec_temp.size(), dec_temp.data(), Op::Decrypt);
|
||||
mbedtls_sha256(dec_temp.data(), dec_temp.size(), temp.data(), 0);
|
||||
|
||||
for (size_t k = 0; k < out.size(); ++k) {
|
||||
if (temp == master_key_hashes[k]) {
|
||||
out[k] = dec_temp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
FileSys::VirtualFile FindFileInDirWithNames(const FileSys::VirtualDir& dir,
|
||||
const std::string& name) {
|
||||
auto upper = name;
|
||||
std::transform(upper.begin(), upper.end(), upper.begin(), [](u8 c) { return std::toupper(c); });
|
||||
for (const auto& fname : {name, name + ".bin", upper, upper + ".BIN"}) {
|
||||
if (dir->GetFile(fname) != nullptr)
|
||||
return dir->GetFile(fname);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PartitionDataManager::PartitionDataManager(const FileSys::VirtualDir& sysdata_dir)
|
||||
: boot0(FindFileInDirWithNames(sysdata_dir, "BOOT0")),
|
||||
fuses(FindFileInDirWithNames(sysdata_dir, "fuses")),
|
||||
kfuses(FindFileInDirWithNames(sysdata_dir, "kfuses")),
|
||||
package2({
|
||||
FindFileInDirWithNames(sysdata_dir, "BCPKG2-1-Normal-Main"),
|
||||
FindFileInDirWithNames(sysdata_dir, "BCPKG2-2-Normal-Sub"),
|
||||
FindFileInDirWithNames(sysdata_dir, "BCPKG2-3-SafeMode-Main"),
|
||||
FindFileInDirWithNames(sysdata_dir, "BCPKG2-4-SafeMode-Sub"),
|
||||
FindFileInDirWithNames(sysdata_dir, "BCPKG2-5-Repair-Main"),
|
||||
FindFileInDirWithNames(sysdata_dir, "BCPKG2-6-Repair-Sub"),
|
||||
}),
|
||||
prodinfo(FindFileInDirWithNames(sysdata_dir, "PRODINFO")),
|
||||
secure_monitor(FindFileInDirWithNames(sysdata_dir, "secmon")),
|
||||
package1_decrypted(FindFileInDirWithNames(sysdata_dir, "pkg1_decr")),
|
||||
secure_monitor_bytes(secure_monitor == nullptr ? std::vector<u8>{}
|
||||
: secure_monitor->ReadAllBytes()),
|
||||
package1_decrypted_bytes(package1_decrypted == nullptr ? std::vector<u8>{}
|
||||
: package1_decrypted->ReadAllBytes()) {
|
||||
}
|
||||
|
||||
PartitionDataManager::~PartitionDataManager() = default;
|
||||
|
||||
bool PartitionDataManager::HasBoot0() const {
|
||||
return boot0 != nullptr;
|
||||
}
|
||||
|
||||
FileSys::VirtualFile PartitionDataManager::GetBoot0Raw() const {
|
||||
return boot0;
|
||||
}
|
||||
|
||||
PartitionDataManager::EncryptedKeyBlob PartitionDataManager::GetEncryptedKeyblob(
|
||||
std::size_t index) const {
|
||||
if (HasBoot0() && index < NUM_ENCRYPTED_KEYBLOBS)
|
||||
return GetEncryptedKeyblobs()[index];
|
||||
return {};
|
||||
}
|
||||
|
||||
PartitionDataManager::EncryptedKeyBlobs PartitionDataManager::GetEncryptedKeyblobs() const {
|
||||
if (!HasBoot0())
|
||||
return {};
|
||||
|
||||
EncryptedKeyBlobs out{};
|
||||
for (size_t i = 0; i < out.size(); ++i)
|
||||
boot0->Read(out[i].data(), out[i].size(), 0x180000 + i * 0x200);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<u8> PartitionDataManager::GetSecureMonitor() const {
|
||||
return secure_monitor_bytes;
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetPackage2KeySource() const {
|
||||
return FindKeyFromHex(secure_monitor_bytes, source_hashes[2]);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetAESKekGenerationSource() const {
|
||||
return FindKeyFromHex(secure_monitor_bytes, source_hashes[3]);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetTitlekekSource() const {
|
||||
return FindKeyFromHex(secure_monitor_bytes, source_hashes[5]);
|
||||
}
|
||||
|
||||
std::array<std::array<u8, 16>, 32> PartitionDataManager::GetTZMasterKeys(
|
||||
std::array<u8, 0x10> master_key) const {
|
||||
return FindEncryptedMasterKeyFromHex(secure_monitor_bytes, master_key);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetRSAKekSeed3() const {
|
||||
return FindKeyFromHex(secure_monitor_bytes, source_hashes[14]);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetRSAKekMask0() const {
|
||||
return FindKeyFromHex(secure_monitor_bytes, source_hashes[15]);
|
||||
}
|
||||
|
||||
std::vector<u8> PartitionDataManager::GetPackage1Decrypted() const {
|
||||
return package1_decrypted_bytes;
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetMasterKeySource() const {
|
||||
return FindKeyFromHex(package1_decrypted_bytes, source_hashes[1]);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetKeyblobMACKeySource() const {
|
||||
return FindKeyFromHex(package1_decrypted_bytes, source_hashes[0]);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetKeyblobKeySource(std::size_t revision) const {
|
||||
if (keyblob_source_hashes[revision] == SHA256Hash{}) {
|
||||
LOG_WARNING(Crypto,
|
||||
"No keyblob source hash for crypto revision {:02X}! Cannot derive keys...",
|
||||
revision);
|
||||
}
|
||||
return FindKeyFromHex(package1_decrypted_bytes, keyblob_source_hashes[revision]);
|
||||
}
|
||||
|
||||
bool PartitionDataManager::HasFuses() const {
|
||||
return fuses != nullptr;
|
||||
}
|
||||
|
||||
FileSys::VirtualFile PartitionDataManager::GetFusesRaw() const {
|
||||
return fuses;
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetSecureBootKey() const {
|
||||
if (!HasFuses())
|
||||
return {};
|
||||
Key128 out{};
|
||||
fuses->Read(out.data(), out.size(), 0xA4);
|
||||
return out;
|
||||
}
|
||||
|
||||
bool PartitionDataManager::HasKFuses() const {
|
||||
return kfuses != nullptr;
|
||||
}
|
||||
|
||||
FileSys::VirtualFile PartitionDataManager::GetKFusesRaw() const {
|
||||
return kfuses;
|
||||
}
|
||||
|
||||
bool PartitionDataManager::HasPackage2(Package2Type type) const {
|
||||
return package2.at(static_cast<size_t>(type)) != nullptr;
|
||||
}
|
||||
|
||||
FileSys::VirtualFile PartitionDataManager::GetPackage2Raw(Package2Type type) const {
|
||||
return package2.at(static_cast<size_t>(type));
|
||||
}
|
||||
|
||||
bool AttemptDecrypt(const std::array<u8, 16>& key, Package2Header& header) {
|
||||
|
||||
const std::vector<u8> iv(header.header_ctr.begin(), header.header_ctr.end());
|
||||
Package2Header temp = header;
|
||||
AESCipher<Key128> cipher(key, Mode::CTR);
|
||||
cipher.SetIV(iv);
|
||||
cipher.Transcode(&temp.header_ctr, sizeof(Package2Header) - 0x100, &temp.header_ctr,
|
||||
Op::Decrypt);
|
||||
if (temp.magic == Common::MakeMagic('P', 'K', '2', '1')) {
|
||||
header = temp;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void PartitionDataManager::DecryptPackage2(const std::array<Key128, 0x20>& package2_keys,
|
||||
Package2Type type) {
|
||||
FileSys::VirtualFile file = std::make_shared<FileSys::OffsetVfsFile>(
|
||||
package2[static_cast<size_t>(type)],
|
||||
package2[static_cast<size_t>(type)]->GetSize() - 0x4000, 0x4000);
|
||||
|
||||
Package2Header header{};
|
||||
if (file->ReadObject(&header) != sizeof(Package2Header))
|
||||
return;
|
||||
|
||||
std::size_t revision = 0xFF;
|
||||
if (header.magic != Common::MakeMagic('P', 'K', '2', '1')) {
|
||||
for (std::size_t i = 0; i < package2_keys.size(); ++i) {
|
||||
if (AttemptDecrypt(package2_keys[i], header)) {
|
||||
revision = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (header.magic != Common::MakeMagic('P', 'K', '2', '1'))
|
||||
return;
|
||||
|
||||
const auto a = std::make_shared<FileSys::OffsetVfsFile>(
|
||||
file, header.section_size[1], header.section_size[0] + sizeof(Package2Header));
|
||||
|
||||
auto c = a->ReadAllBytes();
|
||||
|
||||
AESCipher<Key128> cipher(package2_keys[revision], Mode::CTR);
|
||||
cipher.SetIV({header.section_ctr[1].begin(), header.section_ctr[1].end()});
|
||||
cipher.Transcode(c.data(), c.size(), c.data(), Op::Decrypt);
|
||||
|
||||
INIHeader ini;
|
||||
std::memcpy(&ini, c.data(), sizeof(INIHeader));
|
||||
if (ini.magic != Common::MakeMagic('I', 'N', 'I', '1'))
|
||||
return;
|
||||
|
||||
u64 offset = sizeof(INIHeader);
|
||||
for (size_t i = 0; i < ini.process_count; ++i) {
|
||||
KIPHeader kip;
|
||||
std::memcpy(&kip, c.data() + offset, sizeof(KIPHeader));
|
||||
if (kip.magic != Common::MakeMagic('K', 'I', 'P', '1'))
|
||||
return;
|
||||
|
||||
const auto name =
|
||||
Common::StringFromFixedZeroTerminatedBuffer(kip.name.data(), kip.name.size());
|
||||
|
||||
if (name != "FS" && name != "spl") {
|
||||
offset += sizeof(KIPHeader) + kip.sections[0].size_compressed +
|
||||
kip.sections[1].size_compressed + kip.sections[2].size_compressed;
|
||||
continue;
|
||||
}
|
||||
|
||||
const u64 initial_offset = sizeof(KIPHeader) + offset;
|
||||
const auto text_begin = c.cbegin() + initial_offset;
|
||||
const auto text_end = text_begin + kip.sections[0].size_compressed;
|
||||
const std::vector<u8> text = DecompressBLZ({text_begin, text_end});
|
||||
|
||||
const auto rodata_end = text_end + kip.sections[1].size_compressed;
|
||||
const std::vector<u8> rodata = DecompressBLZ({text_end, rodata_end});
|
||||
|
||||
const auto data_end = rodata_end + kip.sections[2].size_compressed;
|
||||
const std::vector<u8> data = DecompressBLZ({rodata_end, data_end});
|
||||
|
||||
std::vector<u8> out;
|
||||
out.reserve(text.size() + rodata.size() + data.size());
|
||||
out.insert(out.end(), text.begin(), text.end());
|
||||
out.insert(out.end(), rodata.begin(), rodata.end());
|
||||
out.insert(out.end(), data.begin(), data.end());
|
||||
|
||||
offset += sizeof(KIPHeader) + out.size();
|
||||
|
||||
if (name == "FS")
|
||||
package2_fs[static_cast<size_t>(type)] = std::move(out);
|
||||
else if (name == "spl")
|
||||
package2_spl[static_cast<size_t>(type)] = std::move(out);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<u8>& PartitionDataManager::GetPackage2FSDecompressed(Package2Type type) const {
|
||||
return package2_fs.at(static_cast<size_t>(type));
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetKeyAreaKeyApplicationSource(Package2Type type) const {
|
||||
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[6]);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetKeyAreaKeyOceanSource(Package2Type type) const {
|
||||
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[7]);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetKeyAreaKeySystemSource(Package2Type type) const {
|
||||
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[8]);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetSDKekSource(Package2Type type) const {
|
||||
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[9]);
|
||||
}
|
||||
|
||||
std::array<u8, 32> PartitionDataManager::GetSDSaveKeySource(Package2Type type) const {
|
||||
return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[10]);
|
||||
}
|
||||
|
||||
std::array<u8, 32> PartitionDataManager::GetSDNCAKeySource(Package2Type type) const {
|
||||
return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[11]);
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetHeaderKekSource(Package2Type type) const {
|
||||
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[12]);
|
||||
}
|
||||
|
||||
std::array<u8, 32> PartitionDataManager::GetHeaderKeySource(Package2Type type) const {
|
||||
return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[13]);
|
||||
}
|
||||
|
||||
const std::vector<u8>& PartitionDataManager::GetPackage2SPLDecompressed(Package2Type type) const {
|
||||
return package2_spl.at(static_cast<size_t>(type));
|
||||
}
|
||||
|
||||
std::array<u8, 16> PartitionDataManager::GetAESKeyGenerationSource(Package2Type type) const {
|
||||
return FindKeyFromHex(package2_spl.at(static_cast<size_t>(type)), source_hashes[4]);
|
||||
}
|
||||
|
||||
bool PartitionDataManager::HasProdInfo() const {
|
||||
return prodinfo != nullptr;
|
||||
}
|
||||
|
||||
FileSys::VirtualFile PartitionDataManager::GetProdInfoRaw() const {
|
||||
return prodinfo;
|
||||
}
|
||||
|
||||
void PartitionDataManager::DecryptProdInfo(std::array<u8, 0x20> bis_key) {
|
||||
if (prodinfo == nullptr)
|
||||
return;
|
||||
|
||||
prodinfo_decrypted = std::make_shared<XTSEncryptionLayer>(prodinfo, bis_key);
|
||||
}
|
||||
|
||||
std::array<u8, 576> PartitionDataManager::GetETicketExtendedKek() const {
|
||||
std::array<u8, 0x240> out{};
|
||||
if (prodinfo_decrypted != nullptr)
|
||||
prodinfo_decrypted->Read(out.data(), out.size(), 0x3890);
|
||||
return out;
|
||||
}
|
||||
} // namespace Core::Crypto
|
||||
109
src/core/crypto/partition_data_manager.h
Normal file
109
src/core/crypto/partition_data_manager.h
Normal file
@@ -0,0 +1,109 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
enum class Package2Type {
|
||||
NormalMain,
|
||||
NormalSub,
|
||||
SafeModeMain,
|
||||
SafeModeSub,
|
||||
RepairMain,
|
||||
RepairSub,
|
||||
};
|
||||
|
||||
class PartitionDataManager {
|
||||
public:
|
||||
static const u8 MAX_KEYBLOB_SOURCE_HASH;
|
||||
static constexpr std::size_t NUM_ENCRYPTED_KEYBLOBS = 32;
|
||||
static constexpr std::size_t ENCRYPTED_KEYBLOB_SIZE = 0xB0;
|
||||
|
||||
using EncryptedKeyBlob = std::array<u8, ENCRYPTED_KEYBLOB_SIZE>;
|
||||
using EncryptedKeyBlobs = std::array<EncryptedKeyBlob, NUM_ENCRYPTED_KEYBLOBS>;
|
||||
|
||||
explicit PartitionDataManager(const FileSys::VirtualDir& sysdata_dir);
|
||||
~PartitionDataManager();
|
||||
|
||||
// BOOT0
|
||||
bool HasBoot0() const;
|
||||
FileSys::VirtualFile GetBoot0Raw() const;
|
||||
EncryptedKeyBlob GetEncryptedKeyblob(std::size_t index) const;
|
||||
EncryptedKeyBlobs GetEncryptedKeyblobs() const;
|
||||
std::vector<u8> GetSecureMonitor() const;
|
||||
std::array<u8, 0x10> GetPackage2KeySource() const;
|
||||
std::array<u8, 0x10> GetAESKekGenerationSource() const;
|
||||
std::array<u8, 0x10> GetTitlekekSource() const;
|
||||
std::array<std::array<u8, 0x10>, 0x20> GetTZMasterKeys(std::array<u8, 0x10> master_key) const;
|
||||
std::array<u8, 0x10> GetRSAKekSeed3() const;
|
||||
std::array<u8, 0x10> GetRSAKekMask0() const;
|
||||
std::vector<u8> GetPackage1Decrypted() const;
|
||||
std::array<u8, 0x10> GetMasterKeySource() const;
|
||||
std::array<u8, 0x10> GetKeyblobMACKeySource() const;
|
||||
std::array<u8, 0x10> GetKeyblobKeySource(std::size_t revision) const;
|
||||
|
||||
// Fuses
|
||||
bool HasFuses() const;
|
||||
FileSys::VirtualFile GetFusesRaw() const;
|
||||
std::array<u8, 0x10> GetSecureBootKey() const;
|
||||
|
||||
// K-Fuses
|
||||
bool HasKFuses() const;
|
||||
FileSys::VirtualFile GetKFusesRaw() const;
|
||||
|
||||
// Package2
|
||||
bool HasPackage2(Package2Type type = Package2Type::NormalMain) const;
|
||||
FileSys::VirtualFile GetPackage2Raw(Package2Type type = Package2Type::NormalMain) const;
|
||||
void DecryptPackage2(const std::array<std::array<u8, 16>, 0x20>& package2_keys,
|
||||
Package2Type type);
|
||||
const std::vector<u8>& GetPackage2FSDecompressed(
|
||||
Package2Type type = Package2Type::NormalMain) const;
|
||||
std::array<u8, 0x10> GetKeyAreaKeyApplicationSource(
|
||||
Package2Type type = Package2Type::NormalMain) const;
|
||||
std::array<u8, 0x10> GetKeyAreaKeyOceanSource(
|
||||
Package2Type type = Package2Type::NormalMain) const;
|
||||
std::array<u8, 0x10> GetKeyAreaKeySystemSource(
|
||||
Package2Type type = Package2Type::NormalMain) const;
|
||||
std::array<u8, 0x10> GetSDKekSource(Package2Type type = Package2Type::NormalMain) const;
|
||||
std::array<u8, 0x20> GetSDSaveKeySource(Package2Type type = Package2Type::NormalMain) const;
|
||||
std::array<u8, 0x20> GetSDNCAKeySource(Package2Type type = Package2Type::NormalMain) const;
|
||||
std::array<u8, 0x10> GetHeaderKekSource(Package2Type type = Package2Type::NormalMain) const;
|
||||
std::array<u8, 0x20> GetHeaderKeySource(Package2Type type = Package2Type::NormalMain) const;
|
||||
const std::vector<u8>& GetPackage2SPLDecompressed(
|
||||
Package2Type type = Package2Type::NormalMain) const;
|
||||
std::array<u8, 0x10> GetAESKeyGenerationSource(
|
||||
Package2Type type = Package2Type::NormalMain) const;
|
||||
|
||||
// PRODINFO
|
||||
bool HasProdInfo() const;
|
||||
FileSys::VirtualFile GetProdInfoRaw() const;
|
||||
void DecryptProdInfo(std::array<u8, 0x20> bis_key);
|
||||
std::array<u8, 0x240> GetETicketExtendedKek() const;
|
||||
|
||||
private:
|
||||
FileSys::VirtualFile boot0;
|
||||
FileSys::VirtualFile fuses;
|
||||
FileSys::VirtualFile kfuses;
|
||||
std::array<FileSys::VirtualFile, 6> package2;
|
||||
FileSys::VirtualFile prodinfo;
|
||||
FileSys::VirtualFile secure_monitor;
|
||||
FileSys::VirtualFile package1_decrypted;
|
||||
|
||||
// Processed
|
||||
std::array<FileSys::VirtualFile, 6> package2_decrypted;
|
||||
FileSys::VirtualFile prodinfo_decrypted;
|
||||
std::vector<u8> secure_monitor_bytes;
|
||||
std::vector<u8> package1_decrypted_bytes;
|
||||
std::array<std::vector<u8>, 6> package2_fs;
|
||||
std::array<std::vector<u8>, 6> package2_spl;
|
||||
};
|
||||
|
||||
std::array<u8, 0x10> FindKeyFromHex16(const std::vector<u8>& binary, std::array<u8, 0x20> hash);
|
||||
|
||||
} // namespace Core::Crypto
|
||||
@@ -10,19 +10,19 @@ namespace FileSys {
|
||||
|
||||
BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_)
|
||||
: nand_root(std::move(nand_root_)), load_root(std::move(load_root_)),
|
||||
sysnand_cache(std::make_shared<RegisteredCache>(
|
||||
sysnand_cache(std::make_unique<RegisteredCache>(
|
||||
GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))),
|
||||
usrnand_cache(std::make_shared<RegisteredCache>(
|
||||
usrnand_cache(std::make_unique<RegisteredCache>(
|
||||
GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))) {}
|
||||
|
||||
BISFactory::~BISFactory() = default;
|
||||
|
||||
std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const {
|
||||
return sysnand_cache;
|
||||
RegisteredCache* BISFactory::GetSystemNANDContents() const {
|
||||
return sysnand_cache.get();
|
||||
}
|
||||
|
||||
std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const {
|
||||
return usrnand_cache;
|
||||
RegisteredCache* BISFactory::GetUserNANDContents() const {
|
||||
return usrnand_cache.get();
|
||||
}
|
||||
|
||||
VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const {
|
||||
|
||||
@@ -20,8 +20,8 @@ public:
|
||||
explicit BISFactory(VirtualDir nand_root, VirtualDir load_root);
|
||||
~BISFactory();
|
||||
|
||||
std::shared_ptr<RegisteredCache> GetSystemNANDContents() const;
|
||||
std::shared_ptr<RegisteredCache> GetUserNANDContents() const;
|
||||
RegisteredCache* GetSystemNANDContents() const;
|
||||
RegisteredCache* GetUserNANDContents() const;
|
||||
|
||||
VirtualDir GetModificationLoadRoot(u64 title_id) const;
|
||||
|
||||
@@ -29,8 +29,8 @@ private:
|
||||
VirtualDir nand_root;
|
||||
VirtualDir load_root;
|
||||
|
||||
std::shared_ptr<RegisteredCache> sysnand_cache;
|
||||
std::shared_ptr<RegisteredCache> usrnand_cache;
|
||||
std::unique_ptr<RegisteredCache> sysnand_cache;
|
||||
std::unique_ptr<RegisteredCache> usrnand_cache;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -122,14 +122,16 @@ u64 XCI::GetProgramTitleID() const {
|
||||
return secure_partition->GetProgramTitleID();
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> XCI::GetProgramNCA() const {
|
||||
return program;
|
||||
bool XCI::HasProgramNCA() const {
|
||||
return program != nullptr;
|
||||
}
|
||||
|
||||
VirtualFile XCI::GetProgramNCAFile() const {
|
||||
if (GetProgramNCA() == nullptr)
|
||||
if (!HasProgramNCA()) {
|
||||
return nullptr;
|
||||
return GetProgramNCA()->GetBaseFile();
|
||||
}
|
||||
|
||||
return program->GetBaseFile();
|
||||
}
|
||||
|
||||
const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {
|
||||
|
||||
@@ -80,7 +80,7 @@ public:
|
||||
|
||||
u64 GetProgramTitleID() const;
|
||||
|
||||
std::shared_ptr<NCA> GetProgramNCA() const;
|
||||
bool HasProgramNCA() const;
|
||||
VirtualFile GetProgramNCAFile() const;
|
||||
const std::vector<std::shared_ptr<NCA>>& GetNCAs() const;
|
||||
std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
|
||||
|
||||
@@ -97,11 +97,288 @@ union NCASectionHeader {
|
||||
};
|
||||
static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
|
||||
|
||||
bool IsValidNCA(const NCAHeader& header) {
|
||||
static bool IsValidNCA(const NCAHeader& header) {
|
||||
// TODO(DarkLordZach): Add NCA2/NCA0 support.
|
||||
return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
|
||||
}
|
||||
|
||||
NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
|
||||
: file(std::move(file_)), bktr_base_romfs(std::move(bktr_base_romfs_)) {
|
||||
if (file == nullptr) {
|
||||
status = Loader::ResultStatus::ErrorNullFile;
|
||||
return;
|
||||
}
|
||||
|
||||
if (sizeof(NCAHeader) != file->ReadObject(&header)) {
|
||||
LOG_ERROR(Loader, "File reader errored out during header read.");
|
||||
status = Loader::ResultStatus::ErrorBadNCAHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HandlePotentialHeaderDecryption()) {
|
||||
return;
|
||||
}
|
||||
|
||||
has_rights_id = std::any_of(header.rights_id.begin(), header.rights_id.end(),
|
||||
[](char c) { return c != '\0'; });
|
||||
|
||||
const std::vector<NCASectionHeader> sections = ReadSectionHeaders();
|
||||
is_update = std::any_of(sections.begin(), sections.end(), [](const NCASectionHeader& header) {
|
||||
return header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
|
||||
});
|
||||
|
||||
if (!ReadSections(sections, bktr_base_ivfc_offset)) {
|
||||
return;
|
||||
}
|
||||
|
||||
status = Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
NCA::~NCA() = default;
|
||||
|
||||
bool NCA::CheckSupportedNCA(const NCAHeader& nca_header) {
|
||||
if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
|
||||
status = Loader::ResultStatus::ErrorNCA2;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
|
||||
status = Loader::ResultStatus::ErrorNCA0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NCA::HandlePotentialHeaderDecryption() {
|
||||
if (IsValidNCA(header)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!CheckSupportedNCA(header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NCAHeader dec_header{};
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
|
||||
keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
|
||||
cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200,
|
||||
Core::Crypto::Op::Decrypt);
|
||||
if (IsValidNCA(dec_header)) {
|
||||
header = dec_header;
|
||||
encrypted = true;
|
||||
} else {
|
||||
if (!CheckSupportedNCA(dec_header)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keys.HasKey(Core::Crypto::S256KeyType::Header)) {
|
||||
status = Loader::ResultStatus::ErrorIncorrectHeaderKey;
|
||||
} else {
|
||||
status = Loader::ResultStatus::ErrorMissingHeaderKey;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<NCASectionHeader> NCA::ReadSectionHeaders() const {
|
||||
const std::ptrdiff_t number_sections =
|
||||
std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
|
||||
[](NCASectionTableEntry entry) { return entry.media_offset > 0; });
|
||||
|
||||
std::vector<NCASectionHeader> sections(number_sections);
|
||||
const auto length_sections = SECTION_HEADER_SIZE * number_sections;
|
||||
|
||||
if (encrypted) {
|
||||
auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET);
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
|
||||
keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
|
||||
cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE,
|
||||
Core::Crypto::Op::Decrypt);
|
||||
} else {
|
||||
file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
|
||||
}
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
bool NCA::ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset) {
|
||||
for (std::size_t i = 0; i < sections.size(); ++i) {
|
||||
const auto& section = sections[i];
|
||||
|
||||
if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
|
||||
if (!ReadRomFSSection(section, header.section_tables[i], bktr_base_ivfc_offset)) {
|
||||
return false;
|
||||
}
|
||||
} else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
|
||||
if (!ReadPFS0Section(section, header.section_tables[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
|
||||
u64 bktr_base_ivfc_offset) {
|
||||
const std::size_t base_offset = entry.media_offset * MEDIA_OFFSET_MULTIPLIER;
|
||||
ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
|
||||
const std::size_t romfs_offset = base_offset + ivfc_offset;
|
||||
const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
|
||||
auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset);
|
||||
auto dec = Decrypt(section, raw, romfs_offset);
|
||||
|
||||
if (dec == nullptr) {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return false;
|
||||
if (has_rights_id)
|
||||
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) {
|
||||
if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') ||
|
||||
section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) {
|
||||
status = Loader::ResultStatus::ErrorBadBKTRHeader;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (section.bktr.relocation.offset + section.bktr.relocation.size !=
|
||||
section.bktr.subsection.offset) {
|
||||
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation;
|
||||
return false;
|
||||
}
|
||||
|
||||
const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
|
||||
if (section.bktr.subsection.offset + section.bktr.subsection.size != size) {
|
||||
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd;
|
||||
return false;
|
||||
}
|
||||
|
||||
const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
|
||||
RelocationBlock relocation_block{};
|
||||
if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) !=
|
||||
sizeof(RelocationBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadRelocationBlock;
|
||||
return false;
|
||||
}
|
||||
SubsectionBlock subsection_block{};
|
||||
if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) !=
|
||||
sizeof(RelocationBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadSubsectionBlock;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<RelocationBucketRaw> relocation_buckets_raw(
|
||||
(section.bktr.relocation.size - sizeof(RelocationBlock)) / sizeof(RelocationBucketRaw));
|
||||
if (dec->ReadBytes(relocation_buckets_raw.data(),
|
||||
section.bktr.relocation.size - sizeof(RelocationBlock),
|
||||
section.bktr.relocation.offset + sizeof(RelocationBlock) - offset) !=
|
||||
section.bktr.relocation.size - sizeof(RelocationBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadRelocationBuckets;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<SubsectionBucketRaw> subsection_buckets_raw(
|
||||
(section.bktr.subsection.size - sizeof(SubsectionBlock)) / sizeof(SubsectionBucketRaw));
|
||||
if (dec->ReadBytes(subsection_buckets_raw.data(),
|
||||
section.bktr.subsection.size - sizeof(SubsectionBlock),
|
||||
section.bktr.subsection.offset + sizeof(SubsectionBlock) - offset) !=
|
||||
section.bktr.subsection.size - sizeof(SubsectionBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadSubsectionBuckets;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size());
|
||||
std::transform(relocation_buckets_raw.begin(), relocation_buckets_raw.end(),
|
||||
relocation_buckets.begin(), &ConvertRelocationBucketRaw);
|
||||
std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size());
|
||||
std::transform(subsection_buckets_raw.begin(), subsection_buckets_raw.end(),
|
||||
subsection_buckets.begin(), &ConvertSubsectionBucketRaw);
|
||||
|
||||
u32 ctr_low;
|
||||
std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low));
|
||||
subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low});
|
||||
subsection_buckets.back().entries.push_back({size, {0}, 0});
|
||||
|
||||
boost::optional<Core::Crypto::Key128> key = boost::none;
|
||||
if (encrypted) {
|
||||
if (has_rights_id) {
|
||||
status = Loader::ResultStatus::Success;
|
||||
key = GetTitlekey();
|
||||
if (key == boost::none) {
|
||||
status = Loader::ResultStatus::ErrorMissingTitlekey;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
|
||||
if (key == boost::none) {
|
||||
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bktr_base_romfs == nullptr) {
|
||||
status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto bktr = std::make_shared<BKTR>(
|
||||
bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
|
||||
relocation_block, relocation_buckets, subsection_block, subsection_buckets, encrypted,
|
||||
encrypted ? key.get() : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset,
|
||||
section.raw.section_ctr);
|
||||
|
||||
// BKTR applies to entire IVFC, so make an offset version to level 6
|
||||
files.push_back(std::make_shared<OffsetVfsFile>(
|
||||
bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset));
|
||||
} else {
|
||||
files.push_back(std::move(dec));
|
||||
}
|
||||
|
||||
romfs = files.back();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry) {
|
||||
const u64 offset = (static_cast<u64>(entry.media_offset) * MEDIA_OFFSET_MULTIPLIER) +
|
||||
section.pfs0.pfs0_header_offset;
|
||||
const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
|
||||
|
||||
auto dec = Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
|
||||
if (dec != nullptr) {
|
||||
auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec));
|
||||
|
||||
if (npfs->GetStatus() == Loader::ResultStatus::Success) {
|
||||
dirs.push_back(std::move(npfs));
|
||||
if (IsDirectoryExeFS(dirs.back()))
|
||||
exefs = dirs.back();
|
||||
} else {
|
||||
if (has_rights_id)
|
||||
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return false;
|
||||
if (has_rights_id)
|
||||
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u8 NCA::GetCryptoRevision() const {
|
||||
u8 master_key_id = header.crypto_type;
|
||||
if (header.crypto_type_2 > master_key_id)
|
||||
@@ -133,7 +410,7 @@ boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType ty
|
||||
static_cast<u8>(type));
|
||||
u128 out_128{};
|
||||
memcpy(out_128.data(), out.data(), 16);
|
||||
LOG_DEBUG(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
|
||||
LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
|
||||
master_key_id, header.key_index, out_128[1], out_128[0]);
|
||||
|
||||
return out;
|
||||
@@ -167,7 +444,7 @@ boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
|
||||
return titlekey;
|
||||
}
|
||||
|
||||
VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting_offset) {
|
||||
VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 starting_offset) {
|
||||
if (!encrypted)
|
||||
return in;
|
||||
|
||||
@@ -215,256 +492,6 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
|
||||
}
|
||||
}
|
||||
|
||||
NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
|
||||
: file(std::move(file_)),
|
||||
bktr_base_romfs(bktr_base_romfs_ ? std::move(bktr_base_romfs_) : nullptr) {
|
||||
status = Loader::ResultStatus::Success;
|
||||
|
||||
if (file == nullptr) {
|
||||
status = Loader::ResultStatus::ErrorNullFile;
|
||||
return;
|
||||
}
|
||||
|
||||
if (sizeof(NCAHeader) != file->ReadObject(&header)) {
|
||||
LOG_ERROR(Loader, "File reader errored out during header read.");
|
||||
status = Loader::ResultStatus::ErrorBadNCAHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
encrypted = false;
|
||||
|
||||
if (!IsValidNCA(header)) {
|
||||
if (header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
|
||||
status = Loader::ResultStatus::ErrorNCA2;
|
||||
return;
|
||||
}
|
||||
if (header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
|
||||
status = Loader::ResultStatus::ErrorNCA0;
|
||||
return;
|
||||
}
|
||||
|
||||
NCAHeader dec_header{};
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
|
||||
keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
|
||||
cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200,
|
||||
Core::Crypto::Op::Decrypt);
|
||||
if (IsValidNCA(dec_header)) {
|
||||
header = dec_header;
|
||||
encrypted = true;
|
||||
} else {
|
||||
if (dec_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
|
||||
status = Loader::ResultStatus::ErrorNCA2;
|
||||
return;
|
||||
}
|
||||
if (dec_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
|
||||
status = Loader::ResultStatus::ErrorNCA0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!keys.HasKey(Core::Crypto::S256KeyType::Header))
|
||||
status = Loader::ResultStatus::ErrorMissingHeaderKey;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorIncorrectHeaderKey;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
has_rights_id = std::find_if_not(header.rights_id.begin(), header.rights_id.end(),
|
||||
[](char c) { return c == '\0'; }) != header.rights_id.end();
|
||||
|
||||
const std::ptrdiff_t number_sections =
|
||||
std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
|
||||
[](NCASectionTableEntry entry) { return entry.media_offset > 0; });
|
||||
|
||||
std::vector<NCASectionHeader> sections(number_sections);
|
||||
const auto length_sections = SECTION_HEADER_SIZE * number_sections;
|
||||
|
||||
if (encrypted) {
|
||||
auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET);
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
|
||||
keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
|
||||
cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE,
|
||||
Core::Crypto::Op::Decrypt);
|
||||
} else {
|
||||
file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
|
||||
}
|
||||
|
||||
is_update = std::find_if(sections.begin(), sections.end(), [](const NCASectionHeader& header) {
|
||||
return header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
|
||||
}) != sections.end();
|
||||
ivfc_offset = 0;
|
||||
|
||||
for (std::ptrdiff_t i = 0; i < number_sections; ++i) {
|
||||
auto section = sections[i];
|
||||
|
||||
if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
|
||||
const std::size_t base_offset =
|
||||
header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER;
|
||||
ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
|
||||
const std::size_t romfs_offset = base_offset + ivfc_offset;
|
||||
const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
|
||||
auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset);
|
||||
auto dec = Decrypt(section, raw, romfs_offset);
|
||||
|
||||
if (dec == nullptr) {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return;
|
||||
if (has_rights_id)
|
||||
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
|
||||
return;
|
||||
}
|
||||
|
||||
if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) {
|
||||
if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') ||
|
||||
section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) {
|
||||
status = Loader::ResultStatus::ErrorBadBKTRHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
if (section.bktr.relocation.offset + section.bktr.relocation.size !=
|
||||
section.bktr.subsection.offset) {
|
||||
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation;
|
||||
return;
|
||||
}
|
||||
|
||||
const u64 size =
|
||||
MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
|
||||
header.section_tables[i].media_offset);
|
||||
if (section.bktr.subsection.offset + section.bktr.subsection.size != size) {
|
||||
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd;
|
||||
return;
|
||||
}
|
||||
|
||||
const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
|
||||
RelocationBlock relocation_block{};
|
||||
if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) !=
|
||||
sizeof(RelocationBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadRelocationBlock;
|
||||
return;
|
||||
}
|
||||
SubsectionBlock subsection_block{};
|
||||
if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) !=
|
||||
sizeof(RelocationBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadSubsectionBlock;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<RelocationBucketRaw> relocation_buckets_raw(
|
||||
(section.bktr.relocation.size - sizeof(RelocationBlock)) /
|
||||
sizeof(RelocationBucketRaw));
|
||||
if (dec->ReadBytes(relocation_buckets_raw.data(),
|
||||
section.bktr.relocation.size - sizeof(RelocationBlock),
|
||||
section.bktr.relocation.offset + sizeof(RelocationBlock) -
|
||||
offset) !=
|
||||
section.bktr.relocation.size - sizeof(RelocationBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadRelocationBuckets;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<SubsectionBucketRaw> subsection_buckets_raw(
|
||||
(section.bktr.subsection.size - sizeof(SubsectionBlock)) /
|
||||
sizeof(SubsectionBucketRaw));
|
||||
if (dec->ReadBytes(subsection_buckets_raw.data(),
|
||||
section.bktr.subsection.size - sizeof(SubsectionBlock),
|
||||
section.bktr.subsection.offset + sizeof(SubsectionBlock) -
|
||||
offset) !=
|
||||
section.bktr.subsection.size - sizeof(SubsectionBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadSubsectionBuckets;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size());
|
||||
std::transform(relocation_buckets_raw.begin(), relocation_buckets_raw.end(),
|
||||
relocation_buckets.begin(), &ConvertRelocationBucketRaw);
|
||||
std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size());
|
||||
std::transform(subsection_buckets_raw.begin(), subsection_buckets_raw.end(),
|
||||
subsection_buckets.begin(), &ConvertSubsectionBucketRaw);
|
||||
|
||||
u32 ctr_low;
|
||||
std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low));
|
||||
subsection_buckets.back().entries.push_back(
|
||||
{section.bktr.relocation.offset, {0}, ctr_low});
|
||||
subsection_buckets.back().entries.push_back({size, {0}, 0});
|
||||
|
||||
boost::optional<Core::Crypto::Key128> key = boost::none;
|
||||
if (encrypted) {
|
||||
if (has_rights_id) {
|
||||
status = Loader::ResultStatus::Success;
|
||||
key = GetTitlekey();
|
||||
if (key == boost::none) {
|
||||
status = Loader::ResultStatus::ErrorMissingTitlekey;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
|
||||
if (key == boost::none) {
|
||||
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bktr_base_romfs == nullptr) {
|
||||
status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
|
||||
return;
|
||||
}
|
||||
|
||||
auto bktr = std::make_shared<BKTR>(
|
||||
bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
|
||||
relocation_block, relocation_buckets, subsection_block, subsection_buckets,
|
||||
encrypted, encrypted ? key.get() : Core::Crypto::Key128{}, base_offset,
|
||||
bktr_base_ivfc_offset, section.raw.section_ctr);
|
||||
|
||||
// BKTR applies to entire IVFC, so make an offset version to level 6
|
||||
|
||||
files.push_back(std::make_shared<OffsetVfsFile>(
|
||||
bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset));
|
||||
romfs = files.back();
|
||||
} else {
|
||||
files.push_back(std::move(dec));
|
||||
romfs = files.back();
|
||||
}
|
||||
} else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
|
||||
u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
|
||||
MEDIA_OFFSET_MULTIPLIER) +
|
||||
section.pfs0.pfs0_header_offset;
|
||||
u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
|
||||
header.section_tables[i].media_offset);
|
||||
auto dec =
|
||||
Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
|
||||
if (dec != nullptr) {
|
||||
auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec));
|
||||
|
||||
if (npfs->GetStatus() == Loader::ResultStatus::Success) {
|
||||
dirs.push_back(std::move(npfs));
|
||||
if (IsDirectoryExeFS(dirs.back()))
|
||||
exefs = dirs.back();
|
||||
} else {
|
||||
if (has_rights_id)
|
||||
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return;
|
||||
if (has_rights_id)
|
||||
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
status = Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
NCA::~NCA() = default;
|
||||
|
||||
Loader::ResultStatus NCA::GetStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -73,8 +73,6 @@ inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) {
|
||||
return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr;
|
||||
}
|
||||
|
||||
bool IsValidNCA(const NCAHeader& header);
|
||||
|
||||
// An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner.
|
||||
// After construction, use GetStatus to determine if the file is valid and ready to be used.
|
||||
class NCA : public ReadOnlyVfsDirectory {
|
||||
@@ -106,10 +104,19 @@ protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
bool CheckSupportedNCA(const NCAHeader& header);
|
||||
bool HandlePotentialHeaderDecryption();
|
||||
|
||||
std::vector<NCASectionHeader> ReadSectionHeaders() const;
|
||||
bool ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset);
|
||||
bool ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
|
||||
u64 bktr_base_ivfc_offset);
|
||||
bool ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry);
|
||||
|
||||
u8 GetCryptoRevision() const;
|
||||
boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
|
||||
boost::optional<Core::Crypto::Key128> GetTitlekey();
|
||||
VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset);
|
||||
VirtualFile Decrypt(const NCASectionHeader& header, VirtualFile in, u64 starting_offset);
|
||||
|
||||
std::vector<VirtualDir> dirs;
|
||||
std::vector<VirtualFile> files;
|
||||
@@ -118,15 +125,15 @@ private:
|
||||
VirtualDir exefs = nullptr;
|
||||
VirtualFile file;
|
||||
VirtualFile bktr_base_romfs;
|
||||
u64 ivfc_offset;
|
||||
u64 ivfc_offset = 0;
|
||||
|
||||
NCAHeader header{};
|
||||
bool has_rights_id{};
|
||||
|
||||
Loader::ResultStatus status{};
|
||||
|
||||
bool encrypted;
|
||||
bool is_update;
|
||||
bool encrypted = false;
|
||||
bool is_update = false;
|
||||
|
||||
Core::Crypto::KeyManager keys;
|
||||
};
|
||||
|
||||
@@ -17,11 +17,13 @@ const std::array<const char*, 15> LANGUAGE_NAMES = {
|
||||
};
|
||||
|
||||
std::string LanguageEntry::GetApplicationName() const {
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(application_name.data(), 0x200);
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(application_name.data(),
|
||||
application_name.size());
|
||||
}
|
||||
|
||||
std::string LanguageEntry::GetDeveloperName() const {
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(), 0x100);
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(),
|
||||
developer_name.size());
|
||||
}
|
||||
|
||||
NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) {
|
||||
@@ -56,7 +58,12 @@ u64 NACP::GetTitleId() const {
|
||||
return raw->title_id;
|
||||
}
|
||||
|
||||
u64 NACP::GetDLCBaseTitleId() const {
|
||||
return raw->dlc_base_title_id;
|
||||
}
|
||||
|
||||
std::string NACP::GetVersionString() const {
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(raw->version_string.data(), 0x10);
|
||||
return Common::StringFromFixedZeroTerminatedBuffer(raw->version_string.data(),
|
||||
raw->version_string.size());
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -79,6 +79,7 @@ public:
|
||||
std::string GetApplicationName(Language language = Language::Default) const;
|
||||
std::string GetDeveloperName(Language language = Language::Default) const;
|
||||
u64 GetTitleId() const;
|
||||
u64 GetDLCBaseTitleId() const;
|
||||
std::string GetVersionString() const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -214,8 +214,14 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
|
||||
|
||||
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,
|
||||
VirtualFile update_raw) const {
|
||||
LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id,
|
||||
static_cast<u8>(type));
|
||||
const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}",
|
||||
title_id, static_cast<u8>(type))
|
||||
.c_str();
|
||||
|
||||
if (type == ContentRecordType::Program)
|
||||
LOG_INFO(Loader, log_string);
|
||||
else
|
||||
LOG_DEBUG(Loader, log_string);
|
||||
|
||||
if (romfs == nullptr)
|
||||
return romfs;
|
||||
@@ -346,7 +352,7 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
||||
}
|
||||
|
||||
std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
|
||||
const auto& installed{Service::FileSystem::GetUnionContents()};
|
||||
const auto installed{Service::FileSystem::GetUnionContents()};
|
||||
|
||||
const auto base_control_nca = installed->GetEntry(title_id, ContentRecordType::Control);
|
||||
if (base_control_nca == nullptr)
|
||||
|
||||
@@ -308,14 +308,14 @@ VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const {
|
||||
return GetEntryRaw(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
const auto raw = GetEntryRaw(title_id, type);
|
||||
if (raw == nullptr)
|
||||
return nullptr;
|
||||
return std::make_shared<NCA>(raw);
|
||||
return std::make_unique<NCA>(raw);
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
|
||||
std::unique_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
|
||||
return GetEntry(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
@@ -516,7 +516,7 @@ bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
|
||||
}) != yuzu_meta.end();
|
||||
}
|
||||
|
||||
RegisteredCacheUnion::RegisteredCacheUnion(std::vector<std::shared_ptr<RegisteredCache>> caches)
|
||||
RegisteredCacheUnion::RegisteredCacheUnion(std::vector<RegisteredCache*> caches)
|
||||
: caches(std::move(caches)) {}
|
||||
|
||||
void RegisteredCacheUnion::Refresh() {
|
||||
@@ -572,14 +572,14 @@ VirtualFile RegisteredCacheUnion::GetEntryRaw(RegisteredCacheEntry entry) const
|
||||
return GetEntryRaw(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> RegisteredCacheUnion::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
const auto raw = GetEntryRaw(title_id, type);
|
||||
if (raw == nullptr)
|
||||
return nullptr;
|
||||
return std::make_shared<NCA>(raw);
|
||||
return std::make_unique<NCA>(raw);
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const {
|
||||
std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const {
|
||||
return GetEntry(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
|
||||
@@ -88,8 +88,8 @@ public:
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
|
||||
std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
|
||||
std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
|
||||
std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::vector<RegisteredCacheEntry> ListEntries() const;
|
||||
// If a parameter is not boost::none, it will be filtered for from all entries.
|
||||
@@ -142,7 +142,7 @@ private:
|
||||
// Combines multiple RegisteredCaches (i.e. SysNAND, UserNAND, SDMC) into one interface.
|
||||
class RegisteredCacheUnion {
|
||||
public:
|
||||
explicit RegisteredCacheUnion(std::vector<std::shared_ptr<RegisteredCache>> caches);
|
||||
explicit RegisteredCacheUnion(std::vector<RegisteredCache*> caches);
|
||||
|
||||
void Refresh();
|
||||
|
||||
@@ -157,8 +157,8 @@ public:
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
|
||||
std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
|
||||
std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
|
||||
std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::vector<RegisteredCacheEntry> ListEntries() const;
|
||||
// If a parameter is not boost::none, it will be filtered for from all entries.
|
||||
@@ -168,7 +168,7 @@ public:
|
||||
boost::optional<u64> title_id = boost::none) const;
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<RegisteredCache>> caches;
|
||||
std::vector<RegisteredCache*> caches;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -51,6 +51,13 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescr
|
||||
meta.title_id);
|
||||
}
|
||||
|
||||
if (meta.type == SaveDataType::DeviceSaveData && meta.user_id != u128{0, 0}) {
|
||||
LOG_WARNING(Service_FS,
|
||||
"Possibly incorrect SaveDataDescriptor, type is DeviceSaveData but user_id is "
|
||||
"non-zero ({:016X}{:016X})",
|
||||
meta.user_id[1], meta.user_id[0]);
|
||||
}
|
||||
|
||||
std::string save_directory =
|
||||
GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id);
|
||||
|
||||
@@ -92,6 +99,9 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
|
||||
case SaveDataSpaceId::NandUser:
|
||||
out = "/user/";
|
||||
break;
|
||||
case SaveDataSpaceId::TemporaryStorage:
|
||||
out = "/temp/";
|
||||
break;
|
||||
default:
|
||||
ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
|
||||
}
|
||||
@@ -100,10 +110,11 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
|
||||
case SaveDataType::SystemSaveData:
|
||||
return fmt::format("{}save/{:016X}/{:016X}{:016X}", out, save_id, user_id[1], user_id[0]);
|
||||
case SaveDataType::SaveData:
|
||||
case SaveDataType::DeviceSaveData:
|
||||
return fmt::format("{}save/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
|
||||
title_id);
|
||||
case SaveDataType::TemporaryStorage:
|
||||
return fmt::format("{}temp/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
|
||||
return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
|
||||
title_id);
|
||||
default:
|
||||
ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type));
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
namespace FileSys {
|
||||
|
||||
SDMCFactory::SDMCFactory(VirtualDir dir_)
|
||||
: dir(std::move(dir_)), contents(std::make_shared<RegisteredCache>(
|
||||
: dir(std::move(dir_)), contents(std::make_unique<RegisteredCache>(
|
||||
GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"),
|
||||
[](const VirtualFile& file, const NcaID& id) {
|
||||
return std::make_shared<NAX>(file, id)->GetDecrypted();
|
||||
return NAX{file, id}.GetDecrypted();
|
||||
})) {}
|
||||
|
||||
SDMCFactory::~SDMCFactory() = default;
|
||||
@@ -22,8 +22,8 @@ ResultVal<VirtualDir> SDMCFactory::Open() {
|
||||
return MakeResult<VirtualDir>(dir);
|
||||
}
|
||||
|
||||
std::shared_ptr<RegisteredCache> SDMCFactory::GetSDMCContents() const {
|
||||
return contents;
|
||||
RegisteredCache* SDMCFactory::GetSDMCContents() const {
|
||||
return contents.get();
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -19,12 +19,12 @@ public:
|
||||
~SDMCFactory();
|
||||
|
||||
ResultVal<VirtualDir> Open();
|
||||
std::shared_ptr<RegisteredCache> GetSDMCContents() const;
|
||||
RegisteredCache* GetSDMCContents() const;
|
||||
|
||||
private:
|
||||
VirtualDir dir;
|
||||
|
||||
std::shared_ptr<RegisteredCache> contents;
|
||||
std::unique_ptr<RegisteredCache> contents;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -12,20 +12,12 @@
|
||||
#include <vector>
|
||||
#include <boost/optional.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class VfsDirectory;
|
||||
class VfsFile;
|
||||
class VfsFilesystem;
|
||||
|
||||
enum class Mode : u32;
|
||||
|
||||
// Convenience typedefs to use Vfs* interfaces
|
||||
using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
|
||||
using VirtualDir = std::shared_ptr<VfsDirectory>;
|
||||
using VirtualFile = std::shared_ptr<VfsFile>;
|
||||
|
||||
// An enumeration representing what can be at the end of a path in a VfsFilesystem
|
||||
enum class VfsEntryType {
|
||||
None,
|
||||
|
||||
21
src/core/file_sys/vfs_types.h
Normal file
21
src/core/file_sys/vfs_types.h
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class VfsDirectory;
|
||||
class VfsFile;
|
||||
class VfsFilesystem;
|
||||
|
||||
// Declarations for Vfs* pointer types
|
||||
|
||||
using VirtualDir = std::shared_ptr<VfsDirectory>;
|
||||
using VirtualFile = std::shared_ptr<VfsFile>;
|
||||
using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -207,7 +207,7 @@ void RegisterModule(std::string name, VAddr beg, VAddr end, bool add_elf_ext) {
|
||||
|
||||
static Kernel::Thread* FindThreadById(int id) {
|
||||
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList();
|
||||
for (auto& thread : threads) {
|
||||
if (thread->GetThreadID() == static_cast<u32>(id)) {
|
||||
current_core = core;
|
||||
@@ -597,7 +597,7 @@ static void HandleQuery() {
|
||||
} else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) {
|
||||
std::string val = "m";
|
||||
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList();
|
||||
for (const auto& thread : threads) {
|
||||
val += fmt::format("{:x}", thread->GetThreadID());
|
||||
val += ",";
|
||||
@@ -612,7 +612,7 @@ static void HandleQuery() {
|
||||
buffer += "l<?xml version=\"1.0\"?>";
|
||||
buffer += "<threads>";
|
||||
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
|
||||
const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList();
|
||||
for (const auto& thread : threads) {
|
||||
buffer +=
|
||||
fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*",
|
||||
|
||||
@@ -39,7 +39,7 @@ static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address)
|
||||
std::vector<SharedPtr<Thread>>& waiting_threads,
|
||||
VAddr arb_addr) {
|
||||
const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
|
||||
const auto& thread_list = scheduler->GetThreadList();
|
||||
const auto& thread_list = scheduler.GetThreadList();
|
||||
|
||||
for (const auto& thread : thread_list) {
|
||||
if (thread->GetArbiterWaitAddress() == arb_addr)
|
||||
|
||||
@@ -22,6 +22,7 @@ enum {
|
||||
HandleTableFull = 105,
|
||||
InvalidMemoryState = 106,
|
||||
InvalidMemoryPermissions = 108,
|
||||
InvalidMemoryRange = 110,
|
||||
InvalidThreadPriority = 112,
|
||||
InvalidProcessorId = 113,
|
||||
InvalidHandle = 114,
|
||||
@@ -56,6 +57,7 @@ constexpr ResultCode ERR_INVALID_ADDRESS(ErrorModule::Kernel, ErrCodes::InvalidA
|
||||
constexpr ResultCode ERR_INVALID_ADDRESS_STATE(ErrorModule::Kernel, ErrCodes::InvalidMemoryState);
|
||||
constexpr ResultCode ERR_INVALID_MEMORY_PERMISSIONS(ErrorModule::Kernel,
|
||||
ErrCodes::InvalidMemoryPermissions);
|
||||
constexpr ResultCode ERR_INVALID_MEMORY_RANGE(ErrorModule::Kernel, ErrCodes::InvalidMemoryRange);
|
||||
constexpr ResultCode ERR_INVALID_HANDLE(ErrorModule::Kernel, ErrCodes::InvalidHandle);
|
||||
constexpr ResultCode ERR_INVALID_PROCESSOR_ID(ErrorModule::Kernel, ErrCodes::InvalidProcessorId);
|
||||
constexpr ResultCode ERR_INVALID_SIZE(ErrorModule::Kernel, ErrCodes::InvalidSize);
|
||||
|
||||
@@ -25,7 +25,6 @@ bool Object::IsWaitable() const {
|
||||
case HandleType::Process:
|
||||
case HandleType::AddressArbiter:
|
||||
case HandleType::ResourceLimit:
|
||||
case HandleType::CodeSet:
|
||||
case HandleType::ClientPort:
|
||||
case HandleType::ClientSession:
|
||||
return false;
|
||||
|
||||
@@ -26,7 +26,6 @@ enum class HandleType : u32 {
|
||||
AddressArbiter,
|
||||
Timer,
|
||||
ResourceLimit,
|
||||
CodeSet,
|
||||
ClientPort,
|
||||
ServerPort,
|
||||
ClientSession,
|
||||
|
||||
@@ -20,13 +20,7 @@
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
SharedPtr<CodeSet> CodeSet::Create(KernelCore& kernel, std::string name) {
|
||||
SharedPtr<CodeSet> codeset(new CodeSet(kernel));
|
||||
codeset->name = std::move(name);
|
||||
return codeset;
|
||||
}
|
||||
|
||||
CodeSet::CodeSet(KernelCore& kernel) : Object{kernel} {}
|
||||
CodeSet::CodeSet() = default;
|
||||
CodeSet::~CodeSet() = default;
|
||||
|
||||
SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) {
|
||||
@@ -159,11 +153,11 @@ void Process::PrepareForTermination() {
|
||||
}
|
||||
};
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
stop_threads(system.Scheduler(0)->GetThreadList());
|
||||
stop_threads(system.Scheduler(1)->GetThreadList());
|
||||
stop_threads(system.Scheduler(2)->GetThreadList());
|
||||
stop_threads(system.Scheduler(3)->GetThreadList());
|
||||
const auto& system = Core::System::GetInstance();
|
||||
stop_threads(system.Scheduler(0).GetThreadList());
|
||||
stop_threads(system.Scheduler(1).GetThreadList());
|
||||
stop_threads(system.Scheduler(2).GetThreadList());
|
||||
stop_threads(system.Scheduler(3).GetThreadList());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,20 +218,20 @@ void Process::FreeTLSSlot(VAddr tls_address) {
|
||||
tls_slots[tls_page].reset(tls_slot);
|
||||
}
|
||||
|
||||
void Process::LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr) {
|
||||
void Process::LoadModule(CodeSet module_, VAddr base_addr) {
|
||||
const auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions,
|
||||
MemoryState memory_state) {
|
||||
auto vma = vm_manager
|
||||
.MapMemoryBlock(segment.addr + base_addr, module_->memory, segment.offset,
|
||||
segment.size, memory_state)
|
||||
.Unwrap();
|
||||
const auto vma = vm_manager
|
||||
.MapMemoryBlock(segment.addr + base_addr, module_.memory,
|
||||
segment.offset, segment.size, memory_state)
|
||||
.Unwrap();
|
||||
vm_manager.Reprotect(vma, permissions);
|
||||
};
|
||||
|
||||
// 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::CodeStatic);
|
||||
MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeMutable);
|
||||
MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeMutable);
|
||||
}
|
||||
|
||||
ResultVal<VAddr> Process::HeapAllocate(VAddr target, u64 size, VMAPermission perms) {
|
||||
|
||||
@@ -24,6 +24,7 @@ class ProgramMetadata;
|
||||
namespace Kernel {
|
||||
|
||||
class KernelCore;
|
||||
class ResourceLimit;
|
||||
|
||||
struct AddressMapping {
|
||||
// Address and size must be page-aligned
|
||||
@@ -57,30 +58,33 @@ union ProcessFlags {
|
||||
BitField<12, 1, u16> loaded_high; ///< Application loaded high (not at 0x00100000).
|
||||
};
|
||||
|
||||
enum class ProcessStatus { Created, Running, Exited };
|
||||
/**
|
||||
* Indicates the status of a Process instance.
|
||||
*
|
||||
* @note These match the values as used by kernel,
|
||||
* so new entries should only be added if RE
|
||||
* shows that a new value has been introduced.
|
||||
*/
|
||||
enum class ProcessStatus {
|
||||
Created,
|
||||
CreatedWithDebuggerAttached,
|
||||
Running,
|
||||
WaitingForDebuggerToAttach,
|
||||
DebuggerAttached,
|
||||
Exiting,
|
||||
Exited,
|
||||
DebugBreak,
|
||||
};
|
||||
|
||||
class ResourceLimit;
|
||||
|
||||
struct CodeSet final : public Object {
|
||||
struct CodeSet final {
|
||||
struct Segment {
|
||||
std::size_t offset = 0;
|
||||
VAddr addr = 0;
|
||||
u32 size = 0;
|
||||
};
|
||||
|
||||
static SharedPtr<CodeSet> Create(KernelCore& kernel, std::string name);
|
||||
|
||||
std::string GetTypeName() const override {
|
||||
return "CodeSet";
|
||||
}
|
||||
std::string GetName() const override {
|
||||
return name;
|
||||
}
|
||||
|
||||
static const HandleType HANDLE_TYPE = HandleType::CodeSet;
|
||||
HandleType GetHandleType() const override {
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
explicit CodeSet();
|
||||
~CodeSet();
|
||||
|
||||
Segment& CodeSegment() {
|
||||
return segments[0];
|
||||
@@ -109,14 +113,7 @@ struct CodeSet final : public Object {
|
||||
std::shared_ptr<std::vector<u8>> memory;
|
||||
|
||||
std::array<Segment, 3> segments;
|
||||
VAddr entrypoint;
|
||||
|
||||
/// Name of the process
|
||||
std::string name;
|
||||
|
||||
private:
|
||||
explicit CodeSet(KernelCore& kernel);
|
||||
~CodeSet() override;
|
||||
VAddr entrypoint = 0;
|
||||
};
|
||||
|
||||
class Process final : public Object {
|
||||
@@ -219,7 +216,7 @@ public:
|
||||
*/
|
||||
void PrepareForTermination();
|
||||
|
||||
void LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr);
|
||||
void LoadModule(CodeSet module_, VAddr base_addr);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Memory Management
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
@@ -36,8 +37,72 @@
|
||||
|
||||
namespace Kernel {
|
||||
namespace {
|
||||
constexpr bool Is4KBAligned(VAddr address) {
|
||||
return (address & 0xFFF) == 0;
|
||||
|
||||
// Checks if address + size is greater than the given address
|
||||
// This can return false if the size causes an overflow of a 64-bit type
|
||||
// or if the given size is zero.
|
||||
constexpr bool IsValidAddressRange(VAddr address, u64 size) {
|
||||
return address + size > address;
|
||||
}
|
||||
|
||||
// Checks if a given address range lies within a larger address range.
|
||||
constexpr bool IsInsideAddressRange(VAddr address, u64 size, VAddr address_range_begin,
|
||||
VAddr address_range_end) {
|
||||
const VAddr end_address = address + size - 1;
|
||||
return address_range_begin <= address && end_address <= address_range_end - 1;
|
||||
}
|
||||
|
||||
bool IsInsideAddressSpace(const VMManager& vm, VAddr address, u64 size) {
|
||||
return IsInsideAddressRange(address, size, vm.GetAddressSpaceBaseAddress(),
|
||||
vm.GetAddressSpaceEndAddress());
|
||||
}
|
||||
|
||||
bool IsInsideNewMapRegion(const VMManager& vm, VAddr address, u64 size) {
|
||||
return IsInsideAddressRange(address, size, vm.GetNewMapRegionBaseAddress(),
|
||||
vm.GetNewMapRegionEndAddress());
|
||||
}
|
||||
|
||||
// Helper function that performs the common sanity checks for svcMapMemory
|
||||
// and svcUnmapMemory. This is doable, as both functions perform their sanitizing
|
||||
// in the same order.
|
||||
ResultCode MapUnmapMemorySanityChecks(const VMManager& vm_manager, VAddr dst_addr, VAddr src_addr,
|
||||
u64 size) {
|
||||
if (!Common::Is4KBAligned(dst_addr) || !Common::Is4KBAligned(src_addr)) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
if (size == 0 || !Common::Is4KBAligned(size)) {
|
||||
return ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
if (!IsValidAddressRange(dst_addr, size)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
if (!IsValidAddressRange(src_addr, size)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
if (!IsInsideAddressSpace(vm_manager, src_addr, size)) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
if (!IsInsideNewMapRegion(vm_manager, dst_addr, size)) {
|
||||
return ERR_INVALID_MEMORY_RANGE;
|
||||
}
|
||||
|
||||
const VAddr dst_end_address = dst_addr + size;
|
||||
if (dst_end_address > vm_manager.GetHeapRegionBaseAddress() &&
|
||||
vm_manager.GetHeapRegionEndAddress() > dst_addr) {
|
||||
return ERR_INVALID_MEMORY_RANGE;
|
||||
}
|
||||
|
||||
if (dst_end_address > vm_manager.GetMapRegionBaseAddress() &&
|
||||
vm_manager.GetMapRegionEndAddress() > dst_addr) {
|
||||
return ERR_INVALID_MEMORY_RANGE;
|
||||
}
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
@@ -69,15 +134,15 @@ static ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
|
||||
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
|
||||
src_addr, size);
|
||||
|
||||
if (!Is4KBAligned(dst_addr) || !Is4KBAligned(src_addr)) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
auto* const current_process = Core::CurrentProcess();
|
||||
const auto& vm_manager = current_process->VMManager();
|
||||
|
||||
const auto result = MapUnmapMemorySanityChecks(vm_manager, dst_addr, src_addr, size);
|
||||
if (result != RESULT_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (size == 0 || !Is4KBAligned(size)) {
|
||||
return ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
return Core::CurrentProcess()->MirrorMemory(dst_addr, src_addr, size);
|
||||
return current_process->MirrorMemory(dst_addr, src_addr, size);
|
||||
}
|
||||
|
||||
/// Unmaps a region that was previously mapped with svcMapMemory
|
||||
@@ -85,15 +150,15 @@ static ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
|
||||
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
|
||||
src_addr, size);
|
||||
|
||||
if (!Is4KBAligned(dst_addr) || !Is4KBAligned(src_addr)) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
auto* const current_process = Core::CurrentProcess();
|
||||
const auto& vm_manager = current_process->VMManager();
|
||||
|
||||
const auto result = MapUnmapMemorySanityChecks(vm_manager, dst_addr, src_addr, size);
|
||||
if (result != RESULT_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (size == 0 || !Is4KBAligned(size)) {
|
||||
return ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
return Core::CurrentProcess()->UnmapMemory(dst_addr, src_addr, size);
|
||||
return current_process->UnmapMemory(dst_addr, src_addr, size);
|
||||
}
|
||||
|
||||
/// Connect to an OS service given the port name, returns the handle to the port to out
|
||||
@@ -285,6 +350,10 @@ static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr,
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
if (!Common::IsWordAligned(mutex_addr)) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
auto& handle_table = Core::System::GetInstance().Kernel().HandleTable();
|
||||
return Mutex::TryAcquire(handle_table, mutex_addr, holding_thread_handle,
|
||||
requesting_thread_handle);
|
||||
@@ -298,20 +367,24 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
if (!Common::IsWordAligned(mutex_addr)) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
return Mutex::Release(mutex_addr);
|
||||
}
|
||||
|
||||
struct BreakReason {
|
||||
union {
|
||||
u64 raw;
|
||||
BitField<31, 1, u64> dont_kill_application;
|
||||
u32 raw;
|
||||
BitField<31, 1, u32> signal_debugger;
|
||||
};
|
||||
};
|
||||
|
||||
/// Break program execution
|
||||
static void Break(u64 reason, u64 info1, u64 info2) {
|
||||
static void Break(u32 reason, u64 info1, u64 info2) {
|
||||
BreakReason break_reason{reason};
|
||||
if (break_reason.dont_kill_application) {
|
||||
if (break_reason.signal_debugger) {
|
||||
LOG_ERROR(
|
||||
Debug_Emulated,
|
||||
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
|
||||
@@ -322,6 +395,12 @@ static void Break(u64 reason, u64 info1, u64 info2) {
|
||||
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
|
||||
reason, info1, info2);
|
||||
ASSERT(false);
|
||||
|
||||
Core::CurrentProcess()->PrepareForTermination();
|
||||
|
||||
// Kill the current thread
|
||||
GetCurrentThread()->Stop();
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,25 +454,12 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
|
||||
case GetInfoType::RandomEntropy:
|
||||
*result = 0;
|
||||
break;
|
||||
case GetInfoType::AddressSpaceBaseAddr:
|
||||
*result = vm_manager.GetCodeRegionBaseAddress();
|
||||
case GetInfoType::ASLRRegionBaseAddr:
|
||||
*result = vm_manager.GetASLRRegionBaseAddress();
|
||||
break;
|
||||
case GetInfoType::AddressSpaceSize: {
|
||||
const u64 width = vm_manager.GetAddressSpaceWidth();
|
||||
|
||||
switch (width) {
|
||||
case 32:
|
||||
*result = 0xFFE00000;
|
||||
break;
|
||||
case 36:
|
||||
*result = 0xFF8000000;
|
||||
break;
|
||||
case 39:
|
||||
*result = 0x7FF8000000;
|
||||
break;
|
||||
}
|
||||
case GetInfoType::ASLRRegionSize:
|
||||
*result = vm_manager.GetASLRRegionSize();
|
||||
break;
|
||||
}
|
||||
case GetInfoType::NewMapRegionBaseAddr:
|
||||
*result = vm_manager.GetNewMapRegionBaseAddress();
|
||||
break;
|
||||
@@ -510,11 +576,11 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s
|
||||
"called, shared_memory_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}",
|
||||
shared_memory_handle, addr, size, permissions);
|
||||
|
||||
if (!Is4KBAligned(addr)) {
|
||||
if (!Common::Is4KBAligned(addr)) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
if (size == 0 || !Is4KBAligned(size)) {
|
||||
if (size == 0 || !Common::Is4KBAligned(size)) {
|
||||
return ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
@@ -539,11 +605,11 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64
|
||||
LOG_WARNING(Kernel_SVC, "called, shared_memory_handle=0x{:08X}, addr=0x{:X}, size=0x{:X}",
|
||||
shared_memory_handle, addr, size);
|
||||
|
||||
if (!Is4KBAligned(addr)) {
|
||||
if (!Common::Is4KBAligned(addr)) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
if (size == 0 || !Is4KBAligned(size)) {
|
||||
if (size == 0 || !Common::Is4KBAligned(size)) {
|
||||
return ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
@@ -736,7 +802,7 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
|
||||
std::vector<SharedPtr<Thread>>& waiting_threads,
|
||||
VAddr condvar_addr) {
|
||||
const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
|
||||
const auto& thread_list = scheduler->GetThreadList();
|
||||
const auto& thread_list = scheduler.GetThreadList();
|
||||
|
||||
for (const auto& thread : thread_list) {
|
||||
if (thread->GetCondVarWaitAddress() == condvar_addr)
|
||||
@@ -1025,6 +1091,29 @@ static ResultCode ClearEvent(Handle handle) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
static ResultCode GetProcessInfo(u64* out, Handle process_handle, u32 type) {
|
||||
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, type=0x{:X}", process_handle, type);
|
||||
|
||||
// This function currently only allows retrieving a process' status.
|
||||
enum class InfoType {
|
||||
Status,
|
||||
};
|
||||
|
||||
const auto& kernel = Core::System::GetInstance().Kernel();
|
||||
const auto process = kernel.HandleTable().Get<Process>(process_handle);
|
||||
if (!process) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
const auto info_type = static_cast<InfoType>(type);
|
||||
if (info_type != InfoType::Status) {
|
||||
return ERR_INVALID_ENUM_VALUE;
|
||||
}
|
||||
|
||||
*out = static_cast<u64>(process->GetStatus());
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct FunctionDef {
|
||||
using Func = void();
|
||||
@@ -1160,7 +1249,7 @@ static const FunctionDef SVC_Table[] = {
|
||||
{0x79, nullptr, "CreateProcess"},
|
||||
{0x7A, nullptr, "StartProcess"},
|
||||
{0x7B, nullptr, "TerminateProcess"},
|
||||
{0x7C, nullptr, "GetProcessInfo"},
|
||||
{0x7C, SvcWrap<GetProcessInfo>, "GetProcessInfo"},
|
||||
{0x7D, nullptr, "CreateResourceLimit"},
|
||||
{0x7E, nullptr, "SetResourceLimitLimitValue"},
|
||||
{0x7F, nullptr, "CallSecureMonitor"},
|
||||
|
||||
@@ -41,8 +41,8 @@ enum class GetInfoType : u64 {
|
||||
RandomEntropy = 11,
|
||||
PerformanceCounter = 0xF0000002,
|
||||
// 2.0.0+
|
||||
AddressSpaceBaseAddr = 12,
|
||||
AddressSpaceSize = 13,
|
||||
ASLRRegionBaseAddr = 12,
|
||||
ASLRRegionSize = 13,
|
||||
NewMapRegionBaseAddr = 14,
|
||||
NewMapRegionSize = 15,
|
||||
// 3.0.0+
|
||||
|
||||
@@ -35,18 +35,18 @@ void SvcWrap() {
|
||||
|
||||
template <ResultCode func(u32)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func((u32)Param(0)).raw);
|
||||
FuncReturn(func(static_cast<u32>(Param(0))).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32, u32)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func((u32)Param(0), (u32)Param(1)).raw);
|
||||
FuncReturn(func(static_cast<u32>(Param(0)), static_cast<u32>(Param(1))).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32*, u32)>
|
||||
void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
u32 retval = func(¶m_1, (u32)Param(1)).raw;
|
||||
u32 retval = func(¶m_1, static_cast<u32>(Param(1))).raw;
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
@@ -61,7 +61,7 @@ void SvcWrap() {
|
||||
|
||||
template <ResultCode func(u64, s32)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func(Param(0), (s32)Param(1)).raw);
|
||||
FuncReturn(func(Param(0), static_cast<s32>(Param(1))).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u64, u32)>
|
||||
@@ -77,21 +77,29 @@ void SvcWrap() {
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(u64*, u32, u32)>
|
||||
void SvcWrap() {
|
||||
u64 param_1 = 0;
|
||||
u32 retval = func(¶m_1, static_cast<u32>(Param(1)), static_cast<u32>(Param(2))).raw;
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32, u64)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func((u32)(Param(0) & 0xFFFFFFFF), Param(1)).raw);
|
||||
FuncReturn(func(static_cast<u32>(Param(0)), Param(1)).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32, u32, u64)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func((u32)(Param(0) & 0xFFFFFFFF), (u32)(Param(1) & 0xFFFFFFFF), Param(2)).raw);
|
||||
FuncReturn(func(static_cast<u32>(Param(0)), static_cast<u32>(Param(1)), Param(2)).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32, u32*, u64*)>
|
||||
void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
u64 param_2 = 0;
|
||||
ResultCode retval = func((u32)(Param(2) & 0xFFFFFFFF), ¶m_1, ¶m_2);
|
||||
ResultCode retval = func(static_cast<u32>(Param(2)), ¶m_1, ¶m_2);
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
Core::CurrentArmInterface().SetReg(2, param_2);
|
||||
FuncReturn(retval.raw);
|
||||
@@ -100,12 +108,12 @@ void SvcWrap() {
|
||||
template <ResultCode func(u64, u64, u32, u32)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(
|
||||
func(Param(0), Param(1), (u32)(Param(3) & 0xFFFFFFFF), (u32)(Param(3) & 0xFFFFFFFF)).raw);
|
||||
func(Param(0), Param(1), static_cast<u32>(Param(3)), static_cast<u32>(Param(3))).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32, u64, u32)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func((u32)Param(0), Param(1), (u32)Param(2)).raw);
|
||||
FuncReturn(func(static_cast<u32>(Param(0)), Param(1), static_cast<u32>(Param(2))).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u64, u64, u64)>
|
||||
@@ -115,25 +123,28 @@ void SvcWrap() {
|
||||
|
||||
template <ResultCode func(u32, u64, u64, u32)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func((u32)Param(0), Param(1), Param(2), (u32)Param(3)).raw);
|
||||
FuncReturn(
|
||||
func(static_cast<u32>(Param(0)), Param(1), Param(2), static_cast<u32>(Param(3))).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32, u64, u64)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func((u32)Param(0), Param(1), Param(2)).raw);
|
||||
FuncReturn(func(static_cast<u32>(Param(0)), Param(1), Param(2)).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u32*, u64, u64, s64)>
|
||||
void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
ResultCode retval = func(¶m_1, Param(1), (u32)(Param(2) & 0xFFFFFFFF), (s64)Param(3));
|
||||
ResultCode retval =
|
||||
func(¶m_1, Param(1), static_cast<u32>(Param(2)), static_cast<s64>(Param(3)));
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval.raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u64, u64, u32, s64)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func(Param(0), Param(1), (u32)Param(2), (s64)Param(3)).raw);
|
||||
FuncReturn(
|
||||
func(Param(0), Param(1), static_cast<u32>(Param(2)), static_cast<s64>(Param(3))).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u64*, u64, u64, u64)>
|
||||
@@ -147,9 +158,9 @@ void SvcWrap() {
|
||||
template <ResultCode func(u32*, u64, u64, u64, u32, s32)>
|
||||
void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
u32 retval =
|
||||
func(¶m_1, Param(1), Param(2), Param(3), (u32)Param(4), (s32)(Param(5) & 0xFFFFFFFF))
|
||||
.raw;
|
||||
u32 retval = func(¶m_1, Param(1), Param(2), Param(3), static_cast<u32>(Param(4)),
|
||||
static_cast<s32>(Param(5)))
|
||||
.raw;
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
@@ -172,7 +183,7 @@ void SvcWrap() {
|
||||
template <ResultCode func(u32*, u64, u64, u32)>
|
||||
void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
u32 retval = func(¶m_1, Param(1), Param(2), (u32)(Param(3) & 0xFFFFFFFF)).raw;
|
||||
u32 retval = func(¶m_1, Param(1), Param(2), static_cast<u32>(Param(3))).raw;
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
@@ -181,22 +192,22 @@ template <ResultCode func(Handle*, u64, u32, u32)>
|
||||
void SvcWrap() {
|
||||
u32 param_1 = 0;
|
||||
u32 retval =
|
||||
func(¶m_1, Param(1), (u32)(Param(2) & 0xFFFFFFFF), (u32)(Param(3) & 0xFFFFFFFF)).raw;
|
||||
func(¶m_1, Param(1), static_cast<u32>(Param(2)), static_cast<u32>(Param(3))).raw;
|
||||
Core::CurrentArmInterface().SetReg(1, param_1);
|
||||
FuncReturn(retval);
|
||||
}
|
||||
|
||||
template <ResultCode func(u64, u32, s32, s64)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(
|
||||
func(Param(0), (u32)(Param(1) & 0xFFFFFFFF), (s32)(Param(2) & 0xFFFFFFFF), (s64)Param(3))
|
||||
.raw);
|
||||
FuncReturn(func(Param(0), static_cast<u32>(Param(1)), static_cast<s32>(Param(2)),
|
||||
static_cast<s64>(Param(3)))
|
||||
.raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(u64, u32, s32, s32)>
|
||||
void SvcWrap() {
|
||||
FuncReturn(func(Param(0), (u32)(Param(1) & 0xFFFFFFFF), (s32)(Param(2) & 0xFFFFFFFF),
|
||||
(s32)(Param(3) & 0xFFFFFFFF))
|
||||
FuncReturn(func(Param(0), static_cast<u32>(Param(1)), static_cast<s32>(Param(2)),
|
||||
static_cast<s32>(Param(3)))
|
||||
.raw);
|
||||
}
|
||||
|
||||
@@ -226,7 +237,7 @@ void SvcWrap() {
|
||||
|
||||
template <void func(s64)>
|
||||
void SvcWrap() {
|
||||
func((s64)Param(0));
|
||||
func(static_cast<s64>(Param(0)));
|
||||
}
|
||||
|
||||
template <void func(u64, u64 len)>
|
||||
@@ -239,4 +250,9 @@ void SvcWrap() {
|
||||
func(Param(0), Param(1), Param(2));
|
||||
}
|
||||
|
||||
template <void func(u32, u64, u64)>
|
||||
void SvcWrap() {
|
||||
func(static_cast<u32>(Param(0)), Param(1), Param(2));
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -97,7 +97,7 @@ void Thread::CancelWakeupTimer() {
|
||||
static boost::optional<s32> GetNextProcessorId(u64 mask) {
|
||||
for (s32 index = 0; index < Core::NUM_CPU_CORES; ++index) {
|
||||
if (mask & (1ULL << index)) {
|
||||
if (!Core::System::GetInstance().Scheduler(index)->GetCurrentThread()) {
|
||||
if (!Core::System::GetInstance().Scheduler(index).GetCurrentThread()) {
|
||||
// Core is enabled and not running any threads, use this one
|
||||
return index;
|
||||
}
|
||||
@@ -147,14 +147,14 @@ void Thread::ResumeFromWait() {
|
||||
new_processor_id = processor_id;
|
||||
}
|
||||
if (ideal_core != -1 &&
|
||||
Core::System::GetInstance().Scheduler(ideal_core)->GetCurrentThread() == nullptr) {
|
||||
Core::System::GetInstance().Scheduler(ideal_core).GetCurrentThread() == nullptr) {
|
||||
new_processor_id = ideal_core;
|
||||
}
|
||||
|
||||
ASSERT(*new_processor_id < 4);
|
||||
|
||||
// Add thread to new core's scheduler
|
||||
auto& next_scheduler = Core::System::GetInstance().Scheduler(*new_processor_id);
|
||||
auto* next_scheduler = &Core::System::GetInstance().Scheduler(*new_processor_id);
|
||||
|
||||
if (*new_processor_id != processor_id) {
|
||||
// Remove thread from previous core's scheduler
|
||||
@@ -169,7 +169,7 @@ void Thread::ResumeFromWait() {
|
||||
next_scheduler->ScheduleThread(this, current_priority);
|
||||
|
||||
// Change thread's scheduler
|
||||
scheduler = next_scheduler.get();
|
||||
scheduler = next_scheduler;
|
||||
|
||||
Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
|
||||
}
|
||||
@@ -183,13 +183,10 @@ void Thread::ResumeFromWait() {
|
||||
*/
|
||||
static void ResetThreadContext(Core::ARM_Interface::ThreadContext& context, VAddr stack_top,
|
||||
VAddr entry_point, u64 arg) {
|
||||
memset(&context, 0, sizeof(Core::ARM_Interface::ThreadContext));
|
||||
|
||||
context = {};
|
||||
context.cpu_registers[0] = arg;
|
||||
context.pc = entry_point;
|
||||
context.sp = stack_top;
|
||||
context.pstate = 0;
|
||||
context.fpcr = 0;
|
||||
}
|
||||
|
||||
ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name, VAddr entry_point,
|
||||
@@ -233,7 +230,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
|
||||
thread->name = std::move(name);
|
||||
thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap();
|
||||
thread->owner_process = &owner_process;
|
||||
thread->scheduler = Core::System::GetInstance().Scheduler(processor_id).get();
|
||||
thread->scheduler = &Core::System::GetInstance().Scheduler(processor_id);
|
||||
thread->scheduler->AddThread(thread, priority);
|
||||
thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread);
|
||||
|
||||
@@ -378,14 +375,14 @@ void Thread::ChangeCore(u32 core, u64 mask) {
|
||||
new_processor_id = processor_id;
|
||||
}
|
||||
if (ideal_core != -1 &&
|
||||
Core::System::GetInstance().Scheduler(ideal_core)->GetCurrentThread() == nullptr) {
|
||||
Core::System::GetInstance().Scheduler(ideal_core).GetCurrentThread() == nullptr) {
|
||||
new_processor_id = ideal_core;
|
||||
}
|
||||
|
||||
ASSERT(*new_processor_id < 4);
|
||||
|
||||
// Add thread to new core's scheduler
|
||||
auto& next_scheduler = Core::System::GetInstance().Scheduler(*new_processor_id);
|
||||
auto* next_scheduler = &Core::System::GetInstance().Scheduler(*new_processor_id);
|
||||
|
||||
if (*new_processor_id != processor_id) {
|
||||
// Remove thread from previous core's scheduler
|
||||
@@ -400,7 +397,7 @@ void Thread::ChangeCore(u32 core, u64 mask) {
|
||||
next_scheduler->ScheduleThread(this, current_priority);
|
||||
|
||||
// Change thread's scheduler
|
||||
scheduler = next_scheduler.get();
|
||||
scheduler = next_scheduler;
|
||||
|
||||
Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
|
||||
}
|
||||
|
||||
@@ -393,30 +393,35 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty
|
||||
|
||||
switch (type) {
|
||||
case FileSys::ProgramAddressSpaceType::Is32Bit:
|
||||
case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
|
||||
address_space_width = 32;
|
||||
code_region_base = 0x200000;
|
||||
code_region_end = code_region_base + 0x3FE00000;
|
||||
map_region_size = 0x40000000;
|
||||
heap_region_size = 0x40000000;
|
||||
aslr_region_base = 0x200000;
|
||||
aslr_region_end = aslr_region_base + 0xFFE00000;
|
||||
if (type == FileSys::ProgramAddressSpaceType::Is32Bit) {
|
||||
map_region_size = 0x40000000;
|
||||
heap_region_size = 0x40000000;
|
||||
} else {
|
||||
map_region_size = 0;
|
||||
heap_region_size = 0x80000000;
|
||||
}
|
||||
break;
|
||||
case FileSys::ProgramAddressSpaceType::Is36Bit:
|
||||
address_space_width = 36;
|
||||
code_region_base = 0x8000000;
|
||||
code_region_end = code_region_base + 0x78000000;
|
||||
aslr_region_base = 0x8000000;
|
||||
aslr_region_end = aslr_region_base + 0xFF8000000;
|
||||
map_region_size = 0x180000000;
|
||||
heap_region_size = 0x180000000;
|
||||
break;
|
||||
case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
|
||||
address_space_width = 32;
|
||||
code_region_base = 0x200000;
|
||||
code_region_end = code_region_base + 0x3FE00000;
|
||||
map_region_size = 0;
|
||||
heap_region_size = 0x80000000;
|
||||
break;
|
||||
case FileSys::ProgramAddressSpaceType::Is39Bit:
|
||||
address_space_width = 39;
|
||||
code_region_base = 0x8000000;
|
||||
code_region_end = code_region_base + 0x80000000;
|
||||
aslr_region_base = 0x8000000;
|
||||
aslr_region_end = aslr_region_base + 0x7FF8000000;
|
||||
map_region_size = 0x1000000000;
|
||||
heap_region_size = 0x180000000;
|
||||
new_map_region_size = 0x80000000;
|
||||
@@ -490,6 +495,18 @@ u64 VMManager::GetAddressSpaceWidth() const {
|
||||
return address_space_width;
|
||||
}
|
||||
|
||||
VAddr VMManager::GetASLRRegionBaseAddress() const {
|
||||
return aslr_region_base;
|
||||
}
|
||||
|
||||
VAddr VMManager::GetASLRRegionEndAddress() const {
|
||||
return aslr_region_end;
|
||||
}
|
||||
|
||||
u64 VMManager::GetASLRRegionSize() const {
|
||||
return aslr_region_end - aslr_region_base;
|
||||
}
|
||||
|
||||
VAddr VMManager::GetCodeRegionBaseAddress() const {
|
||||
return code_region_base;
|
||||
}
|
||||
|
||||
@@ -205,6 +205,15 @@ public:
|
||||
/// Gets the address space width in bits.
|
||||
u64 GetAddressSpaceWidth() const;
|
||||
|
||||
/// Gets the base address of the ASLR region.
|
||||
VAddr GetASLRRegionBaseAddress() const;
|
||||
|
||||
/// Gets the end address of the ASLR region.
|
||||
VAddr GetASLRRegionEndAddress() const;
|
||||
|
||||
/// Gets the size of the ASLR region
|
||||
u64 GetASLRRegionSize() const;
|
||||
|
||||
/// Gets the base address of the code region.
|
||||
VAddr GetCodeRegionBaseAddress() const;
|
||||
|
||||
@@ -306,6 +315,9 @@ private:
|
||||
VAddr address_space_base = 0;
|
||||
VAddr address_space_end = 0;
|
||||
|
||||
VAddr aslr_region_base = 0;
|
||||
VAddr aslr_region_end = 0;
|
||||
|
||||
VAddr code_region_base = 0;
|
||||
VAddr code_region_end = 0;
|
||||
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
#include <vector>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
@@ -19,7 +21,7 @@
|
||||
namespace Service::AOC {
|
||||
|
||||
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
|
||||
constexpr u64 DLC_BASE_TO_AOC_ID_MASK = 0x1000;
|
||||
constexpr u64 DLC_BASE_TO_AOC_ID = 0x1000;
|
||||
|
||||
static bool CheckAOCTitleIDMatchesBase(u64 base, u64 aoc) {
|
||||
return (aoc & DLC_BASE_TITLE_ID_MASK) == base;
|
||||
@@ -97,14 +99,24 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
ctx.WriteBuffer(out);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(count);
|
||||
}
|
||||
|
||||
void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(Core::System::GetInstance().CurrentProcess()->GetTitleID() | DLC_BASE_TO_AOC_ID_MASK);
|
||||
const auto title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
|
||||
FileSys::PatchManager pm{title_id};
|
||||
|
||||
const auto res = pm.GetControlMetadata();
|
||||
if (res.first == nullptr) {
|
||||
rb.Push(title_id + DLC_BASE_TO_AOC_ID);
|
||||
return;
|
||||
}
|
||||
|
||||
rb.Push(res.first->GetDLCBaseTitleId());
|
||||
}
|
||||
|
||||
void AOC_U::PrepareAddOnContent(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <opus.h>
|
||||
@@ -33,7 +35,8 @@ public:
|
||||
{1, nullptr, "SetContext"},
|
||||
{2, nullptr, "DecodeInterleavedForMultiStream"},
|
||||
{3, nullptr, "SetContextForMultiStream"},
|
||||
{4, nullptr, "Unknown4"},
|
||||
{4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerformance,
|
||||
"DecodeInterleavedWithPerformance"},
|
||||
{5, nullptr, "Unknown5"},
|
||||
{6, nullptr, "Unknown6"},
|
||||
{7, nullptr, "Unknown7"},
|
||||
@@ -59,8 +62,31 @@ private:
|
||||
ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16));
|
||||
}
|
||||
|
||||
bool Decoder_DecodeInterleaved(u32& consumed, u32& sample_count, const std::vector<u8>& input,
|
||||
std::vector<opus_int16>& output) {
|
||||
void DecodeInterleavedWithPerformance(Kernel::HLERequestContext& ctx) {
|
||||
u32 consumed = 0;
|
||||
u32 sample_count = 0;
|
||||
u64 performance = 0;
|
||||
std::vector<opus_int16> samples(ctx.GetWriteBufferSize() / sizeof(opus_int16));
|
||||
if (!Decoder_DecodeInterleaved(consumed, sample_count, ctx.ReadBuffer(), samples,
|
||||
performance)) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(ogniK): Use correct error code
|
||||
rb.Push(ResultCode(-1));
|
||||
return;
|
||||
}
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(consumed);
|
||||
rb.Push<u64>(performance);
|
||||
rb.Push<u32>(sample_count);
|
||||
ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16));
|
||||
}
|
||||
|
||||
bool Decoder_DecodeInterleaved(
|
||||
u32& consumed, u32& sample_count, const std::vector<u8>& input,
|
||||
std::vector<opus_int16>& output,
|
||||
std::optional<std::reference_wrapper<u64>> performance_time = std::nullopt) {
|
||||
const auto start_time = std::chrono::high_resolution_clock::now();
|
||||
std::size_t raw_output_sz = output.size() * sizeof(opus_int16);
|
||||
if (sizeof(OpusHeader) > input.size())
|
||||
return false;
|
||||
@@ -80,8 +106,13 @@ private:
|
||||
(static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)), 0);
|
||||
if (out_sample_count < 0)
|
||||
return false;
|
||||
const auto end_time = std::chrono::high_resolution_clock::now() - start_time;
|
||||
sample_count = out_sample_count;
|
||||
consumed = static_cast<u32>(sizeof(OpusHeader) + hdr.sz);
|
||||
if (performance_time.has_value()) {
|
||||
performance_time->get() =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -319,13 +319,12 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() {
|
||||
return sdmc_factory->Open();
|
||||
}
|
||||
|
||||
std::shared_ptr<FileSys::RegisteredCacheUnion> GetUnionContents() {
|
||||
return std::make_shared<FileSys::RegisteredCacheUnion>(
|
||||
std::vector<std::shared_ptr<FileSys::RegisteredCache>>{
|
||||
GetSystemNANDContents(), GetUserNANDContents(), GetSDMCContents()});
|
||||
std::unique_ptr<FileSys::RegisteredCacheUnion> GetUnionContents() {
|
||||
return std::make_unique<FileSys::RegisteredCacheUnion>(std::vector<FileSys::RegisteredCache*>{
|
||||
GetSystemNANDContents(), GetUserNANDContents(), GetSDMCContents()});
|
||||
}
|
||||
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() {
|
||||
FileSys::RegisteredCache* GetSystemNANDContents() {
|
||||
LOG_TRACE(Service_FS, "Opening System NAND Contents");
|
||||
|
||||
if (bis_factory == nullptr)
|
||||
@@ -334,7 +333,7 @@ std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() {
|
||||
return bis_factory->GetSystemNANDContents();
|
||||
}
|
||||
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() {
|
||||
FileSys::RegisteredCache* GetUserNANDContents() {
|
||||
LOG_TRACE(Service_FS, "Opening User NAND Contents");
|
||||
|
||||
if (bis_factory == nullptr)
|
||||
@@ -343,7 +342,7 @@ std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() {
|
||||
return bis_factory->GetUserNANDContents();
|
||||
}
|
||||
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents() {
|
||||
FileSys::RegisteredCache* GetSDMCContents() {
|
||||
LOG_TRACE(Service_FS, "Opening SDMC Contents");
|
||||
|
||||
if (sdmc_factory == nullptr)
|
||||
@@ -361,19 +360,19 @@ FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) {
|
||||
return bis_factory->GetModificationLoadRoot(title_id);
|
||||
}
|
||||
|
||||
void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) {
|
||||
void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
|
||||
if (overwrite) {
|
||||
bis_factory = nullptr;
|
||||
save_data_factory = nullptr;
|
||||
sdmc_factory = nullptr;
|
||||
}
|
||||
|
||||
auto nand_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir),
|
||||
FileSys::Mode::ReadWrite);
|
||||
auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),
|
||||
FileSys::Mode::ReadWrite);
|
||||
auto load_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
|
||||
FileSys::Mode::ReadWrite);
|
||||
auto nand_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir),
|
||||
FileSys::Mode::ReadWrite);
|
||||
auto sd_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),
|
||||
FileSys::Mode::ReadWrite);
|
||||
auto load_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
|
||||
FileSys::Mode::ReadWrite);
|
||||
|
||||
if (bis_factory == nullptr)
|
||||
bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory, load_directory);
|
||||
@@ -383,7 +382,7 @@ void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) {
|
||||
sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
|
||||
}
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs) {
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager, FileSys::VfsFilesystem& vfs) {
|
||||
romfs_factory = nullptr;
|
||||
CreateFactories(vfs, false);
|
||||
std::make_shared<FSP_LDR>()->InstallAsService(service_manager);
|
||||
|
||||
@@ -47,19 +47,19 @@ ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
|
||||
FileSys::SaveDataDescriptor save_struct);
|
||||
ResultVal<FileSys::VirtualDir> OpenSDMC();
|
||||
|
||||
std::shared_ptr<FileSys::RegisteredCacheUnion> GetUnionContents();
|
||||
std::unique_ptr<FileSys::RegisteredCacheUnion> GetUnionContents();
|
||||
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents();
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents();
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents();
|
||||
FileSys::RegisteredCache* GetSystemNANDContents();
|
||||
FileSys::RegisteredCache* GetUserNANDContents();
|
||||
FileSys::RegisteredCache* GetSDMCContents();
|
||||
|
||||
FileSys::VirtualDir GetModificationLoadRoot(u64 title_id);
|
||||
|
||||
// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
|
||||
// above is called.
|
||||
void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite = true);
|
||||
void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs);
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager, FileSys::VfsFilesystem& vfs);
|
||||
|
||||
// A class that wraps a VfsDirectory with methods that return ResultVal and ResultCode instead of
|
||||
// pointers and booleans. This makes using a VfsDirectory with switch services much easier and
|
||||
|
||||
28
src/core/hle/service/hid/controllers/controller_base.cpp
Normal file
28
src/core/hle/service/hid/controllers/controller_base.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/hle/service/hid/controllers/controller_base.h"
|
||||
|
||||
namespace Service::HID {
|
||||
ControllerBase::~ControllerBase() = default;
|
||||
|
||||
void ControllerBase::ActivateController() {
|
||||
if (is_activated) {
|
||||
OnRelease();
|
||||
}
|
||||
is_activated = true;
|
||||
OnInit();
|
||||
}
|
||||
|
||||
void ControllerBase::DeactivateController() {
|
||||
if (is_activated) {
|
||||
OnRelease();
|
||||
}
|
||||
is_activated = false;
|
||||
}
|
||||
|
||||
bool ControllerBase::IsControllerActivated() const {
|
||||
return is_activated;
|
||||
}
|
||||
} // namespace Service::HID
|
||||
45
src/core/hle/service/hid/controllers/controller_base.h
Normal file
45
src/core/hle/service/hid/controllers/controller_base.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
|
||||
namespace Service::HID {
|
||||
class ControllerBase {
|
||||
public:
|
||||
ControllerBase() = default;
|
||||
virtual ~ControllerBase() = 0;
|
||||
|
||||
// Called when the controller is initialized
|
||||
virtual void OnInit() = 0;
|
||||
|
||||
// When the controller is released
|
||||
virtual void OnRelease() = 0;
|
||||
|
||||
// When the controller is requesting an update for the shared memory
|
||||
virtual void OnUpdate(u8* data, std::size_t size) = 0;
|
||||
|
||||
// Called when input devices should be loaded
|
||||
virtual void OnLoadInputDevices() = 0;
|
||||
|
||||
void ActivateController();
|
||||
|
||||
void DeactivateController();
|
||||
|
||||
bool IsControllerActivated() const;
|
||||
|
||||
protected:
|
||||
bool is_activated{false};
|
||||
|
||||
struct CommonHeader {
|
||||
s64_le timestamp;
|
||||
s64_le total_entry_count;
|
||||
s64_le last_entry_index;
|
||||
s64_le entry_count;
|
||||
};
|
||||
static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
|
||||
};
|
||||
} // namespace Service::HID
|
||||
42
src/core/hle/service/hid/controllers/debug_pad.cpp
Normal file
42
src/core/hle/service/hid/controllers/debug_pad.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/service/hid/controllers/debug_pad.h"
|
||||
|
||||
namespace Service::HID {
|
||||
|
||||
Controller_DebugPad::Controller_DebugPad() = default;
|
||||
|
||||
void Controller_DebugPad::OnInit() {}
|
||||
|
||||
void Controller_DebugPad::OnRelease() {}
|
||||
|
||||
void Controller_DebugPad::OnUpdate(u8* data, std::size_t size) {
|
||||
shared_memory.header.timestamp = CoreTiming::GetTicks();
|
||||
shared_memory.header.total_entry_count = 17;
|
||||
|
||||
if (!IsControllerActivated()) {
|
||||
shared_memory.header.entry_count = 0;
|
||||
shared_memory.header.last_entry_index = 0;
|
||||
return;
|
||||
}
|
||||
shared_memory.header.entry_count = 16;
|
||||
|
||||
const auto& last_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
|
||||
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
|
||||
auto& cur_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
|
||||
|
||||
cur_entry.sampling_number = last_entry.sampling_number + 1;
|
||||
cur_entry.sampling_number2 = cur_entry.sampling_number;
|
||||
// TODO(ogniK): Update debug pad states
|
||||
|
||||
std::memcpy(data, &shared_memory, sizeof(SharedMemory));
|
||||
}
|
||||
|
||||
void Controller_DebugPad::OnLoadInputDevices() {}
|
||||
} // namespace Service::HID
|
||||
55
src/core/hle/service/hid/controllers/debug_pad.h
Normal file
55
src/core/hle/service/hid/controllers/debug_pad.h
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/service/hid/controllers/controller_base.h"
|
||||
|
||||
namespace Service::HID {
|
||||
class Controller_DebugPad final : public ControllerBase {
|
||||
public:
|
||||
Controller_DebugPad();
|
||||
|
||||
// Called when the controller is initialized
|
||||
void OnInit() override;
|
||||
|
||||
// When the controller is released
|
||||
void OnRelease() override;
|
||||
|
||||
// When the controller is requesting an update for the shared memory
|
||||
void OnUpdate(u8* data, std::size_t size) override;
|
||||
|
||||
// Called when input devices should be loaded
|
||||
void OnLoadInputDevices() override;
|
||||
|
||||
private:
|
||||
struct AnalogStick {
|
||||
s32_le x;
|
||||
s32_le y;
|
||||
};
|
||||
static_assert(sizeof(AnalogStick) == 0x8);
|
||||
|
||||
struct PadStates {
|
||||
s64_le sampling_number;
|
||||
s64_le sampling_number2;
|
||||
u32_le attribute;
|
||||
u32_le button_state;
|
||||
AnalogStick r_stick;
|
||||
AnalogStick l_stick;
|
||||
};
|
||||
static_assert(sizeof(PadStates) == 0x28, "PadStates is an invalid state");
|
||||
|
||||
struct SharedMemory {
|
||||
CommonHeader header;
|
||||
std::array<PadStates, 17> pad_states;
|
||||
INSERT_PADDING_BYTES(0x138);
|
||||
};
|
||||
static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size");
|
||||
SharedMemory shared_memory{};
|
||||
};
|
||||
} // namespace Service::HID
|
||||
43
src/core/hle/service/hid/controllers/gesture.cpp
Normal file
43
src/core/hle/service/hid/controllers/gesture.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/service/hid/controllers/gesture.h"
|
||||
|
||||
namespace Service::HID {
|
||||
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3BA00;
|
||||
|
||||
Controller_Gesture::Controller_Gesture() = default;
|
||||
|
||||
void Controller_Gesture::OnInit() {}
|
||||
|
||||
void Controller_Gesture::OnRelease() {}
|
||||
|
||||
void Controller_Gesture::OnUpdate(u8* data, std::size_t size) {
|
||||
shared_memory.header.timestamp = CoreTiming::GetTicks();
|
||||
shared_memory.header.total_entry_count = 17;
|
||||
|
||||
if (!IsControllerActivated()) {
|
||||
shared_memory.header.entry_count = 0;
|
||||
shared_memory.header.last_entry_index = 0;
|
||||
return;
|
||||
}
|
||||
shared_memory.header.entry_count = 16;
|
||||
|
||||
const auto& last_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
|
||||
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
|
||||
auto& cur_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
|
||||
|
||||
cur_entry.sampling_number = last_entry.sampling_number + 1;
|
||||
cur_entry.sampling_number2 = cur_entry.sampling_number;
|
||||
// TODO(ogniK): Update gesture states
|
||||
|
||||
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
|
||||
}
|
||||
|
||||
void Controller_Gesture::OnLoadInputDevices() {}
|
||||
} // namespace Service::HID
|
||||
62
src/core/hle/service/hid/controllers/gesture.h
Normal file
62
src/core/hle/service/hid/controllers/gesture.h
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/service/hid/controllers/controller_base.h"
|
||||
|
||||
namespace Service::HID {
|
||||
class Controller_Gesture final : public ControllerBase {
|
||||
public:
|
||||
Controller_Gesture();
|
||||
|
||||
// Called when the controller is initialized
|
||||
void OnInit() override;
|
||||
|
||||
// When the controller is released
|
||||
void OnRelease() override;
|
||||
|
||||
// When the controller is requesting an update for the shared memory
|
||||
void OnUpdate(u8* data, size_t size) override;
|
||||
|
||||
// Called when input devices should be loaded
|
||||
void OnLoadInputDevices() override;
|
||||
|
||||
private:
|
||||
struct Locations {
|
||||
s32_le x;
|
||||
s32_le y;
|
||||
};
|
||||
|
||||
struct GestureState {
|
||||
s64_le sampling_number;
|
||||
s64_le sampling_number2;
|
||||
|
||||
s64_le detection_count;
|
||||
s32_le type;
|
||||
s32_le dir;
|
||||
s32_le x;
|
||||
s32_le y;
|
||||
s32_le delta_x;
|
||||
s32_le delta_y;
|
||||
f32 vel_x;
|
||||
f32 vel_y;
|
||||
s32_le attributes;
|
||||
f32 scale;
|
||||
f32 rotation;
|
||||
s32_le location_count;
|
||||
std::array<Locations, 4> locations;
|
||||
};
|
||||
static_assert(sizeof(GestureState) == 0x68, "GestureState is an invalid size");
|
||||
|
||||
struct SharedMemory {
|
||||
CommonHeader header;
|
||||
std::array<GestureState, 17> gesture_states;
|
||||
};
|
||||
SharedMemory shared_memory{};
|
||||
};
|
||||
} // namespace Service::HID
|
||||
43
src/core/hle/service/hid/controllers/keyboard.cpp
Normal file
43
src/core/hle/service/hid/controllers/keyboard.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/service/hid/controllers/keyboard.h"
|
||||
|
||||
namespace Service::HID {
|
||||
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800;
|
||||
|
||||
Controller_Keyboard::Controller_Keyboard() = default;
|
||||
|
||||
void Controller_Keyboard::OnInit() {}
|
||||
|
||||
void Controller_Keyboard::OnRelease() {}
|
||||
|
||||
void Controller_Keyboard::OnUpdate(u8* data, std::size_t size) {
|
||||
shared_memory.header.timestamp = CoreTiming::GetTicks();
|
||||
shared_memory.header.total_entry_count = 17;
|
||||
|
||||
if (!IsControllerActivated()) {
|
||||
shared_memory.header.entry_count = 0;
|
||||
shared_memory.header.last_entry_index = 0;
|
||||
return;
|
||||
}
|
||||
shared_memory.header.entry_count = 16;
|
||||
|
||||
const auto& last_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
|
||||
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
|
||||
auto& cur_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
|
||||
|
||||
cur_entry.sampling_number = last_entry.sampling_number + 1;
|
||||
cur_entry.sampling_number2 = cur_entry.sampling_number;
|
||||
// TODO(ogniK): Update keyboard states
|
||||
|
||||
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
|
||||
}
|
||||
|
||||
void Controller_Keyboard::OnLoadInputDevices() {}
|
||||
} // namespace Service::HID
|
||||
49
src/core/hle/service/hid/controllers/keyboard.h
Normal file
49
src/core/hle/service/hid/controllers/keyboard.h
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/service/hid/controllers/controller_base.h"
|
||||
|
||||
namespace Service::HID {
|
||||
class Controller_Keyboard final : public ControllerBase {
|
||||
public:
|
||||
Controller_Keyboard();
|
||||
|
||||
// Called when the controller is initialized
|
||||
void OnInit() override;
|
||||
|
||||
// When the controller is released
|
||||
void OnRelease() override;
|
||||
|
||||
// When the controller is requesting an update for the shared memory
|
||||
void OnUpdate(u8* data, std::size_t size) override;
|
||||
|
||||
// Called when input devices should be loaded
|
||||
void OnLoadInputDevices() override;
|
||||
|
||||
private:
|
||||
struct KeyboardState {
|
||||
s64_le sampling_number;
|
||||
s64_le sampling_number2;
|
||||
|
||||
s32_le modifier;
|
||||
s32_le attribute;
|
||||
std::array<u8, 32> key;
|
||||
};
|
||||
static_assert(sizeof(KeyboardState) == 0x38, "KeyboardState is an invalid size");
|
||||
|
||||
struct SharedMemory {
|
||||
CommonHeader header;
|
||||
std::array<KeyboardState, 17> pad_states;
|
||||
INSERT_PADDING_BYTES(0x28);
|
||||
};
|
||||
static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size");
|
||||
SharedMemory shared_memory{};
|
||||
};
|
||||
} // namespace Service::HID
|
||||
43
src/core/hle/service/hid/controllers/mouse.cpp
Normal file
43
src/core/hle/service/hid/controllers/mouse.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/service/hid/controllers/mouse.h"
|
||||
|
||||
namespace Service::HID {
|
||||
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3400;
|
||||
|
||||
Controller_Mouse::Controller_Mouse() = default;
|
||||
|
||||
void Controller_Mouse::OnInit() {}
|
||||
|
||||
void Controller_Mouse::OnRelease() {}
|
||||
|
||||
void Controller_Mouse::OnUpdate(u8* data, std::size_t size) {
|
||||
shared_memory.header.timestamp = CoreTiming::GetTicks();
|
||||
shared_memory.header.total_entry_count = 17;
|
||||
|
||||
if (!IsControllerActivated()) {
|
||||
shared_memory.header.entry_count = 0;
|
||||
shared_memory.header.last_entry_index = 0;
|
||||
return;
|
||||
}
|
||||
shared_memory.header.entry_count = 16;
|
||||
|
||||
auto& last_entry = shared_memory.mouse_states[shared_memory.header.last_entry_index];
|
||||
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
|
||||
auto& cur_entry = shared_memory.mouse_states[shared_memory.header.last_entry_index];
|
||||
|
||||
cur_entry.sampling_number = last_entry.sampling_number + 1;
|
||||
cur_entry.sampling_number2 = cur_entry.sampling_number;
|
||||
// TODO(ogniK): Update mouse states
|
||||
|
||||
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
|
||||
}
|
||||
|
||||
void Controller_Mouse::OnLoadInputDevices() {}
|
||||
} // namespace Service::HID
|
||||
49
src/core/hle/service/hid/controllers/mouse.h
Normal file
49
src/core/hle/service/hid/controllers/mouse.h
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/service/hid/controllers/controller_base.h"
|
||||
|
||||
namespace Service::HID {
|
||||
class Controller_Mouse final : public ControllerBase {
|
||||
public:
|
||||
Controller_Mouse();
|
||||
|
||||
// Called when the controller is initialized
|
||||
void OnInit() override;
|
||||
|
||||
// When the controller is released
|
||||
void OnRelease() override;
|
||||
|
||||
// When the controller is requesting an update for the shared memory
|
||||
void OnUpdate(u8* data, std::size_t size) override;
|
||||
|
||||
// Called when input devices should be loaded
|
||||
void OnLoadInputDevices() override;
|
||||
|
||||
private:
|
||||
struct MouseState {
|
||||
s64_le sampling_number;
|
||||
s64_le sampling_number2;
|
||||
s32_le x;
|
||||
s32_le y;
|
||||
s32_le delta_x;
|
||||
s32_le delta_y;
|
||||
s32_le mouse_wheel;
|
||||
s32_le button;
|
||||
s32_le attribute;
|
||||
};
|
||||
static_assert(sizeof(MouseState) == 0x30, "MouseState is an invalid size");
|
||||
|
||||
struct SharedMemory {
|
||||
CommonHeader header;
|
||||
std::array<MouseState, 17> mouse_states;
|
||||
};
|
||||
SharedMemory shared_memory{};
|
||||
};
|
||||
} // namespace Service::HID
|
||||
429
src/core/hle/service/hid/controllers/npad.cpp
Normal file
429
src/core/hle/service/hid/controllers/npad.cpp
Normal file
@@ -0,0 +1,429 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/frontend/input.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/service/hid/controllers/npad.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service::HID {
|
||||
constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
|
||||
constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
|
||||
constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6;
|
||||
constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E;
|
||||
constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
|
||||
constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
|
||||
constexpr std::size_t NPAD_OFFSET = 0x9A00;
|
||||
constexpr u32 BATTERY_FULL = 2;
|
||||
enum class JoystickId : std::size_t { Joystick_Left, Joystick_Right };
|
||||
|
||||
Controller_NPad::Controller_NPad() = default;
|
||||
|
||||
void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
|
||||
const auto controller_type = connected_controllers[controller_idx].type;
|
||||
auto& controller = shared_memory_entries[controller_idx];
|
||||
if (controller_type == NPadControllerType::None) {
|
||||
return;
|
||||
}
|
||||
controller.joy_styles.raw = 0; // Zero out
|
||||
controller.device_type.raw = 0;
|
||||
switch (controller_type) {
|
||||
case NPadControllerType::Handheld:
|
||||
controller.joy_styles.handheld.Assign(1);
|
||||
controller.device_type.handheld.Assign(1);
|
||||
controller.pad_assignment = NPadAssignments::Dual;
|
||||
break;
|
||||
case NPadControllerType::JoyDual:
|
||||
controller.joy_styles.joycon_dual.Assign(1);
|
||||
controller.device_type.joycon_left.Assign(1);
|
||||
controller.device_type.joycon_right.Assign(1);
|
||||
controller.pad_assignment = NPadAssignments::Dual;
|
||||
break;
|
||||
case NPadControllerType::JoyLeft:
|
||||
controller.joy_styles.joycon_left.Assign(1);
|
||||
controller.device_type.joycon_left.Assign(1);
|
||||
controller.pad_assignment = NPadAssignments::Dual;
|
||||
break;
|
||||
case NPadControllerType::JoyRight:
|
||||
controller.joy_styles.joycon_right.Assign(1);
|
||||
controller.device_type.joycon_right.Assign(1);
|
||||
controller.pad_assignment = NPadAssignments::Dual;
|
||||
break;
|
||||
case NPadControllerType::Pokeball:
|
||||
controller.joy_styles.pokeball.Assign(1);
|
||||
controller.device_type.pokeball.Assign(1);
|
||||
controller.pad_assignment = NPadAssignments::Single;
|
||||
break;
|
||||
case NPadControllerType::ProController:
|
||||
controller.joy_styles.pro_controller.Assign(1);
|
||||
controller.device_type.pro_controller.Assign(1);
|
||||
controller.pad_assignment = NPadAssignments::Single;
|
||||
break;
|
||||
}
|
||||
|
||||
controller.single_color_error = ColorReadError::ReadOk;
|
||||
controller.single_color.body_color = 0;
|
||||
controller.single_color.button_color = 0;
|
||||
|
||||
controller.dual_color_error = ColorReadError::ReadOk;
|
||||
controller.left_color.body_color = JOYCON_BODY_NEON_BLUE;
|
||||
controller.left_color.button_color = JOYCON_BUTTONS_NEON_BLUE;
|
||||
controller.right_color.body_color = JOYCON_BODY_NEON_RED;
|
||||
controller.right_color.button_color = JOYCON_BUTTONS_NEON_RED;
|
||||
|
||||
controller.properties.is_vertical.Assign(1); // TODO(ogniK): Swap joycons orientations
|
||||
controller.properties.use_plus.Assign(1);
|
||||
controller.properties.use_minus.Assign(1);
|
||||
controller.battery_level[0] = BATTERY_FULL;
|
||||
controller.battery_level[1] = BATTERY_FULL;
|
||||
controller.battery_level[2] = BATTERY_FULL;
|
||||
}
|
||||
|
||||
void Controller_NPad::OnInit() {
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
styleset_changed_event =
|
||||
Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "npad:NpadStyleSetChanged");
|
||||
|
||||
if (!IsControllerActivated())
|
||||
return;
|
||||
std::size_t controller{};
|
||||
if (style.raw == 0) {
|
||||
// We want to support all controllers
|
||||
style.handheld.Assign(1);
|
||||
style.joycon_left.Assign(1);
|
||||
style.joycon_right.Assign(1);
|
||||
style.joycon_dual.Assign(1);
|
||||
style.pro_controller.Assign(1);
|
||||
style.pokeball.Assign(1);
|
||||
}
|
||||
if (std::none_of(connected_controllers.begin(), connected_controllers.end(),
|
||||
[](const ControllerHolder& controller) { return controller.is_connected; })) {
|
||||
supported_npad_id_types.resize(npad_id_list.size());
|
||||
std::memcpy(supported_npad_id_types.data(), npad_id_list.data(),
|
||||
npad_id_list.size() * sizeof(u32));
|
||||
AddNewController(NPadControllerType::JoyDual);
|
||||
}
|
||||
}
|
||||
|
||||
void Controller_NPad::OnLoadInputDevices() {
|
||||
std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
|
||||
Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
|
||||
buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
|
||||
std::transform(Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
|
||||
Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
|
||||
sticks.begin(), Input::CreateDevice<Input::AnalogDevice>);
|
||||
}
|
||||
|
||||
void Controller_NPad::OnRelease() {}
|
||||
|
||||
void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) {
|
||||
if (!IsControllerActivated())
|
||||
return;
|
||||
for (std::size_t i = 0; i < shared_memory_entries.size(); i++) {
|
||||
auto& npad = shared_memory_entries[i];
|
||||
const std::array<NPadGeneric*, 7> controller_npads{&npad.main_controller_states,
|
||||
&npad.handheld_states,
|
||||
&npad.dual_states,
|
||||
&npad.left_joy_states,
|
||||
&npad.right_joy_states,
|
||||
&npad.pokeball_states,
|
||||
&npad.libnx};
|
||||
|
||||
for (auto* main_controller : controller_npads) {
|
||||
main_controller->common.entry_count = 16;
|
||||
main_controller->common.total_entry_count = 17;
|
||||
|
||||
const auto& last_entry =
|
||||
main_controller->npad[main_controller->common.last_entry_index];
|
||||
|
||||
main_controller->common.timestamp = CoreTiming::GetTicks();
|
||||
main_controller->common.last_entry_index =
|
||||
(main_controller->common.last_entry_index + 1) % 17;
|
||||
|
||||
auto& cur_entry = main_controller->npad[main_controller->common.last_entry_index];
|
||||
|
||||
cur_entry.timestamp = last_entry.timestamp + 1;
|
||||
cur_entry.timestamp2 = cur_entry.timestamp;
|
||||
}
|
||||
|
||||
const auto& controller_type = connected_controllers[i].type;
|
||||
|
||||
if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Pad states
|
||||
ControllerPadState pad_state{};
|
||||
using namespace Settings::NativeButton;
|
||||
pad_state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.l_stick.Assign(buttons[LStick - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.r_stick.Assign(buttons[RStick - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus());
|
||||
|
||||
pad_state.d_left.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.d_up.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.d_right.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.d_down.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus());
|
||||
|
||||
pad_state.l_stick_left.Assign(buttons[LStick_Left - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.l_stick_up.Assign(buttons[LStick_Up - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.l_stick_right.Assign(buttons[LStick_Right - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.l_stick_down.Assign(buttons[LStick_Down - BUTTON_HID_BEGIN]->GetStatus());
|
||||
|
||||
pad_state.r_stick_left.Assign(buttons[RStick_Left - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.r_stick_up.Assign(buttons[RStick_Up - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.r_stick_right.Assign(buttons[RStick_Right - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.r_stick_down.Assign(buttons[RStick_Down - BUTTON_HID_BEGIN]->GetStatus());
|
||||
|
||||
pad_state.sl.Assign(buttons[SL - BUTTON_HID_BEGIN]->GetStatus());
|
||||
pad_state.sr.Assign(buttons[SR - BUTTON_HID_BEGIN]->GetStatus());
|
||||
|
||||
AnalogPosition lstick_entry{};
|
||||
AnalogPosition rstick_entry{};
|
||||
|
||||
const auto [stick_l_x_f, stick_l_y_f] =
|
||||
sticks[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus();
|
||||
const auto [stick_r_x_f, stick_r_y_f] =
|
||||
sticks[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus();
|
||||
lstick_entry.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
|
||||
lstick_entry.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
|
||||
rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
|
||||
rstick_entry.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
|
||||
|
||||
auto& main_controller =
|
||||
npad.main_controller_states.npad[npad.main_controller_states.common.last_entry_index];
|
||||
auto& handheld_entry =
|
||||
npad.handheld_states.npad[npad.handheld_states.common.last_entry_index];
|
||||
auto& dual_entry = npad.dual_states.npad[npad.dual_states.common.last_entry_index];
|
||||
auto& left_entry = npad.left_joy_states.npad[npad.left_joy_states.common.last_entry_index];
|
||||
auto& right_entry =
|
||||
npad.right_joy_states.npad[npad.right_joy_states.common.last_entry_index];
|
||||
auto& pokeball_entry =
|
||||
npad.pokeball_states.npad[npad.pokeball_states.common.last_entry_index];
|
||||
auto& libnx_entry = npad.libnx.npad[npad.libnx.common.last_entry_index];
|
||||
|
||||
if (hold_type == NpadHoldType::Horizontal) {
|
||||
// TODO(ogniK): Remap buttons for different orientations
|
||||
}
|
||||
libnx_entry.connection_status.raw = 0;
|
||||
|
||||
switch (controller_type) {
|
||||
case NPadControllerType::Handheld:
|
||||
handheld_entry.connection_status.raw = 0;
|
||||
handheld_entry.connection_status.IsConnected.Assign(1);
|
||||
if (!Settings::values.use_docked_mode) {
|
||||
handheld_entry.connection_status.IsWired.Assign(1);
|
||||
}
|
||||
handheld_entry.pad_states.raw = pad_state.raw;
|
||||
handheld_entry.l_stick = lstick_entry;
|
||||
handheld_entry.r_stick = rstick_entry;
|
||||
break;
|
||||
case NPadControllerType::JoyDual:
|
||||
dual_entry.connection_status.raw = 0;
|
||||
|
||||
dual_entry.connection_status.IsLeftJoyConnected.Assign(1);
|
||||
dual_entry.connection_status.IsRightJoyConnected.Assign(1);
|
||||
dual_entry.connection_status.IsConnected.Assign(1);
|
||||
|
||||
libnx_entry.connection_status.IsLeftJoyConnected.Assign(1);
|
||||
libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
|
||||
libnx_entry.connection_status.IsConnected.Assign(1);
|
||||
|
||||
dual_entry.pad_states.raw = pad_state.raw;
|
||||
dual_entry.l_stick = lstick_entry;
|
||||
dual_entry.r_stick = rstick_entry;
|
||||
case NPadControllerType::JoyLeft:
|
||||
left_entry.connection_status.raw = 0;
|
||||
|
||||
left_entry.connection_status.IsConnected.Assign(1);
|
||||
left_entry.pad_states.raw = pad_state.raw;
|
||||
left_entry.l_stick = lstick_entry;
|
||||
left_entry.r_stick = rstick_entry;
|
||||
break;
|
||||
case NPadControllerType::JoyRight:
|
||||
right_entry.connection_status.raw = 0;
|
||||
|
||||
right_entry.connection_status.IsConnected.Assign(1);
|
||||
right_entry.pad_states.raw = pad_state.raw;
|
||||
right_entry.l_stick = lstick_entry;
|
||||
right_entry.r_stick = rstick_entry;
|
||||
break;
|
||||
case NPadControllerType::Pokeball:
|
||||
pokeball_entry.connection_status.raw = 0;
|
||||
|
||||
pokeball_entry.connection_status.IsConnected.Assign(1);
|
||||
pokeball_entry.connection_status.IsWired.Assign(1);
|
||||
|
||||
pokeball_entry.pad_states.raw = pad_state.raw;
|
||||
pokeball_entry.l_stick = lstick_entry;
|
||||
pokeball_entry.r_stick = rstick_entry;
|
||||
break;
|
||||
case NPadControllerType::ProController:
|
||||
main_controller.connection_status.raw = 0;
|
||||
|
||||
main_controller.connection_status.IsConnected.Assign(1);
|
||||
main_controller.connection_status.IsWired.Assign(1);
|
||||
main_controller.pad_states.raw = pad_state.raw;
|
||||
main_controller.l_stick = lstick_entry;
|
||||
main_controller.r_stick = rstick_entry;
|
||||
break;
|
||||
}
|
||||
|
||||
// LibNX exclusively uses this section, so we always update it since LibNX doesn't activate
|
||||
// any controllers.
|
||||
libnx_entry.pad_states.raw = pad_state.raw;
|
||||
libnx_entry.l_stick = lstick_entry;
|
||||
libnx_entry.r_stick = rstick_entry;
|
||||
}
|
||||
std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(),
|
||||
shared_memory_entries.size() * sizeof(NPadEntry));
|
||||
} // namespace Service::HID
|
||||
|
||||
void Controller_NPad::SetSupportedStyleSet(NPadType style_set) {
|
||||
style.raw = style_set.raw;
|
||||
}
|
||||
|
||||
Controller_NPad::NPadType Controller_NPad::GetSupportedStyleSet() const {
|
||||
return style;
|
||||
}
|
||||
|
||||
void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) {
|
||||
ASSERT(length > 0 && (length % sizeof(u32)) == 0);
|
||||
supported_npad_id_types.clear();
|
||||
supported_npad_id_types.resize(length / sizeof(u32));
|
||||
std::memcpy(supported_npad_id_types.data(), data, length);
|
||||
}
|
||||
|
||||
const void Controller_NPad::GetSupportedNpadIdTypes(u32* data, std::size_t max_length) {
|
||||
ASSERT(max_length < supported_npad_id_types.size());
|
||||
std::memcpy(data, supported_npad_id_types.data(), supported_npad_id_types.size());
|
||||
}
|
||||
|
||||
std::size_t Controller_NPad::GetSupportedNPadIdTypesSize() const {
|
||||
return supported_npad_id_types.size();
|
||||
}
|
||||
|
||||
void Controller_NPad::SetHoldType(NpadHoldType joy_hold_type) {
|
||||
hold_type = joy_hold_type;
|
||||
}
|
||||
Controller_NPad::NpadHoldType Controller_NPad::GetHoldType() const {
|
||||
return hold_type;
|
||||
}
|
||||
|
||||
void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) {
|
||||
ASSERT(npad_id < shared_memory_entries.size());
|
||||
shared_memory_entries[npad_id].pad_assignment = assignment_mode;
|
||||
}
|
||||
|
||||
void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids,
|
||||
const std::vector<Vibration>& vibrations) {
|
||||
if (!can_controllers_vibrate) {
|
||||
return;
|
||||
}
|
||||
for (std::size_t i = 0; i < controller_ids.size(); i++) {
|
||||
std::size_t controller_pos = i;
|
||||
// Handheld controller conversion
|
||||
if (controller_pos == 32) {
|
||||
controller_pos = 8;
|
||||
}
|
||||
// Unknown controller conversion
|
||||
if (controller_pos == 16) {
|
||||
controller_pos = 9;
|
||||
}
|
||||
if (connected_controllers[controller_pos].is_connected) {
|
||||
// TODO(ogniK): Vibrate the physical controller
|
||||
}
|
||||
}
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
last_processed_vibration = vibrations.back();
|
||||
}
|
||||
|
||||
Kernel::SharedPtr<Kernel::Event> Controller_NPad::GetStyleSetChangedEvent() const {
|
||||
return styleset_changed_event;
|
||||
}
|
||||
|
||||
Controller_NPad::Vibration Controller_NPad::GetLastVibration() const {
|
||||
return last_processed_vibration;
|
||||
}
|
||||
void Controller_NPad::AddNewController(NPadControllerType controller) {
|
||||
if (controller == NPadControllerType::Handheld) {
|
||||
connected_controllers[8] = {controller, true};
|
||||
InitNewlyAddedControler(8);
|
||||
return;
|
||||
}
|
||||
const auto pos =
|
||||
std::find_if(connected_controllers.begin(), connected_controllers.end() - 2,
|
||||
[](const ControllerHolder& holder) { return !holder.is_connected; });
|
||||
if (pos == connected_controllers.end() - 2) {
|
||||
LOG_ERROR(Service_HID, "Cannot connect any more controllers!");
|
||||
return;
|
||||
}
|
||||
const auto controller_id = std::distance(connected_controllers.begin(), pos);
|
||||
connected_controllers[controller_id] = {controller, true};
|
||||
InitNewlyAddedControler(controller_id);
|
||||
}
|
||||
|
||||
void Controller_NPad::ConnectNPad(u32 npad_id) {
|
||||
if (npad_id >= connected_controllers.size())
|
||||
return;
|
||||
connected_controllers[npad_id].is_connected = true;
|
||||
}
|
||||
|
||||
void Controller_NPad::DisconnectNPad(u32 npad_id) {
|
||||
if (npad_id >= connected_controllers.size())
|
||||
return;
|
||||
connected_controllers[npad_id].is_connected = false;
|
||||
}
|
||||
|
||||
Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) {
|
||||
if (npad_id == npad_id_list.back() || npad_id == npad_id_list[npad_id_list.size() - 2]) {
|
||||
// These are controllers without led patterns
|
||||
return LedPattern{0, 0, 0, 0};
|
||||
}
|
||||
switch (npad_id) {
|
||||
case 0:
|
||||
return LedPattern{1, 0, 0, 0};
|
||||
case 1:
|
||||
return LedPattern{0, 1, 0, 0};
|
||||
case 2:
|
||||
return LedPattern{0, 0, 1, 0};
|
||||
case 3:
|
||||
return LedPattern{0, 0, 0, 1};
|
||||
case 4:
|
||||
return LedPattern{1, 0, 0, 1};
|
||||
case 5:
|
||||
return LedPattern{1, 0, 1, 0};
|
||||
case 6:
|
||||
return LedPattern{1, 0, 1, 1};
|
||||
case 7:
|
||||
return LedPattern{0, 1, 1, 0};
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unhandled npad_id {}", npad_id);
|
||||
return LedPattern{0, 0, 0, 0};
|
||||
};
|
||||
}
|
||||
void Controller_NPad::SetVibrationEnabled(bool can_vibrate) {
|
||||
can_controllers_vibrate = can_vibrate;
|
||||
}
|
||||
} // namespace Service::HID
|
||||
288
src/core/hle/service/hid/controllers/npad.h
Normal file
288
src/core/hle/service/hid/controllers/npad.h
Normal file
@@ -0,0 +1,288 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include "common/common_types.h"
|
||||
#include "core/frontend/input.h"
|
||||
#include "core/hle/service/hid/controllers/controller_base.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service::HID {
|
||||
|
||||
class Controller_NPad final : public ControllerBase {
|
||||
public:
|
||||
Controller_NPad();
|
||||
|
||||
// Called when the controller is initialized
|
||||
void OnInit() override;
|
||||
|
||||
// When the controller is released
|
||||
void OnRelease() override;
|
||||
|
||||
// When the controller is requesting an update for the shared memory
|
||||
void OnUpdate(u8* data, std::size_t size) override;
|
||||
|
||||
// Called when input devices should be loaded
|
||||
void OnLoadInputDevices() override;
|
||||
|
||||
struct NPadType {
|
||||
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<6, 1, u32_le> pokeball; // TODO(ogniK): Confirm when possible
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(NPadType) == 4, "NPadType is an invalid size");
|
||||
|
||||
struct Vibration {
|
||||
f32 amp_low;
|
||||
f32 freq_low;
|
||||
f32 amp_high;
|
||||
f32 freq_high;
|
||||
};
|
||||
static_assert(sizeof(Vibration) == 0x10, "Vibration is an invalid size");
|
||||
|
||||
enum class NpadHoldType : u64 {
|
||||
Vertical = 0,
|
||||
Horizontal = 1,
|
||||
};
|
||||
|
||||
enum class NPadAssignments : u32_le {
|
||||
Dual = 0,
|
||||
Single = 1,
|
||||
};
|
||||
|
||||
enum class NPadControllerType {
|
||||
None,
|
||||
ProController,
|
||||
Handheld,
|
||||
JoyDual,
|
||||
JoyLeft,
|
||||
JoyRight,
|
||||
Pokeball,
|
||||
};
|
||||
|
||||
struct LedPattern {
|
||||
explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) {
|
||||
position1.Assign(light1);
|
||||
position1.Assign(light2);
|
||||
position1.Assign(light3);
|
||||
position1.Assign(light4);
|
||||
};
|
||||
union {
|
||||
u64 raw{};
|
||||
BitField<0, 1, u64> position1;
|
||||
BitField<1, 1, u64> position2;
|
||||
BitField<2, 1, u64> position3;
|
||||
BitField<3, 1, u64> position4;
|
||||
};
|
||||
};
|
||||
|
||||
void SetSupportedStyleSet(NPadType style_set);
|
||||
NPadType GetSupportedStyleSet() const;
|
||||
|
||||
void SetSupportedNPadIdTypes(u8* data, std::size_t length);
|
||||
const void GetSupportedNpadIdTypes(u32* data, std::size_t max_length);
|
||||
std::size_t GetSupportedNPadIdTypesSize() const;
|
||||
|
||||
void SetHoldType(NpadHoldType joy_hold_type);
|
||||
NpadHoldType GetHoldType() const;
|
||||
|
||||
void SetNpadMode(u32 npad_id, NPadAssignments assignment_mode);
|
||||
|
||||
void VibrateController(const std::vector<u32>& controller_ids,
|
||||
const std::vector<Vibration>& vibrations);
|
||||
|
||||
Kernel::SharedPtr<Kernel::Event> GetStyleSetChangedEvent() const;
|
||||
Vibration GetLastVibration() const;
|
||||
|
||||
void AddNewController(NPadControllerType controller);
|
||||
|
||||
void ConnectNPad(u32 npad_id);
|
||||
void DisconnectNPad(u32 npad_id);
|
||||
LedPattern GetLedPattern(u32 npad_id);
|
||||
void SetVibrationEnabled(bool can_vibrate);
|
||||
|
||||
private:
|
||||
struct CommonHeader {
|
||||
s64_le timestamp;
|
||||
s64_le total_entry_count;
|
||||
s64_le last_entry_index;
|
||||
s64_le entry_count;
|
||||
};
|
||||
static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
|
||||
|
||||
struct ControllerColor {
|
||||
u32_le body_color;
|
||||
u32_le button_color;
|
||||
};
|
||||
static_assert(sizeof(ControllerColor) == 8, "ControllerColor is an invalid size");
|
||||
|
||||
struct ControllerPadState {
|
||||
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;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
|
||||
// Not always active?
|
||||
BitField<24, 1, u64_le> sl;
|
||||
BitField<25, 1, u64_le> sr;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(ControllerPadState) == 8, "ControllerPadState is an invalid size");
|
||||
|
||||
struct AnalogPosition {
|
||||
s32_le x;
|
||||
s32_le y;
|
||||
};
|
||||
static_assert(sizeof(AnalogPosition) == 8, "AnalogPosition is an invalid size");
|
||||
|
||||
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;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(ConnectionState) == 4, "ConnectionState is an invalid size");
|
||||
|
||||
struct GenericStates {
|
||||
s64_le timestamp;
|
||||
s64_le timestamp2;
|
||||
ControllerPadState pad_states;
|
||||
AnalogPosition l_stick;
|
||||
AnalogPosition r_stick;
|
||||
ConnectionState connection_status;
|
||||
};
|
||||
static_assert(sizeof(GenericStates) == 0x30, "NPadGenericStates is an invalid size");
|
||||
|
||||
struct NPadGeneric {
|
||||
CommonHeader common;
|
||||
std::array<GenericStates, 17> npad;
|
||||
};
|
||||
static_assert(sizeof(NPadGeneric) == 0x350, "NPadGeneric is an invalid size");
|
||||
|
||||
enum class ColorReadError : u32_le {
|
||||
ReadOk = 0,
|
||||
ColorDoesntExist = 1,
|
||||
NoController = 2,
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
};
|
||||
|
||||
struct NPadEntry {
|
||||
NPadType joy_styles;
|
||||
NPadAssignments pad_assignment;
|
||||
|
||||
ColorReadError single_color_error;
|
||||
ControllerColor single_color;
|
||||
|
||||
ColorReadError dual_color_error;
|
||||
ControllerColor left_color;
|
||||
ControllerColor right_color;
|
||||
|
||||
NPadGeneric main_controller_states;
|
||||
NPadGeneric handheld_states;
|
||||
NPadGeneric dual_states;
|
||||
NPadGeneric left_joy_states;
|
||||
NPadGeneric right_joy_states;
|
||||
NPadGeneric pokeball_states;
|
||||
NPadGeneric libnx; // TODO(ogniK): Find out what this actually is, libnx seems to only be
|
||||
// relying on this for the time being
|
||||
INSERT_PADDING_BYTES(
|
||||
0x708 *
|
||||
6); // TODO(ogniK): SixAxis states, require more information before implementation
|
||||
NPadDevice device_type;
|
||||
NPadProperties properties;
|
||||
INSERT_PADDING_WORDS(1);
|
||||
std::array<u32, 3> battery_level;
|
||||
INSERT_PADDING_BYTES(0x5c);
|
||||
INSERT_PADDING_BYTES(0xdf8);
|
||||
};
|
||||
static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size");
|
||||
|
||||
struct ControllerHolder {
|
||||
Controller_NPad::NPadControllerType type;
|
||||
bool is_connected;
|
||||
};
|
||||
|
||||
NPadType style{};
|
||||
std::array<NPadEntry, 10> shared_memory_entries{};
|
||||
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
|
||||
buttons;
|
||||
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> sticks;
|
||||
std::vector<u32> supported_npad_id_types{};
|
||||
NpadHoldType hold_type{NpadHoldType::Vertical};
|
||||
Kernel::SharedPtr<Kernel::Event> styleset_changed_event;
|
||||
std::size_t dump_idx{};
|
||||
Vibration last_processed_vibration{};
|
||||
static constexpr std::array<u32, 10> npad_id_list{0, 1, 2, 3, 4, 5, 6, 7, 32, 16};
|
||||
std::array<ControllerHolder, 10> connected_controllers{};
|
||||
bool can_controllers_vibrate{true};
|
||||
|
||||
void InitNewlyAddedControler(std::size_t controller_idx);
|
||||
};
|
||||
} // namespace Service::HID
|
||||
39
src/core/hle/service/hid/controllers/stubbed.cpp
Normal file
39
src/core/hle/service/hid/controllers/stubbed.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/service/hid/controllers/stubbed.h"
|
||||
|
||||
namespace Service::HID {
|
||||
|
||||
Controller_Stubbed::Controller_Stubbed() = default;
|
||||
|
||||
void Controller_Stubbed::OnInit() {}
|
||||
|
||||
void Controller_Stubbed::OnRelease() {}
|
||||
|
||||
void Controller_Stubbed::OnUpdate(u8* data, std::size_t size) {
|
||||
if (!smart_update) {
|
||||
return;
|
||||
}
|
||||
|
||||
CommonHeader header{};
|
||||
header.timestamp = CoreTiming::GetTicks();
|
||||
header.total_entry_count = 17;
|
||||
header.entry_count = 0;
|
||||
header.last_entry_index = 0;
|
||||
|
||||
std::memcpy(data + common_offset, &header, sizeof(CommonHeader));
|
||||
}
|
||||
|
||||
void Controller_Stubbed::OnLoadInputDevices() {}
|
||||
|
||||
void Controller_Stubbed::SetCommonHeaderOffset(std::size_t off) {
|
||||
common_offset = off;
|
||||
smart_update = true;
|
||||
}
|
||||
} // namespace Service::HID
|
||||
33
src/core/hle/service/hid/controllers/stubbed.h
Normal file
33
src/core/hle/service/hid/controllers/stubbed.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/hid/controllers/controller_base.h"
|
||||
|
||||
namespace Service::HID {
|
||||
class Controller_Stubbed final : public ControllerBase {
|
||||
public:
|
||||
Controller_Stubbed();
|
||||
|
||||
// Called when the controller is initialized
|
||||
void OnInit() override;
|
||||
|
||||
// When the controller is released
|
||||
void OnRelease() override;
|
||||
|
||||
// When the controller is requesting an update for the shared memory
|
||||
void OnUpdate(u8* data, std::size_t size) override;
|
||||
|
||||
// Called when input devices should be loaded
|
||||
void OnLoadInputDevices() override;
|
||||
|
||||
void SetCommonHeaderOffset(std::size_t off);
|
||||
|
||||
private:
|
||||
bool smart_update{};
|
||||
std::size_t common_offset{};
|
||||
};
|
||||
} // namespace Service::HID
|
||||
65
src/core/hle/service/hid/controllers/touchscreen.cpp
Normal file
65
src/core/hle/service/hid/controllers/touchscreen.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/frontend/input.h"
|
||||
#include "core/hle/service/hid/controllers/touchscreen.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service::HID {
|
||||
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400;
|
||||
|
||||
Controller_Touchscreen::Controller_Touchscreen() = default;
|
||||
|
||||
void Controller_Touchscreen::OnInit() {}
|
||||
|
||||
void Controller_Touchscreen::OnRelease() {}
|
||||
|
||||
void Controller_Touchscreen::OnUpdate(u8* data, std::size_t size) {
|
||||
shared_memory.header.timestamp = CoreTiming::GetTicks();
|
||||
shared_memory.header.total_entry_count = 17;
|
||||
|
||||
if (!IsControllerActivated()) {
|
||||
shared_memory.header.entry_count = 0;
|
||||
shared_memory.header.last_entry_index = 0;
|
||||
return;
|
||||
}
|
||||
shared_memory.header.entry_count = 16;
|
||||
|
||||
const auto& last_entry =
|
||||
shared_memory.shared_memory_entries[shared_memory.header.last_entry_index];
|
||||
shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
|
||||
auto& cur_entry = shared_memory.shared_memory_entries[shared_memory.header.last_entry_index];
|
||||
|
||||
cur_entry.sampling_number = last_entry.sampling_number + 1;
|
||||
cur_entry.sampling_number2 = cur_entry.sampling_number;
|
||||
|
||||
const auto [x, y, pressed] = touch_device->GetStatus();
|
||||
auto& touch_entry = cur_entry.states[0];
|
||||
if (pressed) {
|
||||
touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width);
|
||||
touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height);
|
||||
touch_entry.diameter_x = 15;
|
||||
touch_entry.diameter_y = 15;
|
||||
touch_entry.rotation_angle = 0;
|
||||
const u64 tick = CoreTiming::GetTicks();
|
||||
touch_entry.delta_time = tick - last_touch;
|
||||
last_touch = tick;
|
||||
touch_entry.finger = 0;
|
||||
cur_entry.entry_count = 1;
|
||||
} else {
|
||||
cur_entry.entry_count = 0;
|
||||
}
|
||||
|
||||
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(TouchScreenSharedMemory));
|
||||
}
|
||||
|
||||
void Controller_Touchscreen::OnLoadInputDevices() {
|
||||
touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device);
|
||||
}
|
||||
} // namespace Service::HID
|
||||
62
src/core/hle/service/hid/controllers/touchscreen.h
Normal file
62
src/core/hle/service/hid/controllers/touchscreen.h
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/frontend/input.h"
|
||||
#include "core/hle/service/hid/controllers/controller_base.h"
|
||||
|
||||
namespace Service::HID {
|
||||
class Controller_Touchscreen final : public ControllerBase {
|
||||
public:
|
||||
Controller_Touchscreen();
|
||||
|
||||
// Called when the controller is initialized
|
||||
void OnInit() override;
|
||||
|
||||
// When the controller is released
|
||||
void OnRelease() override;
|
||||
|
||||
// When the controller is requesting an update for the shared memory
|
||||
void OnUpdate(u8* data, std::size_t size) override;
|
||||
|
||||
// Called when input devices should be loaded
|
||||
void OnLoadInputDevices() override;
|
||||
|
||||
private:
|
||||
struct TouchState {
|
||||
u64_le delta_time;
|
||||
u32_le attribute;
|
||||
u32_le finger;
|
||||
u32_le x;
|
||||
u32_le y;
|
||||
u32_le diameter_x;
|
||||
u32_le diameter_y;
|
||||
u32_le rotation_angle;
|
||||
};
|
||||
static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
|
||||
|
||||
struct TouchScreenEntry {
|
||||
s64_le sampling_number;
|
||||
s64_le sampling_number2;
|
||||
s32_le entry_count;
|
||||
std::array<TouchState, 16> states;
|
||||
};
|
||||
static_assert(sizeof(TouchScreenEntry) == 0x298, "TouchScreenEntry is an invalid size");
|
||||
|
||||
struct TouchScreenSharedMemory {
|
||||
CommonHeader header;
|
||||
std::array<TouchScreenEntry, 17> shared_memory_entries{};
|
||||
INSERT_PADDING_BYTES(0x3c8);
|
||||
};
|
||||
static_assert(sizeof(TouchScreenSharedMemory) == 0x3000,
|
||||
"TouchScreenSharedMemory is an invalid size");
|
||||
TouchScreenSharedMemory shared_memory{};
|
||||
std::unique_ptr<Input::TouchDevice> touch_device;
|
||||
s64_le last_touch{};
|
||||
};
|
||||
} // namespace Service::HID
|
||||
45
src/core/hle/service/hid/controllers/xpad.cpp
Normal file
45
src/core/hle/service/hid/controllers/xpad.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/service/hid/controllers/xpad.h"
|
||||
|
||||
namespace Service::HID {
|
||||
constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C00;
|
||||
|
||||
Controller_XPad::Controller_XPad() = default;
|
||||
|
||||
void Controller_XPad::OnInit() {}
|
||||
|
||||
void Controller_XPad::OnRelease() {}
|
||||
|
||||
void Controller_XPad::OnUpdate(u8* data, std::size_t size) {
|
||||
for (auto& xpad_entry : shared_memory.shared_memory_entries) {
|
||||
xpad_entry.header.timestamp = CoreTiming::GetTicks();
|
||||
xpad_entry.header.total_entry_count = 17;
|
||||
|
||||
if (!IsControllerActivated()) {
|
||||
xpad_entry.header.entry_count = 0;
|
||||
xpad_entry.header.last_entry_index = 0;
|
||||
return;
|
||||
}
|
||||
xpad_entry.header.entry_count = 16;
|
||||
|
||||
const auto& last_entry = xpad_entry.pad_states[xpad_entry.header.last_entry_index];
|
||||
xpad_entry.header.last_entry_index = (xpad_entry.header.last_entry_index + 1) % 17;
|
||||
auto& cur_entry = xpad_entry.pad_states[xpad_entry.header.last_entry_index];
|
||||
|
||||
cur_entry.sampling_number = last_entry.sampling_number + 1;
|
||||
cur_entry.sampling_number2 = cur_entry.sampling_number;
|
||||
}
|
||||
// TODO(ogniK): Update xpad states
|
||||
|
||||
std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
|
||||
}
|
||||
|
||||
void Controller_XPad::OnLoadInputDevices() {}
|
||||
} // namespace Service::HID
|
||||
60
src/core/hle/service/hid/controllers/xpad.h
Normal file
60
src/core/hle/service/hid/controllers/xpad.h
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/frontend/input.h"
|
||||
#include "core/hle/service/hid/controllers/controller_base.h"
|
||||
|
||||
namespace Service::HID {
|
||||
class Controller_XPad final : public ControllerBase {
|
||||
public:
|
||||
Controller_XPad();
|
||||
|
||||
// Called when the controller is initialized
|
||||
void OnInit() override;
|
||||
|
||||
// When the controller is released
|
||||
void OnRelease() override;
|
||||
|
||||
// When the controller is requesting an update for the shared memory
|
||||
void OnUpdate(u8* data, std::size_t size) override;
|
||||
|
||||
// Called when input devices should be loaded
|
||||
void OnLoadInputDevices() override;
|
||||
|
||||
private:
|
||||
struct AnalogStick {
|
||||
s32_le x;
|
||||
s32_le y;
|
||||
};
|
||||
static_assert(sizeof(AnalogStick) == 0x8, "AnalogStick is an invalid size");
|
||||
|
||||
struct XPadState {
|
||||
s64_le sampling_number;
|
||||
s64_le sampling_number2;
|
||||
s32_le attributes;
|
||||
u32_le pad_states;
|
||||
AnalogStick x_stick;
|
||||
AnalogStick y_stick;
|
||||
};
|
||||
static_assert(sizeof(XPadState) == 0x28, "XPadState is an invalid size");
|
||||
|
||||
struct XPadEntry {
|
||||
CommonHeader header;
|
||||
std::array<XPadState, 17> pad_states{};
|
||||
INSERT_PADDING_BYTES(0x138);
|
||||
};
|
||||
static_assert(sizeof(XPadEntry) == 0x400, "XPadEntry is an invalid size");
|
||||
|
||||
struct SharedMemory {
|
||||
std::array<XPadEntry, 4> shared_memory_entries{};
|
||||
};
|
||||
static_assert(sizeof(SharedMemory) == 0x1000, "SharedMemory is an invalid size");
|
||||
SharedMemory shared_memory{};
|
||||
};
|
||||
} // namespace Service::HID
|
||||
@@ -2,6 +2,8 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
@@ -19,6 +21,16 @@
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
#include "core/hle/service/hid/controllers/controller_base.h"
|
||||
#include "core/hle/service/hid/controllers/debug_pad.h"
|
||||
#include "core/hle/service/hid/controllers/gesture.h"
|
||||
#include "core/hle/service/hid/controllers/keyboard.h"
|
||||
#include "core/hle/service/hid/controllers/mouse.h"
|
||||
#include "core/hle/service/hid/controllers/npad.h"
|
||||
#include "core/hle/service/hid/controllers/stubbed.h"
|
||||
#include "core/hle/service/hid/controllers/touchscreen.h"
|
||||
#include "core/hle/service/hid/controllers/xpad.h"
|
||||
|
||||
namespace Service::HID {
|
||||
|
||||
// Updating period for each HID device.
|
||||
@@ -26,6 +38,22 @@ namespace Service::HID {
|
||||
constexpr u64 pad_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
|
||||
constexpr u64 accelerometer_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
|
||||
constexpr u64 gyroscope_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
|
||||
constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
|
||||
enum class HidController : std::size_t {
|
||||
DebugPad,
|
||||
Touchscreen,
|
||||
Mouse,
|
||||
Keyboard,
|
||||
XPad,
|
||||
Unknown1,
|
||||
Unknown2,
|
||||
Unknown3,
|
||||
SixAxisSensor,
|
||||
NPad,
|
||||
Gesture,
|
||||
|
||||
MaxControllers,
|
||||
};
|
||||
|
||||
class IAppletResource final : public ServiceFramework<IAppletResource> {
|
||||
public:
|
||||
@@ -37,19 +65,57 @@ public:
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
shared_mem = Kernel::SharedMemory::Create(
|
||||
kernel, nullptr, 0x40000, Kernel::MemoryPermission::ReadWrite,
|
||||
kernel, nullptr, SHARED_MEMORY_SIZE, Kernel::MemoryPermission::ReadWrite,
|
||||
Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "HID:SharedMemory");
|
||||
|
||||
MakeController<Controller_DebugPad>(HidController::DebugPad);
|
||||
MakeController<Controller_Touchscreen>(HidController::Touchscreen);
|
||||
MakeController<Controller_Mouse>(HidController::Mouse);
|
||||
MakeController<Controller_Keyboard>(HidController::Keyboard);
|
||||
MakeController<Controller_XPad>(HidController::XPad);
|
||||
MakeController<Controller_Stubbed>(HidController::Unknown1);
|
||||
MakeController<Controller_Stubbed>(HidController::Unknown2);
|
||||
MakeController<Controller_Stubbed>(HidController::Unknown3);
|
||||
MakeController<Controller_Stubbed>(HidController::SixAxisSensor);
|
||||
MakeController<Controller_NPad>(HidController::NPad);
|
||||
MakeController<Controller_Gesture>(HidController::Gesture);
|
||||
|
||||
// Homebrew doesn't try to activate some controllers, so we activate them by default
|
||||
GetController<Controller_NPad>(HidController::NPad).ActivateController();
|
||||
GetController<Controller_Touchscreen>(HidController::Touchscreen).ActivateController();
|
||||
|
||||
GetController<Controller_Stubbed>(HidController::Unknown1).SetCommonHeaderOffset(0x4c00);
|
||||
GetController<Controller_Stubbed>(HidController::Unknown2).SetCommonHeaderOffset(0x4e00);
|
||||
GetController<Controller_Stubbed>(HidController::Unknown3).SetCommonHeaderOffset(0x5000);
|
||||
|
||||
// Register update callbacks
|
||||
pad_update_event = CoreTiming::RegisterEvent(
|
||||
"HID::UpdatePadCallback",
|
||||
[this](u64 userdata, int cycles_late) { UpdatePadCallback(userdata, cycles_late); });
|
||||
[this](u64 userdata, int cycles_late) { UpdateControllers(userdata, cycles_late); });
|
||||
|
||||
// TODO(shinyquagsire23): Other update callbacks? (accel, gyro?)
|
||||
|
||||
CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event);
|
||||
}
|
||||
|
||||
void ActivateController(HidController controller) {
|
||||
controllers[static_cast<size_t>(controller)]->ActivateController();
|
||||
}
|
||||
|
||||
void DeactivateController(HidController controller) {
|
||||
controllers[static_cast<size_t>(controller)]->DeactivateController();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void MakeController(HidController controller) {
|
||||
controllers[static_cast<std::size_t>(controller)] = std::make_unique<T>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T& GetController(HidController controller) {
|
||||
return static_cast<T&>(*controllers[static_cast<size_t>(controller)]);
|
||||
}
|
||||
|
||||
~IAppletResource() {
|
||||
CoreTiming::UnscheduleEvent(pad_update_event, 0);
|
||||
}
|
||||
@@ -62,200 +128,15 @@ private:
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void LoadInputDevices() {
|
||||
std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
|
||||
Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
|
||||
buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
|
||||
std::transform(Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
|
||||
Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
|
||||
sticks.begin(), Input::CreateDevice<Input::AnalogDevice>);
|
||||
touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device);
|
||||
// TODO(shinyquagsire23): gyro, mouse, keyboard
|
||||
}
|
||||
|
||||
void UpdatePadCallback(u64 userdata, int cycles_late) {
|
||||
SharedMemory mem{};
|
||||
std::memcpy(&mem, shared_mem->GetPointer(), sizeof(SharedMemory));
|
||||
|
||||
if (Settings::values.is_device_reload_pending.exchange(false))
|
||||
LoadInputDevices();
|
||||
|
||||
// Set up controllers as neon red+blue Joy-Con attached to console
|
||||
ControllerHeader& controller_header = mem.controllers[Controller_Handheld].header;
|
||||
controller_header.type = ControllerType_Handheld;
|
||||
controller_header.single_colors_descriptor = ColorDesc_ColorsNonexistent;
|
||||
controller_header.right_color_body = JOYCON_BODY_NEON_RED;
|
||||
controller_header.right_color_buttons = JOYCON_BUTTONS_NEON_RED;
|
||||
controller_header.left_color_body = JOYCON_BODY_NEON_BLUE;
|
||||
controller_header.left_color_buttons = JOYCON_BUTTONS_NEON_BLUE;
|
||||
|
||||
for (std::size_t controller = 0; controller < mem.controllers.size(); controller++) {
|
||||
for (auto& layout : mem.controllers[controller].layouts) {
|
||||
layout.header.num_entries = HID_NUM_ENTRIES;
|
||||
layout.header.max_entry_index = HID_NUM_ENTRIES - 1;
|
||||
|
||||
// HID shared memory stores the state of the past 17 samples in a circlular buffer,
|
||||
// each with a timestamp in number of samples since boot.
|
||||
const ControllerInputEntry& last_entry = layout.entries[layout.header.latest_entry];
|
||||
|
||||
layout.header.timestamp_ticks = CoreTiming::GetTicks();
|
||||
layout.header.latest_entry = (layout.header.latest_entry + 1) % HID_NUM_ENTRIES;
|
||||
|
||||
ControllerInputEntry& entry = layout.entries[layout.header.latest_entry];
|
||||
entry.timestamp = last_entry.timestamp + 1;
|
||||
// TODO(shinyquagsire23): Is this always identical to timestamp?
|
||||
entry.timestamp_2 = entry.timestamp;
|
||||
|
||||
// TODO(shinyquagsire23): More than just handheld input
|
||||
if (controller != Controller_Handheld)
|
||||
continue;
|
||||
|
||||
entry.connection_state = ConnectionState_Connected | ConnectionState_Wired;
|
||||
|
||||
// TODO(shinyquagsire23): Set up some LUTs for each layout mapping in the future?
|
||||
// For now everything is just the default handheld layout, but split Joy-Con will
|
||||
// rotate the face buttons and directions for certain layouts.
|
||||
ControllerPadState& state = entry.buttons;
|
||||
using namespace Settings::NativeButton;
|
||||
state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.lstick.Assign(buttons[LStick - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.rstick.Assign(buttons[RStick - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus());
|
||||
|
||||
state.dleft.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.dup.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.dright.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.ddown.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus());
|
||||
|
||||
state.lstick_left.Assign(buttons[LStick_Left - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.lstick_up.Assign(buttons[LStick_Up - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.lstick_right.Assign(buttons[LStick_Right - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.lstick_down.Assign(buttons[LStick_Down - BUTTON_HID_BEGIN]->GetStatus());
|
||||
|
||||
state.rstick_left.Assign(buttons[RStick_Left - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.rstick_up.Assign(buttons[RStick_Up - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.rstick_right.Assign(buttons[RStick_Right - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.rstick_down.Assign(buttons[RStick_Down - BUTTON_HID_BEGIN]->GetStatus());
|
||||
|
||||
state.sl.Assign(buttons[SL - BUTTON_HID_BEGIN]->GetStatus());
|
||||
state.sr.Assign(buttons[SR - BUTTON_HID_BEGIN]->GetStatus());
|
||||
|
||||
const auto [stick_l_x_f, stick_l_y_f] = sticks[Joystick_Left]->GetStatus();
|
||||
const auto [stick_r_x_f, stick_r_y_f] = sticks[Joystick_Right]->GetStatus();
|
||||
entry.joystick_left_x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
|
||||
entry.joystick_left_y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
|
||||
entry.joystick_right_x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
|
||||
entry.joystick_right_y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
|
||||
void UpdateControllers(u64 userdata, int cycles_late) {
|
||||
const bool should_reload = Settings::values.is_device_reload_pending.exchange(false);
|
||||
for (const auto& controller : controllers) {
|
||||
if (should_reload) {
|
||||
controller->OnLoadInputDevices();
|
||||
}
|
||||
controller->OnUpdate(shared_mem->GetPointer(), SHARED_MEMORY_SIZE);
|
||||
}
|
||||
|
||||
TouchScreen& touchscreen = mem.touchscreen;
|
||||
const u64 last_entry = touchscreen.header.latest_entry;
|
||||
const u64 curr_entry = (last_entry + 1) % touchscreen.entries.size();
|
||||
const u64 timestamp = CoreTiming::GetTicks();
|
||||
const u64 sample_counter = touchscreen.entries[last_entry].header.timestamp + 1;
|
||||
touchscreen.header.timestamp_ticks = timestamp;
|
||||
touchscreen.header.num_entries = touchscreen.entries.size();
|
||||
touchscreen.header.latest_entry = curr_entry;
|
||||
touchscreen.header.max_entry_index = touchscreen.entries.size();
|
||||
touchscreen.header.timestamp = timestamp;
|
||||
touchscreen.entries[curr_entry].header.timestamp = sample_counter;
|
||||
|
||||
TouchScreenEntryTouch touch_entry{};
|
||||
auto [x, y, pressed] = touch_device->GetStatus();
|
||||
touch_entry.timestamp = timestamp;
|
||||
touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width);
|
||||
touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height);
|
||||
touch_entry.touch_index = 0;
|
||||
|
||||
// TODO(DarkLordZach): Maybe try to derive these from EmuWindow?
|
||||
touch_entry.diameter_x = 15;
|
||||
touch_entry.diameter_y = 15;
|
||||
touch_entry.angle = 0;
|
||||
|
||||
// TODO(DarkLordZach): Implement multi-touch support
|
||||
if (pressed) {
|
||||
touchscreen.entries[curr_entry].header.num_touches = 1;
|
||||
touchscreen.entries[curr_entry].touches[0] = touch_entry;
|
||||
} else {
|
||||
touchscreen.entries[curr_entry].header.num_touches = 0;
|
||||
}
|
||||
|
||||
// TODO(shinyquagsire23): Properly implement mouse
|
||||
Mouse& mouse = mem.mouse;
|
||||
const u64 last_mouse_entry = mouse.header.latest_entry;
|
||||
const u64 curr_mouse_entry = (mouse.header.latest_entry + 1) % mouse.entries.size();
|
||||
const u64 mouse_sample_counter = mouse.entries[last_mouse_entry].timestamp + 1;
|
||||
mouse.header.timestamp_ticks = timestamp;
|
||||
mouse.header.num_entries = mouse.entries.size();
|
||||
mouse.header.max_entry_index = mouse.entries.size();
|
||||
mouse.header.latest_entry = curr_mouse_entry;
|
||||
|
||||
mouse.entries[curr_mouse_entry].timestamp = mouse_sample_counter;
|
||||
mouse.entries[curr_mouse_entry].timestamp_2 = mouse_sample_counter;
|
||||
|
||||
// TODO(shinyquagsire23): Properly implement keyboard
|
||||
Keyboard& keyboard = mem.keyboard;
|
||||
const u64 last_keyboard_entry = keyboard.header.latest_entry;
|
||||
const u64 curr_keyboard_entry =
|
||||
(keyboard.header.latest_entry + 1) % keyboard.entries.size();
|
||||
const u64 keyboard_sample_counter = keyboard.entries[last_keyboard_entry].timestamp + 1;
|
||||
keyboard.header.timestamp_ticks = timestamp;
|
||||
keyboard.header.num_entries = keyboard.entries.size();
|
||||
keyboard.header.latest_entry = last_keyboard_entry;
|
||||
keyboard.header.max_entry_index = keyboard.entries.size();
|
||||
|
||||
keyboard.entries[curr_keyboard_entry].timestamp = keyboard_sample_counter;
|
||||
keyboard.entries[curr_keyboard_entry].timestamp_2 = keyboard_sample_counter;
|
||||
|
||||
// TODO(shinyquagsire23): Figure out what any of these are
|
||||
for (auto& input : mem.unk_input_1) {
|
||||
const u64 last_input_entry = input.header.latest_entry;
|
||||
const u64 curr_input_entry = (input.header.latest_entry + 1) % input.entries.size();
|
||||
const u64 input_sample_counter = input.entries[last_input_entry].timestamp + 1;
|
||||
|
||||
input.header.timestamp_ticks = timestamp;
|
||||
input.header.num_entries = input.entries.size();
|
||||
input.header.latest_entry = last_input_entry;
|
||||
input.header.max_entry_index = input.entries.size();
|
||||
|
||||
input.entries[curr_input_entry].timestamp = input_sample_counter;
|
||||
input.entries[curr_input_entry].timestamp_2 = input_sample_counter;
|
||||
}
|
||||
|
||||
for (auto& input : mem.unk_input_2) {
|
||||
input.header.timestamp_ticks = timestamp;
|
||||
input.header.num_entries = 17;
|
||||
input.header.latest_entry = 0;
|
||||
input.header.max_entry_index = 0;
|
||||
}
|
||||
|
||||
UnkInput3& input = mem.unk_input_3;
|
||||
const u64 last_input_entry = input.header.latest_entry;
|
||||
const u64 curr_input_entry = (input.header.latest_entry + 1) % input.entries.size();
|
||||
const u64 input_sample_counter = input.entries[last_input_entry].timestamp + 1;
|
||||
|
||||
input.header.timestamp_ticks = timestamp;
|
||||
input.header.num_entries = input.entries.size();
|
||||
input.header.latest_entry = last_input_entry;
|
||||
input.header.max_entry_index = input.entries.size();
|
||||
|
||||
input.entries[curr_input_entry].timestamp = input_sample_counter;
|
||||
input.entries[curr_input_entry].timestamp_2 = input_sample_counter;
|
||||
|
||||
// TODO(shinyquagsire23): Signal events
|
||||
|
||||
std::memcpy(shared_mem->GetPointer(), &mem, sizeof(SharedMemory));
|
||||
|
||||
// Reschedule recurrent event
|
||||
CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event);
|
||||
}
|
||||
|
||||
@@ -265,11 +146,8 @@ private:
|
||||
// CoreTiming update events
|
||||
CoreTiming::EventType* pad_update_event;
|
||||
|
||||
// Stored input state info
|
||||
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
|
||||
buttons;
|
||||
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> sticks;
|
||||
std::unique_ptr<Input::TouchDevice> touch_device;
|
||||
std::array<std::unique_ptr<ControllerBase>, static_cast<size_t>(HidController::MaxControllers)>
|
||||
controllers{};
|
||||
};
|
||||
|
||||
class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> {
|
||||
@@ -301,7 +179,7 @@ public:
|
||||
{31, &Hid::ActivateKeyboard, "ActivateKeyboard"},
|
||||
{40, nullptr, "AcquireXpadIdEventHandle"},
|
||||
{41, nullptr, "ReleaseXpadIdEventHandle"},
|
||||
{51, nullptr, "ActivateXpad"},
|
||||
{51, &Hid::ActivateXpad, "ActivateXpad"},
|
||||
{55, nullptr, "GetXpadIds"},
|
||||
{56, nullptr, "ActivateJoyXpad"},
|
||||
{58, nullptr, "GetJoyXpadLifoHandle"},
|
||||
@@ -362,8 +240,8 @@ public:
|
||||
{206, &Hid::SendVibrationValues, "SendVibrationValues"},
|
||||
{207, nullptr, "SendVibrationGcErmCommand"},
|
||||
{208, nullptr, "GetActualVibrationGcErmCommand"},
|
||||
{209, nullptr, "BeginPermitVibrationSession"},
|
||||
{210, nullptr, "EndPermitVibrationSession"},
|
||||
{209, &Hid::BeginPermitVibrationSession, "BeginPermitVibrationSession"},
|
||||
{210, &Hid::EndPermitVibrationSession, "EndPermitVibrationSession"},
|
||||
{300, &Hid::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"},
|
||||
{301, &Hid::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"},
|
||||
{302, nullptr, "StopConsoleSixAxisSensor"},
|
||||
@@ -401,16 +279,11 @@ public:
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "hid:EventHandle");
|
||||
}
|
||||
~Hid() = default;
|
||||
|
||||
private:
|
||||
std::shared_ptr<IAppletResource> applet_resource;
|
||||
u32 joy_hold_type{0};
|
||||
Kernel::SharedPtr<Kernel::Event> event;
|
||||
|
||||
void CreateAppletResource(Kernel::HLERequestContext& ctx) {
|
||||
if (applet_resource == nullptr) {
|
||||
@@ -423,31 +296,59 @@ private:
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void ActivateDebugPad(Kernel::HLERequestContext& ctx) {
|
||||
void ActivateXpad(Kernel::HLERequestContext& ctx) {
|
||||
applet_resource->ActivateController(HidController::XPad);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void ActivateDebugPad(Kernel::HLERequestContext& ctx) {
|
||||
applet_resource->ActivateController(HidController::DebugPad);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void ActivateTouchScreen(Kernel::HLERequestContext& ctx) {
|
||||
applet_resource->ActivateController(HidController::Touchscreen);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void ActivateMouse(Kernel::HLERequestContext& ctx) {
|
||||
applet_resource->ActivateController(HidController::Mouse);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void ActivateKeyboard(Kernel::HLERequestContext& ctx) {
|
||||
applet_resource->ActivateController(HidController::Keyboard);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void ActivateGesture(Kernel::HLERequestContext& ctx) {
|
||||
applet_resource->ActivateController(HidController::Gesture);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) {
|
||||
// Should have no effect with how our npad sets up the data
|
||||
applet_resource->ActivateController(HidController::NPad);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void StartSixAxisSensor(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto handle = rp.PopRaw<u32>();
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
@@ -468,84 +369,168 @@ private:
|
||||
}
|
||||
|
||||
void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto supported_styleset = rp.PopRaw<u32>();
|
||||
applet_resource->GetController<Controller_NPad>(HidController::NPad)
|
||||
.SetSupportedStyleSet({supported_styleset});
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
|
||||
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(0);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
rb.Push<u32>(controller.GetSupportedStyleSet().raw);
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) {
|
||||
applet_resource->GetController<Controller_NPad>(HidController::NPad)
|
||||
.SetSupportedNPadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void ActivateNpad(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
applet_resource->ActivateController(HidController::NPad);
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto npad_id = rp.PopRaw<u32>();
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushCopyObjects(event);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad)
|
||||
.GetStyleSetChangedEvent());
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void DisconnectNpad(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto npad_id = rp.PopRaw<u32>();
|
||||
applet_resource->GetController<Controller_NPad>(HidController::NPad)
|
||||
.DisconnectNPad(npad_id);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void GetPlayerLedPattern(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto npad_id = rp.PopRaw<u32>();
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
rb.PushRaw<u64>(applet_resource->GetController<Controller_NPad>(HidController::NPad)
|
||||
.GetLedPattern(npad_id)
|
||||
.raw);
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
|
||||
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto hold_type = rp.PopRaw<u64>();
|
||||
controller.SetHoldType(Controller_NPad::NpadHoldType{hold_type});
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
const auto& controller =
|
||||
applet_resource->GetController<Controller_NPad>(HidController::NPad);
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(joy_hold_type);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
rb.Push<u64>(static_cast<u64>(controller.GetHoldType()));
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto npad_id = rp.PopRaw<u32>();
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) {
|
||||
applet_resource->GetController<Controller_NPad>(HidController::NPad)
|
||||
.SetVibrationEnabled(true);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void EndPermitVibrationSession(Kernel::HLERequestContext& ctx) {
|
||||
applet_resource->GetController<Controller_NPad>(HidController::NPad)
|
||||
.SetVibrationEnabled(false);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void SendVibrationValue(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto controller_id = rp.PopRaw<u32>();
|
||||
const auto vibration_values = rp.PopRaw<Controller_NPad::Vibration>();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
|
||||
applet_resource->GetController<Controller_NPad>(HidController::NPad)
|
||||
.VibrateController({controller_id}, {vibration_values});
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void SendVibrationValues(Kernel::HLERequestContext& ctx) {
|
||||
const auto controllers = ctx.ReadBuffer(0);
|
||||
const auto vibrations = ctx.ReadBuffer(1);
|
||||
|
||||
std::vector<u32> controller_list(controllers.size() / sizeof(u32));
|
||||
std::vector<Controller_NPad::Vibration> vibration_list(vibrations.size() /
|
||||
sizeof(Controller_NPad::Vibration));
|
||||
|
||||
std::memcpy(controller_list.data(), controllers.data(), controllers.size());
|
||||
std::memcpy(vibration_list.data(), vibrations.data(), vibrations.size());
|
||||
std::transform(controller_list.begin(), controller_list.end(), controller_list.begin(),
|
||||
[](u32 controller_id) { return controller_id - 3; });
|
||||
|
||||
applet_resource->GetController<Controller_NPad>(HidController::NPad)
|
||||
.VibrateController(controller_list, vibration_list);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void GetActualVibrationValue(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
rb.PushRaw<Controller_NPad::Vibration>(
|
||||
applet_resource->GetController<Controller_NPad>(HidController::NPad)
|
||||
.GetLastVibration());
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto npad_id = rp.PopRaw<u32>();
|
||||
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
|
||||
controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Dual);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) {
|
||||
@@ -555,6 +540,8 @@ private:
|
||||
}
|
||||
|
||||
void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto mode = rp.PopRaw<u32>();
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
@@ -563,8 +550,9 @@ private:
|
||||
void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u64>(0);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
rb.Push<u32>(1);
|
||||
rb.Push<u32>(0);
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) {
|
||||
@@ -574,12 +562,6 @@ private:
|
||||
LOG_DEBUG(Service_HID, "called");
|
||||
}
|
||||
|
||||
void SendVibrationValues(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -597,18 +579,6 @@ private:
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void ActivateGesture(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called");
|
||||
}
|
||||
};
|
||||
|
||||
class HidDbg final : public ServiceFramework<HidDbg> {
|
||||
|
||||
@@ -4,408 +4,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/service.h"
|
||||
namespace SM {
|
||||
class ServiceManager;
|
||||
}
|
||||
|
||||
namespace Service::HID {
|
||||
|
||||
// Begin enums and output structs
|
||||
|
||||
constexpr u32 HID_NUM_ENTRIES = 17;
|
||||
constexpr u32 HID_NUM_LAYOUTS = 7;
|
||||
constexpr s32 HID_JOYSTICK_MAX = 0x8000;
|
||||
constexpr s32 HID_JOYSTICK_MIN = -0x8000;
|
||||
|
||||
constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
|
||||
constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
|
||||
constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6;
|
||||
constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E;
|
||||
|
||||
enum ControllerType : u32 {
|
||||
ControllerType_ProController = 1 << 0,
|
||||
ControllerType_Handheld = 1 << 1,
|
||||
ControllerType_JoyconPair = 1 << 2,
|
||||
ControllerType_JoyconLeft = 1 << 3,
|
||||
ControllerType_JoyconRight = 1 << 4,
|
||||
};
|
||||
|
||||
enum ControllerLayoutType : u32 {
|
||||
Layout_ProController = 0, // Pro Controller or HID gamepad
|
||||
Layout_Handheld = 1, // Two Joy-Con docked to rails
|
||||
Layout_Single = 2, // Horizontal single Joy-Con or pair of Joy-Con, adjusted for orientation
|
||||
Layout_Left = 3, // Only raw left Joy-Con state, no orientation adjustment
|
||||
Layout_Right = 4, // Only raw right Joy-Con state, no orientation adjustment
|
||||
Layout_DefaultDigital = 5, // Same as next, but sticks have 8-direction values only
|
||||
Layout_Default = 6, // Safe default, single Joy-Con have buttons/sticks rotated for orientation
|
||||
};
|
||||
|
||||
enum ControllerColorDescription {
|
||||
ColorDesc_ColorsNonexistent = 1 << 1,
|
||||
};
|
||||
|
||||
enum ControllerConnectionState {
|
||||
ConnectionState_Connected = 1 << 0,
|
||||
ConnectionState_Wired = 1 << 1,
|
||||
};
|
||||
|
||||
enum ControllerJoystick {
|
||||
Joystick_Left = 0,
|
||||
Joystick_Right = 1,
|
||||
};
|
||||
|
||||
enum ControllerID {
|
||||
Controller_Player1 = 0,
|
||||
Controller_Player2 = 1,
|
||||
Controller_Player3 = 2,
|
||||
Controller_Player4 = 3,
|
||||
Controller_Player5 = 4,
|
||||
Controller_Player6 = 5,
|
||||
Controller_Player7 = 6,
|
||||
Controller_Player8 = 7,
|
||||
Controller_Handheld = 8,
|
||||
Controller_Unknown = 9,
|
||||
};
|
||||
|
||||
// End enums and output structs
|
||||
|
||||
// Begin UnkInput3
|
||||
|
||||
struct UnkInput3Header {
|
||||
u64 timestamp_ticks;
|
||||
u64 num_entries;
|
||||
u64 latest_entry;
|
||||
u64 max_entry_index;
|
||||
};
|
||||
static_assert(sizeof(UnkInput3Header) == 0x20, "HID UnkInput3 header structure has incorrect size");
|
||||
|
||||
struct UnkInput3Entry {
|
||||
u64 timestamp;
|
||||
u64 timestamp_2;
|
||||
u64 unk_8;
|
||||
u64 unk_10;
|
||||
u64 unk_18;
|
||||
};
|
||||
static_assert(sizeof(UnkInput3Entry) == 0x28, "HID UnkInput3 entry structure has incorrect size");
|
||||
|
||||
struct UnkInput3 {
|
||||
UnkInput3Header header;
|
||||
std::array<UnkInput3Entry, 17> entries;
|
||||
std::array<u8, 0x138> padding;
|
||||
};
|
||||
static_assert(sizeof(UnkInput3) == 0x400, "HID UnkInput3 structure has incorrect size");
|
||||
|
||||
// End UnkInput3
|
||||
|
||||
// Begin TouchScreen
|
||||
|
||||
struct TouchScreenHeader {
|
||||
u64 timestamp_ticks;
|
||||
u64 num_entries;
|
||||
u64 latest_entry;
|
||||
u64 max_entry_index;
|
||||
u64 timestamp;
|
||||
};
|
||||
static_assert(sizeof(TouchScreenHeader) == 0x28,
|
||||
"HID touch screen header structure has incorrect size");
|
||||
|
||||
struct TouchScreenEntryHeader {
|
||||
u64 timestamp;
|
||||
u64 num_touches;
|
||||
};
|
||||
static_assert(sizeof(TouchScreenEntryHeader) == 0x10,
|
||||
"HID touch screen entry header structure has incorrect size");
|
||||
|
||||
struct TouchScreenEntryTouch {
|
||||
u64 timestamp;
|
||||
u32 padding;
|
||||
u32 touch_index;
|
||||
u32 x;
|
||||
u32 y;
|
||||
u32 diameter_x;
|
||||
u32 diameter_y;
|
||||
u32 angle;
|
||||
u32 padding_2;
|
||||
};
|
||||
static_assert(sizeof(TouchScreenEntryTouch) == 0x28,
|
||||
"HID touch screen touch structure has incorrect size");
|
||||
|
||||
struct TouchScreenEntry {
|
||||
TouchScreenEntryHeader header;
|
||||
std::array<TouchScreenEntryTouch, 16> touches;
|
||||
u64 unk;
|
||||
};
|
||||
static_assert(sizeof(TouchScreenEntry) == 0x298,
|
||||
"HID touch screen entry structure has incorrect size");
|
||||
|
||||
struct TouchScreen {
|
||||
TouchScreenHeader header;
|
||||
std::array<TouchScreenEntry, 17> entries;
|
||||
std::array<u8, 0x3c0> padding;
|
||||
};
|
||||
static_assert(sizeof(TouchScreen) == 0x3000, "HID touch screen structure has incorrect size");
|
||||
|
||||
// End TouchScreen
|
||||
|
||||
// Begin Mouse
|
||||
|
||||
struct MouseHeader {
|
||||
u64 timestamp_ticks;
|
||||
u64 num_entries;
|
||||
u64 latest_entry;
|
||||
u64 max_entry_index;
|
||||
};
|
||||
static_assert(sizeof(MouseHeader) == 0x20, "HID mouse header structure has incorrect size");
|
||||
|
||||
struct MouseButtonState {
|
||||
union {
|
||||
u64 hex{};
|
||||
|
||||
// Buttons
|
||||
BitField<0, 1, u64> left;
|
||||
BitField<1, 1, u64> right;
|
||||
BitField<2, 1, u64> middle;
|
||||
BitField<3, 1, u64> forward;
|
||||
BitField<4, 1, u64> back;
|
||||
};
|
||||
};
|
||||
|
||||
struct MouseEntry {
|
||||
u64 timestamp;
|
||||
u64 timestamp_2;
|
||||
u32 x;
|
||||
u32 y;
|
||||
u32 velocity_x;
|
||||
u32 velocity_y;
|
||||
u32 scroll_velocity_x;
|
||||
u32 scroll_velocity_y;
|
||||
MouseButtonState buttons;
|
||||
};
|
||||
static_assert(sizeof(MouseEntry) == 0x30, "HID mouse entry structure has incorrect size");
|
||||
|
||||
struct Mouse {
|
||||
MouseHeader header;
|
||||
std::array<MouseEntry, 17> entries;
|
||||
std::array<u8, 0xB0> padding;
|
||||
};
|
||||
static_assert(sizeof(Mouse) == 0x400, "HID mouse structure has incorrect size");
|
||||
|
||||
// End Mouse
|
||||
|
||||
// Begin Keyboard
|
||||
|
||||
struct KeyboardHeader {
|
||||
u64 timestamp_ticks;
|
||||
u64 num_entries;
|
||||
u64 latest_entry;
|
||||
u64 max_entry_index;
|
||||
};
|
||||
static_assert(sizeof(KeyboardHeader) == 0x20, "HID keyboard header structure has incorrect size");
|
||||
|
||||
struct KeyboardModifierKeyState {
|
||||
union {
|
||||
u64 hex{};
|
||||
|
||||
// Buttons
|
||||
BitField<0, 1, u64> lctrl;
|
||||
BitField<1, 1, u64> lshift;
|
||||
BitField<2, 1, u64> lalt;
|
||||
BitField<3, 1, u64> lmeta;
|
||||
BitField<4, 1, u64> rctrl;
|
||||
BitField<5, 1, u64> rshift;
|
||||
BitField<6, 1, u64> ralt;
|
||||
BitField<7, 1, u64> rmeta;
|
||||
BitField<8, 1, u64> capslock;
|
||||
BitField<9, 1, u64> scrolllock;
|
||||
BitField<10, 1, u64> numlock;
|
||||
};
|
||||
};
|
||||
|
||||
struct KeyboardEntry {
|
||||
u64 timestamp;
|
||||
u64 timestamp_2;
|
||||
KeyboardModifierKeyState modifier;
|
||||
u32 keys[8];
|
||||
};
|
||||
static_assert(sizeof(KeyboardEntry) == 0x38, "HID keyboard entry structure has incorrect size");
|
||||
|
||||
struct Keyboard {
|
||||
KeyboardHeader header;
|
||||
std::array<KeyboardEntry, 17> entries;
|
||||
std::array<u8, 0x28> padding;
|
||||
};
|
||||
static_assert(sizeof(Keyboard) == 0x400, "HID keyboard structure has incorrect size");
|
||||
|
||||
// End Keyboard
|
||||
|
||||
// Begin UnkInput1
|
||||
|
||||
struct UnkInput1Header {
|
||||
u64 timestamp_ticks;
|
||||
u64 num_entries;
|
||||
u64 latest_entry;
|
||||
u64 max_entry_index;
|
||||
};
|
||||
static_assert(sizeof(UnkInput1Header) == 0x20, "HID UnkInput1 header structure has incorrect size");
|
||||
|
||||
struct UnkInput1Entry {
|
||||
u64 timestamp;
|
||||
u64 timestamp_2;
|
||||
u64 unk_8;
|
||||
u64 unk_10;
|
||||
u64 unk_18;
|
||||
};
|
||||
static_assert(sizeof(UnkInput1Entry) == 0x28, "HID UnkInput1 entry structure has incorrect size");
|
||||
|
||||
struct UnkInput1 {
|
||||
UnkInput1Header header;
|
||||
std::array<UnkInput1Entry, 17> entries;
|
||||
std::array<u8, 0x138> padding;
|
||||
};
|
||||
static_assert(sizeof(UnkInput1) == 0x400, "HID UnkInput1 structure has incorrect size");
|
||||
|
||||
// End UnkInput1
|
||||
|
||||
// Begin UnkInput2
|
||||
|
||||
struct UnkInput2Header {
|
||||
u64 timestamp_ticks;
|
||||
u64 num_entries;
|
||||
u64 latest_entry;
|
||||
u64 max_entry_index;
|
||||
};
|
||||
static_assert(sizeof(UnkInput2Header) == 0x20, "HID UnkInput2 header structure has incorrect size");
|
||||
|
||||
struct UnkInput2 {
|
||||
UnkInput2Header header;
|
||||
std::array<u8, 0x1E0> padding;
|
||||
};
|
||||
static_assert(sizeof(UnkInput2) == 0x200, "HID UnkInput2 structure has incorrect size");
|
||||
|
||||
// End UnkInput2
|
||||
|
||||
// Begin Controller
|
||||
|
||||
struct ControllerMAC {
|
||||
u64 timestamp;
|
||||
std::array<u8, 0x8> mac;
|
||||
u64 unk;
|
||||
u64 timestamp_2;
|
||||
};
|
||||
static_assert(sizeof(ControllerMAC) == 0x20, "HID controller MAC structure has incorrect size");
|
||||
|
||||
struct ControllerHeader {
|
||||
u32 type;
|
||||
u32 is_half;
|
||||
u32 single_colors_descriptor;
|
||||
u32 single_color_body;
|
||||
u32 single_color_buttons;
|
||||
u32 split_colors_descriptor;
|
||||
u32 left_color_body;
|
||||
u32 left_color_buttons;
|
||||
u32 right_color_body;
|
||||
u32 right_color_buttons;
|
||||
};
|
||||
static_assert(sizeof(ControllerHeader) == 0x28,
|
||||
"HID controller header structure has incorrect size");
|
||||
|
||||
struct ControllerLayoutHeader {
|
||||
u64 timestamp_ticks;
|
||||
u64 num_entries;
|
||||
u64 latest_entry;
|
||||
u64 max_entry_index;
|
||||
};
|
||||
static_assert(sizeof(ControllerLayoutHeader) == 0x20,
|
||||
"HID controller layout header structure has incorrect size");
|
||||
|
||||
struct ControllerPadState {
|
||||
union {
|
||||
u64 hex{};
|
||||
|
||||
// Buttons
|
||||
BitField<0, 1, u64> a;
|
||||
BitField<1, 1, u64> b;
|
||||
BitField<2, 1, u64> x;
|
||||
BitField<3, 1, u64> y;
|
||||
BitField<4, 1, u64> lstick;
|
||||
BitField<5, 1, u64> rstick;
|
||||
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 buttons
|
||||
BitField<12, 1, u64> dleft;
|
||||
BitField<13, 1, u64> dup;
|
||||
BitField<14, 1, u64> dright;
|
||||
BitField<15, 1, u64> ddown;
|
||||
|
||||
// Left stick directions
|
||||
BitField<16, 1, u64> lstick_left;
|
||||
BitField<17, 1, u64> lstick_up;
|
||||
BitField<18, 1, u64> lstick_right;
|
||||
BitField<19, 1, u64> lstick_down;
|
||||
|
||||
// Right stick directions
|
||||
BitField<20, 1, u64> rstick_left;
|
||||
BitField<21, 1, u64> rstick_up;
|
||||
BitField<22, 1, u64> rstick_right;
|
||||
BitField<23, 1, u64> rstick_down;
|
||||
|
||||
BitField<24, 1, u64> sl;
|
||||
BitField<25, 1, u64> sr;
|
||||
};
|
||||
};
|
||||
|
||||
struct ControllerInputEntry {
|
||||
u64 timestamp;
|
||||
u64 timestamp_2;
|
||||
ControllerPadState buttons;
|
||||
s32 joystick_left_x;
|
||||
s32 joystick_left_y;
|
||||
s32 joystick_right_x;
|
||||
s32 joystick_right_y;
|
||||
u64 connection_state;
|
||||
};
|
||||
static_assert(sizeof(ControllerInputEntry) == 0x30,
|
||||
"HID controller input entry structure has incorrect size");
|
||||
|
||||
struct ControllerLayout {
|
||||
ControllerLayoutHeader header;
|
||||
std::array<ControllerInputEntry, 17> entries;
|
||||
};
|
||||
static_assert(sizeof(ControllerLayout) == 0x350,
|
||||
"HID controller layout structure has incorrect size");
|
||||
|
||||
struct Controller {
|
||||
ControllerHeader header;
|
||||
std::array<ControllerLayout, HID_NUM_LAYOUTS> layouts;
|
||||
std::array<u8, 0x2a70> unk_1;
|
||||
ControllerMAC mac_left;
|
||||
ControllerMAC mac_right;
|
||||
std::array<u8, 0xdf8> unk_2;
|
||||
};
|
||||
static_assert(sizeof(Controller) == 0x5000, "HID controller structure has incorrect size");
|
||||
|
||||
// End Controller
|
||||
|
||||
struct SharedMemory {
|
||||
UnkInput3 unk_input_3;
|
||||
TouchScreen touchscreen;
|
||||
Mouse mouse;
|
||||
Keyboard keyboard;
|
||||
std::array<UnkInput1, 4> unk_input_1;
|
||||
std::array<UnkInput2, 3> unk_input_2;
|
||||
std::array<u8, 0x800> unk_section_8;
|
||||
std::array<u8, 0x4000> controller_serials;
|
||||
std::array<Controller, 10> controllers;
|
||||
std::array<u8, 0x4600> unk_section_9;
|
||||
};
|
||||
static_assert(sizeof(SharedMemory) == 0x40000, "HID Shared Memory structure has incorrect size");
|
||||
|
||||
/// Reload input devices. Used when input configuration changed
|
||||
void ReloadInputDevices();
|
||||
|
||||
|
||||
@@ -14,14 +14,14 @@ public:
|
||||
explicit MM_U() : ServiceFramework{"mm:u"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &MM_U::Initialize, "InitializeOld"},
|
||||
{1, &MM_U::Finalize, "FinalizeOld"},
|
||||
{2, &MM_U::SetAndWait, "SetAndWaitOld"},
|
||||
{3, &MM_U::Get, "GetOld"},
|
||||
{4, &MM_U::Initialize, "Initialize"},
|
||||
{5, &MM_U::Finalize, "Finalize"},
|
||||
{6, &MM_U::SetAndWait, "SetAndWait"},
|
||||
{7, &MM_U::Get, "Get"},
|
||||
{0, &MM_U::Initialize, "Initialize"},
|
||||
{1, &MM_U::Finalize, "Finalize"},
|
||||
{2, &MM_U::SetAndWait, "SetAndWait"},
|
||||
{3, &MM_U::Get, "Get"},
|
||||
{4, &MM_U::InitializeWithId, "InitializeWithId"},
|
||||
{5, &MM_U::FinalizeWithId, "FinalizeWithId"},
|
||||
{6, &MM_U::SetAndWaitWithId, "SetAndWaitWithId"},
|
||||
{7, &MM_U::GetWithId, "GetWithId"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@@ -59,9 +59,43 @@ private:
|
||||
rb.Push(current);
|
||||
}
|
||||
|
||||
void InitializeWithId(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_MM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(id); // Any non zero value
|
||||
}
|
||||
|
||||
void FinalizeWithId(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_MM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void SetAndWaitWithId(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
u32 input_id = rp.Pop<u32>();
|
||||
min = rp.Pop<u32>();
|
||||
max = rp.Pop<u32>();
|
||||
current = min;
|
||||
|
||||
LOG_WARNING(Service_MM, "(STUBBED) called, input_id=0x{:X}, min=0x{:X}, max=0x{:X}",
|
||||
input_id, min, max);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void GetWithId(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_MM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(current);
|
||||
}
|
||||
|
||||
u32 min{0};
|
||||
u32 max{0};
|
||||
u32 current{0};
|
||||
u32 id{1};
|
||||
};
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
|
||||
@@ -144,7 +144,7 @@ private:
|
||||
}
|
||||
|
||||
const u64 device_handle{0xDEAD};
|
||||
const HID::ControllerID npad_id{HID::Controller_Player1};
|
||||
const u32 npad_id{0}; // This is the first player controller id
|
||||
State state{State::NonInitialized};
|
||||
DeviceState device_state{DeviceState::Initialized};
|
||||
Kernel::SharedPtr<Kernel::Event> activate_event;
|
||||
|
||||
@@ -161,7 +161,7 @@ PL_U::PL_U() : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()} {
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
// Attempt to load shared font data from disk
|
||||
const auto nand = FileSystem::GetSystemNANDContents();
|
||||
const auto* nand = FileSystem::GetSystemNANDContents();
|
||||
std::size_t offset = 0;
|
||||
// Rebuild shared fonts from data ncas
|
||||
if (nand->HasEntry(static_cast<u64>(FontArchives::Standard),
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
namespace Service::Nvidia::Devices {
|
||||
namespace NvErrCodes {
|
||||
enum {
|
||||
InvalidNmapHandle = -22,
|
||||
};
|
||||
}
|
||||
|
||||
nvhost_as_gpu::nvhost_as_gpu(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {}
|
||||
nvhost_as_gpu::~nvhost_as_gpu() = default;
|
||||
@@ -79,14 +84,16 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output)
|
||||
std::memcpy(entries.data(), input.data(), input.size());
|
||||
|
||||
auto& gpu = Core::System::GetInstance().GPU();
|
||||
|
||||
for (const auto& entry : entries) {
|
||||
LOG_WARNING(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}",
|
||||
entry.offset, entry.nvmap_handle, entry.pages);
|
||||
Tegra::GPUVAddr offset = static_cast<Tegra::GPUVAddr>(entry.offset) << 0x10;
|
||||
|
||||
auto object = nvmap_dev->GetObject(entry.nvmap_handle);
|
||||
ASSERT(object);
|
||||
if (!object) {
|
||||
LOG_CRITICAL(Service_NVDRV, "nvmap {} is an invalid handle!", entry.nvmap_handle);
|
||||
std::memcpy(output.data(), entries.data(), output.size());
|
||||
return static_cast<u32>(NvErrCodes::InvalidNmapHandle);
|
||||
}
|
||||
|
||||
ASSERT(object->status == nvmap::Object::Status::Allocated);
|
||||
|
||||
@@ -167,10 +174,11 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou
|
||||
auto& system_instance = Core::System::GetInstance();
|
||||
|
||||
// Remove this memory region from the rasterizer cache.
|
||||
system_instance.Renderer().Rasterizer().FlushAndInvalidateRegion(params.offset,
|
||||
itr->second.size);
|
||||
|
||||
auto& gpu = system_instance.GPU();
|
||||
auto cpu_addr = gpu.MemoryManager().GpuToCpuAddress(params.offset);
|
||||
ASSERT(cpu_addr);
|
||||
system_instance.Renderer().Rasterizer().FlushAndInvalidateRegion(*cpu_addr, itr->second.size);
|
||||
|
||||
params.offset = gpu.MemoryManager().UnmapBuffer(params.offset, itr->second.size);
|
||||
|
||||
buffer_mappings.erase(itr->second.offset);
|
||||
|
||||
@@ -11,6 +11,13 @@
|
||||
|
||||
namespace Service::Nvidia::Devices {
|
||||
|
||||
namespace NvErrCodes {
|
||||
enum {
|
||||
OperationNotPermitted = -1,
|
||||
InvalidValue = -22,
|
||||
};
|
||||
}
|
||||
|
||||
nvmap::nvmap() = default;
|
||||
nvmap::~nvmap() = default;
|
||||
|
||||
@@ -44,7 +51,11 @@ u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& o
|
||||
u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IocCreateParams params;
|
||||
std::memcpy(¶ms, input.data(), sizeof(params));
|
||||
LOG_DEBUG(Service_NVDRV, "size=0x{:08X}", params.size);
|
||||
|
||||
if (!params.size) {
|
||||
return static_cast<u32>(NvErrCodes::InvalidValue);
|
||||
}
|
||||
// Create a new nvmap object and obtain a handle to it.
|
||||
auto object = std::make_shared<Object>();
|
||||
object->id = next_id++;
|
||||
@@ -55,8 +66,6 @@ u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
u32 handle = next_handle++;
|
||||
handles[handle] = std::move(object);
|
||||
|
||||
LOG_DEBUG(Service_NVDRV, "size=0x{:08X}", params.size);
|
||||
|
||||
params.handle = handle;
|
||||
|
||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
||||
@@ -66,9 +75,29 @@ u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IocAllocParams params;
|
||||
std::memcpy(¶ms, input.data(), sizeof(params));
|
||||
LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.addr);
|
||||
|
||||
if (!params.handle) {
|
||||
return static_cast<u32>(NvErrCodes::InvalidValue);
|
||||
}
|
||||
|
||||
if ((params.align - 1) & params.align) {
|
||||
return static_cast<u32>(NvErrCodes::InvalidValue);
|
||||
}
|
||||
|
||||
const u32 min_alignment = 0x1000;
|
||||
if (params.align < min_alignment) {
|
||||
params.align = min_alignment;
|
||||
}
|
||||
|
||||
auto object = GetObject(params.handle);
|
||||
ASSERT(object);
|
||||
if (!object) {
|
||||
return static_cast<u32>(NvErrCodes::InvalidValue);
|
||||
}
|
||||
|
||||
if (object->status == Object::Status::Allocated) {
|
||||
return static_cast<u32>(NvErrCodes::OperationNotPermitted);
|
||||
}
|
||||
|
||||
object->flags = params.flags;
|
||||
object->align = params.align;
|
||||
@@ -76,8 +105,6 @@ u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
object->addr = params.addr;
|
||||
object->status = Object::Status::Allocated;
|
||||
|
||||
LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.addr);
|
||||
|
||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
||||
return 0;
|
||||
}
|
||||
@@ -88,8 +115,14 @@ u32 nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
|
||||
LOG_WARNING(Service_NVDRV, "called");
|
||||
|
||||
if (!params.handle) {
|
||||
return static_cast<u32>(NvErrCodes::InvalidValue);
|
||||
}
|
||||
|
||||
auto object = GetObject(params.handle);
|
||||
ASSERT(object);
|
||||
if (!object) {
|
||||
return static_cast<u32>(NvErrCodes::OperationNotPermitted);
|
||||
}
|
||||
|
||||
params.id = object->id;
|
||||
|
||||
@@ -105,7 +138,14 @@ u32 nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
|
||||
auto itr = std::find_if(handles.begin(), handles.end(),
|
||||
[&](const auto& entry) { return entry.second->id == params.id; });
|
||||
ASSERT(itr != handles.end());
|
||||
if (itr == handles.end()) {
|
||||
return static_cast<u32>(NvErrCodes::InvalidValue);
|
||||
}
|
||||
|
||||
auto& object = itr->second;
|
||||
if (object->status != Object::Status::Allocated) {
|
||||
return static_cast<u32>(NvErrCodes::InvalidValue);
|
||||
}
|
||||
|
||||
itr->second->refcount++;
|
||||
|
||||
@@ -125,8 +165,13 @@ u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called type={}", params.param);
|
||||
|
||||
auto object = GetObject(params.handle);
|
||||
ASSERT(object);
|
||||
ASSERT(object->status == Object::Status::Allocated);
|
||||
if (!object) {
|
||||
return static_cast<u32>(NvErrCodes::InvalidValue);
|
||||
}
|
||||
|
||||
if (object->status != Object::Status::Allocated) {
|
||||
return static_cast<u32>(NvErrCodes::OperationNotPermitted);
|
||||
}
|
||||
|
||||
switch (static_cast<ParamTypes>(params.param)) {
|
||||
case ParamTypes::Size:
|
||||
@@ -163,9 +208,12 @@ u32 nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
|
||||
|
||||
auto itr = handles.find(params.handle);
|
||||
ASSERT(itr != handles.end());
|
||||
|
||||
ASSERT(itr->second->refcount > 0);
|
||||
if (itr == handles.end()) {
|
||||
return static_cast<u32>(NvErrCodes::InvalidValue);
|
||||
}
|
||||
if (!itr->second->refcount) {
|
||||
return static_cast<u32>(NvErrCodes::InvalidValue);
|
||||
}
|
||||
|
||||
itr->second->refcount--;
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
|
||||
// Module interface
|
||||
|
||||
/// Initialize ServiceManager
|
||||
void Init(std::shared_ptr<SM::ServiceManager>& sm, const FileSys::VirtualFilesystem& rfs) {
|
||||
void Init(std::shared_ptr<SM::ServiceManager>& sm, FileSys::VfsFilesystem& vfs) {
|
||||
// NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it
|
||||
// here and pass it into the respective InstallInterfaces functions.
|
||||
auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>();
|
||||
@@ -220,7 +220,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, const FileSys::VirtualFilesys
|
||||
EUPLD::InstallInterfaces(*sm);
|
||||
Fatal::InstallInterfaces(*sm);
|
||||
FGM::InstallInterfaces(*sm);
|
||||
FileSystem::InstallInterfaces(*sm, rfs);
|
||||
FileSystem::InstallInterfaces(*sm, vfs);
|
||||
Friend::InstallInterfaces(*sm);
|
||||
GRC::InstallInterfaces(*sm);
|
||||
HID::InstallInterfaces(*sm);
|
||||
|
||||
@@ -180,8 +180,7 @@ private:
|
||||
};
|
||||
|
||||
/// Initialize ServiceManager
|
||||
void Init(std::shared_ptr<SM::ServiceManager>& sm,
|
||||
const std::shared_ptr<FileSys::VfsFilesystem>& vfs);
|
||||
void Init(std::shared_ptr<SM::ServiceManager>& sm, FileSys::VfsFilesystem& vfs);
|
||||
|
||||
/// Shutdown ServiceManager
|
||||
void Shutdown();
|
||||
|
||||
@@ -968,6 +968,54 @@ private:
|
||||
rb.PushCopyObjects(vsync_event);
|
||||
}
|
||||
|
||||
enum class ConvertedScaleMode : u64 {
|
||||
None = 0, // VI seems to name this as "Unknown" but lots of games pass it, assume it's no
|
||||
// scaling/default
|
||||
Freeze = 1,
|
||||
ScaleToWindow = 2,
|
||||
Crop = 3,
|
||||
NoCrop = 4,
|
||||
};
|
||||
|
||||
// This struct is different, currently it's 1:1 but this might change in the future.
|
||||
enum class NintendoScaleMode : u32 {
|
||||
None = 0,
|
||||
Freeze = 1,
|
||||
ScaleToWindow = 2,
|
||||
Crop = 3,
|
||||
NoCrop = 4,
|
||||
};
|
||||
|
||||
void ConvertScalingMode(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto mode = rp.PopEnum<NintendoScaleMode>();
|
||||
LOG_DEBUG(Service_VI, "called mode={}", static_cast<u32>(mode));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
switch (mode) {
|
||||
case NintendoScaleMode::None:
|
||||
rb.PushEnum(ConvertedScaleMode::None);
|
||||
break;
|
||||
case NintendoScaleMode::Freeze:
|
||||
rb.PushEnum(ConvertedScaleMode::Freeze);
|
||||
break;
|
||||
case NintendoScaleMode::ScaleToWindow:
|
||||
rb.PushEnum(ConvertedScaleMode::ScaleToWindow);
|
||||
break;
|
||||
case NintendoScaleMode::Crop:
|
||||
rb.PushEnum(ConvertedScaleMode::Crop);
|
||||
break;
|
||||
case NintendoScaleMode::NoCrop:
|
||||
rb.PushEnum(ConvertedScaleMode::NoCrop);
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unknown scaling mode {}", static_cast<u32>(mode));
|
||||
rb.PushEnum(ConvertedScaleMode::None);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
|
||||
};
|
||||
|
||||
@@ -991,7 +1039,7 @@ IApplicationDisplayService::IApplicationDisplayService(
|
||||
{2030, &IApplicationDisplayService::CreateStrayLayer, "CreateStrayLayer"},
|
||||
{2031, &IApplicationDisplayService::DestroyStrayLayer, "DestroyStrayLayer"},
|
||||
{2101, &IApplicationDisplayService::SetLayerScalingMode, "SetLayerScalingMode"},
|
||||
{2102, nullptr, "ConvertScalingMode"},
|
||||
{2102, &IApplicationDisplayService::ConvertScalingMode, "ConvertScalingMode"},
|
||||
{2450, nullptr, "GetIndirectLayerImageMap"},
|
||||
{2451, nullptr, "GetIndirectLayerImageCropMap"},
|
||||
{2460, nullptr, "GetIndirectLayerImageRequiredMemoryInfo"},
|
||||
|
||||
@@ -139,14 +139,22 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process)
|
||||
for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3",
|
||||
"subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) {
|
||||
const FileSys::VirtualFile module_file = dir->GetFile(module);
|
||||
if (module_file != nullptr) {
|
||||
const VAddr load_addr = next_load_addr;
|
||||
next_load_addr = AppLoader_NSO::LoadModule(module_file, load_addr,
|
||||
std::strcmp(module, "rtld") == 0, pm);
|
||||
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
|
||||
// Register module with GDBStub
|
||||
GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false);
|
||||
if (module_file == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const VAddr load_addr = next_load_addr;
|
||||
const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
|
||||
const auto tentative_next_load_addr =
|
||||
AppLoader_NSO::LoadModule(*module_file, load_addr, should_pass_arguments, pm);
|
||||
if (!tentative_next_load_addr) {
|
||||
return ResultStatus::ErrorLoadingNSO;
|
||||
}
|
||||
|
||||
next_load_addr = *tentative_next_load_addr;
|
||||
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
|
||||
// Register module with GDBStub
|
||||
GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false);
|
||||
}
|
||||
|
||||
process.Run(base_address, metadata.GetMainThreadPriority(), metadata.GetMainThreadStackSize());
|
||||
|
||||
@@ -9,16 +9,11 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/loader/elf.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
using Kernel::CodeSet;
|
||||
using Kernel::SharedPtr;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ELF Header Constants
|
||||
|
||||
@@ -211,7 +206,7 @@ public:
|
||||
u32 GetFlags() const {
|
||||
return (u32)(header->e_flags);
|
||||
}
|
||||
SharedPtr<CodeSet> LoadInto(VAddr vaddr);
|
||||
Kernel::CodeSet LoadInto(VAddr vaddr);
|
||||
|
||||
int GetNumSegments() const {
|
||||
return (int)(header->e_phnum);
|
||||
@@ -274,7 +269,7 @@ const char* ElfReader::GetSectionName(int section) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SharedPtr<CodeSet> ElfReader::LoadInto(VAddr vaddr) {
|
||||
Kernel::CodeSet ElfReader::LoadInto(VAddr vaddr) {
|
||||
LOG_DEBUG(Loader, "String section: {}", header->e_shstrndx);
|
||||
|
||||
// Should we relocate?
|
||||
@@ -302,8 +297,7 @@ SharedPtr<CodeSet> ElfReader::LoadInto(VAddr vaddr) {
|
||||
std::vector<u8> program_image(total_image_size);
|
||||
std::size_t current_image_position = 0;
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
SharedPtr<CodeSet> codeset = CodeSet::Create(kernel, "");
|
||||
Kernel::CodeSet codeset;
|
||||
|
||||
for (unsigned int i = 0; i < header->e_phnum; ++i) {
|
||||
const Elf32_Phdr* p = &segments[i];
|
||||
@@ -311,14 +305,14 @@ SharedPtr<CodeSet> ElfReader::LoadInto(VAddr vaddr) {
|
||||
p->p_vaddr, p->p_filesz, p->p_memsz);
|
||||
|
||||
if (p->p_type == PT_LOAD) {
|
||||
CodeSet::Segment* codeset_segment;
|
||||
Kernel::CodeSet::Segment* codeset_segment;
|
||||
u32 permission_flags = p->p_flags & (PF_R | PF_W | PF_X);
|
||||
if (permission_flags == (PF_R | PF_X)) {
|
||||
codeset_segment = &codeset->CodeSegment();
|
||||
codeset_segment = &codeset.CodeSegment();
|
||||
} else if (permission_flags == (PF_R)) {
|
||||
codeset_segment = &codeset->RODataSegment();
|
||||
codeset_segment = &codeset.RODataSegment();
|
||||
} else if (permission_flags == (PF_R | PF_W)) {
|
||||
codeset_segment = &codeset->DataSegment();
|
||||
codeset_segment = &codeset.DataSegment();
|
||||
} else {
|
||||
LOG_ERROR(Loader, "Unexpected ELF PT_LOAD segment id {} with flags {:X}", i,
|
||||
p->p_flags);
|
||||
@@ -345,8 +339,8 @@ SharedPtr<CodeSet> ElfReader::LoadInto(VAddr vaddr) {
|
||||
}
|
||||
}
|
||||
|
||||
codeset->entrypoint = base_addr + header->e_entry;
|
||||
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
|
||||
codeset.entrypoint = base_addr + header->e_entry;
|
||||
codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image));
|
||||
|
||||
LOG_DEBUG(Loader, "Done loading.");
|
||||
|
||||
@@ -397,11 +391,11 @@ ResultStatus AppLoader_ELF::Load(Kernel::Process& process) {
|
||||
|
||||
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
|
||||
ElfReader elf_reader(&buffer[0]);
|
||||
SharedPtr<CodeSet> codeset = elf_reader.LoadInto(base_address);
|
||||
codeset->name = file->GetName();
|
||||
Kernel::CodeSet codeset = elf_reader.LoadInto(base_address);
|
||||
const VAddr entry_point = codeset.entrypoint;
|
||||
|
||||
process.LoadModule(codeset, codeset->entrypoint);
|
||||
process.Run(codeset->entrypoint, 48, Memory::DEFAULT_STACK_SIZE);
|
||||
process.LoadModule(std::move(codeset), entry_point);
|
||||
process.Run(entry_point, 48, Memory::DEFAULT_STACK_SIZE);
|
||||
|
||||
is_loaded = true;
|
||||
return ResultStatus::Success;
|
||||
|
||||
@@ -93,7 +93,7 @@ std::string GetFileTypeString(FileType type) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
constexpr std::array<const char*, 59> RESULT_MESSAGES{
|
||||
constexpr std::array<const char*, 60> RESULT_MESSAGES{
|
||||
"The operation completed successfully.",
|
||||
"The loader requested to load is already loaded.",
|
||||
"The operation is not implemented.",
|
||||
@@ -128,6 +128,7 @@ constexpr std::array<const char*, 59> RESULT_MESSAGES{
|
||||
"The RomFS could not be found.",
|
||||
"The ELF file has incorrect size as determined by the header.",
|
||||
"There was a general error loading the NRO into emulated memory.",
|
||||
"There was a general error loading the NSO into emulated memory.",
|
||||
"There is no icon available.",
|
||||
"There is no control data available.",
|
||||
"The NAX file has a bad header.",
|
||||
|
||||
@@ -90,6 +90,7 @@ enum class ResultStatus : u16 {
|
||||
ErrorNoRomFS,
|
||||
ErrorIncorrectELFFileSize,
|
||||
ErrorLoadingNRO,
|
||||
ErrorLoadingNSO,
|
||||
ErrorNoIcon,
|
||||
ErrorNoControl,
|
||||
ErrorBadNAXHeader,
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/loader/nro.h"
|
||||
@@ -128,10 +127,10 @@ static constexpr u32 PageAlignSize(u32 size) {
|
||||
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
|
||||
}
|
||||
|
||||
bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) {
|
||||
bool AppLoader_NRO::LoadNro(const FileSys::VfsFile& file, VAddr load_base) {
|
||||
// Read NSO header
|
||||
NroHeader nro_header{};
|
||||
if (sizeof(NroHeader) != file->ReadObject(&nro_header)) {
|
||||
if (sizeof(NroHeader) != file.ReadObject(&nro_header)) {
|
||||
return {};
|
||||
}
|
||||
if (nro_header.magic != Common::MakeMagic('N', 'R', 'O', '0')) {
|
||||
@@ -139,22 +138,21 @@ bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) {
|
||||
}
|
||||
|
||||
// Build program image
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(kernel, "");
|
||||
std::vector<u8> program_image = file->ReadBytes(PageAlignSize(nro_header.file_size));
|
||||
std::vector<u8> program_image = file.ReadBytes(PageAlignSize(nro_header.file_size));
|
||||
if (program_image.size() != PageAlignSize(nro_header.file_size)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Kernel::CodeSet codeset;
|
||||
for (std::size_t i = 0; i < nro_header.segments.size(); ++i) {
|
||||
codeset->segments[i].addr = nro_header.segments[i].offset;
|
||||
codeset->segments[i].offset = nro_header.segments[i].offset;
|
||||
codeset->segments[i].size = PageAlignSize(nro_header.segments[i].size);
|
||||
codeset.segments[i].addr = nro_header.segments[i].offset;
|
||||
codeset.segments[i].offset = nro_header.segments[i].offset;
|
||||
codeset.segments[i].size = PageAlignSize(nro_header.segments[i].size);
|
||||
}
|
||||
|
||||
if (!Settings::values.program_args.empty()) {
|
||||
const auto arg_data = Settings::values.program_args;
|
||||
codeset->DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
|
||||
codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
|
||||
NSOArgumentHeader args_header{
|
||||
NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
|
||||
const auto end_offset = program_image.size();
|
||||
@@ -176,16 +174,15 @@ bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) {
|
||||
// Resize program image to include .bss section and page align each section
|
||||
bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset);
|
||||
}
|
||||
codeset->DataSegment().size += bss_size;
|
||||
codeset.DataSegment().size += bss_size;
|
||||
program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
|
||||
|
||||
// Load codeset for current process
|
||||
codeset->name = file->GetName();
|
||||
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
|
||||
Core::CurrentProcess()->LoadModule(codeset, load_base);
|
||||
codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image));
|
||||
Core::CurrentProcess()->LoadModule(std::move(codeset), load_base);
|
||||
|
||||
// Register module with GDBStub
|
||||
GDBStub::RegisterModule(codeset->name, load_base, load_base);
|
||||
GDBStub::RegisterModule(file.GetName(), load_base, load_base);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -198,7 +195,7 @@ ResultStatus AppLoader_NRO::Load(Kernel::Process& process) {
|
||||
// Load NRO
|
||||
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
|
||||
|
||||
if (!LoadNro(file, base_address)) {
|
||||
if (!LoadNro(*file, base_address)) {
|
||||
return ResultStatus::ErrorLoadingNRO;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
bool IsRomFSUpdatable() const override;
|
||||
|
||||
private:
|
||||
bool LoadNro(FileSys::VirtualFile file, VAddr load_base);
|
||||
bool LoadNro(const FileSys::VfsFile& file, VAddr load_base);
|
||||
|
||||
std::vector<u8> icon_data;
|
||||
std::unique_ptr<FileSys::NACP> nacp;
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/loader/nso.h"
|
||||
@@ -94,42 +93,38 @@ static constexpr u32 PageAlignSize(u32 size) {
|
||||
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
|
||||
}
|
||||
|
||||
VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base,
|
||||
bool should_pass_arguments,
|
||||
boost::optional<FileSys::PatchManager> pm) {
|
||||
if (file == nullptr)
|
||||
return {};
|
||||
|
||||
if (file->GetSize() < sizeof(NsoHeader))
|
||||
std::optional<VAddr> AppLoader_NSO::LoadModule(const FileSys::VfsFile& file, VAddr load_base,
|
||||
bool should_pass_arguments,
|
||||
std::optional<FileSys::PatchManager> pm) {
|
||||
if (file.GetSize() < sizeof(NsoHeader))
|
||||
return {};
|
||||
|
||||
NsoHeader nso_header{};
|
||||
if (sizeof(NsoHeader) != file->ReadObject(&nso_header))
|
||||
if (sizeof(NsoHeader) != file.ReadObject(&nso_header))
|
||||
return {};
|
||||
|
||||
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
|
||||
return {};
|
||||
|
||||
// Build program image
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(kernel, "");
|
||||
Kernel::CodeSet codeset;
|
||||
std::vector<u8> program_image;
|
||||
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
|
||||
std::vector<u8> data =
|
||||
file->ReadBytes(nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
|
||||
file.ReadBytes(nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
|
||||
if (nso_header.IsSegmentCompressed(i)) {
|
||||
data = DecompressSegment(data, nso_header.segments[i]);
|
||||
}
|
||||
program_image.resize(nso_header.segments[i].location);
|
||||
program_image.insert(program_image.end(), data.begin(), data.end());
|
||||
codeset->segments[i].addr = nso_header.segments[i].location;
|
||||
codeset->segments[i].offset = nso_header.segments[i].location;
|
||||
codeset->segments[i].size = PageAlignSize(static_cast<u32>(data.size()));
|
||||
codeset.segments[i].addr = nso_header.segments[i].location;
|
||||
codeset.segments[i].offset = nso_header.segments[i].location;
|
||||
codeset.segments[i].size = PageAlignSize(static_cast<u32>(data.size()));
|
||||
}
|
||||
|
||||
if (should_pass_arguments && !Settings::values.program_args.empty()) {
|
||||
const auto arg_data = Settings::values.program_args;
|
||||
codeset->DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
|
||||
codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
|
||||
NSOArgumentHeader args_header{
|
||||
NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
|
||||
const auto end_offset = program_image.size();
|
||||
@@ -154,12 +149,12 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base,
|
||||
// Resize program image to include .bss section and page align each section
|
||||
bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset);
|
||||
}
|
||||
codeset->DataSegment().size += bss_size;
|
||||
codeset.DataSegment().size += bss_size;
|
||||
const u32 image_size{PageAlignSize(static_cast<u32>(program_image.size()) + bss_size)};
|
||||
program_image.resize(image_size);
|
||||
|
||||
// Apply patches if necessary
|
||||
if (pm != boost::none && pm->HasNSOPatch(nso_header.build_id)) {
|
||||
if (pm && pm->HasNSOPatch(nso_header.build_id)) {
|
||||
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());
|
||||
@@ -170,12 +165,11 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base,
|
||||
}
|
||||
|
||||
// Load codeset for current process
|
||||
codeset->name = file->GetName();
|
||||
codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
|
||||
Core::CurrentProcess()->LoadModule(codeset, load_base);
|
||||
codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image));
|
||||
Core::CurrentProcess()->LoadModule(std::move(codeset), load_base);
|
||||
|
||||
// Register module with GDBStub
|
||||
GDBStub::RegisterModule(codeset->name, load_base, load_base);
|
||||
GDBStub::RegisterModule(file.GetName(), load_base, load_base);
|
||||
|
||||
return load_base + image_size;
|
||||
}
|
||||
@@ -187,7 +181,9 @@ ResultStatus AppLoader_NSO::Load(Kernel::Process& process) {
|
||||
|
||||
// Load module
|
||||
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
|
||||
LoadModule(file, base_address, true);
|
||||
if (!LoadModule(*file, base_address, true)) {
|
||||
return ResultStatus::ErrorLoadingNSO;
|
||||
}
|
||||
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address);
|
||||
|
||||
process.Run(base_address, Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/loader/linker.h"
|
||||
@@ -36,8 +37,9 @@ public:
|
||||
return IdentifyType(file);
|
||||
}
|
||||
|
||||
static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base, bool should_pass_arguments,
|
||||
boost::optional<FileSys::PatchManager> pm = boost::none);
|
||||
static std::optional<VAddr> LoadModule(const FileSys::VfsFile& file, VAddr load_base,
|
||||
bool should_pass_arguments,
|
||||
std::optional<FileSys::PatchManager> pm = {});
|
||||
|
||||
ResultStatus Load(Kernel::Process& process) override;
|
||||
};
|
||||
|
||||
@@ -59,8 +59,7 @@ ResultStatus AppLoader_XCI::Load(Kernel::Process& process) {
|
||||
if (xci->GetProgramNCAStatus() != ResultStatus::Success)
|
||||
return xci->GetProgramNCAStatus();
|
||||
|
||||
const auto nca = xci->GetProgramNCA();
|
||||
if (nca == nullptr && !Core::Crypto::KeyManager::KeyFileExists(false))
|
||||
if (!xci->HasProgramNCA() && !Core::Crypto::KeyManager::KeyFileExists(false))
|
||||
return ResultStatus::ErrorMissingProductionKeyFile;
|
||||
|
||||
const auto result = nca_loader->Load(process);
|
||||
|
||||
@@ -136,7 +136,7 @@ struct Values {
|
||||
float resolution_factor;
|
||||
bool use_frame_limit;
|
||||
u16 frame_limit;
|
||||
bool use_accurate_framebuffers;
|
||||
bool use_accurate_gpu_emulation;
|
||||
|
||||
float bg_red;
|
||||
float bg_green;
|
||||
|
||||
@@ -163,8 +163,8 @@ TelemetrySession::TelemetrySession() {
|
||||
AddField(Telemetry::FieldType::UserConfig, "Renderer_UseFrameLimit",
|
||||
Settings::values.use_frame_limit);
|
||||
AddField(Telemetry::FieldType::UserConfig, "Renderer_FrameLimit", Settings::values.frame_limit);
|
||||
AddField(Telemetry::FieldType::UserConfig, "Renderer_UseAccurateFramebuffers",
|
||||
Settings::values.use_accurate_framebuffers);
|
||||
AddField(Telemetry::FieldType::UserConfig, "Renderer_UseAccurateGpuEmulation",
|
||||
Settings::values.use_accurate_gpu_emulation);
|
||||
AddField(Telemetry::FieldType::UserConfig, "System_UseDockedMode",
|
||||
Settings::values.use_docked_mode);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ add_library(video_core STATIC
|
||||
renderer_opengl/gl_shader_cache.h
|
||||
renderer_opengl/gl_shader_decompiler.cpp
|
||||
renderer_opengl/gl_shader_decompiler.h
|
||||
renderer_opengl/gl_shader_dumper.cpp
|
||||
renderer_opengl/gl_shader_dumper.h
|
||||
renderer_opengl/gl_shader_gen.cpp
|
||||
renderer_opengl/gl_shader_gen.h
|
||||
renderer_opengl/gl_shader_manager.cpp
|
||||
|
||||
@@ -62,14 +62,16 @@ void Fermi2D::HandleSurfaceCopy() {
|
||||
u8* dst_buffer = Memory::GetPointer(dest_cpu);
|
||||
if (!regs.src.linear && regs.dst.linear) {
|
||||
// If the input is tiled and the output is linear, deswizzle the input and copy it over.
|
||||
Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
|
||||
dst_bytes_per_pixel, src_buffer, dst_buffer, true,
|
||||
regs.src.BlockHeight());
|
||||
Texture::CopySwizzledData(regs.src.width, regs.src.height, regs.src.depth,
|
||||
src_bytes_per_pixel, dst_bytes_per_pixel, src_buffer,
|
||||
dst_buffer, true, regs.src.BlockHeight(),
|
||||
regs.src.BlockDepth());
|
||||
} else {
|
||||
// If the input is linear and the output is tiled, swizzle the input and copy it over.
|
||||
Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
|
||||
dst_bytes_per_pixel, dst_buffer, src_buffer, false,
|
||||
regs.dst.BlockHeight());
|
||||
Texture::CopySwizzledData(regs.src.width, regs.src.height, regs.src.depth,
|
||||
src_bytes_per_pixel, dst_bytes_per_pixel, dst_buffer,
|
||||
src_buffer, false, regs.dst.BlockHeight(),
|
||||
regs.dst.BlockDepth());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,9 +36,9 @@ public:
|
||||
RenderTargetFormat format;
|
||||
BitField<0, 1, u32> linear;
|
||||
union {
|
||||
BitField<0, 4, u32> block_depth;
|
||||
BitField<0, 4, u32> block_width;
|
||||
BitField<4, 4, u32> block_height;
|
||||
BitField<8, 4, u32> block_width;
|
||||
BitField<8, 4, u32> block_depth;
|
||||
};
|
||||
u32 depth;
|
||||
u32 layer;
|
||||
@@ -53,10 +53,20 @@ public:
|
||||
address_low);
|
||||
}
|
||||
|
||||
u32 BlockWidth() const {
|
||||
// The block width is stored in log2 format.
|
||||
return 1 << block_width;
|
||||
}
|
||||
|
||||
u32 BlockHeight() const {
|
||||
// The block height is stored in log2 format.
|
||||
return 1 << block_height;
|
||||
}
|
||||
|
||||
u32 BlockDepth() const {
|
||||
// The block depth is stored in log2 format.
|
||||
return 1 << block_depth;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(Surface) == 0x28, "Surface has incorrect size");
|
||||
|
||||
|
||||
@@ -347,6 +347,16 @@ public:
|
||||
DecrWrap = 8,
|
||||
};
|
||||
|
||||
enum class MemoryLayout : u32 {
|
||||
Linear = 0,
|
||||
BlockLinear = 1,
|
||||
};
|
||||
|
||||
enum class InvMemoryLayout : u32 {
|
||||
BlockLinear = 0,
|
||||
Linear = 1,
|
||||
};
|
||||
|
||||
struct Cull {
|
||||
enum class FrontFace : u32 {
|
||||
ClockWise = 0x0900,
|
||||
@@ -432,8 +442,16 @@ public:
|
||||
u32 width;
|
||||
u32 height;
|
||||
Tegra::RenderTargetFormat format;
|
||||
u32 block_dimensions;
|
||||
u32 array_mode;
|
||||
union {
|
||||
BitField<0, 3, u32> block_width;
|
||||
BitField<4, 3, u32> block_height;
|
||||
BitField<8, 3, u32> block_depth;
|
||||
BitField<12, 1, InvMemoryLayout> type;
|
||||
} memory_layout;
|
||||
union {
|
||||
BitField<0, 16, u32> array_mode;
|
||||
BitField<16, 1, u32> volume;
|
||||
};
|
||||
u32 layer_stride;
|
||||
u32 base_layer;
|
||||
INSERT_PADDING_WORDS(7);
|
||||
@@ -562,7 +580,12 @@ public:
|
||||
u32 address_high;
|
||||
u32 address_low;
|
||||
Tegra::DepthFormat format;
|
||||
u32 block_dimensions;
|
||||
union {
|
||||
BitField<0, 4, u32> block_width;
|
||||
BitField<4, 4, u32> block_height;
|
||||
BitField<8, 4, u32> block_depth;
|
||||
BitField<20, 1, InvMemoryLayout> type;
|
||||
} memory_layout;
|
||||
u32 layer_stride;
|
||||
|
||||
GPUVAddr Address() const {
|
||||
|
||||
@@ -68,12 +68,14 @@ void MaxwellDMA::HandleCopy() {
|
||||
|
||||
if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) {
|
||||
// If the input is tiled and the output is linear, deswizzle the input and copy it over.
|
||||
Texture::CopySwizzledData(regs.src_params.size_x, regs.src_params.size_y, 1, 1, src_buffer,
|
||||
dst_buffer, true, regs.src_params.BlockHeight());
|
||||
Texture::CopySwizzledData(regs.src_params.size_x, regs.src_params.size_y,
|
||||
regs.src_params.size_z, 1, 1, src_buffer, dst_buffer, true,
|
||||
regs.src_params.BlockHeight(), regs.src_params.BlockDepth());
|
||||
} else {
|
||||
// If the input is linear and the output is tiled, swizzle the input and copy it over.
|
||||
Texture::CopySwizzledData(regs.dst_params.size_x, regs.dst_params.size_y, 1, 1, dst_buffer,
|
||||
src_buffer, false, regs.dst_params.BlockHeight());
|
||||
Texture::CopySwizzledData(regs.dst_params.size_x, regs.dst_params.size_y,
|
||||
regs.dst_params.size_z, 1, 1, dst_buffer, src_buffer, false,
|
||||
regs.dst_params.BlockHeight(), regs.dst_params.BlockDepth());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,10 @@ public:
|
||||
u32 BlockHeight() const {
|
||||
return 1 << block_height;
|
||||
}
|
||||
|
||||
u32 BlockDepth() const {
|
||||
return 1 << block_depth;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(Parameters) == 24, "Parameters has wrong size");
|
||||
|
||||
@@ -214,6 +214,18 @@ enum class IMinMaxExchange : u64 {
|
||||
XHi = 3,
|
||||
};
|
||||
|
||||
enum class VmadType : u64 {
|
||||
Size16_Low = 0,
|
||||
Size16_High = 1,
|
||||
Size32 = 2,
|
||||
Invalid = 3,
|
||||
};
|
||||
|
||||
enum class VmadShr : u64 {
|
||||
Shr7 = 1,
|
||||
Shr15 = 2,
|
||||
};
|
||||
|
||||
enum class XmadMode : u64 {
|
||||
None = 0,
|
||||
CLo = 1,
|
||||
@@ -255,7 +267,7 @@ enum class ControlCode : u64 {
|
||||
GTU = 12,
|
||||
NEU = 13,
|
||||
GEU = 14,
|
||||
//
|
||||
T = 15,
|
||||
OFF = 16,
|
||||
LO = 17,
|
||||
SFF = 18,
|
||||
@@ -452,6 +464,7 @@ union Instruction {
|
||||
BitField<48, 16, u64> opcode;
|
||||
|
||||
union {
|
||||
BitField<20, 16, u64> imm20_16;
|
||||
BitField<20, 19, u64> imm20_19;
|
||||
BitField<20, 32, s64> imm20_32;
|
||||
BitField<45, 1, u64> negate_b;
|
||||
@@ -493,6 +506,10 @@ union Instruction {
|
||||
}
|
||||
} lop3;
|
||||
|
||||
u16 GetImm20_16() const {
|
||||
return static_cast<u16>(imm20_16);
|
||||
}
|
||||
|
||||
u32 GetImm20_19() const {
|
||||
u32 imm{static_cast<u32>(imm20_19)};
|
||||
imm <<= 12;
|
||||
@@ -1016,6 +1033,23 @@ union Instruction {
|
||||
BitField<47, 2, IsberdShift> shift;
|
||||
} isberd;
|
||||
|
||||
union {
|
||||
BitField<48, 1, u64> signed_a;
|
||||
BitField<38, 1, u64> is_byte_chunk_a;
|
||||
BitField<36, 2, VmadType> type_a;
|
||||
BitField<36, 2, u64> byte_height_a;
|
||||
|
||||
BitField<49, 1, u64> signed_b;
|
||||
BitField<50, 1, u64> use_register_b;
|
||||
BitField<30, 1, u64> is_byte_chunk_b;
|
||||
BitField<28, 2, VmadType> type_b;
|
||||
BitField<28, 2, u64> byte_height_b;
|
||||
|
||||
BitField<51, 2, VmadShr> shr;
|
||||
BitField<55, 1, u64> saturate; // Saturates the result (a * b + c)
|
||||
BitField<47, 1, u64> cc;
|
||||
} vmad;
|
||||
|
||||
union {
|
||||
BitField<20, 16, u64> imm20_16;
|
||||
BitField<36, 1, u64> product_shift_left;
|
||||
@@ -1083,6 +1117,7 @@ public:
|
||||
IPA,
|
||||
OUT_R, // Emit vertex/primitive
|
||||
ISBERD,
|
||||
VMAD,
|
||||
FFMA_IMM, // Fused Multiply and Add
|
||||
FFMA_CR,
|
||||
FFMA_RC,
|
||||
@@ -1320,6 +1355,7 @@ private:
|
||||
INST("11100000--------", Id::IPA, Type::Trivial, "IPA"),
|
||||
INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"),
|
||||
INST("1110111111010---", Id::ISBERD, Type::Trivial, "ISBERD"),
|
||||
INST("01011111--------", Id::VMAD, Type::Trivial, "VMAD"),
|
||||
INST("0011001-1-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"),
|
||||
INST("010010011-------", Id::FFMA_CR, Type::Ffma, "FFMA_CR"),
|
||||
INST("010100011-------", Id::FFMA_RC, Type::Ffma, "FFMA_RC"),
|
||||
|
||||
@@ -87,6 +87,16 @@ GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) {
|
||||
return gpu_addr;
|
||||
}
|
||||
|
||||
GPUVAddr MemoryManager::GetRegionEnd(GPUVAddr region_start) const {
|
||||
for (const auto& region : mapped_regions) {
|
||||
const GPUVAddr region_end{region.gpu_addr + region.size};
|
||||
if (region_start >= region.gpu_addr && region_start < region_end) {
|
||||
return region_end;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
boost::optional<GPUVAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) {
|
||||
GPUVAddr gpu_addr = 0;
|
||||
u64 free_space = 0;
|
||||
|
||||
@@ -26,6 +26,7 @@ public:
|
||||
GPUVAddr MapBufferEx(VAddr cpu_addr, u64 size);
|
||||
GPUVAddr MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size);
|
||||
GPUVAddr UnmapBuffer(GPUVAddr gpu_addr, u64 size);
|
||||
GPUVAddr GetRegionEnd(GPUVAddr region_start) const;
|
||||
boost::optional<VAddr> GpuToCpuAddress(GPUVAddr gpu_addr);
|
||||
std::vector<GPUVAddr> CpuToGpuAddress(VAddr cpu_addr) const;
|
||||
|
||||
|
||||
@@ -11,32 +11,77 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "core/settings.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
class RasterizerCacheObject {
|
||||
public:
|
||||
/// Gets the address of the shader in guest memory, required for cache management
|
||||
virtual VAddr GetAddr() const = 0;
|
||||
|
||||
/// Gets the size of the shader in guest memory, required for cache management
|
||||
virtual std::size_t GetSizeInBytes() const = 0;
|
||||
|
||||
/// Wriets any cached resources back to memory
|
||||
virtual void Flush() = 0;
|
||||
|
||||
/// Sets whether the cached object should be considered registered
|
||||
void SetIsRegistered(bool registered) {
|
||||
is_registered = registered;
|
||||
}
|
||||
|
||||
/// Returns true if the cached object is registered
|
||||
bool IsRegistered() const {
|
||||
return is_registered;
|
||||
}
|
||||
|
||||
/// Returns true if the cached object is dirty
|
||||
bool IsDirty() const {
|
||||
return is_dirty;
|
||||
}
|
||||
|
||||
/// Returns ticks from when this cached object was last modified
|
||||
u64 GetLastModifiedTicks() const {
|
||||
return last_modified_ticks;
|
||||
}
|
||||
|
||||
/// Marks an object as recently modified, used to specify whether it is clean or dirty
|
||||
template <class T>
|
||||
void MarkAsModified(bool dirty, T& cache) {
|
||||
is_dirty = dirty;
|
||||
last_modified_ticks = cache.GetModifiedTicks();
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_registered{}; ///< Whether the object is currently registered with the cache
|
||||
bool is_dirty{}; ///< Whether the object is dirty (out of sync with guest memory)
|
||||
u64 last_modified_ticks{}; ///< When the object was last modified, used for in-order flushing
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class RasterizerCache : NonCopyable {
|
||||
friend class RasterizerCacheObject;
|
||||
|
||||
public:
|
||||
/// Write any cached resources overlapping the specified region back to memory
|
||||
void FlushRegion(Tegra::GPUVAddr addr, size_t size) {
|
||||
const auto& objects{GetSortedObjectsFromRegion(addr, size)};
|
||||
for (auto& object : objects) {
|
||||
FlushObject(object);
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark the specified region as being invalidated
|
||||
void InvalidateRegion(VAddr addr, u64 size) {
|
||||
if (size == 0)
|
||||
return;
|
||||
|
||||
const ObjectInterval interval{addr, addr + size};
|
||||
for (auto& pair : boost::make_iterator_range(object_cache.equal_range(interval))) {
|
||||
for (auto& cached_object : pair.second) {
|
||||
if (!cached_object)
|
||||
continue;
|
||||
|
||||
remove_objects.emplace(cached_object);
|
||||
const auto& objects{GetSortedObjectsFromRegion(addr, size)};
|
||||
for (auto& object : objects) {
|
||||
if (!object->IsRegistered()) {
|
||||
// Skip duplicates
|
||||
continue;
|
||||
}
|
||||
Unregister(object);
|
||||
}
|
||||
|
||||
for (auto& remove_object : remove_objects) {
|
||||
Unregister(remove_object);
|
||||
}
|
||||
|
||||
remove_objects.clear();
|
||||
}
|
||||
|
||||
/// Invalidates everything in the cache
|
||||
@@ -62,6 +107,7 @@ protected:
|
||||
|
||||
/// Register an object into the cache
|
||||
void Register(const T& object) {
|
||||
object->SetIsRegistered(true);
|
||||
object_cache.add({GetInterval(object), ObjectSet{object}});
|
||||
auto& rasterizer = Core::System::GetInstance().Renderer().Rasterizer();
|
||||
rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), 1);
|
||||
@@ -69,12 +115,57 @@ protected:
|
||||
|
||||
/// Unregisters an object from the cache
|
||||
void Unregister(const T& object) {
|
||||
object->SetIsRegistered(false);
|
||||
auto& rasterizer = Core::System::GetInstance().Renderer().Rasterizer();
|
||||
rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), -1);
|
||||
|
||||
// Only flush if use_accurate_gpu_emulation is enabled, as it incurs a performance hit
|
||||
if (Settings::values.use_accurate_gpu_emulation) {
|
||||
FlushObject(object);
|
||||
}
|
||||
|
||||
object_cache.subtract({GetInterval(object), ObjectSet{object}});
|
||||
}
|
||||
|
||||
/// Returns a ticks counter used for tracking when cached objects were last modified
|
||||
u64 GetModifiedTicks() {
|
||||
return ++modified_ticks;
|
||||
}
|
||||
|
||||
private:
|
||||
/// Returns a list of cached objects from the specified memory region, ordered by access time
|
||||
std::vector<T> GetSortedObjectsFromRegion(VAddr addr, u64 size) {
|
||||
if (size == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<T> objects;
|
||||
const ObjectInterval interval{addr, addr + size};
|
||||
for (auto& pair : boost::make_iterator_range(object_cache.equal_range(interval))) {
|
||||
for (auto& cached_object : pair.second) {
|
||||
if (!cached_object) {
|
||||
continue;
|
||||
}
|
||||
objects.push_back(cached_object);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(objects.begin(), objects.end(), [](const T& a, const T& b) -> bool {
|
||||
return a->GetLastModifiedTicks() < b->GetLastModifiedTicks();
|
||||
});
|
||||
|
||||
return objects;
|
||||
}
|
||||
|
||||
/// Flushes the specified object, updating appropriate cache state as needed
|
||||
void FlushObject(const T& object) {
|
||||
if (!object->IsDirty()) {
|
||||
return;
|
||||
}
|
||||
object->Flush();
|
||||
object->MarkAsModified(false, *this);
|
||||
}
|
||||
|
||||
using ObjectSet = std::set<T>;
|
||||
using ObjectCache = boost::icl::interval_map<VAddr, ObjectSet>;
|
||||
using ObjectInterval = typename ObjectCache::interval_type;
|
||||
@@ -84,6 +175,6 @@ private:
|
||||
object->GetAddr() + object->GetSizeInBytes());
|
||||
}
|
||||
|
||||
ObjectCache object_cache;
|
||||
ObjectSet remove_objects;
|
||||
ObjectCache object_cache; ///< Cache of objects
|
||||
u64 modified_ticks{}; ///< Counter of cache state ticks, used for in-order flushing
|
||||
};
|
||||
|
||||
@@ -15,15 +15,18 @@
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
struct CachedBufferEntry final {
|
||||
VAddr GetAddr() const {
|
||||
struct CachedBufferEntry final : public RasterizerCacheObject {
|
||||
VAddr GetAddr() const override {
|
||||
return addr;
|
||||
}
|
||||
|
||||
std::size_t GetSizeInBytes() const {
|
||||
std::size_t GetSizeInBytes() const override {
|
||||
return size;
|
||||
}
|
||||
|
||||
// We do not have to flush this cache as things in it are never modified by us.
|
||||
void Flush() override {}
|
||||
|
||||
VAddr addr;
|
||||
std::size_t size;
|
||||
GLintptr offset;
|
||||
|
||||
@@ -286,7 +286,8 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
|
||||
&ubo, sizeof(ubo), static_cast<std::size_t>(uniform_buffer_alignment));
|
||||
|
||||
// Bind the buffer
|
||||
glBindBufferRange(GL_UNIFORM_BUFFER, stage, buffer_cache.GetHandle(), offset, sizeof(ubo));
|
||||
glBindBufferRange(GL_UNIFORM_BUFFER, static_cast<GLuint>(stage), buffer_cache.GetHandle(),
|
||||
offset, static_cast<GLsizeiptr>(sizeof(ubo)));
|
||||
|
||||
Shader shader{shader_cache.GetStageProgram(program)};
|
||||
|
||||
@@ -423,6 +424,13 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep
|
||||
// Used when just a single color attachment is enabled, e.g. for clearing a color buffer
|
||||
Surface color_surface =
|
||||
res_cache.GetColorBufferSurface(*single_color_target, preserve_contents);
|
||||
|
||||
if (color_surface) {
|
||||
// Assume that a surface will be written to if it is used as a framebuffer, even if
|
||||
// the shader doesn't actually write to it.
|
||||
color_surface->MarkAsModified(true, res_cache);
|
||||
}
|
||||
|
||||
glFramebufferTexture2D(
|
||||
GL_DRAW_FRAMEBUFFER,
|
||||
GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target), GL_TEXTURE_2D,
|
||||
@@ -433,6 +441,13 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep
|
||||
std::array<GLenum, Maxwell::NumRenderTargets> buffers;
|
||||
for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
|
||||
Surface color_surface = res_cache.GetColorBufferSurface(index, preserve_contents);
|
||||
|
||||
if (color_surface) {
|
||||
// Assume that a surface will be written to if it is used as a framebuffer, even
|
||||
// if the shader doesn't actually write to it.
|
||||
color_surface->MarkAsModified(true, res_cache);
|
||||
}
|
||||
|
||||
buffers[index] = GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index);
|
||||
glFramebufferTexture2D(
|
||||
GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index),
|
||||
@@ -452,6 +467,10 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep
|
||||
}
|
||||
|
||||
if (depth_surface) {
|
||||
// Assume that a surface will be written to if it is used as a framebuffer, even if
|
||||
// the shader doesn't actually write to it.
|
||||
depth_surface->MarkAsModified(true, res_cache);
|
||||
|
||||
if (regs.stencil_enable) {
|
||||
// Attach both depth and stencil
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
|
||||
@@ -616,7 +635,14 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
|
||||
void RasterizerOpenGL::FlushAll() {}
|
||||
|
||||
void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {}
|
||||
void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
|
||||
if (Settings::values.use_accurate_gpu_emulation) {
|
||||
// Only flush if use_accurate_gpu_emulation is enabled, as it incurs a performance hit
|
||||
res_cache.FlushRegion(addr, size);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
@@ -626,6 +652,7 @@ void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
|
||||
FlushRegion(addr, size);
|
||||
InvalidateRegion(addr, size);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,18 +34,57 @@ struct FormatTuple {
|
||||
bool compressed;
|
||||
};
|
||||
|
||||
static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
|
||||
auto& gpu{Core::System::GetInstance().GPU()};
|
||||
const auto cpu_addr{gpu.MemoryManager().GpuToCpuAddress(gpu_addr)};
|
||||
return cpu_addr ? *cpu_addr : 0;
|
||||
static bool IsPixelFormatASTC(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::ASTC_2D_4X4:
|
||||
case PixelFormat::ASTC_2D_5X4:
|
||||
case PixelFormat::ASTC_2D_8X8:
|
||||
case PixelFormat::ASTC_2D_8X5:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::ASTC_2D_4X4:
|
||||
return {4, 4};
|
||||
case PixelFormat::ASTC_2D_5X4:
|
||||
return {5, 4};
|
||||
case PixelFormat::ASTC_2D_8X8:
|
||||
return {8, 8};
|
||||
case PixelFormat::ASTC_2D_8X5:
|
||||
return {8, 5};
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled format: {}", static_cast<u32>(format));
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
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_;
|
||||
size_in_bytes = SizeInBytesRaw();
|
||||
|
||||
if (IsPixelFormatASTC(pixel_format)) {
|
||||
// ASTC is uncompressed in software, in emulated as RGBA8
|
||||
size_in_bytes_gl = width * height * depth * 4;
|
||||
} else {
|
||||
size_in_bytes_gl = SizeInBytesGL();
|
||||
}
|
||||
}
|
||||
|
||||
/*static*/ SurfaceParams SurfaceParams::CreateForTexture(
|
||||
const Tegra::Texture::FullTextureInfo& config, const GLShader::SamplerEntry& entry) {
|
||||
SurfaceParams params{};
|
||||
params.addr = TryGetCpuAddr(config.tic.Address());
|
||||
params.is_tiled = config.tic.IsTiled();
|
||||
params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0,
|
||||
params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0,
|
||||
params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0,
|
||||
params.pixel_format =
|
||||
PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value());
|
||||
params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value());
|
||||
@@ -85,20 +124,23 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
|
||||
break;
|
||||
}
|
||||
|
||||
params.size_in_bytes_total = params.SizeInBytesTotal();
|
||||
params.size_in_bytes_2d = params.SizeInBytes2D();
|
||||
params.max_mip_level = config.tic.max_mip_level + 1;
|
||||
params.rt = {};
|
||||
|
||||
params.InitCacheParameters(config.tic.Address());
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/*static*/ SurfaceParams SurfaceParams::CreateForFramebuffer(std::size_t index) {
|
||||
const auto& config{Core::System::GetInstance().GPU().Maxwell3D().regs.rt[index]};
|
||||
SurfaceParams params{};
|
||||
params.addr = TryGetCpuAddr(config.Address());
|
||||
params.is_tiled = true;
|
||||
params.block_height = Tegra::Texture::TICEntry::DefaultBlockHeight;
|
||||
|
||||
params.is_tiled =
|
||||
config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
|
||||
params.block_width = 1 << config.memory_layout.block_width;
|
||||
params.block_height = 1 << config.memory_layout.block_height;
|
||||
params.block_depth = 1 << config.memory_layout.block_depth;
|
||||
params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
|
||||
params.component_type = ComponentTypeFromRenderTarget(config.format);
|
||||
params.type = GetFormatType(params.pixel_format);
|
||||
@@ -107,26 +149,30 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
|
||||
params.unaligned_height = config.height;
|
||||
params.target = SurfaceTarget::Texture2D;
|
||||
params.depth = 1;
|
||||
params.size_in_bytes_total = params.SizeInBytesTotal();
|
||||
params.size_in_bytes_2d = params.SizeInBytes2D();
|
||||
params.max_mip_level = 0;
|
||||
|
||||
// Render target specific parameters, not used for caching
|
||||
params.rt.index = static_cast<u32>(index);
|
||||
params.rt.array_mode = config.array_mode;
|
||||
params.rt.layer_stride = config.layer_stride;
|
||||
params.rt.volume = config.volume;
|
||||
params.rt.base_layer = config.base_layer;
|
||||
|
||||
params.InitCacheParameters(config.Address());
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/*static*/ SurfaceParams SurfaceParams::CreateForDepthBuffer(u32 zeta_width, u32 zeta_height,
|
||||
Tegra::GPUVAddr zeta_address,
|
||||
Tegra::DepthFormat format) {
|
||||
/*static*/ SurfaceParams SurfaceParams::CreateForDepthBuffer(
|
||||
u32 zeta_width, u32 zeta_height, Tegra::GPUVAddr zeta_address, Tegra::DepthFormat format,
|
||||
u32 block_width, u32 block_height, u32 block_depth,
|
||||
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) {
|
||||
SurfaceParams params{};
|
||||
params.addr = TryGetCpuAddr(zeta_address);
|
||||
params.is_tiled = true;
|
||||
params.block_height = Tegra::Texture::TICEntry::DefaultBlockHeight;
|
||||
|
||||
params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
|
||||
params.block_width = 1 << std::min(block_width, 5U);
|
||||
params.block_height = 1 << std::min(block_height, 5U);
|
||||
params.block_depth = 1 << std::min(block_depth, 5U);
|
||||
params.pixel_format = PixelFormatFromDepthFormat(format);
|
||||
params.component_type = ComponentTypeFromDepthFormat(format);
|
||||
params.type = GetFormatType(params.pixel_format);
|
||||
@@ -135,20 +181,22 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
|
||||
params.unaligned_height = zeta_height;
|
||||
params.target = SurfaceTarget::Texture2D;
|
||||
params.depth = 1;
|
||||
params.size_in_bytes_total = params.SizeInBytesTotal();
|
||||
params.size_in_bytes_2d = params.SizeInBytes2D();
|
||||
params.max_mip_level = 0;
|
||||
params.rt = {};
|
||||
|
||||
params.InitCacheParameters(zeta_address);
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/*static*/ SurfaceParams SurfaceParams::CreateForFermiCopySurface(
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& config) {
|
||||
SurfaceParams params{};
|
||||
params.addr = TryGetCpuAddr(config.Address());
|
||||
|
||||
params.is_tiled = !config.linear;
|
||||
params.block_height = params.is_tiled ? config.BlockHeight() : 0,
|
||||
params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0,
|
||||
params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0,
|
||||
params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 32U) : 0,
|
||||
params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
|
||||
params.component_type = ComponentTypeFromRenderTarget(config.format);
|
||||
params.type = GetFormatType(params.pixel_format);
|
||||
@@ -157,11 +205,11 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
|
||||
params.unaligned_height = config.height;
|
||||
params.target = SurfaceTarget::Texture2D;
|
||||
params.depth = 1;
|
||||
params.size_in_bytes_total = params.SizeInBytesTotal();
|
||||
params.size_in_bytes_2d = params.SizeInBytes2D();
|
||||
params.max_mip_level = 0;
|
||||
params.rt = {};
|
||||
|
||||
params.InitCacheParameters(config.Address());
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
@@ -221,6 +269,8 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
|
||||
{GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RG32UI
|
||||
{GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // R32UI
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X4
|
||||
|
||||
// Depth formats
|
||||
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, ComponentType::Float, false}, // Z32F
|
||||
@@ -264,28 +314,6 @@ static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType
|
||||
return format;
|
||||
}
|
||||
|
||||
static bool IsPixelFormatASTC(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::ASTC_2D_4X4:
|
||||
case PixelFormat::ASTC_2D_8X8:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::ASTC_2D_4X4:
|
||||
return {4, 4};
|
||||
case PixelFormat::ASTC_2D_8X8:
|
||||
return {8, 8};
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled format: {}", static_cast<u32>(format));
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
MathUtil::Rectangle<u32> SurfaceParams::GetRect() const {
|
||||
u32 actual_height{unaligned_height};
|
||||
if (IsPixelFormatASTC(pixel_format)) {
|
||||
@@ -313,29 +341,27 @@ static bool IsFormatBCn(PixelFormat format) {
|
||||
}
|
||||
|
||||
template <bool morton_to_gl, PixelFormat format>
|
||||
void MortonCopy(u32 stride, u32 block_height, u32 height, u8* gl_buffer, std::size_t gl_buffer_size,
|
||||
VAddr addr) {
|
||||
constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / CHAR_BIT;
|
||||
constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format);
|
||||
void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 depth, u8* gl_buffer,
|
||||
std::size_t gl_buffer_size, VAddr addr) {
|
||||
constexpr u32 bytes_per_pixel = SurfaceParams::GetBytesPerPixel(format);
|
||||
|
||||
// With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
|
||||
// pixel values.
|
||||
const u32 tile_size{IsFormatBCn(format) ? 4U : 1U};
|
||||
|
||||
if (morton_to_gl) {
|
||||
// With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
|
||||
// pixel values.
|
||||
const u32 tile_size{IsFormatBCn(format) ? 4U : 1U};
|
||||
const std::vector<u8> data = Tegra::Texture::UnswizzleTexture(
|
||||
addr, tile_size, bytes_per_pixel, stride, height, block_height);
|
||||
addr, tile_size, bytes_per_pixel, stride, height, depth, block_height, block_depth);
|
||||
const std::size_t size_to_copy{std::min(gl_buffer_size, data.size())};
|
||||
memcpy(gl_buffer, data.data(), size_to_copy);
|
||||
} else {
|
||||
// TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should
|
||||
// check the configuration for this and perform more generic un/swizzle
|
||||
LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!");
|
||||
VideoCore::MortonCopyPixels128(stride, height, bytes_per_pixel, gl_bytes_per_pixel,
|
||||
Memory::GetPointer(addr), gl_buffer, morton_to_gl);
|
||||
Tegra::Texture::CopySwizzledData(stride / tile_size, height / tile_size, depth,
|
||||
bytes_per_pixel, bytes_per_pixel, Memory::GetPointer(addr),
|
||||
gl_buffer, false, block_height, block_depth);
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr),
|
||||
static constexpr std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t, VAddr),
|
||||
SurfaceParams::MaxPixelFormat>
|
||||
morton_to_gl_fns = {
|
||||
// clang-format off
|
||||
@@ -385,6 +411,8 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr),
|
||||
MortonCopy<true, PixelFormat::RG32UI>,
|
||||
MortonCopy<true, PixelFormat::R32UI>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_8X8>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_8X5>,
|
||||
MortonCopy<true, PixelFormat::ASTC_2D_5X4>,
|
||||
MortonCopy<true, PixelFormat::Z32F>,
|
||||
MortonCopy<true, PixelFormat::Z16>,
|
||||
MortonCopy<true, PixelFormat::Z24S8>,
|
||||
@@ -393,7 +421,7 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr),
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr),
|
||||
static constexpr std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t, VAddr),
|
||||
SurfaceParams::MaxPixelFormat>
|
||||
gl_to_morton_fns = {
|
||||
// clang-format off
|
||||
@@ -410,17 +438,16 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr),
|
||||
MortonCopy<false, PixelFormat::RGBA16UI>,
|
||||
MortonCopy<false, PixelFormat::R11FG11FB10F>,
|
||||
MortonCopy<false, PixelFormat::RGBA32UI>,
|
||||
// TODO(Subv): Swizzling DXT1/DXT23/DXT45/DXN1/DXN2/BC7U/BC6H_UF16/BC6H_SF16/ASTC_2D_4X4
|
||||
// formats are not supported
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
MortonCopy<false, PixelFormat::DXT1>,
|
||||
MortonCopy<false, PixelFormat::DXT23>,
|
||||
MortonCopy<false, PixelFormat::DXT45>,
|
||||
MortonCopy<false, PixelFormat::DXN1>,
|
||||
MortonCopy<false, PixelFormat::DXN2UNORM>,
|
||||
MortonCopy<false, PixelFormat::DXN2SNORM>,
|
||||
MortonCopy<false, PixelFormat::BC7U>,
|
||||
MortonCopy<false, PixelFormat::BC6H_UF16>,
|
||||
MortonCopy<false, PixelFormat::BC6H_SF16>,
|
||||
// TODO(Subv): Swizzling ASTC formats are not supported
|
||||
nullptr,
|
||||
MortonCopy<false, PixelFormat::G8R8U>,
|
||||
MortonCopy<false, PixelFormat::G8R8S>,
|
||||
@@ -445,6 +472,8 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr),
|
||||
MortonCopy<false, PixelFormat::RG32UI>,
|
||||
MortonCopy<false, PixelFormat::R32UI>,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
MortonCopy<false, PixelFormat::Z32F>,
|
||||
MortonCopy<false, PixelFormat::Z16>,
|
||||
MortonCopy<false, PixelFormat::Z24S8>,
|
||||
@@ -604,22 +633,21 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
|
||||
auto source_format = GetFormatTuple(src_params.pixel_format, src_params.component_type);
|
||||
auto dest_format = GetFormatTuple(dst_params.pixel_format, dst_params.component_type);
|
||||
|
||||
std::size_t buffer_size =
|
||||
std::max(src_params.size_in_bytes_total, dst_params.size_in_bytes_total);
|
||||
std::size_t buffer_size = std::max(src_params.size_in_bytes, dst_params.size_in_bytes);
|
||||
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, copy_pbo_handle);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW_ARB);
|
||||
if (source_format.compressed) {
|
||||
glGetCompressedTextureImage(src_surface->Texture().handle, src_attachment,
|
||||
static_cast<GLsizei>(src_params.size_in_bytes_total), nullptr);
|
||||
static_cast<GLsizei>(src_params.size_in_bytes), nullptr);
|
||||
} else {
|
||||
glGetTextureImage(src_surface->Texture().handle, src_attachment, source_format.format,
|
||||
source_format.type, static_cast<GLsizei>(src_params.size_in_bytes_total),
|
||||
source_format.type, static_cast<GLsizei>(src_params.size_in_bytes),
|
||||
nullptr);
|
||||
}
|
||||
// If the new texture is bigger than the previous one, we need to fill in the rest with data
|
||||
// from the CPU.
|
||||
if (src_params.size_in_bytes_total < dst_params.size_in_bytes_total) {
|
||||
if (src_params.size_in_bytes < dst_params.size_in_bytes) {
|
||||
// Upload the rest of the memory.
|
||||
if (dst_params.is_tiled) {
|
||||
// TODO(Subv): We might have to de-tile the subtexture and re-tile it with the rest
|
||||
@@ -629,12 +657,12 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
|
||||
LOG_DEBUG(HW_GPU, "Trying to upload extra texture data from the CPU during "
|
||||
"reinterpretation but the texture is tiled.");
|
||||
}
|
||||
std::size_t remaining_size =
|
||||
dst_params.size_in_bytes_total - src_params.size_in_bytes_total;
|
||||
std::size_t remaining_size = dst_params.size_in_bytes - src_params.size_in_bytes;
|
||||
std::vector<u8> data(remaining_size);
|
||||
Memory::ReadBlock(dst_params.addr + src_params.size_in_bytes_total, data.data(),
|
||||
data.size());
|
||||
glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes_total, remaining_size,
|
||||
std::memcpy(data.data(), Memory::GetPointer(dst_params.addr + src_params.size_in_bytes),
|
||||
data.size());
|
||||
|
||||
glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes, remaining_size,
|
||||
data.data());
|
||||
}
|
||||
|
||||
@@ -680,7 +708,8 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
|
||||
}
|
||||
|
||||
CachedSurface::CachedSurface(const SurfaceParams& params)
|
||||
: params(params), gl_target(SurfaceTargetToGL(params.target)) {
|
||||
: params(params), gl_target(SurfaceTargetToGL(params.target)),
|
||||
cached_size_in_bytes(params.size_in_bytes) {
|
||||
texture.Create();
|
||||
const auto& rect{params.GetRect()};
|
||||
|
||||
@@ -730,9 +759,21 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
|
||||
|
||||
VideoCore::LabelGLObject(GL_TEXTURE, texture.handle, params.addr,
|
||||
SurfaceParams::SurfaceTargetName(params.target));
|
||||
|
||||
// Clamp size to mapped GPU memory region
|
||||
// TODO(bunnei): Super Mario Odyssey maps a 0x40000 byte region and then uses it for a 0x80000
|
||||
// R32F render buffer. We do not yet know if this is a game bug or something else, but this
|
||||
// check is necessary to prevent flushing from overwriting unmapped memory.
|
||||
|
||||
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
|
||||
const u64 max_size{memory_manager.GetRegionEnd(params.gpu_addr) - params.gpu_addr};
|
||||
if (cached_size_in_bytes > max_size) {
|
||||
LOG_ERROR(HW_GPU, "Surface size {} exceeds region size {}", params.size_in_bytes, max_size);
|
||||
cached_size_in_bytes = max_size;
|
||||
}
|
||||
}
|
||||
|
||||
static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
|
||||
static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height, bool reverse) {
|
||||
union S8Z24 {
|
||||
BitField<0, 24, u32> z24;
|
||||
BitField<24, 8, u32> s8;
|
||||
@@ -745,22 +786,29 @@ static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
|
||||
};
|
||||
static_assert(sizeof(Z24S8) == 4, "Z24S8 is incorrect size");
|
||||
|
||||
S8Z24 input_pixel{};
|
||||
Z24S8 output_pixel{};
|
||||
constexpr auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::S8Z24)};
|
||||
S8Z24 s8z24_pixel{};
|
||||
Z24S8 z24s8_pixel{};
|
||||
constexpr auto bpp{SurfaceParams::GetBytesPerPixel(PixelFormat::S8Z24)};
|
||||
for (std::size_t y = 0; y < height; ++y) {
|
||||
for (std::size_t x = 0; x < width; ++x) {
|
||||
const std::size_t offset{bpp * (y * width + x)};
|
||||
std::memcpy(&input_pixel, &data[offset], sizeof(S8Z24));
|
||||
output_pixel.s8.Assign(input_pixel.s8);
|
||||
output_pixel.z24.Assign(input_pixel.z24);
|
||||
std::memcpy(&data[offset], &output_pixel, sizeof(Z24S8));
|
||||
if (reverse) {
|
||||
std::memcpy(&z24s8_pixel, &data[offset], sizeof(Z24S8));
|
||||
s8z24_pixel.s8.Assign(z24s8_pixel.s8);
|
||||
s8z24_pixel.z24.Assign(z24s8_pixel.z24);
|
||||
std::memcpy(&data[offset], &s8z24_pixel, sizeof(S8Z24));
|
||||
} else {
|
||||
std::memcpy(&s8z24_pixel, &data[offset], sizeof(S8Z24));
|
||||
z24s8_pixel.s8.Assign(s8z24_pixel.s8);
|
||||
z24s8_pixel.z24.Assign(s8z24_pixel.z24);
|
||||
std::memcpy(&data[offset], &z24s8_pixel, sizeof(Z24S8));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ConvertG8R8ToR8G8(std::vector<u8>& data, u32 width, u32 height) {
|
||||
constexpr auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::G8R8U)};
|
||||
constexpr auto bpp{SurfaceParams::GetBytesPerPixel(PixelFormat::G8R8U)};
|
||||
for (std::size_t y = 0; y < height; ++y) {
|
||||
for (std::size_t x = 0; x < width; ++x) {
|
||||
const std::size_t offset{bpp * (y * width + x)};
|
||||
@@ -780,7 +828,9 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma
|
||||
u32 width, u32 height) {
|
||||
switch (pixel_format) {
|
||||
case PixelFormat::ASTC_2D_4X4:
|
||||
case PixelFormat::ASTC_2D_8X8: {
|
||||
case PixelFormat::ASTC_2D_8X8:
|
||||
case PixelFormat::ASTC_2D_8X5:
|
||||
case PixelFormat::ASTC_2D_5X4: {
|
||||
// Convert ASTC pixel formats to RGBA8, as most desktop GPUs do not support ASTC.
|
||||
u32 block_width{};
|
||||
u32 block_height{};
|
||||
@@ -790,7 +840,7 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma
|
||||
}
|
||||
case PixelFormat::S8Z24:
|
||||
// Convert the S8Z24 depth format to Z24S8, as OpenGL does not support S8Z24.
|
||||
ConvertS8Z24ToZ24S8(data, width, height);
|
||||
ConvertS8Z24ToZ24S8(data, width, height, false);
|
||||
break;
|
||||
|
||||
case PixelFormat::G8R8U:
|
||||
@@ -801,49 +851,54 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to perform software conversion (as needed) when flushing a buffer from OpenGL to
|
||||
* Switch memory. This is for Maxwell pixel formats that cannot be represented as-is in OpenGL or
|
||||
* with typical desktop GPUs.
|
||||
*/
|
||||
static void ConvertFormatAsNeeded_FlushGLBuffer(std::vector<u8>& data, PixelFormat pixel_format,
|
||||
u32 width, u32 height) {
|
||||
switch (pixel_format) {
|
||||
case PixelFormat::G8R8U:
|
||||
case PixelFormat::G8R8S:
|
||||
case PixelFormat::ASTC_2D_4X4:
|
||||
case PixelFormat::ASTC_2D_8X8: {
|
||||
LOG_CRITICAL(HW_GPU, "Conversion of format {} after texture flushing is not implemented",
|
||||
static_cast<u32>(pixel_format));
|
||||
UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
case PixelFormat::S8Z24:
|
||||
// Convert the Z24S8 depth format to S8Z24, as OpenGL does not support S8Z24.
|
||||
ConvertS8Z24ToZ24S8(data, width, height, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64, 192));
|
||||
void CachedSurface::LoadGLBuffer() {
|
||||
ASSERT(params.type != SurfaceType::Fill);
|
||||
|
||||
const u8* const texture_src_data = Memory::GetPointer(params.addr);
|
||||
|
||||
ASSERT(texture_src_data);
|
||||
|
||||
const u32 bytes_per_pixel = GetGLBytesPerPixel(params.pixel_format);
|
||||
const u32 copy_size = params.width * params.height * bytes_per_pixel;
|
||||
const std::size_t total_size = copy_size * params.depth;
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_SurfaceLoad);
|
||||
|
||||
gl_buffer.resize(params.size_in_bytes_gl);
|
||||
if (params.is_tiled) {
|
||||
gl_buffer.resize(total_size);
|
||||
u32 depth = params.depth;
|
||||
u32 block_depth = params.block_depth;
|
||||
|
||||
// TODO(bunnei): This only unswizzles and copies a 2D texture - we do not yet know how to do
|
||||
// this for 3D textures, etc.
|
||||
switch (params.target) {
|
||||
case SurfaceParams::SurfaceTarget::Texture2D:
|
||||
// Pass impl. to the fallback code below
|
||||
break;
|
||||
case SurfaceParams::SurfaceTarget::Texture2DArray:
|
||||
case SurfaceParams::SurfaceTarget::TextureCubemap:
|
||||
for (std::size_t index = 0; index < params.depth; ++index) {
|
||||
const std::size_t offset{index * copy_size};
|
||||
morton_to_gl_fns[static_cast<std::size_t>(params.pixel_format)](
|
||||
params.width, params.block_height, params.height, gl_buffer.data() + offset,
|
||||
copy_size, params.addr + offset);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented tiled load for target={}",
|
||||
static_cast<u32>(params.target));
|
||||
UNREACHABLE();
|
||||
ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}",
|
||||
params.block_width, static_cast<u32>(params.target));
|
||||
|
||||
if (params.target == SurfaceParams::SurfaceTarget::Texture2D) {
|
||||
// TODO(Blinkhawk): Eliminate this condition once all texture types are implemented.
|
||||
depth = 1U;
|
||||
block_depth = 1U;
|
||||
}
|
||||
|
||||
morton_to_gl_fns[static_cast<std::size_t>(params.pixel_format)](
|
||||
params.width, params.block_height, params.height, gl_buffer.data(), copy_size,
|
||||
params.addr);
|
||||
params.width, params.block_height, params.height, block_depth, depth, gl_buffer.data(),
|
||||
gl_buffer.size(), params.addr);
|
||||
} else {
|
||||
const u8* const texture_src_data_end{texture_src_data + total_size};
|
||||
const auto texture_src_data{Memory::GetPointer(params.addr)};
|
||||
const auto texture_src_data_end{texture_src_data + params.size_in_bytes_gl};
|
||||
gl_buffer.assign(texture_src_data, texture_src_data_end);
|
||||
}
|
||||
|
||||
@@ -852,7 +907,44 @@ void CachedSurface::LoadGLBuffer() {
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64));
|
||||
void CachedSurface::FlushGLBuffer() {
|
||||
ASSERT_MSG(false, "Unimplemented");
|
||||
MICROPROFILE_SCOPE(OpenGL_SurfaceFlush);
|
||||
|
||||
ASSERT_MSG(!IsPixelFormatASTC(params.pixel_format), "Unimplemented");
|
||||
|
||||
// OpenGL temporary buffer needs to be big enough to store raw texture size
|
||||
gl_buffer.resize(GetSizeInBytes());
|
||||
|
||||
const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
|
||||
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
|
||||
ASSERT(params.width * SurfaceParams::GetBytesPerPixel(params.pixel_format) % 4 == 0);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.width));
|
||||
ASSERT(!tuple.compressed);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
glGetTextureImage(texture.handle, 0, tuple.format, tuple.type, gl_buffer.size(),
|
||||
gl_buffer.data());
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
ConvertFormatAsNeeded_FlushGLBuffer(gl_buffer, params.pixel_format, params.width,
|
||||
params.height);
|
||||
ASSERT(params.type != SurfaceType::Fill);
|
||||
const u8* const texture_src_data = Memory::GetPointer(params.addr);
|
||||
ASSERT(texture_src_data);
|
||||
if (params.is_tiled) {
|
||||
u32 depth = params.depth;
|
||||
u32 block_depth = params.block_depth;
|
||||
|
||||
ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}",
|
||||
params.block_width, static_cast<u32>(params.target));
|
||||
|
||||
if (params.target == SurfaceParams::SurfaceTarget::Texture2D) {
|
||||
// TODO(Blinkhawk): Eliminate this condition once all texture types are implemented.
|
||||
depth = 1U;
|
||||
}
|
||||
gl_to_morton_fns[static_cast<size_t>(params.pixel_format)](
|
||||
params.width, params.block_height, params.height, block_depth, depth, gl_buffer.data(),
|
||||
gl_buffer.size(), GetAddr());
|
||||
} else {
|
||||
std::memcpy(Memory::GetPointer(GetAddr()), gl_buffer.data(), GetSizeInBytes());
|
||||
}
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192));
|
||||
@@ -862,9 +954,6 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_TextureUL);
|
||||
|
||||
ASSERT(gl_buffer.size() == static_cast<std::size_t>(params.width) * params.height *
|
||||
GetGLBytesPerPixel(params.pixel_format) * params.depth);
|
||||
|
||||
const auto& rect{params.GetRect()};
|
||||
|
||||
// Load data from memory to the surface
|
||||
@@ -873,7 +962,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
|
||||
std::size_t buffer_offset =
|
||||
static_cast<std::size_t>(static_cast<std::size_t>(y0) * params.width +
|
||||
static_cast<std::size_t>(x0)) *
|
||||
GetGLBytesPerPixel(params.pixel_format);
|
||||
SurfaceParams::GetBytesPerPixel(params.pixel_format);
|
||||
|
||||
const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
|
||||
const GLuint target_tex = texture.handle;
|
||||
@@ -889,7 +978,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
|
||||
cur_state.Apply();
|
||||
|
||||
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
|
||||
ASSERT(params.width * GetGLBytesPerPixel(params.pixel_format) % 4 == 0);
|
||||
ASSERT(params.width * SurfaceParams::GetBytesPerPixel(params.pixel_format) % 4 == 0);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.width));
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
@@ -899,7 +988,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
|
||||
glCompressedTexImage2D(
|
||||
SurfaceTargetToGL(params.target), 0, tuple.internal_format,
|
||||
static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height), 0,
|
||||
static_cast<GLsizei>(params.size_in_bytes_2d), &gl_buffer[buffer_offset]);
|
||||
static_cast<GLsizei>(params.size_in_bytes_gl), &gl_buffer[buffer_offset]);
|
||||
break;
|
||||
case SurfaceParams::SurfaceTarget::Texture3D:
|
||||
case SurfaceParams::SurfaceTarget::Texture2DArray:
|
||||
@@ -907,16 +996,16 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
|
||||
SurfaceTargetToGL(params.target), 0, tuple.internal_format,
|
||||
static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height),
|
||||
static_cast<GLsizei>(params.depth), 0,
|
||||
static_cast<GLsizei>(params.size_in_bytes_total), &gl_buffer[buffer_offset]);
|
||||
static_cast<GLsizei>(params.size_in_bytes_gl), &gl_buffer[buffer_offset]);
|
||||
break;
|
||||
case SurfaceParams::SurfaceTarget::TextureCubemap:
|
||||
for (std::size_t face = 0; face < params.depth; ++face) {
|
||||
glCompressedTexImage2D(static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face),
|
||||
0, tuple.internal_format, static_cast<GLsizei>(params.width),
|
||||
static_cast<GLsizei>(params.height), 0,
|
||||
static_cast<GLsizei>(params.size_in_bytes_2d),
|
||||
static_cast<GLsizei>(params.SizeInBytesCubeFaceGL()),
|
||||
&gl_buffer[buffer_offset]);
|
||||
buffer_offset += params.size_in_bytes_2d;
|
||||
buffer_offset += params.SizeInBytesCubeFace();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -926,7 +1015,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
|
||||
glCompressedTexImage2D(
|
||||
GL_TEXTURE_2D, 0, tuple.internal_format, static_cast<GLsizei>(params.width),
|
||||
static_cast<GLsizei>(params.height), 0,
|
||||
static_cast<GLsizei>(params.size_in_bytes_2d), &gl_buffer[buffer_offset]);
|
||||
static_cast<GLsizei>(params.size_in_bytes_gl), &gl_buffer[buffer_offset]);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -955,7 +1044,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
|
||||
y0, static_cast<GLsizei>(rect.GetWidth()),
|
||||
static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
|
||||
&gl_buffer[buffer_offset]);
|
||||
buffer_offset += params.size_in_bytes_2d;
|
||||
buffer_offset += params.SizeInBytesCubeFace();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -989,7 +1078,9 @@ Surface RasterizerCacheOpenGL::GetDepthBufferSurface(bool preserve_contents) {
|
||||
}
|
||||
|
||||
SurfaceParams depth_params{SurfaceParams::CreateForDepthBuffer(
|
||||
regs.zeta_width, regs.zeta_height, regs.zeta.Address(), regs.zeta.format)};
|
||||
regs.zeta_width, regs.zeta_height, regs.zeta.Address(), regs.zeta.format,
|
||||
regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height,
|
||||
regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)};
|
||||
|
||||
return GetSurface(depth_params, preserve_contents);
|
||||
}
|
||||
@@ -1015,10 +1106,7 @@ Surface RasterizerCacheOpenGL::GetColorBufferSurface(std::size_t index, bool pre
|
||||
void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) {
|
||||
surface->LoadGLBuffer();
|
||||
surface->UploadGLTexture(read_framebuffer.handle, draw_framebuffer.handle);
|
||||
}
|
||||
|
||||
void RasterizerCacheOpenGL::FlushSurface(const Surface& surface) {
|
||||
surface->FlushGLBuffer();
|
||||
surface->MarkAsModified(false, *this);
|
||||
}
|
||||
|
||||
Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool preserve_contents) {
|
||||
@@ -1035,8 +1123,8 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool pres
|
||||
} else if (preserve_contents) {
|
||||
// If surface parameters changed and we care about keeping the previous data, recreate
|
||||
// the surface from the old one
|
||||
Unregister(surface);
|
||||
Surface new_surface{RecreateSurface(surface, params)};
|
||||
Unregister(surface);
|
||||
Register(new_surface);
|
||||
return new_surface;
|
||||
} else {
|
||||
@@ -1087,6 +1175,14 @@ void RasterizerCacheOpenGL::FermiCopySurface(
|
||||
FastCopySurface(GetSurface(src_params, true), GetSurface(dst_params, false));
|
||||
}
|
||||
|
||||
void RasterizerCacheOpenGL::AccurateCopySurface(const Surface& src_surface,
|
||||
const Surface& dst_surface) {
|
||||
const auto& src_params{src_surface->GetSurfaceParams()};
|
||||
const auto& dst_params{dst_surface->GetSurfaceParams()};
|
||||
FlushRegion(src_params.addr, dst_params.size_in_bytes);
|
||||
LoadSurface(dst_surface);
|
||||
}
|
||||
|
||||
Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
|
||||
const SurfaceParams& new_params) {
|
||||
// Verify surface is compatible for blitting
|
||||
@@ -1095,6 +1191,12 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
|
||||
// Get a new surface with the new parameters, and blit the previous surface to it
|
||||
Surface new_surface{GetUncachedSurface(new_params)};
|
||||
|
||||
// With use_accurate_gpu_emulation enabled, do an accurate surface copy
|
||||
if (Settings::values.use_accurate_gpu_emulation) {
|
||||
AccurateCopySurface(old_surface, new_surface);
|
||||
return new_surface;
|
||||
}
|
||||
|
||||
// For compatible surfaces, we can just do fast glCopyImageSubData based copy
|
||||
if (old_params.target == new_params.target && old_params.type == new_params.type &&
|
||||
old_params.depth == new_params.depth && old_params.depth == 1 &&
|
||||
@@ -1106,11 +1208,10 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
|
||||
|
||||
// If the format is the same, just do a framebuffer blit. This is significantly faster than
|
||||
// using PBOs. The is also likely less accurate, as textures will be converted rather than
|
||||
// reinterpreted. When use_accurate_framebuffers setting is enabled, perform a more accurate
|
||||
// reinterpreted. When use_accurate_gpu_emulation setting is enabled, perform a more accurate
|
||||
// surface copy, where pixels are reinterpreted as a new format (without conversion). This
|
||||
// code path uses OpenGL PBOs and is quite slow.
|
||||
const bool is_blit{old_params.pixel_format == new_params.pixel_format ||
|
||||
!Settings::values.use_accurate_framebuffers};
|
||||
const bool is_blit{old_params.pixel_format == new_params.pixel_format};
|
||||
|
||||
switch (new_params.target) {
|
||||
case SurfaceParams::SurfaceTarget::Texture2D:
|
||||
@@ -1120,6 +1221,9 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
|
||||
CopySurface(old_surface, new_surface, copy_pbo.handle);
|
||||
}
|
||||
break;
|
||||
case SurfaceParams::SurfaceTarget::Texture3D:
|
||||
AccurateCopySurface(old_surface, new_surface);
|
||||
break;
|
||||
case SurfaceParams::SurfaceTarget::TextureCubemap: {
|
||||
if (old_params.rt.array_mode != 1) {
|
||||
// TODO(bunnei): This is used by Breath of the Wild, I'm not sure how to implement this
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "video_core/rasterizer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_gen.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
|
||||
namespace OpenGL {
|
||||
@@ -74,19 +75,21 @@ struct SurfaceParams {
|
||||
RG32UI = 43,
|
||||
R32UI = 44,
|
||||
ASTC_2D_8X8 = 45,
|
||||
ASTC_2D_8X5 = 46,
|
||||
ASTC_2D_5X4 = 47,
|
||||
|
||||
MaxColorFormat,
|
||||
|
||||
// Depth formats
|
||||
Z32F = 46,
|
||||
Z16 = 47,
|
||||
Z32F = 48,
|
||||
Z16 = 49,
|
||||
|
||||
MaxDepthFormat,
|
||||
|
||||
// DepthStencil formats
|
||||
Z24S8 = 48,
|
||||
S8Z24 = 49,
|
||||
Z32FS8 = 50,
|
||||
Z24S8 = 50,
|
||||
S8Z24 = 51,
|
||||
Z32FS8 = 52,
|
||||
|
||||
MaxDepthStencilFormat,
|
||||
|
||||
@@ -129,6 +132,8 @@ struct SurfaceParams {
|
||||
case Tegra::Texture::TextureType::Texture2D:
|
||||
case Tegra::Texture::TextureType::Texture2DNoMipmap:
|
||||
return SurfaceTarget::Texture2D;
|
||||
case Tegra::Texture::TextureType::Texture3D:
|
||||
return SurfaceTarget::Texture3D;
|
||||
case Tegra::Texture::TextureType::TextureCubemap:
|
||||
return SurfaceTarget::TextureCubemap;
|
||||
case Tegra::Texture::TextureType::Texture1DArray:
|
||||
@@ -220,6 +225,8 @@ struct SurfaceParams {
|
||||
1, // RG32UI
|
||||
1, // R32UI
|
||||
4, // ASTC_2D_8X8
|
||||
4, // ASTC_2D_8X5
|
||||
4, // ASTC_2D_5X4
|
||||
1, // Z32F
|
||||
1, // Z16
|
||||
1, // Z24S8
|
||||
@@ -282,6 +289,8 @@ struct SurfaceParams {
|
||||
64, // RG32UI
|
||||
32, // R32UI
|
||||
16, // ASTC_2D_8X8
|
||||
32, // ASTC_2D_8X5
|
||||
32, // ASTC_2D_5X4
|
||||
32, // Z32F
|
||||
16, // Z16
|
||||
32, // Z24S8
|
||||
@@ -553,8 +562,12 @@ struct SurfaceParams {
|
||||
return PixelFormat::BC6H_SF16;
|
||||
case Tegra::Texture::TextureFormat::ASTC_2D_4X4:
|
||||
return PixelFormat::ASTC_2D_4X4;
|
||||
case Tegra::Texture::TextureFormat::ASTC_2D_5X4:
|
||||
return PixelFormat::ASTC_2D_5X4;
|
||||
case Tegra::Texture::TextureFormat::ASTC_2D_8X8:
|
||||
return PixelFormat::ASTC_2D_8X8;
|
||||
case Tegra::Texture::TextureFormat::ASTC_2D_8X5:
|
||||
return PixelFormat::ASTC_2D_8X5;
|
||||
case Tegra::Texture::TextureFormat::R16_G16:
|
||||
switch (component_type) {
|
||||
case Tegra::Texture::ComponentType::FLOAT:
|
||||
@@ -691,21 +704,42 @@ struct SurfaceParams {
|
||||
return SurfaceType::Invalid;
|
||||
}
|
||||
|
||||
/// Returns the sizer in bytes of the specified pixel format
|
||||
static constexpr u32 GetBytesPerPixel(PixelFormat pixel_format) {
|
||||
if (pixel_format == SurfaceParams::PixelFormat::Invalid) {
|
||||
return 0;
|
||||
}
|
||||
return GetFormatBpp(pixel_format) / CHAR_BIT;
|
||||
}
|
||||
|
||||
/// Returns the rectangle corresponding to this surface
|
||||
MathUtil::Rectangle<u32> GetRect() const;
|
||||
|
||||
/// Returns the size of this surface as a 2D texture in bytes, adjusted for compression
|
||||
std::size_t SizeInBytes2D() const {
|
||||
/// Returns the total size of this surface in bytes, adjusted for compression
|
||||
std::size_t SizeInBytesRaw(bool ignore_tiled = false) const {
|
||||
const u32 compression_factor{GetCompressionFactor(pixel_format)};
|
||||
ASSERT(width % compression_factor == 0);
|
||||
ASSERT(height % compression_factor == 0);
|
||||
return (width / compression_factor) * (height / compression_factor) *
|
||||
GetFormatBpp(pixel_format) / CHAR_BIT;
|
||||
const u32 bytes_per_pixel{GetBytesPerPixel(pixel_format)};
|
||||
const size_t uncompressed_size{
|
||||
Tegra::Texture::CalculateSize((ignore_tiled ? false : is_tiled), bytes_per_pixel, width,
|
||||
height, depth, block_height, block_depth)};
|
||||
|
||||
// Divide by compression_factor^2, as height and width are factored by this
|
||||
return uncompressed_size / (compression_factor * compression_factor);
|
||||
}
|
||||
|
||||
/// Returns the total size of this surface in bytes, adjusted for compression
|
||||
std::size_t SizeInBytesTotal() const {
|
||||
return SizeInBytes2D() * depth;
|
||||
/// Returns the size of this surface as an OpenGL texture in bytes
|
||||
std::size_t SizeInBytesGL() const {
|
||||
return SizeInBytesRaw(true);
|
||||
}
|
||||
|
||||
/// Returns the size of this surface as a cube face in bytes
|
||||
std::size_t SizeInBytesCubeFace() const {
|
||||
return size_in_bytes / 6;
|
||||
}
|
||||
|
||||
/// Returns the size of this surface as an OpenGL cube face in bytes
|
||||
std::size_t SizeInBytesCubeFaceGL() const {
|
||||
return size_in_bytes_gl / 6;
|
||||
}
|
||||
|
||||
/// Creates SurfaceParams from a texture configuration
|
||||
@@ -716,9 +750,10 @@ struct SurfaceParams {
|
||||
static SurfaceParams CreateForFramebuffer(std::size_t index);
|
||||
|
||||
/// Creates SurfaceParams for a depth buffer configuration
|
||||
static SurfaceParams CreateForDepthBuffer(u32 zeta_width, u32 zeta_height,
|
||||
Tegra::GPUVAddr zeta_address,
|
||||
Tegra::DepthFormat format);
|
||||
static SurfaceParams CreateForDepthBuffer(
|
||||
u32 zeta_width, u32 zeta_height, Tegra::GPUVAddr zeta_address, Tegra::DepthFormat format,
|
||||
u32 block_width, u32 block_height, u32 block_depth,
|
||||
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type);
|
||||
|
||||
/// Creates SurfaceParams for a Fermi2D surface copy
|
||||
static SurfaceParams CreateForFermiCopySurface(
|
||||
@@ -731,9 +766,13 @@ struct SurfaceParams {
|
||||
other.depth);
|
||||
}
|
||||
|
||||
VAddr addr;
|
||||
/// Initializes parameters for caching, should be called after everything has been initialized
|
||||
void InitCacheParameters(Tegra::GPUVAddr gpu_addr);
|
||||
|
||||
bool is_tiled;
|
||||
u32 block_width;
|
||||
u32 block_height;
|
||||
u32 block_depth;
|
||||
PixelFormat pixel_format;
|
||||
ComponentType component_type;
|
||||
SurfaceType type;
|
||||
@@ -741,15 +780,20 @@ struct SurfaceParams {
|
||||
u32 height;
|
||||
u32 depth;
|
||||
u32 unaligned_height;
|
||||
std::size_t size_in_bytes_total;
|
||||
std::size_t size_in_bytes_2d;
|
||||
SurfaceTarget target;
|
||||
u32 max_mip_level;
|
||||
|
||||
// Parameters used for caching
|
||||
VAddr addr;
|
||||
Tegra::GPUVAddr gpu_addr;
|
||||
std::size_t size_in_bytes;
|
||||
std::size_t size_in_bytes_gl;
|
||||
|
||||
// Render target specific parameters, not used in caching
|
||||
struct {
|
||||
u32 index;
|
||||
u32 array_mode;
|
||||
u32 volume;
|
||||
u32 layer_stride;
|
||||
u32 base_layer;
|
||||
} rt;
|
||||
@@ -762,7 +806,8 @@ struct SurfaceReserveKey : Common::HashableStruct<OpenGL::SurfaceParams> {
|
||||
static SurfaceReserveKey Create(const OpenGL::SurfaceParams& params) {
|
||||
SurfaceReserveKey res;
|
||||
res.state = params;
|
||||
res.state.rt = {}; // Ignore rt config in caching
|
||||
res.state.gpu_addr = {}; // Ignore GPU vaddr in caching
|
||||
res.state.rt = {}; // Ignore rt config in caching
|
||||
return res;
|
||||
}
|
||||
};
|
||||
@@ -777,16 +822,20 @@ struct hash<SurfaceReserveKey> {
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class CachedSurface final {
|
||||
class CachedSurface final : public RasterizerCacheObject {
|
||||
public:
|
||||
CachedSurface(const SurfaceParams& params);
|
||||
|
||||
VAddr GetAddr() const {
|
||||
VAddr GetAddr() const override {
|
||||
return params.addr;
|
||||
}
|
||||
|
||||
std::size_t GetSizeInBytes() const {
|
||||
return params.size_in_bytes_total;
|
||||
std::size_t GetSizeInBytes() const override {
|
||||
return cached_size_in_bytes;
|
||||
}
|
||||
|
||||
void Flush() override {
|
||||
FlushGLBuffer();
|
||||
}
|
||||
|
||||
const OGLTexture& Texture() const {
|
||||
@@ -797,13 +846,6 @@ public:
|
||||
return gl_target;
|
||||
}
|
||||
|
||||
static constexpr unsigned int GetGLBytesPerPixel(SurfaceParams::PixelFormat format) {
|
||||
if (format == SurfaceParams::PixelFormat::Invalid)
|
||||
return 0;
|
||||
|
||||
return SurfaceParams::GetFormatBpp(format) / CHAR_BIT;
|
||||
}
|
||||
|
||||
const SurfaceParams& GetSurfaceParams() const {
|
||||
return params;
|
||||
}
|
||||
@@ -820,6 +862,7 @@ private:
|
||||
std::vector<u8> gl_buffer;
|
||||
SurfaceParams params;
|
||||
GLenum gl_target;
|
||||
std::size_t cached_size_in_bytes;
|
||||
};
|
||||
|
||||
class RasterizerCacheOpenGL final : public RasterizerCache<Surface> {
|
||||
@@ -836,9 +879,6 @@ public:
|
||||
/// Get the color surface based on the framebuffer configuration and the specified render target
|
||||
Surface GetColorBufferSurface(std::size_t index, bool preserve_contents);
|
||||
|
||||
/// Flushes the surface to Switch memory
|
||||
void FlushSurface(const Surface& surface);
|
||||
|
||||
/// Tries to find a framebuffer using on the provided CPU address
|
||||
Surface TryFindFramebufferSurface(VAddr addr) const;
|
||||
|
||||
@@ -862,6 +902,9 @@ private:
|
||||
/// Tries to get a reserved surface for the specified parameters
|
||||
Surface TryGetReservedSurface(const SurfaceParams& params);
|
||||
|
||||
/// Performs a slow but accurate surface copy, flushing to RAM and reinterpreting the data
|
||||
void AccurateCopySurface(const Surface& src_surface, const Surface& dst_surface);
|
||||
|
||||
/// The surface reserve is a "backup" cache, this is where we put unique surfaces that have
|
||||
/// previously been used. This is to prevent surfaces from being constantly created and
|
||||
/// destroyed when used with different surface parameters.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user