Compare commits
127 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90f8474fc1 | ||
|
|
2f8ca32020 | ||
|
|
b183ce4365 | ||
|
|
92fae7e1ab | ||
|
|
1c7a7ed79b | ||
|
|
1584fb6b38 | ||
|
|
c2aa4293ec | ||
|
|
38b027aa81 | ||
|
|
ffcda6c08e | ||
|
|
97b6405a17 | ||
|
|
2946d4bdbe | ||
|
|
1abed2f4c4 | ||
|
|
0f7ab3e21a | ||
|
|
f9d03b1d41 | ||
|
|
dc328440c8 | ||
|
|
b492d43e63 | ||
|
|
4d2de6564f | ||
|
|
c55b5de0fb | ||
|
|
4ccf30dfaa | ||
|
|
9bf409f275 | ||
|
|
3fd26b7147 | ||
|
|
bc293e1751 | ||
|
|
83ac3e6395 | ||
|
|
c7763603ef | ||
|
|
5dd538cace | ||
|
|
17290a4416 | ||
|
|
bf795edac4 | ||
|
|
fa10905e1e | ||
|
|
6d82c4adf9 | ||
|
|
72e9cb523e | ||
|
|
03ec936ca0 | ||
|
|
ee1b204749 | ||
|
|
68b3d8b7a9 | ||
|
|
5c0408596f | ||
|
|
5461b21c7a | ||
|
|
3ac874c32e | ||
|
|
5f4ee6f0c8 | ||
|
|
2db37ddea9 | ||
|
|
09b6dda8f0 | ||
|
|
a4412c8e22 | ||
|
|
af653906d0 | ||
|
|
bc6939beaa | ||
|
|
0b3d4db98b | ||
|
|
fe16905de1 | ||
|
|
89939be9e6 | ||
|
|
141a0d9386 | ||
|
|
6aab309e41 | ||
|
|
6e27c5d4d1 | ||
|
|
e3b4d31f4e | ||
|
|
8aa4889e76 | ||
|
|
1964f4bbb3 | ||
|
|
8723cc8798 | ||
|
|
6636f3ff47 | ||
|
|
4f24343f32 | ||
|
|
465175cdf5 | ||
|
|
9ff743bc0a | ||
|
|
f7d2889fb4 | ||
|
|
93ac8d0fea | ||
|
|
567e818440 | ||
|
|
be97fc884d | ||
|
|
f5631e78d1 | ||
|
|
30ff42b8cc | ||
|
|
a47c1c77e6 | ||
|
|
af3ba94b2a | ||
|
|
c50f66a8eb | ||
|
|
561d79e034 | ||
|
|
6b48ba5271 | ||
|
|
fd891ee9c0 | ||
|
|
3f1f82a8c4 | ||
|
|
ae982a9bdf | ||
|
|
c5c184246d | ||
|
|
7c2d6ef210 | ||
|
|
ee4d538850 | ||
|
|
4d0c682468 | ||
|
|
f945e9767c | ||
|
|
081f5c1dbf | ||
|
|
8bbc12b9c2 | ||
|
|
95dff555a4 | ||
|
|
e09505ff61 | ||
|
|
3ec054643e | ||
|
|
8f958b89e7 | ||
|
|
3edafc6802 | ||
|
|
29dc6f4519 | ||
|
|
4aad010f7a | ||
|
|
d041d6231c | ||
|
|
a57aac5772 | ||
|
|
d7398283e3 | ||
|
|
d6a0d5d432 | ||
|
|
c79d2ca6cf | ||
|
|
e4602748d6 | ||
|
|
9e34303fb9 | ||
|
|
1fa6ee4723 | ||
|
|
ce05df0a6d | ||
|
|
721632fe66 | ||
|
|
89ad82ce5c | ||
|
|
fa3f3cd07f | ||
|
|
6e4d2e672d | ||
|
|
ceef334c1c | ||
|
|
2534af040e | ||
|
|
2c0b0ad50d | ||
|
|
1cc5e6e9bc | ||
|
|
2de52e3af6 | ||
|
|
6f420a40cf | ||
|
|
44a3baf410 | ||
|
|
2fbb20b2b5 | ||
|
|
752faff2bc | ||
|
|
8e6311bfd2 | ||
|
|
690f326613 | ||
|
|
9aec85d39c | ||
|
|
011cf77796 | ||
|
|
749aef3dd0 | ||
|
|
38c2ac95af | ||
|
|
5acaeb04c4 | ||
|
|
cf7aba4817 | ||
|
|
d79d4fd764 | ||
|
|
5045748829 | ||
|
|
e948fbf5d0 | ||
|
|
d0e6b93695 | ||
|
|
c1e069c066 | ||
|
|
bc4bec8a60 | ||
|
|
110d578470 | ||
|
|
70bd2bb1d3 | ||
|
|
9669cdb710 | ||
|
|
8886f2e55e | ||
|
|
306739c2c4 | ||
|
|
f62227aa95 | ||
|
|
f664437ae8 |
2
externals/mbedtls
vendored
2
externals/mbedtls
vendored
Submodule externals/mbedtls updated: d409b75a4c...a280e602f3
@@ -30,7 +30,7 @@ public:
|
||||
return info;
|
||||
}
|
||||
|
||||
VoiceInfo& Info() {
|
||||
VoiceInfo& GetInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -51,9 +51,30 @@ private:
|
||||
VoiceInfo info{};
|
||||
};
|
||||
|
||||
class AudioRenderer::EffectState {
|
||||
public:
|
||||
const EffectOutStatus& GetOutStatus() const {
|
||||
return out_status;
|
||||
}
|
||||
|
||||
const EffectInStatus& GetInfo() const {
|
||||
return info;
|
||||
}
|
||||
|
||||
EffectInStatus& GetInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
void UpdateState();
|
||||
|
||||
private:
|
||||
EffectOutStatus out_status{};
|
||||
EffectInStatus info{};
|
||||
};
|
||||
AudioRenderer::AudioRenderer(AudioRendererParameter params,
|
||||
Kernel::SharedPtr<Kernel::Event> buffer_event)
|
||||
: worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count) {
|
||||
: worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count),
|
||||
effects(params.effect_count) {
|
||||
|
||||
audio_out = std::make_unique<AudioCore::AudioOut>();
|
||||
stream = audio_out->OpenStream(STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, "AudioRenderer",
|
||||
@@ -96,11 +117,29 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_
|
||||
memory_pool_count * sizeof(MemoryPoolInfo));
|
||||
|
||||
// Copy VoiceInfo structs
|
||||
std::size_t offset{sizeof(UpdateDataHeader) + config.behavior_size + config.memory_pools_size +
|
||||
config.voice_resource_size};
|
||||
std::size_t voice_offset{sizeof(UpdateDataHeader) + config.behavior_size +
|
||||
config.memory_pools_size + config.voice_resource_size};
|
||||
for (auto& voice : voices) {
|
||||
std::memcpy(&voice.Info(), input_params.data() + offset, sizeof(VoiceInfo));
|
||||
offset += sizeof(VoiceInfo);
|
||||
std::memcpy(&voice.GetInfo(), input_params.data() + voice_offset, sizeof(VoiceInfo));
|
||||
voice_offset += sizeof(VoiceInfo);
|
||||
}
|
||||
|
||||
std::size_t effect_offset{sizeof(UpdateDataHeader) + config.behavior_size +
|
||||
config.memory_pools_size + config.voice_resource_size +
|
||||
config.voices_size};
|
||||
for (auto& effect : effects) {
|
||||
std::memcpy(&effect.GetInfo(), input_params.data() + effect_offset, sizeof(EffectInStatus));
|
||||
effect_offset += sizeof(EffectInStatus);
|
||||
}
|
||||
|
||||
// Update memory pool state
|
||||
std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
|
||||
for (std::size_t index = 0; index < memory_pool.size(); ++index) {
|
||||
if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) {
|
||||
memory_pool[index].state = MemoryPoolStates::Attached;
|
||||
} else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) {
|
||||
memory_pool[index].state = MemoryPoolStates::Detached;
|
||||
}
|
||||
}
|
||||
|
||||
// Update voices
|
||||
@@ -114,14 +153,8 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_
|
||||
}
|
||||
}
|
||||
|
||||
// Update memory pool state
|
||||
std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
|
||||
for (std::size_t index = 0; index < memory_pool.size(); ++index) {
|
||||
if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) {
|
||||
memory_pool[index].state = MemoryPoolStates::Attached;
|
||||
} else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) {
|
||||
memory_pool[index].state = MemoryPoolStates::Detached;
|
||||
}
|
||||
for (auto& effect : effects) {
|
||||
effect.UpdateState();
|
||||
}
|
||||
|
||||
// Release previous buffers and queue next ones for playback
|
||||
@@ -144,6 +177,14 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_
|
||||
voice_out_status_offset += sizeof(VoiceOutStatus);
|
||||
}
|
||||
|
||||
std::size_t effect_out_status_offset{
|
||||
sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
|
||||
response_data.voice_resource_size};
|
||||
for (const auto& effect : effects) {
|
||||
std::memcpy(output_params.data() + effect_out_status_offset, &effect.GetOutStatus(),
|
||||
sizeof(EffectOutStatus));
|
||||
effect_out_status_offset += sizeof(EffectOutStatus);
|
||||
}
|
||||
return output_params;
|
||||
}
|
||||
|
||||
@@ -244,11 +285,29 @@ void AudioRenderer::VoiceState::RefreshBuffer() {
|
||||
break;
|
||||
}
|
||||
|
||||
samples = Interpolate(interp_state, std::move(samples), Info().sample_rate, STREAM_SAMPLE_RATE);
|
||||
samples =
|
||||
Interpolate(interp_state, std::move(samples), GetInfo().sample_rate, STREAM_SAMPLE_RATE);
|
||||
|
||||
is_refresh_pending = false;
|
||||
}
|
||||
|
||||
void AudioRenderer::EffectState::UpdateState() {
|
||||
if (info.is_new) {
|
||||
out_status.state = EffectStatus::New;
|
||||
} else {
|
||||
if (info.type == Effect::Aux) {
|
||||
ASSERT_MSG(Memory::Read32(info.aux_info.return_buffer_info) == 0,
|
||||
"Aux buffers tried to update");
|
||||
ASSERT_MSG(Memory::Read32(info.aux_info.send_buffer_info) == 0,
|
||||
"Aux buffers tried to update");
|
||||
ASSERT_MSG(Memory::Read32(info.aux_info.return_buffer_base) == 0,
|
||||
"Aux buffers tried to update");
|
||||
ASSERT_MSG(Memory::Read32(info.aux_info.send_buffer_base) == 0,
|
||||
"Aux buffers tried to update");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr s16 ClampToS16(s32 value) {
|
||||
return static_cast<s16>(std::clamp(value, -32768, 32767));
|
||||
}
|
||||
|
||||
@@ -28,6 +28,16 @@ enum class PlayState : u8 {
|
||||
Paused = 2,
|
||||
};
|
||||
|
||||
enum class Effect : u8 {
|
||||
None = 0,
|
||||
Aux = 2,
|
||||
};
|
||||
|
||||
enum class EffectStatus : u8 {
|
||||
None = 0,
|
||||
New = 1,
|
||||
};
|
||||
|
||||
struct AudioRendererParameter {
|
||||
u32_le sample_rate;
|
||||
u32_le sample_count;
|
||||
@@ -128,6 +138,43 @@ struct VoiceOutStatus {
|
||||
};
|
||||
static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size");
|
||||
|
||||
struct AuxInfo {
|
||||
std::array<u8, 24> input_mix_buffers;
|
||||
std::array<u8, 24> output_mix_buffers;
|
||||
u32_le mix_buffer_count;
|
||||
u32_le sample_rate; // Stored in the aux buffer currently
|
||||
u32_le sampe_count;
|
||||
u64_le send_buffer_info;
|
||||
u64_le send_buffer_base;
|
||||
|
||||
u64_le return_buffer_info;
|
||||
u64_le return_buffer_base;
|
||||
};
|
||||
static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
|
||||
|
||||
struct EffectInStatus {
|
||||
Effect type;
|
||||
u8 is_new;
|
||||
u8 is_enabled;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
u32_le mix_id;
|
||||
u64_le buffer_base;
|
||||
u64_le buffer_sz;
|
||||
s32_le priority;
|
||||
INSERT_PADDING_BYTES(4);
|
||||
union {
|
||||
std::array<u8, 0xa0> raw;
|
||||
AuxInfo aux_info;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(EffectInStatus) == 0xc0, "EffectInStatus is an invalid size");
|
||||
|
||||
struct EffectOutStatus {
|
||||
EffectStatus state;
|
||||
INSERT_PADDING_BYTES(0xf);
|
||||
};
|
||||
static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size");
|
||||
|
||||
struct UpdateDataHeader {
|
||||
UpdateDataHeader() {}
|
||||
|
||||
@@ -173,11 +220,13 @@ public:
|
||||
Stream::State GetStreamState() const;
|
||||
|
||||
private:
|
||||
class EffectState;
|
||||
class VoiceState;
|
||||
|
||||
AudioRendererParameter worker_params;
|
||||
Kernel::SharedPtr<Kernel::Event> buffer_event;
|
||||
std::vector<VoiceState> voices;
|
||||
std::vector<EffectState> effects;
|
||||
std::unique_ptr<AudioOut> audio_out;
|
||||
AudioCore::StreamPtr stream;
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ if ($ENV{CI})
|
||||
if (BUILD_VERSION)
|
||||
# This leaves a trailing space on the last word, but we actually want that
|
||||
# because of how it's styled in the title bar.
|
||||
set(BUILD_FULLNAME "${REPO_NAME} #${BUILD_VERSION} ")
|
||||
set(BUILD_FULLNAME "${REPO_NAME} ${BUILD_VERSION} ")
|
||||
else()
|
||||
set(BUILD_FULLNAME "")
|
||||
endif()
|
||||
|
||||
@@ -18,6 +18,25 @@ u8 ToHexNibble(char c1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<u8> HexStringToVector(std::string_view str, bool little_endian) {
|
||||
std::vector<u8> out(str.size() / 2);
|
||||
if (little_endian) {
|
||||
for (std::size_t i = str.size() - 2; i <= str.size(); i -= 2)
|
||||
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
|
||||
} else {
|
||||
for (std::size_t i = 0; i < str.size(); i += 2)
|
||||
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string HexVectorToString(const std::vector<u8>& vector, bool upper) {
|
||||
std::string out;
|
||||
for (u8 c : vector)
|
||||
out += fmt::format(upper ? "{:02X}" : "{:02x}", c);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::array<u8, 16> operator""_array16(const char* str, std::size_t len) {
|
||||
if (len != 32) {
|
||||
LOG_ERROR(Common,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <fmt/format.h>
|
||||
#include "common/common_types.h"
|
||||
|
||||
@@ -14,6 +15,8 @@ namespace Common {
|
||||
|
||||
u8 ToHexNibble(char c1);
|
||||
|
||||
std::vector<u8> HexStringToVector(std::string_view str, bool little_endian);
|
||||
|
||||
template <std::size_t Size, bool le = false>
|
||||
std::array<u8, Size> HexStringToArray(std::string_view str) {
|
||||
std::array<u8, Size> out{};
|
||||
@@ -27,6 +30,8 @@ std::array<u8, Size> HexStringToArray(std::string_view str) {
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string HexVectorToString(const std::vector<u8>& vector, bool upper = true);
|
||||
|
||||
template <std::size_t Size>
|
||||
std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) {
|
||||
std::string out;
|
||||
|
||||
@@ -20,7 +20,15 @@ constexpr char KEY_VALUE_SEPARATOR_ESCAPE[] = "$0";
|
||||
constexpr char PARAM_SEPARATOR_ESCAPE[] = "$1";
|
||||
constexpr char ESCAPE_CHARACTER_ESCAPE[] = "$2";
|
||||
|
||||
/// A placeholder for empty param packages to avoid empty strings
|
||||
/// (they may be recognized as "not set" by some frontend libraries like qt)
|
||||
constexpr char EMPTY_PLACEHOLDER[] = "[empty]";
|
||||
|
||||
ParamPackage::ParamPackage(const std::string& serialized) {
|
||||
if (serialized == EMPTY_PLACEHOLDER) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> pairs;
|
||||
Common::SplitString(serialized, PARAM_SEPARATOR, pairs);
|
||||
|
||||
@@ -46,7 +54,7 @@ ParamPackage::ParamPackage(std::initializer_list<DataType::value_type> list) : d
|
||||
|
||||
std::string ParamPackage::Serialize() const {
|
||||
if (data.empty())
|
||||
return "";
|
||||
return EMPTY_PLACEHOLDER;
|
||||
|
||||
std::string result;
|
||||
|
||||
@@ -120,4 +128,12 @@ bool ParamPackage::Has(const std::string& key) const {
|
||||
return data.find(key) != data.end();
|
||||
}
|
||||
|
||||
void ParamPackage::Erase(const std::string& key) {
|
||||
data.erase(key);
|
||||
}
|
||||
|
||||
void ParamPackage::Clear() {
|
||||
data.clear();
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -32,6 +32,8 @@ public:
|
||||
void Set(const std::string& key, int value);
|
||||
void Set(const std::string& key, float value);
|
||||
bool Has(const std::string& key) const;
|
||||
void Erase(const std::string& key);
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
DataType data;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -129,7 +129,7 @@ public:
|
||||
};
|
||||
|
||||
std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
|
||||
auto& current_process = Core::CurrentProcess();
|
||||
auto* current_process = Core::CurrentProcess();
|
||||
auto** const page_table = current_process->VMManager().page_table.pointers.data();
|
||||
|
||||
Dynarmic::A64::UserConfig config;
|
||||
|
||||
@@ -136,7 +136,8 @@ struct System::Impl {
|
||||
if (virtual_filesystem == nullptr)
|
||||
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
|
||||
|
||||
kernel.MakeCurrentProcess(Kernel::Process::Create(kernel, "main"));
|
||||
auto main_process = Kernel::Process::Create(kernel, "main");
|
||||
kernel.MakeCurrentProcess(main_process.get());
|
||||
|
||||
cpu_barrier = std::make_shared<CpuBarrier>();
|
||||
cpu_exclusive_monitor = Cpu::MakeExclusiveMonitor(cpu_cores.size());
|
||||
@@ -361,11 +362,11 @@ const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(std::size_t core_ind
|
||||
return impl->cpu_cores[core_index]->Scheduler();
|
||||
}
|
||||
|
||||
Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() {
|
||||
Kernel::Process* System::CurrentProcess() {
|
||||
return impl->kernel.CurrentProcess();
|
||||
}
|
||||
|
||||
const Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() const {
|
||||
const Kernel::Process* System::CurrentProcess() const {
|
||||
return impl->kernel.CurrentProcess();
|
||||
}
|
||||
|
||||
|
||||
@@ -174,11 +174,11 @@ public:
|
||||
/// Gets the scheduler for the CPU core with the specified index
|
||||
const std::shared_ptr<Kernel::Scheduler>& Scheduler(std::size_t core_index);
|
||||
|
||||
/// Provides a reference to the current process
|
||||
Kernel::SharedPtr<Kernel::Process>& CurrentProcess();
|
||||
/// Provides a pointer to the current process
|
||||
Kernel::Process* CurrentProcess();
|
||||
|
||||
/// Provides a constant reference to the current process.
|
||||
const Kernel::SharedPtr<Kernel::Process>& CurrentProcess() const;
|
||||
/// Provides a constant pointer to the current process.
|
||||
const Kernel::Process* CurrentProcess() const;
|
||||
|
||||
/// Provides a reference to the kernel instance.
|
||||
Kernel::KernelCore& Kernel();
|
||||
@@ -246,7 +246,7 @@ inline TelemetrySession& Telemetry() {
|
||||
return System::GetInstance().TelemetrySession();
|
||||
}
|
||||
|
||||
inline Kernel::SharedPtr<Kernel::Process>& CurrentProcess() {
|
||||
inline Kernel::Process* CurrentProcess() {
|
||||
return System::GetInstance().CurrentProcess();
|
||||
}
|
||||
|
||||
|
||||
@@ -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,6 +70,77 @@ 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(u8 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",
|
||||
@@ -71,23 +175,24 @@ boost::optional<Key128> DeriveSDSeed() {
|
||||
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 +223,141 @@ 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());
|
||||
ticket_save.ReadBytes(buffer.data(), buffer.size());
|
||||
|
||||
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) {
|
||||
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] = 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 +374,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 +404,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 +423,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 +480,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 +522,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 +559,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 +569,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 +585,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 +622,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 +652,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+");
|
||||
|
||||
auto res = GetTicketblob(save1);
|
||||
const auto res2 = GetTicketblob(save2);
|
||||
std::copy(res2.begin(), res2.end(), std::back_inserter(res));
|
||||
|
||||
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(u8 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
|
||||
|
||||
601
src/core/crypto/partition_data_manager.cpp
Normal file
601
src/core/crypto/partition_data_manager.cpp
Normal file
@@ -0,0 +1,601 @@
|
||||
// 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 <boost/optional/optional.hpp>
|
||||
#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 "core/crypto/ctr_encryption_layer.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(FileSys::VirtualDir sysdata_dir)
|
||||
: boot0(FindFileInDirWithNames(sysdata_dir, "BOOT0")),
|
||||
fuses(FindFileInDirWithNames(sysdata_dir, "fuse")),
|
||||
kfuses(FindFileInDirWithNames(sysdata_dir, "kfuse")),
|
||||
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"),
|
||||
}),
|
||||
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()),
|
||||
prodinfo(FindFileInDirWithNames(sysdata_dir, "PRODINFO")) {}
|
||||
|
||||
PartitionDataManager::~PartitionDataManager() = default;
|
||||
|
||||
bool PartitionDataManager::HasBoot0() const {
|
||||
return boot0 != nullptr;
|
||||
}
|
||||
|
||||
FileSys::VirtualFile PartitionDataManager::GetBoot0Raw() const {
|
||||
return boot0;
|
||||
}
|
||||
|
||||
std::array<u8, 176> PartitionDataManager::GetEncryptedKeyblob(u8 index) const {
|
||||
if (HasBoot0() && index < 32)
|
||||
return GetEncryptedKeyblobs()[index];
|
||||
return {};
|
||||
}
|
||||
|
||||
std::array<std::array<u8, 176>, 32> PartitionDataManager::GetEncryptedKeyblobs() const {
|
||||
if (!HasBoot0())
|
||||
return {};
|
||||
|
||||
std::array<std::array<u8, 176>, 32> out{};
|
||||
for (size_t i = 0; i < 0x20; ++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(u8 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(std::array<std::array<u8, 16>, 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;
|
||||
|
||||
u8 revision = 0xFF;
|
||||
if (header.magic != Common::MakeMagic('P', 'K', '2', '1')) {
|
||||
for (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 std::vector<u8> s1_iv(header.section_ctr[1].begin(), header.section_ctr[1].end());
|
||||
|
||||
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(s1_iv);
|
||||
cipher.Transcode(c.data(), c.size(), c.data(), Op::Decrypt);
|
||||
|
||||
// package2_decrypted[static_cast<size_t>(type)] = s1;
|
||||
|
||||
INIHeader ini;
|
||||
std::memcpy(&ini, c.data(), sizeof(INIHeader));
|
||||
if (ini.magic != Common::MakeMagic('I', 'N', 'I', '1'))
|
||||
return;
|
||||
|
||||
std::map<u64, KIPHeader> kips{};
|
||||
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;
|
||||
kips.emplace(offset, kip);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
std::vector<u8> text(kip.sections[0].size_compressed);
|
||||
std::vector<u8> rodata(kip.sections[1].size_compressed);
|
||||
std::vector<u8> data(kip.sections[2].size_compressed);
|
||||
|
||||
u64 offset_sec = sizeof(KIPHeader) + offset;
|
||||
std::memcpy(text.data(), c.data() + offset_sec, text.size());
|
||||
offset_sec += text.size();
|
||||
std::memcpy(rodata.data(), c.data() + offset_sec, rodata.size());
|
||||
offset_sec += rodata.size();
|
||||
std::memcpy(data.data(), c.data() + offset_sec, data.size());
|
||||
|
||||
offset += sizeof(KIPHeader) + kip.sections[0].size_compressed +
|
||||
kip.sections[1].size_compressed + kip.sections[2].size_compressed;
|
||||
|
||||
text = DecompressBLZ(text);
|
||||
rodata = DecompressBLZ(rodata);
|
||||
data = DecompressBLZ(data);
|
||||
|
||||
std::vector<u8> out(text.size() + rodata.size() + data.size());
|
||||
std::memcpy(out.data(), text.data(), text.size());
|
||||
std::memcpy(out.data() + text.size(), rodata.data(), rodata.size());
|
||||
std::memcpy(out.data() + text.size() + rodata.size(), data.data(), data.size());
|
||||
|
||||
if (name == "FS")
|
||||
package2_fs[static_cast<size_t>(type)] = out;
|
||||
else if (name == "spl")
|
||||
package2_spl[static_cast<size_t>(type)] = 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
|
||||
105
src/core/crypto/partition_data_manager.h
Normal file
105
src/core/crypto/partition_data_manager.h
Normal file
@@ -0,0 +1,105 @@
|
||||
// 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_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.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;
|
||||
|
||||
explicit PartitionDataManager(FileSys::VirtualDir sysdata_dir);
|
||||
~PartitionDataManager();
|
||||
|
||||
// BOOT0
|
||||
bool HasBoot0() const;
|
||||
FileSys::VirtualFile GetBoot0Raw() const;
|
||||
std::array<u8, 0xB0> GetEncryptedKeyblob(u8 index) const;
|
||||
std::array<std::array<u8, 0xB0>, 0x20> 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(u8 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(std::array<std::array<u8, 16>, 0x20> package2, 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
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/file_sys/fsmitm_romfsbuild.h"
|
||||
#include "core/file_sys/ips_layer.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
|
||||
@@ -123,7 +124,7 @@ static u64 romfs_get_hash_table_count(u64 num_entries) {
|
||||
return count;
|
||||
}
|
||||
|
||||
void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
|
||||
void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, VirtualDir ext,
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> parent) {
|
||||
std::vector<std::shared_ptr<RomFSBuildDirectoryContext>> child_dirs;
|
||||
|
||||
@@ -144,6 +145,9 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
|
||||
child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
|
||||
child->path = parent->path + "/" + kv.first;
|
||||
|
||||
if (ext != nullptr && ext->GetFileRelative(child->path + ".stub") != nullptr)
|
||||
continue;
|
||||
|
||||
// Sanity check on path_len
|
||||
ASSERT(child->path_len < FS_MAX_PATH);
|
||||
|
||||
@@ -157,11 +161,24 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
|
||||
child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
|
||||
child->path = parent->path + "/" + kv.first;
|
||||
|
||||
if (ext != nullptr && ext->GetFileRelative(child->path + ".stub") != nullptr)
|
||||
continue;
|
||||
|
||||
// Sanity check on path_len
|
||||
ASSERT(child->path_len < FS_MAX_PATH);
|
||||
|
||||
child->source = root_romfs->GetFileRelative(child->path);
|
||||
|
||||
if (ext != nullptr) {
|
||||
const auto ips = ext->GetFileRelative(child->path + ".ips");
|
||||
|
||||
if (ips != nullptr) {
|
||||
auto patched = PatchIPS(child->source, ips);
|
||||
if (patched != nullptr)
|
||||
child->source = std::move(patched);
|
||||
}
|
||||
}
|
||||
|
||||
child->size = child->source->GetSize();
|
||||
|
||||
AddFile(parent, child);
|
||||
@@ -169,7 +186,7 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
|
||||
}
|
||||
|
||||
for (auto& child : child_dirs) {
|
||||
this->VisitDirectory(root_romfs, child);
|
||||
this->VisitDirectory(root_romfs, ext, child);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,14 +225,15 @@ bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> pare
|
||||
return true;
|
||||
}
|
||||
|
||||
RomFSBuildContext::RomFSBuildContext(VirtualDir base_) : base(std::move(base_)) {
|
||||
RomFSBuildContext::RomFSBuildContext(VirtualDir base_, VirtualDir ext_)
|
||||
: base(std::move(base_)), ext(std::move(ext_)) {
|
||||
root = std::make_shared<RomFSBuildDirectoryContext>();
|
||||
root->path = "\0";
|
||||
directories.emplace(root->path, root);
|
||||
num_dirs = 1;
|
||||
dir_table_size = 0x18;
|
||||
|
||||
VisitDirectory(base, root);
|
||||
VisitDirectory(base, ext, root);
|
||||
}
|
||||
|
||||
RomFSBuildContext::~RomFSBuildContext() = default;
|
||||
|
||||
@@ -40,7 +40,7 @@ struct RomFSFileEntry;
|
||||
|
||||
class RomFSBuildContext {
|
||||
public:
|
||||
explicit RomFSBuildContext(VirtualDir base);
|
||||
explicit RomFSBuildContext(VirtualDir base, VirtualDir ext = nullptr);
|
||||
~RomFSBuildContext();
|
||||
|
||||
// This finalizes the context.
|
||||
@@ -48,6 +48,7 @@ public:
|
||||
|
||||
private:
|
||||
VirtualDir base;
|
||||
VirtualDir ext;
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> root;
|
||||
std::map<std::string, std::shared_ptr<RomFSBuildDirectoryContext>, std::less<>> directories;
|
||||
std::map<std::string, std::shared_ptr<RomFSBuildFileContext>, std::less<>> files;
|
||||
@@ -59,7 +60,8 @@ private:
|
||||
u64 file_hash_table_size = 0;
|
||||
u64 file_partition_size = 0;
|
||||
|
||||
void VisitDirectory(VirtualDir filesys, std::shared_ptr<RomFSBuildDirectoryContext> parent);
|
||||
void VisitDirectory(VirtualDir filesys, VirtualDir ext,
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> parent);
|
||||
|
||||
bool AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
|
||||
std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx);
|
||||
|
||||
@@ -2,7 +2,15 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/ips_layer.h"
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
@@ -15,16 +23,48 @@ enum class IPSFileType {
|
||||
Error,
|
||||
};
|
||||
|
||||
constexpr std::array<std::pair<const char*, const char*>, 11> ESCAPE_CHARACTER_MAP{{
|
||||
{"\\a", "\a"},
|
||||
{"\\b", "\b"},
|
||||
{"\\f", "\f"},
|
||||
{"\\n", "\n"},
|
||||
{"\\r", "\r"},
|
||||
{"\\t", "\t"},
|
||||
{"\\v", "\v"},
|
||||
{"\\\\", "\\"},
|
||||
{"\\\'", "\'"},
|
||||
{"\\\"", "\""},
|
||||
{"\\\?", "\?"},
|
||||
}};
|
||||
|
||||
static IPSFileType IdentifyMagic(const std::vector<u8>& magic) {
|
||||
if (magic.size() != 5)
|
||||
if (magic.size() != 5) {
|
||||
return IPSFileType::Error;
|
||||
if (magic == std::vector<u8>{'P', 'A', 'T', 'C', 'H'})
|
||||
}
|
||||
|
||||
constexpr std::array<u8, 5> patch_magic{{'P', 'A', 'T', 'C', 'H'}};
|
||||
if (std::equal(magic.begin(), magic.end(), patch_magic.begin())) {
|
||||
return IPSFileType::IPS;
|
||||
if (magic == std::vector<u8>{'I', 'P', 'S', '3', '2'})
|
||||
}
|
||||
|
||||
constexpr std::array<u8, 5> ips32_magic{{'I', 'P', 'S', '3', '2'}};
|
||||
if (std::equal(magic.begin(), magic.end(), ips32_magic.begin())) {
|
||||
return IPSFileType::IPS32;
|
||||
}
|
||||
|
||||
return IPSFileType::Error;
|
||||
}
|
||||
|
||||
static bool IsEOF(IPSFileType type, const std::vector<u8>& data) {
|
||||
constexpr std::array<u8, 3> eof{{'E', 'O', 'F'}};
|
||||
if (type == IPSFileType::IPS && std::equal(data.begin(), data.end(), eof.begin())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr std::array<u8, 4> eeof{{'E', 'E', 'O', 'F'}};
|
||||
return type == IPSFileType::IPS32 && std::equal(data.begin(), data.end(), eeof.begin());
|
||||
}
|
||||
|
||||
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
|
||||
if (in == nullptr || ips == nullptr)
|
||||
return nullptr;
|
||||
@@ -39,8 +79,7 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
|
||||
u64 offset = 5; // After header
|
||||
while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) {
|
||||
offset += temp.size();
|
||||
if (type == IPSFileType::IPS32 && temp == std::vector<u8>{'E', 'E', 'O', 'F'} ||
|
||||
type == IPSFileType::IPS && temp == std::vector<u8>{'E', 'O', 'F'}) {
|
||||
if (IsEOF(type, temp)) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -68,21 +107,232 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
|
||||
return nullptr;
|
||||
|
||||
if (real_offset + rle_size > in_data.size())
|
||||
rle_size = in_data.size() - real_offset;
|
||||
rle_size = static_cast<u16>(in_data.size() - real_offset);
|
||||
std::memset(in_data.data() + real_offset, data.get(), rle_size);
|
||||
} else { // Standard Patch
|
||||
auto read = data_size;
|
||||
if (real_offset + read > in_data.size())
|
||||
read = in_data.size() - real_offset;
|
||||
read = static_cast<u16>(in_data.size() - real_offset);
|
||||
if (ips->Read(in_data.data() + real_offset, read, offset) != data_size)
|
||||
return nullptr;
|
||||
offset += data_size;
|
||||
}
|
||||
}
|
||||
|
||||
if (temp != std::vector<u8>{'E', 'E', 'O', 'F'} && temp != std::vector<u8>{'E', 'O', 'F'})
|
||||
if (!IsEOF(type, temp)) {
|
||||
return nullptr;
|
||||
return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory());
|
||||
}
|
||||
|
||||
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
|
||||
in->GetContainingDirectory());
|
||||
}
|
||||
|
||||
struct IPSwitchCompiler::IPSwitchPatch {
|
||||
std::string name;
|
||||
bool enabled;
|
||||
std::map<u32, std::vector<u8>> records;
|
||||
};
|
||||
|
||||
IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) {
|
||||
Parse();
|
||||
}
|
||||
|
||||
IPSwitchCompiler::~IPSwitchCompiler() = default;
|
||||
|
||||
std::array<u8, 32> IPSwitchCompiler::GetBuildID() const {
|
||||
return nso_build_id;
|
||||
}
|
||||
|
||||
bool IPSwitchCompiler::IsValid() const {
|
||||
return valid;
|
||||
}
|
||||
|
||||
static bool StartsWith(std::string_view base, std::string_view check) {
|
||||
return base.size() >= check.size() && base.substr(0, check.size()) == check;
|
||||
}
|
||||
|
||||
static std::string EscapeStringSequences(std::string in) {
|
||||
for (const auto& seq : ESCAPE_CHARACTER_MAP) {
|
||||
for (auto index = in.find(seq.first); index != std::string::npos;
|
||||
index = in.find(seq.first, index)) {
|
||||
in.replace(index, std::strlen(seq.first), seq.second);
|
||||
index += std::strlen(seq.second);
|
||||
}
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
void IPSwitchCompiler::ParseFlag(const std::string& line) {
|
||||
if (StartsWith(line, "@flag offset_shift ")) {
|
||||
// Offset Shift Flag
|
||||
offset_shift = std::stoll(line.substr(19), nullptr, 0);
|
||||
} else if (StartsWith(line, "@little-endian")) {
|
||||
// Set values to read as little endian
|
||||
is_little_endian = true;
|
||||
} else if (StartsWith(line, "@big-endian")) {
|
||||
// Set values to read as big endian
|
||||
is_little_endian = false;
|
||||
} else if (StartsWith(line, "@flag print_values")) {
|
||||
// Force printing of applied values
|
||||
print_values = true;
|
||||
}
|
||||
}
|
||||
|
||||
void IPSwitchCompiler::Parse() {
|
||||
const auto bytes = patch_text->ReadAllBytes();
|
||||
std::stringstream s;
|
||||
s.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
|
||||
|
||||
std::vector<std::string> lines;
|
||||
std::string stream_line;
|
||||
while (std::getline(s, stream_line)) {
|
||||
// Remove a trailing \r
|
||||
if (!stream_line.empty() && stream_line.back() == '\r')
|
||||
stream_line.pop_back();
|
||||
lines.push_back(std::move(stream_line));
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < lines.size(); ++i) {
|
||||
auto line = lines[i];
|
||||
|
||||
// Remove midline comments
|
||||
std::size_t comment_index = std::string::npos;
|
||||
bool within_string = false;
|
||||
for (std::size_t k = 0; k < line.size(); ++k) {
|
||||
if (line[k] == '\"' && (k > 0 && line[k - 1] != '\\')) {
|
||||
within_string = !within_string;
|
||||
} else if (line[k] == '\\' && (k < line.size() - 1 && line[k + 1] == '\\')) {
|
||||
comment_index = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!StartsWith(line, "//") && comment_index != std::string::npos) {
|
||||
last_comment = line.substr(comment_index + 2);
|
||||
line = line.substr(0, comment_index);
|
||||
}
|
||||
|
||||
if (StartsWith(line, "@stop")) {
|
||||
// Force stop
|
||||
break;
|
||||
} else if (StartsWith(line, "@nsobid-")) {
|
||||
// NSO Build ID Specifier
|
||||
auto raw_build_id = line.substr(8);
|
||||
if (raw_build_id.size() != 0x40)
|
||||
raw_build_id.resize(0x40, '0');
|
||||
nso_build_id = Common::HexStringToArray<0x20>(raw_build_id);
|
||||
} else if (StartsWith(line, "#")) {
|
||||
// Mandatory Comment
|
||||
LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Forced output comment: {}",
|
||||
patch_text->GetName(), line.substr(1));
|
||||
} else if (StartsWith(line, "//")) {
|
||||
// Normal Comment
|
||||
last_comment = line.substr(2);
|
||||
if (last_comment.find_first_not_of(' ') == std::string::npos)
|
||||
continue;
|
||||
if (last_comment.find_first_not_of(' ') != 0)
|
||||
last_comment = last_comment.substr(last_comment.find_first_not_of(' '));
|
||||
} else if (StartsWith(line, "@enabled") || StartsWith(line, "@disabled")) {
|
||||
// Start of patch
|
||||
const auto enabled = StartsWith(line, "@enabled");
|
||||
if (i == 0)
|
||||
return;
|
||||
LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Parsing patch '{}' ({})",
|
||||
patch_text->GetName(), last_comment, line.substr(1));
|
||||
|
||||
IPSwitchPatch patch{last_comment, enabled, {}};
|
||||
|
||||
// Read rest of patch
|
||||
while (true) {
|
||||
if (i + 1 >= lines.size())
|
||||
break;
|
||||
const auto patch_line = lines[++i];
|
||||
|
||||
// Start of new patch
|
||||
if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) {
|
||||
--i;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for a flag
|
||||
if (StartsWith(patch_line, "@")) {
|
||||
ParseFlag(patch_line);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 11 - 8 hex digit offset + space + minimum two digit overwrite val
|
||||
if (patch_line.length() < 11)
|
||||
break;
|
||||
auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16);
|
||||
offset += static_cast<unsigned long>(offset_shift);
|
||||
|
||||
std::vector<u8> replace;
|
||||
// 9 - first char of replacement val
|
||||
if (patch_line[9] == '\"') {
|
||||
// string replacement
|
||||
auto end_index = patch_line.find('\"', 10);
|
||||
if (end_index == std::string::npos || end_index < 10)
|
||||
return;
|
||||
while (patch_line[end_index - 1] == '\\') {
|
||||
end_index = patch_line.find('\"', end_index + 1);
|
||||
if (end_index == std::string::npos || end_index < 10)
|
||||
return;
|
||||
}
|
||||
|
||||
auto value = patch_line.substr(10, end_index - 10);
|
||||
value = EscapeStringSequences(value);
|
||||
replace.reserve(value.size());
|
||||
std::copy(value.begin(), value.end(), std::back_inserter(replace));
|
||||
} else {
|
||||
// hex replacement
|
||||
const auto value = patch_line.substr(9);
|
||||
replace.reserve(value.size() / 2);
|
||||
replace = Common::HexStringToVector(value, is_little_endian);
|
||||
}
|
||||
|
||||
if (print_values) {
|
||||
LOG_INFO(Loader,
|
||||
"[IPSwitchCompiler ('{}')] - Patching value at offset 0x{:08X} "
|
||||
"with byte string '{}'",
|
||||
patch_text->GetName(), offset, Common::HexVectorToString(replace));
|
||||
}
|
||||
|
||||
patch.records.insert_or_assign(offset, std::move(replace));
|
||||
}
|
||||
|
||||
patches.push_back(std::move(patch));
|
||||
} else if (StartsWith(line, "@")) {
|
||||
ParseFlag(line);
|
||||
}
|
||||
}
|
||||
|
||||
valid = true;
|
||||
}
|
||||
|
||||
VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const {
|
||||
if (in == nullptr || !valid)
|
||||
return nullptr;
|
||||
|
||||
auto in_data = in->ReadAllBytes();
|
||||
|
||||
for (const auto& patch : patches) {
|
||||
if (!patch.enabled)
|
||||
continue;
|
||||
|
||||
for (const auto& record : patch.records) {
|
||||
if (record.first >= in_data.size())
|
||||
continue;
|
||||
auto replace_size = record.second.size();
|
||||
if (record.first + replace_size > in_data.size())
|
||||
replace_size = in_data.size() - record.first;
|
||||
for (std::size_t i = 0; i < replace_size; ++i)
|
||||
in_data[i + record.first] = record.second[i];
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
|
||||
in->GetContainingDirectory());
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -4,12 +4,41 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips);
|
||||
|
||||
class IPSwitchCompiler {
|
||||
public:
|
||||
explicit IPSwitchCompiler(VirtualFile patch_text);
|
||||
~IPSwitchCompiler();
|
||||
|
||||
std::array<u8, 0x20> GetBuildID() const;
|
||||
bool IsValid() const;
|
||||
VirtualFile Apply(const VirtualFile& in) const;
|
||||
|
||||
private:
|
||||
struct IPSwitchPatch;
|
||||
|
||||
void ParseFlag(const std::string& flag);
|
||||
void Parse();
|
||||
|
||||
bool valid = false;
|
||||
|
||||
VirtualFile patch_text;
|
||||
std::vector<IPSwitchPatch> patches;
|
||||
std::array<u8, 0x20> nso_build_id{};
|
||||
bool is_little_endian = false;
|
||||
s64 offset_shift = 0;
|
||||
bool print_values = false;
|
||||
std::string last_comment = "";
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -73,27 +73,38 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
return exefs;
|
||||
}
|
||||
|
||||
static std::vector<VirtualFile> CollectIPSPatches(const std::vector<VirtualDir>& patch_dirs,
|
||||
const std::string& build_id) {
|
||||
std::vector<VirtualFile> ips;
|
||||
ips.reserve(patch_dirs.size());
|
||||
static std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
|
||||
const std::string& build_id) {
|
||||
std::vector<VirtualFile> out;
|
||||
out.reserve(patch_dirs.size());
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
auto exefs_dir = subdir->GetSubdirectory("exefs");
|
||||
if (exefs_dir != nullptr) {
|
||||
for (const auto& file : exefs_dir->GetFiles()) {
|
||||
if (file->GetExtension() != "ips")
|
||||
continue;
|
||||
auto name = file->GetName();
|
||||
const auto p1 = name.substr(0, name.find('.'));
|
||||
const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1);
|
||||
if (file->GetExtension() == "ips") {
|
||||
auto name = file->GetName();
|
||||
const auto p1 = name.substr(0, name.find('.'));
|
||||
const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1);
|
||||
|
||||
if (build_id == this_build_id)
|
||||
ips.push_back(file);
|
||||
if (build_id == this_build_id)
|
||||
out.push_back(file);
|
||||
} else if (file->GetExtension() == "pchtxt") {
|
||||
IPSwitchCompiler compiler{file};
|
||||
if (!compiler.IsValid())
|
||||
continue;
|
||||
|
||||
auto this_build_id = Common::HexArrayToString(compiler.GetBuildID());
|
||||
this_build_id =
|
||||
this_build_id.substr(0, this_build_id.find_last_not_of('0') + 1);
|
||||
|
||||
if (build_id == this_build_id)
|
||||
out.push_back(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ips;
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
|
||||
@@ -115,15 +126,24 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
const auto ips = CollectIPSPatches(patch_dirs, build_id);
|
||||
const auto patches = CollectPatches(patch_dirs, build_id);
|
||||
|
||||
auto out = nso;
|
||||
for (const auto& ips_file : ips) {
|
||||
LOG_INFO(Loader, " - Appling IPS patch from mod \"{}\"",
|
||||
ips_file->GetContainingDirectory()->GetParentDirectory()->GetName());
|
||||
const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), ips_file);
|
||||
if (patched != nullptr)
|
||||
out = patched->ReadAllBytes();
|
||||
for (const auto& patch_file : patches) {
|
||||
if (patch_file->GetExtension() == "ips") {
|
||||
LOG_INFO(Loader, " - Applying IPS patch from mod \"{}\"",
|
||||
patch_file->GetContainingDirectory()->GetParentDirectory()->GetName());
|
||||
const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), patch_file);
|
||||
if (patched != nullptr)
|
||||
out = patched->ReadAllBytes();
|
||||
} else if (patch_file->GetExtension() == "pchtxt") {
|
||||
LOG_INFO(Loader, " - Applying IPSwitch patch from mod \"{}\"",
|
||||
patch_file->GetContainingDirectory()->GetParentDirectory()->GetName());
|
||||
const IPSwitchCompiler compiler{patch_file};
|
||||
const auto patched = compiler.Apply(std::make_shared<VectorVfsFile>(out));
|
||||
if (patched != nullptr)
|
||||
out = patched->ReadAllBytes();
|
||||
}
|
||||
}
|
||||
|
||||
if (out.size() < 0x100)
|
||||
@@ -143,7 +163,7 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
return !CollectIPSPatches(patch_dirs, build_id).empty();
|
||||
return !CollectPatches(patch_dirs, build_id).empty();
|
||||
}
|
||||
|
||||
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
|
||||
@@ -162,11 +182,17 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
|
||||
std::vector<VirtualDir> layers;
|
||||
std::vector<VirtualDir> layers_ext;
|
||||
layers.reserve(patch_dirs.size() + 1);
|
||||
layers_ext.reserve(patch_dirs.size() + 1);
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
auto romfs_dir = subdir->GetSubdirectory("romfs");
|
||||
if (romfs_dir != nullptr)
|
||||
layers.push_back(std::move(romfs_dir));
|
||||
|
||||
auto ext_dir = subdir->GetSubdirectory("romfs_ext");
|
||||
if (ext_dir != nullptr)
|
||||
layers_ext.push_back(std::move(ext_dir));
|
||||
}
|
||||
layers.push_back(std::move(extracted));
|
||||
|
||||
@@ -175,7 +201,9 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
|
||||
return;
|
||||
}
|
||||
|
||||
auto packed = CreateRomFS(std::move(layered));
|
||||
auto layered_ext = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers_ext));
|
||||
|
||||
auto packed = CreateRomFS(std::move(layered), std::move(layered_ext));
|
||||
if (packed == nullptr) {
|
||||
return;
|
||||
}
|
||||
@@ -184,8 +212,8 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
|
||||
romfs = std::move(packed);
|
||||
}
|
||||
|
||||
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
|
||||
ContentRecordType type) const {
|
||||
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));
|
||||
|
||||
@@ -205,6 +233,13 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
|
||||
FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
|
||||
romfs = new_nca->GetRomFS();
|
||||
}
|
||||
} else if (update_raw != nullptr) {
|
||||
const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset);
|
||||
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
|
||||
new_nca->GetRomFS() != nullptr) {
|
||||
LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully");
|
||||
romfs = new_nca->GetRomFS();
|
||||
}
|
||||
}
|
||||
|
||||
// LayeredFS
|
||||
@@ -224,7 +259,8 @@ static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
|
||||
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
|
||||
}
|
||||
|
||||
std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames() const {
|
||||
std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
|
||||
VirtualFile update_raw) const {
|
||||
std::map<std::string, std::string, std::less<>> out;
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
|
||||
@@ -245,6 +281,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
||||
"Update",
|
||||
FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements));
|
||||
}
|
||||
} else if (update_raw != nullptr) {
|
||||
out.insert_or_assign("Update", "PACKED");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,8 +291,24 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
||||
if (mod_dir != nullptr && mod_dir->GetSize() > 0) {
|
||||
for (const auto& mod : mod_dir->GetSubdirectories()) {
|
||||
std::string types;
|
||||
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("exefs")))
|
||||
AppendCommaIfNotEmpty(types, "IPS");
|
||||
|
||||
const auto exefs_dir = mod->GetSubdirectory("exefs");
|
||||
if (IsDirValidAndNonEmpty(exefs_dir)) {
|
||||
bool ips = false;
|
||||
bool ipswitch = false;
|
||||
|
||||
for (const auto& file : exefs_dir->GetFiles()) {
|
||||
if (file->GetExtension() == "ips")
|
||||
ips = true;
|
||||
else if (file->GetExtension() == "pchtxt")
|
||||
ipswitch = true;
|
||||
}
|
||||
|
||||
if (ips)
|
||||
AppendCommaIfNotEmpty(types, "IPS");
|
||||
if (ipswitch)
|
||||
AppendCommaIfNotEmpty(types, "IPSwitch");
|
||||
}
|
||||
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
|
||||
AppendCommaIfNotEmpty(types, "LayeredFS");
|
||||
|
||||
@@ -291,23 +345,22 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
||||
return out;
|
||||
}
|
||||
|
||||
std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
|
||||
std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
|
||||
const auto& installed{Service::FileSystem::GetUnionContents()};
|
||||
|
||||
const auto base_control_nca = installed->GetEntry(title_id, ContentRecordType::Control);
|
||||
if (base_control_nca == nullptr)
|
||||
return {};
|
||||
|
||||
return ParseControlNCA(base_control_nca);
|
||||
return ParseControlNCA(*base_control_nca);
|
||||
}
|
||||
|
||||
std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(
|
||||
const std::shared_ptr<NCA>& nca) const {
|
||||
const auto base_romfs = nca->GetRomFS();
|
||||
std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(const NCA& nca) const {
|
||||
const auto base_romfs = nca.GetRomFS();
|
||||
if (base_romfs == nullptr)
|
||||
return {};
|
||||
|
||||
const auto romfs = PatchRomFS(base_romfs, nca->GetBaseIVFCOffset(), ContentRecordType::Control);
|
||||
const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control);
|
||||
if (romfs == nullptr)
|
||||
return {};
|
||||
|
||||
@@ -319,7 +372,7 @@ std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(
|
||||
if (nacp_file == nullptr)
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
|
||||
const auto nacp = nacp_file == nullptr ? nullptr : std::make_shared<NACP>(nacp_file);
|
||||
auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file);
|
||||
|
||||
VirtualFile icon_file;
|
||||
for (const auto& language : FileSys::LANGUAGE_NAMES) {
|
||||
@@ -328,6 +381,6 @@ std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(
|
||||
break;
|
||||
}
|
||||
|
||||
return {nacp, icon_file};
|
||||
return {std::move(nacp), icon_file};
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -36,6 +36,7 @@ public:
|
||||
|
||||
// Currently tracked NSO patches:
|
||||
// - IPS
|
||||
// - IPSwitch
|
||||
std::vector<u8> PatchNSO(const std::vector<u8>& nso) const;
|
||||
|
||||
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
|
||||
@@ -46,19 +47,20 @@ public:
|
||||
// - Game Updates
|
||||
// - LayeredFS
|
||||
VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
|
||||
ContentRecordType type = ContentRecordType::Program) const;
|
||||
ContentRecordType type = ContentRecordType::Program,
|
||||
VirtualFile update_raw = nullptr) const;
|
||||
|
||||
// Returns a vector of pairs between patch names and patch versions.
|
||||
// i.e. Update 3.2.2 will return {"Update", "3.2.2"}
|
||||
std::map<std::string, std::string, std::less<>> GetPatchVersionNames() const;
|
||||
std::map<std::string, std::string, std::less<>> GetPatchVersionNames(
|
||||
VirtualFile update_raw = nullptr) const;
|
||||
|
||||
// Given title_id of the program, attempts to get the control data of the update and parse it,
|
||||
// falling back to the base control data.
|
||||
std::pair<std::shared_ptr<NACP>, VirtualFile> GetControlMetadata() const;
|
||||
std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const;
|
||||
|
||||
// Version of GetControlMetadata that takes an arbitrary NCA
|
||||
std::pair<std::shared_ptr<NACP>, VirtualFile> ParseControlNCA(
|
||||
const std::shared_ptr<NCA>& nca) const;
|
||||
std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const;
|
||||
|
||||
private:
|
||||
u64 title_id;
|
||||
|
||||
@@ -129,11 +129,11 @@ VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
|
||||
return out;
|
||||
}
|
||||
|
||||
VirtualFile CreateRomFS(VirtualDir dir) {
|
||||
VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext) {
|
||||
if (dir == nullptr)
|
||||
return nullptr;
|
||||
|
||||
RomFSBuildContext ctx{dir};
|
||||
RomFSBuildContext ctx{dir, ext};
|
||||
return ConcatenatedVfsFile::MakeConcatenatedFile(0, ctx.Build(), dir->GetName());
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,6 @@ VirtualDir ExtractRomFS(VirtualFile file,
|
||||
|
||||
// Converts a VFS filesystem into a RomFS binary
|
||||
// Returns nullptr on failure
|
||||
VirtualFile CreateRomFS(VirtualDir dir);
|
||||
VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext = nullptr);
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -30,12 +30,17 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) {
|
||||
|
||||
RomFSFactory::~RomFSFactory() = default;
|
||||
|
||||
void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) {
|
||||
this->update_raw = std::move(update_raw);
|
||||
}
|
||||
|
||||
ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() {
|
||||
if (!updatable)
|
||||
return MakeResult<VirtualFile>(file);
|
||||
|
||||
const PatchManager patch_manager(Core::CurrentProcess()->GetTitleID());
|
||||
return MakeResult<VirtualFile>(patch_manager.PatchRomFS(file, ivfc_offset));
|
||||
return MakeResult<VirtualFile>(
|
||||
patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw));
|
||||
}
|
||||
|
||||
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) {
|
||||
|
||||
@@ -32,11 +32,13 @@ public:
|
||||
explicit RomFSFactory(Loader::AppLoader& app_loader);
|
||||
~RomFSFactory();
|
||||
|
||||
void SetPackedUpdate(VirtualFile update_raw);
|
||||
ResultVal<VirtualFile> OpenCurrentProcess();
|
||||
ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type);
|
||||
|
||||
private:
|
||||
VirtualFile file;
|
||||
VirtualFile update_raw;
|
||||
bool updatable;
|
||||
u64 ivfc_offset;
|
||||
};
|
||||
|
||||
@@ -259,8 +259,11 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
|
||||
auto next_nca = std::make_shared<NCA>(next_file);
|
||||
if (next_nca->GetType() == NCAContentType::Program)
|
||||
program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
|
||||
if (next_nca->GetStatus() == Loader::ResultStatus::Success)
|
||||
if (next_nca->GetStatus() == Loader::ResultStatus::Success ||
|
||||
(next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
|
||||
(cnmt.GetTitleID() & 0x800) != 0)) {
|
||||
ncas_title[rec.type] = std::move(next_nca);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -116,7 +116,7 @@ struct KernelCore::Impl {
|
||||
next_thread_id = 1;
|
||||
|
||||
process_list.clear();
|
||||
current_process.reset();
|
||||
current_process = nullptr;
|
||||
|
||||
handle_table.Clear();
|
||||
resource_limits.fill(nullptr);
|
||||
@@ -207,7 +207,7 @@ struct KernelCore::Impl {
|
||||
|
||||
// Lists all processes that exist in the current session.
|
||||
std::vector<SharedPtr<Process>> process_list;
|
||||
SharedPtr<Process> current_process;
|
||||
Process* current_process = nullptr;
|
||||
|
||||
Kernel::HandleTable handle_table;
|
||||
std::array<SharedPtr<ResourceLimit>, 4> resource_limits;
|
||||
@@ -266,15 +266,15 @@ void KernelCore::AppendNewProcess(SharedPtr<Process> process) {
|
||||
impl->process_list.push_back(std::move(process));
|
||||
}
|
||||
|
||||
void KernelCore::MakeCurrentProcess(SharedPtr<Process> process) {
|
||||
impl->current_process = std::move(process);
|
||||
void KernelCore::MakeCurrentProcess(Process* process) {
|
||||
impl->current_process = process;
|
||||
}
|
||||
|
||||
SharedPtr<Process>& KernelCore::CurrentProcess() {
|
||||
Process* KernelCore::CurrentProcess() {
|
||||
return impl->current_process;
|
||||
}
|
||||
|
||||
const SharedPtr<Process>& KernelCore::CurrentProcess() const {
|
||||
const Process* KernelCore::CurrentProcess() const {
|
||||
return impl->current_process;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,13 +66,13 @@ public:
|
||||
void AppendNewProcess(SharedPtr<Process> process);
|
||||
|
||||
/// Makes the given process the new current process.
|
||||
void MakeCurrentProcess(SharedPtr<Process> process);
|
||||
void MakeCurrentProcess(Process* process);
|
||||
|
||||
/// Retrieves a reference to the current process.
|
||||
SharedPtr<Process>& CurrentProcess();
|
||||
/// Retrieves a pointer to the current process.
|
||||
Process* CurrentProcess();
|
||||
|
||||
/// Retrieves a const reference to the current process.
|
||||
const SharedPtr<Process>& CurrentProcess() const;
|
||||
/// Retrieves a const pointer to the current process.
|
||||
const Process* CurrentProcess() const;
|
||||
|
||||
/// Adds a port to the named port table
|
||||
void AddNamedPort(std::string name, SharedPtr<ClientPort> port);
|
||||
|
||||
@@ -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) {
|
||||
@@ -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
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
|
||||
@@ -78,16 +78,16 @@ void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
// Cancel any outstanding wakeup events for this thread
|
||||
new_thread->CancelWakeupTimer();
|
||||
|
||||
auto previous_process = Core::CurrentProcess();
|
||||
auto* const previous_process = Core::CurrentProcess();
|
||||
|
||||
current_thread = new_thread;
|
||||
|
||||
ready_queue.remove(new_thread->GetPriority(), new_thread);
|
||||
new_thread->SetStatus(ThreadStatus::Running);
|
||||
|
||||
const auto thread_owner_process = current_thread->GetOwnerProcess();
|
||||
auto* const thread_owner_process = current_thread->GetOwnerProcess();
|
||||
if (previous_process != thread_owner_process) {
|
||||
Core::CurrentProcess() = thread_owner_process;
|
||||
Core::System::GetInstance().Kernel().MakeCurrentProcess(thread_owner_process);
|
||||
SetCurrentPageTable(&Core::CurrentProcess()->VMManager().page_table);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,73 @@ 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 (!Is4KBAligned(dst_addr) || !Is4KBAligned(src_addr)) {
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
if (size == 0 || !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
|
||||
|
||||
/// Set the process heap to a given Size. It can both extend and shrink the heap.
|
||||
@@ -69,15 +136,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 +152,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
|
||||
@@ -301,13 +368,34 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
|
||||
return Mutex::Release(mutex_addr);
|
||||
}
|
||||
|
||||
struct BreakReason {
|
||||
union {
|
||||
u32 raw;
|
||||
BitField<31, 1, u32> signal_debugger;
|
||||
};
|
||||
};
|
||||
|
||||
/// Break program execution
|
||||
static void Break(u64 reason, u64 info1, u64 info2) {
|
||||
LOG_CRITICAL(
|
||||
Debug_Emulated,
|
||||
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
|
||||
reason, info1, info2);
|
||||
ASSERT(false);
|
||||
static void Break(u32 reason, u64 info1, u64 info2) {
|
||||
BreakReason break_reason{reason};
|
||||
if (break_reason.signal_debugger) {
|
||||
LOG_ERROR(
|
||||
Debug_Emulated,
|
||||
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
|
||||
reason, info1, info2);
|
||||
} else {
|
||||
LOG_CRITICAL(
|
||||
Debug_Emulated,
|
||||
"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();
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to output a message on a debug hardware unit - does nothing on a retail unit
|
||||
@@ -326,7 +414,7 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
|
||||
LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id,
|
||||
info_sub_id, handle);
|
||||
|
||||
const auto& current_process = Core::CurrentProcess();
|
||||
const auto* current_process = Core::CurrentProcess();
|
||||
const auto& vm_manager = current_process->VMManager();
|
||||
|
||||
switch (static_cast<GetInfoType>(info_id)) {
|
||||
@@ -360,25 +448,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;
|
||||
@@ -424,7 +499,7 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
const auto current_process = Core::CurrentProcess();
|
||||
const auto* current_process = Core::CurrentProcess();
|
||||
if (thread->GetOwnerProcess() != current_process) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
@@ -516,7 +591,7 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
return shared_memory->Map(Core::CurrentProcess().get(), addr, permissions_type,
|
||||
return shared_memory->Map(Core::CurrentProcess(), addr, permissions_type,
|
||||
MemoryPermission::DontCare);
|
||||
}
|
||||
|
||||
@@ -535,7 +610,7 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
auto shared_memory = kernel.HandleTable().Get<SharedMemory>(shared_memory_handle);
|
||||
|
||||
return shared_memory->Unmap(Core::CurrentProcess().get(), addr);
|
||||
return shared_memory->Unmap(Core::CurrentProcess(), addr);
|
||||
}
|
||||
|
||||
/// Query process memory
|
||||
@@ -573,7 +648,7 @@ static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, VAdd
|
||||
|
||||
/// Exits the current process
|
||||
static void ExitProcess() {
|
||||
auto& current_process = Core::CurrentProcess();
|
||||
auto* current_process = Core::CurrentProcess();
|
||||
|
||||
LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessID());
|
||||
ASSERT_MSG(current_process->GetStatus() == ProcessStatus::Running,
|
||||
@@ -621,7 +696,7 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
CASCADE_RESULT(SharedPtr<Thread> thread,
|
||||
Thread::Create(kernel, name, entry_point, priority, arg, processor_id, stack_top,
|
||||
Core::CurrentProcess()));
|
||||
*Core::CurrentProcess()));
|
||||
const auto new_guest_handle = kernel.HandleTable().Create(thread);
|
||||
if (new_guest_handle.Failed()) {
|
||||
return new_guest_handle.Code();
|
||||
@@ -1010,6 +1085,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();
|
||||
@@ -1145,7 +1243,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
|
||||
|
||||
@@ -183,18 +183,15 @@ 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,
|
||||
u32 priority, u64 arg, s32 processor_id,
|
||||
VAddr stack_top, SharedPtr<Process> owner_process) {
|
||||
VAddr stack_top, Process& owner_process) {
|
||||
// Check if priority is in ranged. Lowest priority -> highest priority id.
|
||||
if (priority > THREADPRIO_LOWEST) {
|
||||
LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority);
|
||||
@@ -208,7 +205,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
|
||||
|
||||
// TODO(yuriks): Other checks, returning 0xD9001BEA
|
||||
|
||||
if (!Memory::IsValidVirtualAddress(*owner_process, entry_point)) {
|
||||
if (!Memory::IsValidVirtualAddress(owner_process, entry_point)) {
|
||||
LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:016X}", name, entry_point);
|
||||
// TODO (bunnei): Find the correct error code to use here
|
||||
return ResultCode(-1);
|
||||
@@ -232,7 +229,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
|
||||
thread->wait_handle = 0;
|
||||
thread->name = std::move(name);
|
||||
thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap();
|
||||
thread->owner_process = owner_process;
|
||||
thread->owner_process = &owner_process;
|
||||
thread->scheduler = Core::System::GetInstance().Scheduler(processor_id).get();
|
||||
thread->scheduler->AddThread(thread, priority);
|
||||
thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread);
|
||||
@@ -264,7 +261,7 @@ SharedPtr<Thread> SetupMainThread(KernelCore& kernel, VAddr entry_point, u32 pri
|
||||
// Initialize new "main" thread
|
||||
const VAddr stack_top = owner_process.VMManager().GetTLSIORegionEndAddress();
|
||||
auto thread_res = Thread::Create(kernel, "main", entry_point, priority, 0, THREADPROCESSORID_0,
|
||||
stack_top, &owner_process);
|
||||
stack_top, owner_process);
|
||||
|
||||
SharedPtr<Thread> thread = std::move(thread_res).Unwrap();
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ public:
|
||||
static ResultVal<SharedPtr<Thread>> Create(KernelCore& kernel, std::string name,
|
||||
VAddr entry_point, u32 priority, u64 arg,
|
||||
s32 processor_id, VAddr stack_top,
|
||||
SharedPtr<Process> owner_process);
|
||||
Process& owner_process);
|
||||
|
||||
std::string GetName() const override {
|
||||
return name;
|
||||
@@ -262,11 +262,11 @@ public:
|
||||
return processor_id;
|
||||
}
|
||||
|
||||
SharedPtr<Process>& GetOwnerProcess() {
|
||||
Process* GetOwnerProcess() {
|
||||
return owner_process;
|
||||
}
|
||||
|
||||
const SharedPtr<Process>& GetOwnerProcess() const {
|
||||
const Process* GetOwnerProcess() const {
|
||||
return owner_process;
|
||||
}
|
||||
|
||||
@@ -386,7 +386,7 @@ private:
|
||||
u64 tpidr_el0 = 0; ///< TPIDR_EL0 read/write system register.
|
||||
|
||||
/// Process that owns this thread
|
||||
SharedPtr<Process> owner_process;
|
||||
Process* owner_process;
|
||||
|
||||
/// Objects that the thread is waiting on, in the same order as they were
|
||||
/// passed to WaitSynchronization1/N.
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -61,13 +61,13 @@ AOC_U::AOC_U() : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs
|
||||
AOC_U::~AOC_U() = default;
|
||||
|
||||
void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID();
|
||||
rb.Push<u32>(std::count_if(add_on_content.begin(), add_on_content.end(), [¤t](u64 tid) {
|
||||
return (tid & DLC_BASE_TITLE_ID_MASK) == current;
|
||||
}));
|
||||
rb.Push<u32>(static_cast<u32>(
|
||||
std::count_if(add_on_content.begin(), add_on_content.end(),
|
||||
[¤t](u64 tid) { return (tid & DLC_BASE_TITLE_ID_MASK) == current; })));
|
||||
}
|
||||
|
||||
void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
|
||||
@@ -91,7 +91,7 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
count = std::min<size_t>(out.size() - offset, count);
|
||||
count = static_cast<u32>(std::min<size_t>(out.size() - offset, count));
|
||||
std::rotate(out.begin(), out.begin() + offset, out.end());
|
||||
out.resize(count);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -264,6 +264,15 @@ ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
void SetPackedUpdate(FileSys::VirtualFile update_raw) {
|
||||
LOG_TRACE(Service_FS, "Setting packed update for romfs");
|
||||
|
||||
if (romfs_factory == nullptr)
|
||||
return;
|
||||
|
||||
romfs_factory->SetPackedUpdate(std::move(update_raw));
|
||||
}
|
||||
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() {
|
||||
LOG_TRACE(Service_FS, "Opening RomFS for current process");
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory)
|
||||
ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory);
|
||||
ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
|
||||
|
||||
void SetPackedUpdate(FileSys::VirtualFile update_raw);
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess();
|
||||
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
|
||||
FileSys::ContentRecordType type);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -157,15 +164,21 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou
|
||||
LOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset);
|
||||
|
||||
const auto itr = buffer_mappings.find(params.offset);
|
||||
ASSERT_MSG(itr != buffer_mappings.end(), "Tried to unmap invalid mapping");
|
||||
if (itr == buffer_mappings.end()) {
|
||||
LOG_WARNING(Service_NVDRV, "Tried to unmap an invalid offset 0x{:X}", params.offset);
|
||||
// Hardware tests shows that unmapping an already unmapped buffer always returns successful
|
||||
// and doesn't fail.
|
||||
return 0;
|
||||
}
|
||||
|
||||
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--;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -140,7 +141,8 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process)
|
||||
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, pm);
|
||||
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);
|
||||
|
||||
@@ -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*, 58> RESULT_MESSAGES{
|
||||
constexpr std::array<const char*, 59> RESULT_MESSAGES{
|
||||
"The operation completed successfully.",
|
||||
"The loader requested to load is already loaded.",
|
||||
"The operation is not implemented.",
|
||||
@@ -152,6 +152,7 @@ constexpr std::array<const char*, 58> RESULT_MESSAGES{
|
||||
"The BKTR-type NCA has a bad Relocation bucket.",
|
||||
"The BKTR-type NCA has a bad Subsection bucket.",
|
||||
"The BKTR-type NCA is missing the base RomFS.",
|
||||
"The NSP or XCI does not contain an update in addition to the base game.",
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, ResultStatus status) {
|
||||
|
||||
@@ -114,6 +114,7 @@ enum class ResultStatus : u16 {
|
||||
ErrorBadRelocationBuckets,
|
||||
ErrorBadSubsectionBuckets,
|
||||
ErrorMissingBKTRBaseRomFS,
|
||||
ErrorNoPackedUpdate,
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, ResultStatus status);
|
||||
@@ -196,10 +197,19 @@ public:
|
||||
/**
|
||||
* Get the RomFS of the application
|
||||
* Since the RomFS can be huge, we return a file reference instead of copying to a buffer
|
||||
* @param dir The directory containing the RomFS
|
||||
* @param file The directory containing the RomFS
|
||||
* @return ResultStatus result of function
|
||||
*/
|
||||
virtual ResultStatus ReadRomFS(FileSys::VirtualFile& dir) {
|
||||
virtual ResultStatus ReadRomFS(FileSys::VirtualFile& file) {
|
||||
return ResultStatus::ErrorNotImplemented;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw update of the application, should it come packed with one
|
||||
* @param file The raw update NCA file (Program-type
|
||||
* @return ResultStatus result of function
|
||||
*/
|
||||
virtual ResultStatus ReadUpdateRaw(FileSys::VirtualFile& file) {
|
||||
return ResultStatus::ErrorNotImplemented;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,10 @@ ResultStatus AppLoader_NAX::ReadRomFS(FileSys::VirtualFile& dir) {
|
||||
return nca_loader->ReadRomFS(dir);
|
||||
}
|
||||
|
||||
u64 AppLoader_NAX::ReadRomFSIVFCOffset() const {
|
||||
return nca_loader->ReadRomFSIVFCOffset();
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) {
|
||||
return nca_loader->ReadProgramId(out_program_id);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ public:
|
||||
ResultStatus Load(Kernel::Process& process) override;
|
||||
|
||||
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
|
||||
u64 ReadRomFSIVFCOffset() const override;
|
||||
ResultStatus ReadProgramId(u64& out_program_id) override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -14,11 +14,12 @@
|
||||
#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"
|
||||
#include "core/loader/nso.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Loader {
|
||||
|
||||
@@ -137,17 +138,29 @@ 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));
|
||||
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;
|
||||
NSOArgumentHeader args_header{
|
||||
NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
|
||||
const auto end_offset = program_image.size();
|
||||
program_image.resize(static_cast<u32>(program_image.size()) +
|
||||
NSO_ARGUMENT_DATA_ALLOCATION_SIZE);
|
||||
std::memcpy(program_image.data() + end_offset, &args_header, sizeof(NSOArgumentHeader));
|
||||
std::memcpy(program_image.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(),
|
||||
arg_data.size());
|
||||
}
|
||||
|
||||
// Read MOD header
|
||||
@@ -161,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;
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
#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"
|
||||
#include "core/memory.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Loader {
|
||||
|
||||
@@ -94,6 +94,7 @@ static constexpr u32 PageAlignSize(u32 size) {
|
||||
}
|
||||
|
||||
VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base,
|
||||
bool should_pass_arguments,
|
||||
boost::optional<FileSys::PatchManager> pm) {
|
||||
if (file == nullptr)
|
||||
return {};
|
||||
@@ -109,8 +110,7 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base,
|
||||
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 =
|
||||
@@ -120,9 +120,22 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base,
|
||||
}
|
||||
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;
|
||||
NSOArgumentHeader args_header{
|
||||
NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
|
||||
const auto end_offset = program_image.size();
|
||||
program_image.resize(static_cast<u32>(program_image.size()) +
|
||||
NSO_ARGUMENT_DATA_ALLOCATION_SIZE);
|
||||
std::memcpy(program_image.data() + end_offset, &args_header, sizeof(NSOArgumentHeader));
|
||||
std::memcpy(program_image.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(),
|
||||
arg_data.size());
|
||||
}
|
||||
|
||||
// MOD header pointer is at .text offset + 4
|
||||
@@ -139,7 +152,7 @@ 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);
|
||||
|
||||
@@ -155,12 +168,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;
|
||||
}
|
||||
@@ -172,7 +184,7 @@ ResultStatus AppLoader_NSO::Load(Kernel::Process& process) {
|
||||
|
||||
// Load module
|
||||
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
|
||||
LoadModule(file, base_address);
|
||||
LoadModule(file, base_address, true);
|
||||
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address);
|
||||
|
||||
process.Run(base_address, Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);
|
||||
|
||||
@@ -11,6 +11,15 @@
|
||||
|
||||
namespace Loader {
|
||||
|
||||
constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000;
|
||||
|
||||
struct NSOArgumentHeader {
|
||||
u32_le allocated_size;
|
||||
u32_le actual_size;
|
||||
INSERT_PADDING_BYTES(0x18);
|
||||
};
|
||||
static_assert(sizeof(NSOArgumentHeader) == 0x20, "NSOArgumentHeader has incorrect size.");
|
||||
|
||||
/// Loads an NSO file
|
||||
class AppLoader_NSO final : public AppLoader, Linker {
|
||||
public:
|
||||
@@ -27,7 +36,7 @@ public:
|
||||
return IdentifyType(file);
|
||||
}
|
||||
|
||||
static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base,
|
||||
static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base, bool should_pass_arguments,
|
||||
boost::optional<FileSys::PatchManager> pm = boost::none);
|
||||
|
||||
ResultStatus Load(Kernel::Process& process) override;
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/deconstructed_rom_directory.h"
|
||||
#include "core/loader/nca.h"
|
||||
#include "core/loader/nsp.h"
|
||||
@@ -33,7 +35,7 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file)
|
||||
return;
|
||||
|
||||
std::tie(nacp_file, icon_file) =
|
||||
FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(control_nca);
|
||||
FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(*control_nca);
|
||||
}
|
||||
|
||||
AppLoader_NSP::~AppLoader_NSP() = default;
|
||||
@@ -91,13 +93,39 @@ ResultStatus AppLoader_NSP::Load(Kernel::Process& process) {
|
||||
if (result != ResultStatus::Success)
|
||||
return result;
|
||||
|
||||
FileSys::VirtualFile update_raw;
|
||||
if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr)
|
||||
Service::FileSystem::SetPackedUpdate(std::move(update_raw));
|
||||
|
||||
is_loaded = true;
|
||||
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& dir) {
|
||||
return secondary_loader->ReadRomFS(dir);
|
||||
ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& file) {
|
||||
return secondary_loader->ReadRomFS(file);
|
||||
}
|
||||
|
||||
u64 AppLoader_NSP::ReadRomFSIVFCOffset() const {
|
||||
return secondary_loader->ReadRomFSIVFCOffset();
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& file) {
|
||||
if (nsp->IsExtractedType())
|
||||
return ResultStatus::ErrorNoPackedUpdate;
|
||||
|
||||
const auto read =
|
||||
nsp->GetNCAFile(FileSys::GetUpdateTitleID(title_id), FileSys::ContentRecordType::Program);
|
||||
|
||||
if (read == nullptr)
|
||||
return ResultStatus::ErrorNoPackedUpdate;
|
||||
const auto nca_test = std::make_shared<FileSys::NCA>(read);
|
||||
|
||||
if (nca_test->GetStatus() != ResultStatus::ErrorMissingBKTRBaseRomFS)
|
||||
return nca_test->GetStatus();
|
||||
|
||||
file = read;
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_NSP::ReadProgramId(u64& out_program_id) {
|
||||
|
||||
@@ -37,7 +37,9 @@ public:
|
||||
|
||||
ResultStatus Load(Kernel::Process& process) override;
|
||||
|
||||
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
|
||||
ResultStatus ReadRomFS(FileSys::VirtualFile& file) override;
|
||||
u64 ReadRomFSIVFCOffset() const override;
|
||||
ResultStatus ReadUpdateRaw(FileSys::VirtualFile& file) override;
|
||||
ResultStatus ReadProgramId(u64& out_program_id) override;
|
||||
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
|
||||
ResultStatus ReadTitle(std::string& title) override;
|
||||
@@ -47,7 +49,7 @@ private:
|
||||
std::unique_ptr<AppLoader> secondary_loader;
|
||||
|
||||
FileSys::VirtualFile icon_file;
|
||||
std::shared_ptr<FileSys::NACP> nacp_file;
|
||||
std::unique_ptr<FileSys::NACP> nacp_file;
|
||||
u64 title_id;
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,11 @@
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/nca.h"
|
||||
#include "core/loader/xci.h"
|
||||
|
||||
@@ -26,7 +30,7 @@ AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file)
|
||||
return;
|
||||
|
||||
std::tie(nacp_file, icon_file) =
|
||||
FileSys::PatchManager(xci->GetProgramTitleID()).ParseControlNCA(control_nca);
|
||||
FileSys::PatchManager(xci->GetProgramTitleID()).ParseControlNCA(*control_nca);
|
||||
}
|
||||
|
||||
AppLoader_XCI::~AppLoader_XCI() = default;
|
||||
@@ -63,13 +67,41 @@ ResultStatus AppLoader_XCI::Load(Kernel::Process& process) {
|
||||
if (result != ResultStatus::Success)
|
||||
return result;
|
||||
|
||||
FileSys::VirtualFile update_raw;
|
||||
if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr)
|
||||
Service::FileSystem::SetPackedUpdate(std::move(update_raw));
|
||||
|
||||
is_loaded = true;
|
||||
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& dir) {
|
||||
return nca_loader->ReadRomFS(dir);
|
||||
ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& file) {
|
||||
return nca_loader->ReadRomFS(file);
|
||||
}
|
||||
|
||||
u64 AppLoader_XCI::ReadRomFSIVFCOffset() const {
|
||||
return nca_loader->ReadRomFSIVFCOffset();
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& file) {
|
||||
u64 program_id{};
|
||||
nca_loader->ReadProgramId(program_id);
|
||||
if (program_id == 0)
|
||||
return ResultStatus::ErrorXCIMissingProgramNCA;
|
||||
|
||||
const auto read = xci->GetSecurePartitionNSP()->GetNCAFile(
|
||||
FileSys::GetUpdateTitleID(program_id), FileSys::ContentRecordType::Program);
|
||||
|
||||
if (read == nullptr)
|
||||
return ResultStatus::ErrorNoPackedUpdate;
|
||||
const auto nca_test = std::make_shared<FileSys::NCA>(read);
|
||||
|
||||
if (nca_test->GetStatus() != ResultStatus::ErrorMissingBKTRBaseRomFS)
|
||||
return nca_test->GetStatus();
|
||||
|
||||
file = read;
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_XCI::ReadProgramId(u64& out_program_id) {
|
||||
|
||||
@@ -37,7 +37,9 @@ public:
|
||||
|
||||
ResultStatus Load(Kernel::Process& process) override;
|
||||
|
||||
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
|
||||
ResultStatus ReadRomFS(FileSys::VirtualFile& file) override;
|
||||
u64 ReadRomFSIVFCOffset() const override;
|
||||
ResultStatus ReadUpdateRaw(FileSys::VirtualFile& file) override;
|
||||
ResultStatus ReadProgramId(u64& out_program_id) override;
|
||||
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
|
||||
ResultStatus ReadTitle(std::string& title) override;
|
||||
@@ -47,7 +49,7 @@ private:
|
||||
std::unique_ptr<AppLoader_NCA> nca_loader;
|
||||
|
||||
FileSys::VirtualFile icon_file;
|
||||
std::shared_ptr<FileSys::NACP> nacp_file;
|
||||
std::unique_ptr<FileSys::NACP> nacp_file;
|
||||
};
|
||||
|
||||
} // namespace Loader
|
||||
|
||||
@@ -155,6 +155,7 @@ struct Values {
|
||||
// Debugging
|
||||
bool use_gdbstub;
|
||||
u16 gdbstub_port;
|
||||
std::string program_args;
|
||||
|
||||
// WebService
|
||||
bool enable_telemetry;
|
||||
|
||||
@@ -2,12 +2,16 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include <array>
|
||||
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
#include <mbedtls/entropy.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
@@ -28,11 +32,11 @@ static u64 GenerateTelemetryId() {
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_entropy_init(&entropy);
|
||||
mbedtls_ctr_drbg_context ctr_drbg;
|
||||
std::string personalization = "yuzu Telemetry ID";
|
||||
constexpr std::array<char, 18> personalization{{"yuzu Telemetry ID"}};
|
||||
|
||||
mbedtls_ctr_drbg_init(&ctr_drbg);
|
||||
ASSERT(mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
|
||||
reinterpret_cast<const unsigned char*>(personalization.c_str()),
|
||||
reinterpret_cast<const unsigned char*>(personalization.data()),
|
||||
personalization.size()) == 0);
|
||||
ASSERT(mbedtls_ctr_drbg_random(&ctr_drbg, reinterpret_cast<unsigned char*>(&telemetry_id),
|
||||
sizeof(u64)) == 0);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/telemetry.h"
|
||||
|
||||
namespace Core {
|
||||
@@ -30,8 +31,6 @@ public:
|
||||
field_collection.AddField(type, name, std::move(value));
|
||||
}
|
||||
|
||||
static void FinalizeAsyncJob();
|
||||
|
||||
private:
|
||||
Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session
|
||||
std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields
|
||||
@@ -53,7 +52,6 @@ u64 RegenerateTelemetryId();
|
||||
* Verifies the username and token.
|
||||
* @param username yuzu username to use for authentication.
|
||||
* @param token yuzu token to use for authentication.
|
||||
* @param func A function that gets exectued when the verification is finished
|
||||
* @returns Future with bool indicating whether the verification succeeded
|
||||
*/
|
||||
bool VerifyLogin(const std::string& username, const std::string& token);
|
||||
|
||||
@@ -15,7 +15,8 @@ namespace ArmTests {
|
||||
TestEnvironment::TestEnvironment(bool mutable_memory_)
|
||||
: mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) {
|
||||
|
||||
Core::CurrentProcess() = Kernel::Process::Create(kernel, "");
|
||||
auto process = Kernel::Process::Create(kernel, "");
|
||||
kernel.MakeCurrentProcess(process.get());
|
||||
page_table = &Core::CurrentProcess()->VMManager().page_table;
|
||||
|
||||
std::fill(page_table->pointers.begin(), page_table->pointers.end(), nullptr);
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
|
||||
#include "core/memory.h"
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
|
||||
namespace Tegra::Engines {
|
||||
|
||||
Fermi2D::Fermi2D(MemoryManager& memory_manager) : memory_manager(memory_manager) {}
|
||||
Fermi2D::Fermi2D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager)
|
||||
: memory_manager(memory_manager), rasterizer{rasterizer} {}
|
||||
|
||||
void Fermi2D::WriteReg(u32 method, u32 value) {
|
||||
ASSERT_MSG(method < Regs::NUM_REGS,
|
||||
@@ -44,27 +46,31 @@ void Fermi2D::HandleSurfaceCopy() {
|
||||
u32 src_bytes_per_pixel = RenderTargetBytesPerPixel(regs.src.format);
|
||||
u32 dst_bytes_per_pixel = RenderTargetBytesPerPixel(regs.dst.format);
|
||||
|
||||
if (regs.src.linear == regs.dst.linear) {
|
||||
// If the input layout and the output layout are the same, just perform a raw copy.
|
||||
ASSERT(regs.src.BlockHeight() == regs.dst.BlockHeight());
|
||||
Memory::CopyBlock(dest_cpu, source_cpu,
|
||||
src_bytes_per_pixel * regs.dst.width * regs.dst.height);
|
||||
return;
|
||||
}
|
||||
if (!rasterizer.AccelerateSurfaceCopy(regs.src, regs.dst)) {
|
||||
// TODO(bunnei): The below implementation currently will not get hit, as
|
||||
// AccelerateSurfaceCopy tries to always copy and will always return success. This should be
|
||||
// changed once we properly support flushing.
|
||||
|
||||
u8* src_buffer = Memory::GetPointer(source_cpu);
|
||||
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());
|
||||
} 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());
|
||||
if (regs.src.linear == regs.dst.linear) {
|
||||
// If the input layout and the output layout are the same, just perform a raw copy.
|
||||
ASSERT(regs.src.BlockHeight() == regs.dst.BlockHeight());
|
||||
Memory::CopyBlock(dest_cpu, source_cpu,
|
||||
src_bytes_per_pixel * regs.dst.width * regs.dst.height);
|
||||
return;
|
||||
}
|
||||
u8* src_buffer = Memory::GetPointer(source_cpu);
|
||||
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());
|
||||
} 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
namespace VideoCore {
|
||||
class RasterizerInterface;
|
||||
}
|
||||
|
||||
namespace Tegra::Engines {
|
||||
|
||||
#define FERMI2D_REG_INDEX(field_name) \
|
||||
@@ -19,7 +23,7 @@ namespace Tegra::Engines {
|
||||
|
||||
class Fermi2D final {
|
||||
public:
|
||||
explicit Fermi2D(MemoryManager& memory_manager);
|
||||
explicit Fermi2D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager);
|
||||
~Fermi2D() = default;
|
||||
|
||||
/// Write the value to the register identified by method.
|
||||
@@ -32,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;
|
||||
@@ -49,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");
|
||||
|
||||
@@ -94,6 +108,8 @@ public:
|
||||
MemoryManager& memory_manager;
|
||||
|
||||
private:
|
||||
VideoCore::RasterizerInterface& rasterizer;
|
||||
|
||||
/// Performs the copy from the source surface to the destination surface as configured in the
|
||||
/// registers.
|
||||
void HandleSurfaceCopy();
|
||||
|
||||
@@ -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,7 +442,12 @@ public:
|
||||
u32 width;
|
||||
u32 height;
|
||||
Tegra::RenderTargetFormat format;
|
||||
u32 block_dimensions;
|
||||
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;
|
||||
u32 array_mode;
|
||||
u32 layer_stride;
|
||||
u32 base_layer;
|
||||
@@ -532,7 +547,21 @@ public:
|
||||
INSERT_PADDING_WORDS(0x3);
|
||||
s32 clear_stencil;
|
||||
|
||||
INSERT_PADDING_WORDS(0x6C);
|
||||
INSERT_PADDING_WORDS(0x17);
|
||||
|
||||
struct {
|
||||
u32 enable;
|
||||
union {
|
||||
BitField<0, 16, u32> min_x;
|
||||
BitField<16, 16, u32> max_x;
|
||||
};
|
||||
union {
|
||||
BitField<0, 16, u32> min_y;
|
||||
BitField<16, 16, u32> max_y;
|
||||
};
|
||||
} scissor_test;
|
||||
|
||||
INSERT_PADDING_WORDS(0x52);
|
||||
|
||||
s32 stencil_back_func_ref;
|
||||
u32 stencil_back_mask;
|
||||
@@ -548,7 +577,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 {
|
||||
@@ -1002,6 +1036,7 @@ ASSERT_REG_POSITION(vertex_buffer, 0x35D);
|
||||
ASSERT_REG_POSITION(clear_color[0], 0x360);
|
||||
ASSERT_REG_POSITION(clear_depth, 0x364);
|
||||
ASSERT_REG_POSITION(clear_stencil, 0x368);
|
||||
ASSERT_REG_POSITION(scissor_test, 0x380);
|
||||
ASSERT_REG_POSITION(stencil_back_func_ref, 0x3D5);
|
||||
ASSERT_REG_POSITION(stencil_back_mask, 0x3D6);
|
||||
ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D7);
|
||||
|
||||
@@ -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,
|
||||
@@ -314,6 +326,15 @@ enum class TextureMiscMode : u64 {
|
||||
PTP,
|
||||
};
|
||||
|
||||
enum class IsberdMode : u64 {
|
||||
None = 0,
|
||||
Patch = 1,
|
||||
Prim = 2,
|
||||
Attr = 3,
|
||||
};
|
||||
|
||||
enum class IsberdShift : u64 { None = 0, U16 = 1, B32 = 2 };
|
||||
|
||||
enum class IpaInterpMode : u64 {
|
||||
Linear = 0,
|
||||
Perspective = 1,
|
||||
@@ -340,6 +361,87 @@ struct IpaMode {
|
||||
}
|
||||
};
|
||||
|
||||
enum class SystemVariable : u64 {
|
||||
LaneId = 0x00,
|
||||
VirtCfg = 0x02,
|
||||
VirtId = 0x03,
|
||||
Pm0 = 0x04,
|
||||
Pm1 = 0x05,
|
||||
Pm2 = 0x06,
|
||||
Pm3 = 0x07,
|
||||
Pm4 = 0x08,
|
||||
Pm5 = 0x09,
|
||||
Pm6 = 0x0a,
|
||||
Pm7 = 0x0b,
|
||||
OrderingTicket = 0x0f,
|
||||
PrimType = 0x10,
|
||||
InvocationId = 0x11,
|
||||
Ydirection = 0x12,
|
||||
ThreadKill = 0x13,
|
||||
ShaderType = 0x14,
|
||||
DirectBeWriteAddressLow = 0x15,
|
||||
DirectBeWriteAddressHigh = 0x16,
|
||||
DirectBeWriteEnabled = 0x17,
|
||||
MachineId0 = 0x18,
|
||||
MachineId1 = 0x19,
|
||||
MachineId2 = 0x1a,
|
||||
MachineId3 = 0x1b,
|
||||
Affinity = 0x1c,
|
||||
InvocationInfo = 0x1d,
|
||||
WscaleFactorXY = 0x1e,
|
||||
WscaleFactorZ = 0x1f,
|
||||
Tid = 0x20,
|
||||
TidX = 0x21,
|
||||
TidY = 0x22,
|
||||
TidZ = 0x23,
|
||||
CtaParam = 0x24,
|
||||
CtaIdX = 0x25,
|
||||
CtaIdY = 0x26,
|
||||
CtaIdZ = 0x27,
|
||||
NtId = 0x28,
|
||||
CirQueueIncrMinusOne = 0x29,
|
||||
Nlatc = 0x2a,
|
||||
SmSpaVersion = 0x2c,
|
||||
MultiPassShaderInfo = 0x2d,
|
||||
LwinHi = 0x2e,
|
||||
SwinHi = 0x2f,
|
||||
SwinLo = 0x30,
|
||||
SwinSz = 0x31,
|
||||
SmemSz = 0x32,
|
||||
SmemBanks = 0x33,
|
||||
LwinLo = 0x34,
|
||||
LwinSz = 0x35,
|
||||
LmemLosz = 0x36,
|
||||
LmemHioff = 0x37,
|
||||
EqMask = 0x38,
|
||||
LtMask = 0x39,
|
||||
LeMask = 0x3a,
|
||||
GtMask = 0x3b,
|
||||
GeMask = 0x3c,
|
||||
RegAlloc = 0x3d,
|
||||
CtxAddr = 0x3e, // .fmask = F_SM50
|
||||
BarrierAlloc = 0x3e, // .fmask = F_SM60
|
||||
GlobalErrorStatus = 0x40,
|
||||
WarpErrorStatus = 0x42,
|
||||
WarpErrorStatusClear = 0x43,
|
||||
PmHi0 = 0x48,
|
||||
PmHi1 = 0x49,
|
||||
PmHi2 = 0x4a,
|
||||
PmHi3 = 0x4b,
|
||||
PmHi4 = 0x4c,
|
||||
PmHi5 = 0x4d,
|
||||
PmHi6 = 0x4e,
|
||||
PmHi7 = 0x4f,
|
||||
ClockLo = 0x50,
|
||||
ClockHi = 0x51,
|
||||
GlobalTimerLo = 0x52,
|
||||
GlobalTimerHi = 0x53,
|
||||
HwTaskId = 0x60,
|
||||
CircularQueueEntryIndex = 0x61,
|
||||
CircularQueueEntryAddressLow = 0x62,
|
||||
CircularQueueEntryAddressHigh = 0x63,
|
||||
};
|
||||
|
||||
union Instruction {
|
||||
Instruction& operator=(const Instruction& instr) {
|
||||
value = instr.value;
|
||||
@@ -362,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;
|
||||
@@ -403,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;
|
||||
@@ -914,6 +1021,35 @@ union Instruction {
|
||||
}
|
||||
} bra;
|
||||
|
||||
union {
|
||||
BitField<39, 1, u64> emit; // EmitVertex
|
||||
BitField<40, 1, u64> cut; // EndPrimitive
|
||||
} out;
|
||||
|
||||
union {
|
||||
BitField<31, 1, u64> skew;
|
||||
BitField<32, 1, u64> o;
|
||||
BitField<33, 2, IsberdMode> mode;
|
||||
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;
|
||||
@@ -936,6 +1072,10 @@ union Instruction {
|
||||
BitField<36, 5, u64> index;
|
||||
} cbuf36;
|
||||
|
||||
// Unsure about the size of this one.
|
||||
// It's always used with a gpr0, so any size should be fine.
|
||||
BitField<20, 8, SystemVariable> sys20;
|
||||
|
||||
BitField<47, 1, u64> generates_cc;
|
||||
BitField<61, 1, u64> is_b_imm;
|
||||
BitField<60, 1, u64> is_b_gpr;
|
||||
@@ -975,6 +1115,9 @@ public:
|
||||
TMML, // Texture Mip Map Level
|
||||
EXIT,
|
||||
IPA,
|
||||
OUT_R, // Emit vertex/primitive
|
||||
ISBERD,
|
||||
VMAD,
|
||||
FFMA_IMM, // Fused Multiply and Add
|
||||
FFMA_CR,
|
||||
FFMA_RC,
|
||||
@@ -1034,6 +1177,7 @@ public:
|
||||
MOV_C,
|
||||
MOV_R,
|
||||
MOV_IMM,
|
||||
MOV_SYS,
|
||||
MOV32_IMM,
|
||||
SHL_C,
|
||||
SHL_R,
|
||||
@@ -1209,6 +1353,9 @@ private:
|
||||
INST("1101111101011---", Id::TMML, Type::Memory, "TMML"),
|
||||
INST("111000110000----", Id::EXIT, Type::Trivial, "EXIT"),
|
||||
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"),
|
||||
@@ -1255,6 +1402,7 @@ private:
|
||||
INST("0100110010011---", Id::MOV_C, Type::Arithmetic, "MOV_C"),
|
||||
INST("0101110010011---", Id::MOV_R, Type::Arithmetic, "MOV_R"),
|
||||
INST("0011100-10011---", Id::MOV_IMM, Type::Arithmetic, "MOV_IMM"),
|
||||
INST("1111000011001---", Id::MOV_SYS, Type::Trivial, "MOV_SYS"),
|
||||
INST("000000010000----", Id::MOV32_IMM, Type::ArithmeticImmediate, "MOV32_IMM"),
|
||||
INST("0100110001100---", Id::FMNMX_C, Type::Arithmetic, "FMNMX_C"),
|
||||
INST("0101110001100---", Id::FMNMX_R, Type::Arithmetic, "FMNMX_R"),
|
||||
|
||||
@@ -25,7 +25,7 @@ u32 FramebufferConfig::BytesPerPixel(PixelFormat format) {
|
||||
GPU::GPU(VideoCore::RasterizerInterface& rasterizer) {
|
||||
memory_manager = std::make_unique<Tegra::MemoryManager>();
|
||||
maxwell_3d = std::make_unique<Engines::Maxwell3D>(rasterizer, *memory_manager);
|
||||
fermi_2d = std::make_unique<Engines::Fermi2D>(*memory_manager);
|
||||
fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer, *memory_manager);
|
||||
maxwell_compute = std::make_unique<Engines::MaxwellCompute>();
|
||||
maxwell_dma = std::make_unique<Engines::MaxwellDMA>(*memory_manager);
|
||||
kepler_memory = std::make_unique<Engines::KeplerMemory>(*memory_manager);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
@@ -33,13 +34,9 @@ public:
|
||||
/// and invalidated
|
||||
virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0;
|
||||
|
||||
/// Attempt to use a faster method to perform a display transfer with is_texture_copy = 0
|
||||
virtual bool AccelerateDisplayTransfer(const void* config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Attempt to use a faster method to perform a display transfer with is_texture_copy = 1
|
||||
virtual bool AccelerateTextureCopy(const void* config) {
|
||||
/// Attempt to use a faster method to perform a surface copy
|
||||
virtual bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& dst) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -255,7 +255,7 @@ DrawParameters RasterizerOpenGL::SetupDraw() {
|
||||
return params;
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetupShaders() {
|
||||
void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Shader);
|
||||
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
|
||||
@@ -270,6 +270,11 @@ void RasterizerOpenGL::SetupShaders() {
|
||||
|
||||
// Skip stages that are not enabled
|
||||
if (!gpu.regs.IsShaderConfigEnabled(index)) {
|
||||
switch (program) {
|
||||
case Maxwell::ShaderProgram::Geometry:
|
||||
shader_program_manager->UseTrivialGeometryShader();
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -288,11 +293,18 @@ void RasterizerOpenGL::SetupShaders() {
|
||||
switch (program) {
|
||||
case Maxwell::ShaderProgram::VertexA:
|
||||
case Maxwell::ShaderProgram::VertexB: {
|
||||
shader_program_manager->UseProgrammableVertexShader(shader->GetProgramHandle());
|
||||
shader_program_manager->UseProgrammableVertexShader(
|
||||
shader->GetProgramHandle(primitive_mode));
|
||||
break;
|
||||
}
|
||||
case Maxwell::ShaderProgram::Geometry: {
|
||||
shader_program_manager->UseProgrammableGeometryShader(
|
||||
shader->GetProgramHandle(primitive_mode));
|
||||
break;
|
||||
}
|
||||
case Maxwell::ShaderProgram::Fragment: {
|
||||
shader_program_manager->UseProgrammableFragmentShader(shader->GetProgramHandle());
|
||||
shader_program_manager->UseProgrammableFragmentShader(
|
||||
shader->GetProgramHandle(primitive_mode));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -302,12 +314,13 @@ void RasterizerOpenGL::SetupShaders() {
|
||||
}
|
||||
|
||||
// Configure the const buffers for this shader stage.
|
||||
current_constbuffer_bindpoint = SetupConstBuffers(static_cast<Maxwell::ShaderStage>(stage),
|
||||
shader, current_constbuffer_bindpoint);
|
||||
current_constbuffer_bindpoint =
|
||||
SetupConstBuffers(static_cast<Maxwell::ShaderStage>(stage), shader, primitive_mode,
|
||||
current_constbuffer_bindpoint);
|
||||
|
||||
// Configure the textures for this shader stage.
|
||||
current_texture_bindpoint = SetupTextures(static_cast<Maxwell::ShaderStage>(stage), shader,
|
||||
current_texture_bindpoint);
|
||||
primitive_mode, current_texture_bindpoint);
|
||||
|
||||
// When VertexA is enabled, we have dual vertex shaders
|
||||
if (program == Maxwell::ShaderProgram::VertexA) {
|
||||
@@ -317,8 +330,6 @@ void RasterizerOpenGL::SetupShaders() {
|
||||
}
|
||||
|
||||
state.Apply();
|
||||
|
||||
shader_program_manager->UseTrivialGeometryShader();
|
||||
}
|
||||
|
||||
std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
|
||||
@@ -541,6 +552,7 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
SyncLogicOpState();
|
||||
SyncCullMode();
|
||||
SyncAlphaTest();
|
||||
SyncScissorTest();
|
||||
SyncTransformFeedback();
|
||||
SyncPointState();
|
||||
|
||||
@@ -580,7 +592,7 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
|
||||
SetupVertexArrays();
|
||||
DrawParameters params = SetupDraw();
|
||||
SetupShaders();
|
||||
SetupShaders(params.primitive_mode);
|
||||
|
||||
buffer_cache.Unmap();
|
||||
|
||||
@@ -617,14 +629,10 @@ void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
|
||||
InvalidateRegion(addr, size);
|
||||
}
|
||||
|
||||
bool RasterizerOpenGL::AccelerateDisplayTransfer(const void* config) {
|
||||
bool RasterizerOpenGL::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& dst) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Blits);
|
||||
UNREACHABLE();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RasterizerOpenGL::AccelerateTextureCopy(const void* config) {
|
||||
UNREACHABLE();
|
||||
res_cache.FermiCopySurface(src, dst);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -662,10 +670,13 @@ void RasterizerOpenGL::SamplerInfo::Create() {
|
||||
sampler.Create();
|
||||
mag_filter = min_filter = Tegra::Texture::TextureFilter::Linear;
|
||||
wrap_u = wrap_v = wrap_p = Tegra::Texture::WrapMode::Wrap;
|
||||
uses_depth_compare = false;
|
||||
depth_compare_func = Tegra::Texture::DepthCompareFunc::Never;
|
||||
|
||||
// default is GL_LINEAR_MIPMAP_LINEAR
|
||||
glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
// Other attributes have correct defaults
|
||||
glSamplerParameteri(sampler.handle, GL_TEXTURE_COMPARE_FUNC, GL_NEVER);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntry& config) {
|
||||
@@ -693,6 +704,21 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(wrap_p));
|
||||
}
|
||||
|
||||
if (uses_depth_compare != (config.depth_compare_enabled == 1)) {
|
||||
uses_depth_compare = (config.depth_compare_enabled == 1);
|
||||
if (uses_depth_compare) {
|
||||
glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
|
||||
} else {
|
||||
glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, GL_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
if (depth_compare_func != config.depth_compare_func) {
|
||||
depth_compare_func = config.depth_compare_func;
|
||||
glSamplerParameteri(s, GL_TEXTURE_COMPARE_FUNC,
|
||||
MaxwellToGL::DepthCompareFunc(depth_compare_func));
|
||||
}
|
||||
|
||||
if (wrap_u == Tegra::Texture::WrapMode::Border || wrap_v == Tegra::Texture::WrapMode::Border ||
|
||||
wrap_p == Tegra::Texture::WrapMode::Border) {
|
||||
const GLvec4 new_border_color = {{config.border_color_r, config.border_color_g,
|
||||
@@ -705,7 +731,7 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr
|
||||
}
|
||||
|
||||
u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shader,
|
||||
u32 current_bindpoint) {
|
||||
GLenum primitive_mode, u32 current_bindpoint) {
|
||||
MICROPROFILE_SCOPE(OpenGL_UBO);
|
||||
const auto& gpu = Core::System::GetInstance().GPU();
|
||||
const auto& maxwell3d = gpu.Maxwell3D();
|
||||
@@ -757,7 +783,7 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shad
|
||||
buffer.address, size, static_cast<std::size_t>(uniform_buffer_alignment));
|
||||
|
||||
// Now configure the bindpoint of the buffer inside the shader
|
||||
glUniformBlockBinding(shader->GetProgramHandle(),
|
||||
glUniformBlockBinding(shader->GetProgramHandle(primitive_mode),
|
||||
shader->GetProgramResourceIndex(used_buffer),
|
||||
current_bindpoint + bindpoint);
|
||||
|
||||
@@ -773,7 +799,8 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shad
|
||||
return current_bindpoint + static_cast<u32>(entries.size());
|
||||
}
|
||||
|
||||
u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader, u32 current_unit) {
|
||||
u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
|
||||
GLenum primitive_mode, u32 current_unit) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Texture);
|
||||
const auto& gpu = Core::System::GetInstance().GPU();
|
||||
const auto& maxwell3d = gpu.Maxwell3D();
|
||||
@@ -788,8 +815,8 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
|
||||
|
||||
// Bind the uniform to the sampler.
|
||||
|
||||
glProgramUniform1i(shader->GetProgramHandle(), shader->GetUniformLocation(entry),
|
||||
current_bindpoint);
|
||||
glProgramUniform1i(shader->GetProgramHandle(primitive_mode),
|
||||
shader->GetUniformLocation(entry), current_bindpoint);
|
||||
|
||||
const auto texture = maxwell3d.GetStageTexture(entry.GetStage(), entry.GetOffset());
|
||||
|
||||
@@ -958,6 +985,22 @@ void RasterizerOpenGL::SyncAlphaTest() {
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncScissorTest() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
|
||||
state.scissor.enabled = (regs.scissor_test.enable != 0);
|
||||
// TODO(Blinkhawk): Figure if the hardware supports scissor testing per viewport and how it's
|
||||
// implemented.
|
||||
if (regs.scissor_test.enable != 0) {
|
||||
const u32 width = regs.scissor_test.max_x - regs.scissor_test.min_x;
|
||||
const u32 height = regs.scissor_test.max_y - regs.scissor_test.min_y;
|
||||
state.scissor.x = regs.scissor_test.min_x;
|
||||
state.scissor.y = regs.scissor_test.min_y;
|
||||
state.scissor.width = width;
|
||||
state.scissor.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncTransformFeedback() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
|
||||
|
||||
@@ -52,8 +52,8 @@ public:
|
||||
void FlushRegion(VAddr addr, u64 size) override;
|
||||
void InvalidateRegion(VAddr addr, u64 size) override;
|
||||
void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
|
||||
bool AccelerateDisplayTransfer(const void* config) override;
|
||||
bool AccelerateTextureCopy(const void* config) override;
|
||||
bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& dst) override;
|
||||
bool AccelerateFill(const void* config) override;
|
||||
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
|
||||
u32 pixel_stride) override;
|
||||
@@ -96,6 +96,8 @@ private:
|
||||
Tegra::Texture::WrapMode wrap_u;
|
||||
Tegra::Texture::WrapMode wrap_v;
|
||||
Tegra::Texture::WrapMode wrap_p;
|
||||
bool uses_depth_compare;
|
||||
Tegra::Texture::DepthCompareFunc depth_compare_func;
|
||||
GLvec4 border_color;
|
||||
};
|
||||
|
||||
@@ -118,7 +120,7 @@ private:
|
||||
* @returns The next available bindpoint for use in the next shader stage.
|
||||
*/
|
||||
u32 SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, Shader& shader,
|
||||
u32 current_bindpoint);
|
||||
GLenum primitive_mode, u32 current_bindpoint);
|
||||
|
||||
/*
|
||||
* Configures the current textures to use for the draw command.
|
||||
@@ -128,7 +130,7 @@ private:
|
||||
* @returns The next available bindpoint for use in the next shader stage.
|
||||
*/
|
||||
u32 SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, Shader& shader,
|
||||
u32 current_unit);
|
||||
GLenum primitive_mode, u32 current_unit);
|
||||
|
||||
/// Syncs the viewport to match the guest state
|
||||
void SyncViewport();
|
||||
@@ -163,6 +165,9 @@ private:
|
||||
/// Syncs the alpha test state to match the guest state
|
||||
void SyncAlphaTest();
|
||||
|
||||
/// Syncs the scissor test state to match the guest state
|
||||
void SyncScissorTest();
|
||||
|
||||
/// Syncs the transform feedback state to match the guest state
|
||||
void SyncTransformFeedback();
|
||||
|
||||
@@ -205,7 +210,7 @@ private:
|
||||
|
||||
DrawParameters SetupDraw();
|
||||
|
||||
void SetupShaders();
|
||||
void SetupShaders(GLenum primitive_mode);
|
||||
|
||||
enum class AccelDraw { Disabled, Arrays, Indexed };
|
||||
AccelDraw accelerate_draw = AccelDraw::Disabled;
|
||||
|
||||
@@ -45,7 +45,9 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
|
||||
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());
|
||||
@@ -97,8 +99,11 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
|
||||
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);
|
||||
@@ -120,13 +125,16 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
|
||||
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);
|
||||
@@ -143,6 +151,30 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
|
||||
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_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);
|
||||
params.width = config.width;
|
||||
params.height = config.height;
|
||||
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 = {};
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_format_tuples = {{
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // ABGR8U
|
||||
{GL_RGBA8, GL_RGBA, GL_BYTE, ComponentType::SNorm, false}, // ABGR8S
|
||||
@@ -559,6 +591,18 @@ static bool BlitSurface(const Surface& src_surface, const Surface& dst_surface,
|
||||
return true;
|
||||
}
|
||||
|
||||
static void FastCopySurface(const Surface& src_surface, const Surface& dst_surface) {
|
||||
const auto& src_params{src_surface->GetSurfaceParams()};
|
||||
const auto& dst_params{dst_surface->GetSurfaceParams()};
|
||||
|
||||
const u32 width{std::min(src_params.width, dst_params.width)};
|
||||
const u32 height{std::min(src_params.height, dst_params.height)};
|
||||
|
||||
glCopyImageSubData(src_surface->Texture().handle, SurfaceTargetToGL(src_params.target), 0, 0, 0,
|
||||
0, dst_surface->Texture().handle, SurfaceTargetToGL(dst_params.target), 0, 0,
|
||||
0, 0, width, height, 1);
|
||||
}
|
||||
|
||||
static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
|
||||
GLuint copy_pbo_handle, GLenum src_attachment = 0,
|
||||
GLenum dst_attachment = 0, std::size_t cubemap_face = 0) {
|
||||
@@ -784,6 +828,11 @@ void CachedSurface::LoadGLBuffer() {
|
||||
if (params.is_tiled) {
|
||||
gl_buffer.resize(total_size);
|
||||
|
||||
ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}",
|
||||
params.block_width, static_cast<u32>(params.target));
|
||||
ASSERT_MSG(params.block_depth == 1, "Block depth is defined as {} on texture type {}",
|
||||
params.block_depth, static_cast<u32>(params.target));
|
||||
|
||||
// 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) {
|
||||
@@ -955,7 +1004,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);
|
||||
}
|
||||
@@ -1033,6 +1084,26 @@ Surface RasterizerCacheOpenGL::GetUncachedSurface(const SurfaceParams& params) {
|
||||
return surface;
|
||||
}
|
||||
|
||||
void RasterizerCacheOpenGL::FermiCopySurface(
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& src_config,
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& dst_config) {
|
||||
|
||||
const auto& src_params = SurfaceParams::CreateForFermiCopySurface(src_config);
|
||||
const auto& dst_params = SurfaceParams::CreateForFermiCopySurface(dst_config);
|
||||
|
||||
ASSERT(src_params.width == dst_params.width);
|
||||
ASSERT(src_params.height == dst_params.height);
|
||||
ASSERT(src_params.pixel_format == dst_params.pixel_format);
|
||||
ASSERT(src_params.block_height == dst_params.block_height);
|
||||
ASSERT(src_params.is_tiled == dst_params.is_tiled);
|
||||
ASSERT(src_params.depth == dst_params.depth);
|
||||
ASSERT(src_params.depth == 1); // Currently, FastCopySurface only works with 2D surfaces
|
||||
ASSERT(src_params.target == dst_params.target);
|
||||
ASSERT(src_params.rt.index == dst_params.rt.index);
|
||||
|
||||
FastCopySurface(GetSurface(src_params, true), GetSurface(dst_params, false));
|
||||
}
|
||||
|
||||
Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
|
||||
const SurfaceParams& new_params) {
|
||||
// Verify surface is compatible for blitting
|
||||
@@ -1041,6 +1112,15 @@ 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)};
|
||||
|
||||
// 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 &&
|
||||
SurfaceParams::GetFormatBpp(old_params.pixel_format) ==
|
||||
SurfaceParams::GetFormatBpp(new_params.pixel_format)) {
|
||||
FastCopySurface(old_surface, new_surface);
|
||||
return new_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
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/hash.h"
|
||||
#include "common/math_util.h"
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/rasterizer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
@@ -715,9 +716,14 @@ 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(
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& config);
|
||||
|
||||
/// Checks if surfaces are compatible for caching
|
||||
bool IsCompatibleSurface(const SurfaceParams& other) const {
|
||||
@@ -728,7 +734,9 @@ struct SurfaceParams {
|
||||
|
||||
VAddr addr;
|
||||
bool is_tiled;
|
||||
u32 block_width;
|
||||
u32 block_height;
|
||||
u32 block_depth;
|
||||
PixelFormat pixel_format;
|
||||
ComponentType component_type;
|
||||
SurfaceType type;
|
||||
@@ -837,6 +845,10 @@ public:
|
||||
/// Tries to find a framebuffer using on the provided CPU address
|
||||
Surface TryFindFramebufferSurface(VAddr addr) const;
|
||||
|
||||
/// Copies the contents of one surface to another
|
||||
void FermiCopySurface(const Tegra::Engines::Fermi2D::Regs::Surface& src_config,
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& dst_config);
|
||||
|
||||
private:
|
||||
void LoadSurface(const Surface& surface);
|
||||
Surface GetSurface(const SurfaceParams& params, bool preserve_contents = true);
|
||||
|
||||
@@ -68,6 +68,10 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type)
|
||||
program_result = GLShader::GenerateVertexShader(setup);
|
||||
gl_type = GL_VERTEX_SHADER;
|
||||
break;
|
||||
case Maxwell::ShaderProgram::Geometry:
|
||||
program_result = GLShader::GenerateGeometryShader(setup);
|
||||
gl_type = GL_GEOMETRY_SHADER;
|
||||
break;
|
||||
case Maxwell::ShaderProgram::Fragment:
|
||||
program_result = GLShader::GenerateFragmentShader(setup);
|
||||
gl_type = GL_FRAGMENT_SHADER;
|
||||
@@ -80,11 +84,16 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type)
|
||||
|
||||
entries = program_result.second;
|
||||
|
||||
OGLShader shader;
|
||||
shader.Create(program_result.first.c_str(), gl_type);
|
||||
program.Create(true, shader.handle);
|
||||
SetShaderUniformBlockBindings(program.handle);
|
||||
VideoCore::LabelGLObject(GL_PROGRAM, program.handle, addr);
|
||||
if (program_type != Maxwell::ShaderProgram::Geometry) {
|
||||
OGLShader shader;
|
||||
shader.Create(program_result.first.c_str(), gl_type);
|
||||
program.Create(true, shader.handle);
|
||||
SetShaderUniformBlockBindings(program.handle);
|
||||
VideoCore::LabelGLObject(GL_PROGRAM, program.handle, addr);
|
||||
} else {
|
||||
// Store shader's code to lazily build it on draw
|
||||
geometry_programs.code = program_result.first;
|
||||
}
|
||||
}
|
||||
|
||||
GLuint CachedShader::GetProgramResourceIndex(const GLShader::ConstBufferEntry& buffer) {
|
||||
@@ -110,6 +119,21 @@ GLint CachedShader::GetUniformLocation(const GLShader::SamplerEntry& sampler) {
|
||||
return search->second;
|
||||
}
|
||||
|
||||
GLuint CachedShader::LazyGeometryProgram(OGLProgram& target_program,
|
||||
const std::string& glsl_topology,
|
||||
const std::string& debug_name) {
|
||||
if (target_program.handle != 0) {
|
||||
return target_program.handle;
|
||||
}
|
||||
const std::string source{geometry_programs.code + "layout (" + glsl_topology + ") in;\n"};
|
||||
OGLShader shader;
|
||||
shader.Create(source.c_str(), GL_GEOMETRY_SHADER);
|
||||
target_program.Create(true, shader.handle);
|
||||
SetShaderUniformBlockBindings(target_program.handle);
|
||||
VideoCore::LabelGLObject(GL_PROGRAM, target_program.handle, addr, debug_name);
|
||||
return target_program.handle;
|
||||
};
|
||||
|
||||
Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
|
||||
const VAddr program_addr{GetShaderAddress(program)};
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/rasterizer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
@@ -38,8 +39,31 @@ public:
|
||||
}
|
||||
|
||||
/// Gets the GL program handle for the shader
|
||||
GLuint GetProgramHandle() const {
|
||||
return program.handle;
|
||||
GLuint GetProgramHandle(GLenum primitive_mode) {
|
||||
if (program_type != Maxwell::ShaderProgram::Geometry) {
|
||||
return program.handle;
|
||||
}
|
||||
switch (primitive_mode) {
|
||||
case GL_POINTS:
|
||||
return LazyGeometryProgram(geometry_programs.points, "points", "ShaderPoints");
|
||||
case GL_LINES:
|
||||
case GL_LINE_STRIP:
|
||||
return LazyGeometryProgram(geometry_programs.lines, "lines", "ShaderLines");
|
||||
case GL_LINES_ADJACENCY:
|
||||
case GL_LINE_STRIP_ADJACENCY:
|
||||
return LazyGeometryProgram(geometry_programs.lines_adjacency, "lines_adjacency",
|
||||
"ShaderLinesAdjacency");
|
||||
case GL_TRIANGLES:
|
||||
case GL_TRIANGLE_STRIP:
|
||||
case GL_TRIANGLE_FAN:
|
||||
return LazyGeometryProgram(geometry_programs.triangles, "triangles", "ShaderTriangles");
|
||||
case GL_TRIANGLES_ADJACENCY:
|
||||
case GL_TRIANGLE_STRIP_ADJACENCY:
|
||||
return LazyGeometryProgram(geometry_programs.triangles_adjacency, "triangles_adjacency",
|
||||
"ShaderLines");
|
||||
default:
|
||||
UNREACHABLE_MSG("Unknown primitive mode.");
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the GL program resource location for the specified resource, caching as needed
|
||||
@@ -49,12 +73,30 @@ public:
|
||||
GLint GetUniformLocation(const GLShader::SamplerEntry& sampler);
|
||||
|
||||
private:
|
||||
/// Generates a geometry shader or returns one that already exists.
|
||||
GLuint LazyGeometryProgram(OGLProgram& target_program, const std::string& glsl_topology,
|
||||
const std::string& debug_name);
|
||||
|
||||
VAddr addr;
|
||||
Maxwell::ShaderProgram program_type;
|
||||
GLShader::ShaderSetup setup;
|
||||
GLShader::ShaderEntries entries;
|
||||
|
||||
// Non-geometry program.
|
||||
OGLProgram program;
|
||||
|
||||
// Geometry programs. These are needed because GLSL needs an input topology but it's not
|
||||
// declared by the hardware. Workaround this issue by generating a different shader per input
|
||||
// topology class.
|
||||
struct {
|
||||
std::string code;
|
||||
OGLProgram points;
|
||||
OGLProgram lines;
|
||||
OGLProgram lines_adjacency;
|
||||
OGLProgram triangles;
|
||||
OGLProgram triangles_adjacency;
|
||||
} geometry_programs;
|
||||
|
||||
std::map<u32, GLuint> resource_cache;
|
||||
std::map<u32, GLint> uniform_cache;
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
@@ -29,11 +30,32 @@ using Tegra::Shader::SubOp;
|
||||
constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH;
|
||||
constexpr u32 PROGRAM_HEADER_SIZE = sizeof(Tegra::Shader::Header);
|
||||
|
||||
enum : u32 { POSITION_VARYING_LOCATION = 0, GENERIC_VARYING_START_LOCATION = 1 };
|
||||
|
||||
constexpr u32 MAX_GEOMETRY_BUFFERS = 6;
|
||||
constexpr u32 MAX_ATTRIBUTES = 0x100; // Size in vec4s, this value is untested
|
||||
|
||||
class DecompileFail : public std::runtime_error {
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
/// Translate topology
|
||||
static std::string GetTopologyName(Tegra::Shader::OutputTopology topology) {
|
||||
switch (topology) {
|
||||
case Tegra::Shader::OutputTopology::PointList:
|
||||
return "points";
|
||||
case Tegra::Shader::OutputTopology::LineStrip:
|
||||
return "line_strip";
|
||||
case Tegra::Shader::OutputTopology::TriangleStrip:
|
||||
return "triangle_strip";
|
||||
default:
|
||||
LOG_CRITICAL(Render_OpenGL, "Unknown output topology {}", static_cast<u32>(topology));
|
||||
UNREACHABLE();
|
||||
return "points";
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes the behaviour of code path of a given entry point and a return point.
|
||||
enum class ExitMethod {
|
||||
Undetermined, ///< Internal value. Only occur when analyzing JMP loop.
|
||||
@@ -253,8 +275,9 @@ enum class InternalFlag : u64 {
|
||||
class GLSLRegisterManager {
|
||||
public:
|
||||
GLSLRegisterManager(ShaderWriter& shader, ShaderWriter& declarations,
|
||||
const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix)
|
||||
: shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix} {
|
||||
const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix,
|
||||
const Tegra::Shader::Header& header)
|
||||
: shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix}, header{header} {
|
||||
BuildRegisterList();
|
||||
BuildInputList();
|
||||
}
|
||||
@@ -358,11 +381,13 @@ public:
|
||||
* @param reg The destination register to use.
|
||||
* @param elem The element to use for the operation.
|
||||
* @param attribute The input attribute to use as the source value.
|
||||
* @param vertex The register that decides which vertex to read from (used in GS).
|
||||
*/
|
||||
void SetRegisterToInputAttibute(const Register& reg, u64 elem, Attribute::Index attribute,
|
||||
const Tegra::Shader::IpaMode& input_mode) {
|
||||
const Tegra::Shader::IpaMode& input_mode,
|
||||
boost::optional<Register> vertex = {}) {
|
||||
const std::string dest = GetRegisterAsFloat(reg);
|
||||
const std::string src = GetInputAttribute(attribute, input_mode) + GetSwizzle(elem);
|
||||
const std::string src = GetInputAttribute(attribute, input_mode, vertex) + GetSwizzle(elem);
|
||||
shader.AddLine(dest + " = " + src + ';');
|
||||
}
|
||||
|
||||
@@ -391,16 +416,29 @@ public:
|
||||
* are stored as floats, so this may require conversion.
|
||||
* @param attribute The destination output attribute.
|
||||
* @param elem The element to use for the operation.
|
||||
* @param reg The register to use as the source value.
|
||||
* @param val_reg The register to use as the source value.
|
||||
* @param buf_reg The register that tells which buffer to write to (used in geometry shaders).
|
||||
*/
|
||||
void SetOutputAttributeToRegister(Attribute::Index attribute, u64 elem, const Register& reg) {
|
||||
void SetOutputAttributeToRegister(Attribute::Index attribute, u64 elem, const Register& val_reg,
|
||||
const Register& buf_reg) {
|
||||
const std::string dest = GetOutputAttribute(attribute);
|
||||
const std::string src = GetRegisterAsFloat(reg);
|
||||
const std::string src = GetRegisterAsFloat(val_reg);
|
||||
|
||||
if (!dest.empty()) {
|
||||
// Can happen with unknown/unimplemented output attributes, in which case we ignore the
|
||||
// instruction for now.
|
||||
shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';');
|
||||
if (stage == Maxwell3D::Regs::ShaderStage::Geometry) {
|
||||
// TODO(Rodrigo): nouveau sets some attributes after setting emitting a geometry
|
||||
// shader. These instructions use a dirty register as buffer index. To avoid some
|
||||
// drivers from complaining for the out of boundary writes, guard them.
|
||||
const std::string buf_index{"min(" + GetRegisterAsInteger(buf_reg) + ", " +
|
||||
std::to_string(MAX_GEOMETRY_BUFFERS - 1) + ')'};
|
||||
shader.AddLine("amem[" + buf_index + "][" +
|
||||
std::to_string(static_cast<u32>(attribute)) + ']' +
|
||||
GetSwizzle(elem) + " = " + src + ';');
|
||||
} else {
|
||||
shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,41 +479,123 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/// Add declarations for registers
|
||||
/// Add declarations.
|
||||
void GenerateDeclarations(const std::string& suffix) {
|
||||
GenerateRegisters(suffix);
|
||||
GenerateInternalFlags();
|
||||
GenerateInputAttrs();
|
||||
GenerateOutputAttrs();
|
||||
GenerateConstBuffers();
|
||||
GenerateSamplers();
|
||||
GenerateGeometry();
|
||||
}
|
||||
|
||||
/// Returns a list of constant buffer declarations.
|
||||
std::vector<ConstBufferEntry> GetConstBuffersDeclarations() const {
|
||||
std::vector<ConstBufferEntry> result;
|
||||
std::copy_if(declr_const_buffers.begin(), declr_const_buffers.end(),
|
||||
std::back_inserter(result), [](const auto& entry) { return entry.IsUsed(); });
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Returns a list of samplers used in the shader.
|
||||
const std::vector<SamplerEntry>& GetSamplers() const {
|
||||
return used_samplers;
|
||||
}
|
||||
|
||||
/// Returns the GLSL sampler used for the input shader sampler, and creates a new one if
|
||||
/// necessary.
|
||||
std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type,
|
||||
bool is_array, bool is_shadow) {
|
||||
const auto offset = static_cast<std::size_t>(sampler.index.Value());
|
||||
|
||||
// If this sampler has already been used, return the existing mapping.
|
||||
const auto itr =
|
||||
std::find_if(used_samplers.begin(), used_samplers.end(),
|
||||
[&](const SamplerEntry& entry) { return entry.GetOffset() == offset; });
|
||||
|
||||
if (itr != used_samplers.end()) {
|
||||
ASSERT(itr->GetType() == type && itr->IsArray() == is_array &&
|
||||
itr->IsShadow() == is_shadow);
|
||||
return itr->GetName();
|
||||
}
|
||||
|
||||
// Otherwise create a new mapping for this sampler
|
||||
const std::size_t next_index = used_samplers.size();
|
||||
const SamplerEntry entry{stage, offset, next_index, type, is_array, is_shadow};
|
||||
used_samplers.emplace_back(entry);
|
||||
return entry.GetName();
|
||||
}
|
||||
|
||||
private:
|
||||
/// Generates declarations for registers.
|
||||
void GenerateRegisters(const std::string& suffix) {
|
||||
for (const auto& reg : regs) {
|
||||
declarations.AddLine(GLSLRegister::GetTypeString() + ' ' + reg.GetPrefixString() +
|
||||
std::to_string(reg.GetIndex()) + '_' + suffix + " = 0;");
|
||||
}
|
||||
declarations.AddNewLine();
|
||||
}
|
||||
|
||||
/// Generates declarations for internal flags.
|
||||
void GenerateInternalFlags() {
|
||||
for (u32 ii = 0; ii < static_cast<u64>(InternalFlag::Amount); ii++) {
|
||||
const InternalFlag code = static_cast<InternalFlag>(ii);
|
||||
declarations.AddLine("bool " + GetInternalFlag(code) + " = false;");
|
||||
}
|
||||
declarations.AddNewLine();
|
||||
}
|
||||
|
||||
/// Generates declarations for input attributes.
|
||||
void GenerateInputAttrs() {
|
||||
if (stage != Maxwell3D::Regs::ShaderStage::Vertex) {
|
||||
const std::string attr =
|
||||
stage == Maxwell3D::Regs::ShaderStage::Geometry ? "gs_position[]" : "position";
|
||||
declarations.AddLine("layout (location = " + std::to_string(POSITION_VARYING_LOCATION) +
|
||||
") in vec4 " + attr + ';');
|
||||
}
|
||||
|
||||
for (const auto element : declr_input_attribute) {
|
||||
// TODO(bunnei): Use proper number of elements for these
|
||||
u32 idx =
|
||||
static_cast<u32>(element.first) - static_cast<u32>(Attribute::Index::Attribute_0);
|
||||
declarations.AddLine("layout(location = " + std::to_string(idx) + ")" +
|
||||
GetInputFlags(element.first) + "in vec4 " +
|
||||
GetInputAttribute(element.first, element.second) + ';');
|
||||
}
|
||||
declarations.AddNewLine();
|
||||
if (stage != Maxwell3D::Regs::ShaderStage::Vertex) {
|
||||
// If inputs are varyings, add an offset
|
||||
idx += GENERIC_VARYING_START_LOCATION;
|
||||
}
|
||||
|
||||
std::string attr{GetInputAttribute(element.first, element.second)};
|
||||
if (stage == Maxwell3D::Regs::ShaderStage::Geometry) {
|
||||
attr = "gs_" + attr + "[]";
|
||||
}
|
||||
declarations.AddLine("layout (location = " + std::to_string(idx) + ") " +
|
||||
GetInputFlags(element.first) + "in vec4 " + attr + ';');
|
||||
}
|
||||
|
||||
declarations.AddNewLine();
|
||||
}
|
||||
|
||||
/// Generates declarations for output attributes.
|
||||
void GenerateOutputAttrs() {
|
||||
if (stage != Maxwell3D::Regs::ShaderStage::Fragment) {
|
||||
declarations.AddLine("layout (location = " + std::to_string(POSITION_VARYING_LOCATION) +
|
||||
") out vec4 position;");
|
||||
}
|
||||
for (const auto& index : declr_output_attribute) {
|
||||
// TODO(bunnei): Use proper number of elements for these
|
||||
declarations.AddLine("layout(location = " +
|
||||
std::to_string(static_cast<u32>(index) -
|
||||
static_cast<u32>(Attribute::Index::Attribute_0)) +
|
||||
") out vec4 " + GetOutputAttribute(index) + ';');
|
||||
const u32 idx = static_cast<u32>(index) -
|
||||
static_cast<u32>(Attribute::Index::Attribute_0) +
|
||||
GENERIC_VARYING_START_LOCATION;
|
||||
declarations.AddLine("layout (location = " + std::to_string(idx) + ") out vec4 " +
|
||||
GetOutputAttribute(index) + ';');
|
||||
}
|
||||
declarations.AddNewLine();
|
||||
}
|
||||
|
||||
/// Generates declarations for constant buffers.
|
||||
void GenerateConstBuffers() {
|
||||
for (const auto& entry : GetConstBuffersDeclarations()) {
|
||||
declarations.AddLine("layout(std140) uniform " + entry.GetName());
|
||||
declarations.AddLine("layout (std140) uniform " + entry.GetName());
|
||||
declarations.AddLine('{');
|
||||
declarations.AddLine(" vec4 c" + std::to_string(entry.GetIndex()) +
|
||||
"[MAX_CONSTBUFFER_ELEMENTS];");
|
||||
@@ -483,7 +603,10 @@ public:
|
||||
declarations.AddNewLine();
|
||||
}
|
||||
declarations.AddNewLine();
|
||||
}
|
||||
|
||||
/// Generates declarations for samplers.
|
||||
void GenerateSamplers() {
|
||||
const auto& samplers = GetSamplers();
|
||||
for (const auto& sampler : samplers) {
|
||||
declarations.AddLine("uniform " + sampler.GetTypeString() + ' ' + sampler.GetName() +
|
||||
@@ -492,43 +615,42 @@ public:
|
||||
declarations.AddNewLine();
|
||||
}
|
||||
|
||||
/// Returns a list of constant buffer declarations
|
||||
std::vector<ConstBufferEntry> GetConstBuffersDeclarations() const {
|
||||
std::vector<ConstBufferEntry> result;
|
||||
std::copy_if(declr_const_buffers.begin(), declr_const_buffers.end(),
|
||||
std::back_inserter(result), [](const auto& entry) { return entry.IsUsed(); });
|
||||
return result;
|
||||
}
|
||||
/// Generates declarations used for geometry shaders.
|
||||
void GenerateGeometry() {
|
||||
if (stage != Maxwell3D::Regs::ShaderStage::Geometry)
|
||||
return;
|
||||
|
||||
/// Returns a list of samplers used in the shader
|
||||
const std::vector<SamplerEntry>& GetSamplers() const {
|
||||
return used_samplers;
|
||||
}
|
||||
declarations.AddLine(
|
||||
"layout (" + GetTopologyName(header.common3.output_topology) +
|
||||
", max_vertices = " + std::to_string(header.common4.max_output_vertices) + ") out;");
|
||||
declarations.AddNewLine();
|
||||
|
||||
/// Returns the GLSL sampler used for the input shader sampler, and creates a new one if
|
||||
/// necessary.
|
||||
std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type,
|
||||
bool is_array) {
|
||||
const std::size_t offset = static_cast<std::size_t>(sampler.index.Value());
|
||||
declarations.AddLine("vec4 amem[" + std::to_string(MAX_GEOMETRY_BUFFERS) + "][" +
|
||||
std::to_string(MAX_ATTRIBUTES) + "];");
|
||||
declarations.AddNewLine();
|
||||
|
||||
// If this sampler has already been used, return the existing mapping.
|
||||
const auto itr =
|
||||
std::find_if(used_samplers.begin(), used_samplers.end(),
|
||||
[&](const SamplerEntry& entry) { return entry.GetOffset() == offset; });
|
||||
|
||||
if (itr != used_samplers.end()) {
|
||||
ASSERT(itr->GetType() == type && itr->IsArray() == is_array);
|
||||
return itr->GetName();
|
||||
constexpr char buffer[] = "amem[output_buffer]";
|
||||
declarations.AddLine("void emit_vertex(uint output_buffer) {");
|
||||
++declarations.scope;
|
||||
for (const auto element : declr_output_attribute) {
|
||||
declarations.AddLine(GetOutputAttribute(element) + " = " + buffer + '[' +
|
||||
std::to_string(static_cast<u32>(element)) + "];");
|
||||
}
|
||||
|
||||
// Otherwise create a new mapping for this sampler
|
||||
const std::size_t next_index = used_samplers.size();
|
||||
const SamplerEntry entry{stage, offset, next_index, type, is_array};
|
||||
used_samplers.emplace_back(entry);
|
||||
return entry.GetName();
|
||||
declarations.AddLine("position = " + std::string(buffer) + '[' +
|
||||
std::to_string(static_cast<u32>(Attribute::Index::Position)) + "];");
|
||||
|
||||
// If a geometry shader is attached, it will always flip (it's the last stage before
|
||||
// fragment). For more info about flipping, refer to gl_shader_gen.cpp.
|
||||
declarations.AddLine("position.xy *= viewport_flip.xy;");
|
||||
declarations.AddLine("gl_Position = position;");
|
||||
declarations.AddLine("position.w = 1.0;");
|
||||
declarations.AddLine("EmitVertex();");
|
||||
--declarations.scope;
|
||||
declarations.AddLine('}');
|
||||
declarations.AddNewLine();
|
||||
}
|
||||
|
||||
private:
|
||||
/// Generates code representing a temporary (GPR) register.
|
||||
std::string GetRegister(const Register& reg, unsigned elem) {
|
||||
if (reg == Register::ZeroIndex) {
|
||||
@@ -585,11 +707,19 @@ private:
|
||||
|
||||
/// Generates code representing an input attribute register.
|
||||
std::string GetInputAttribute(Attribute::Index attribute,
|
||||
const Tegra::Shader::IpaMode& input_mode) {
|
||||
const Tegra::Shader::IpaMode& input_mode,
|
||||
boost::optional<Register> vertex = {}) {
|
||||
auto GeometryPass = [&](const std::string& name) {
|
||||
if (stage == Maxwell3D::Regs::ShaderStage::Geometry && vertex) {
|
||||
return "gs_" + name + '[' + GetRegisterAsInteger(vertex.value(), 0, false) + ']';
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
||||
switch (attribute) {
|
||||
case Attribute::Index::Position:
|
||||
if (stage != Maxwell3D::Regs::ShaderStage::Fragment) {
|
||||
return "position";
|
||||
return GeometryPass("position");
|
||||
} else {
|
||||
return "vec4(gl_FragCoord.x, gl_FragCoord.y, gl_FragCoord.z, 1.0)";
|
||||
}
|
||||
@@ -618,7 +748,7 @@ private:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
return "input_attribute_" + std::to_string(index);
|
||||
return GeometryPass("input_attribute_" + std::to_string(index));
|
||||
}
|
||||
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled input attribute: {}", static_cast<u32>(attribute));
|
||||
@@ -671,7 +801,7 @@ private:
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Generates code representing an output attribute register.
|
||||
/// Generates code representing the declaration name of an output attribute register.
|
||||
std::string GetOutputAttribute(Attribute::Index attribute) {
|
||||
switch (attribute) {
|
||||
case Attribute::Index::Position:
|
||||
@@ -707,6 +837,7 @@ private:
|
||||
std::vector<SamplerEntry> used_samplers;
|
||||
const Maxwell3D::Regs::ShaderStage& stage;
|
||||
const std::string& suffix;
|
||||
const Tegra::Shader::Header& header;
|
||||
};
|
||||
|
||||
class GLSLGenerator {
|
||||
@@ -747,8 +878,9 @@ private:
|
||||
}
|
||||
|
||||
/// Generates code representing a texture sampler.
|
||||
std::string GetSampler(const Sampler& sampler, Tegra::Shader::TextureType type, bool is_array) {
|
||||
return regs.AccessSampler(sampler, type, is_array);
|
||||
std::string GetSampler(const Sampler& sampler, Tegra::Shader::TextureType type, bool is_array,
|
||||
bool is_shadow) {
|
||||
return regs.AccessSampler(sampler, type, is_array, is_shadow);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1002,6 +1134,24 @@ private:
|
||||
shader.AddLine('}');
|
||||
}
|
||||
|
||||
static u32 TextureCoordinates(Tegra::Shader::TextureType texture_type) {
|
||||
switch (texture_type) {
|
||||
case Tegra::Shader::TextureType::Texture1D: {
|
||||
return 1;
|
||||
}
|
||||
case Tegra::Shader::TextureType::Texture2D: {
|
||||
return 2;
|
||||
}
|
||||
case Tegra::Shader::TextureType::TextureCube: {
|
||||
return 3;
|
||||
}
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", static_cast<u32>(texture_type));
|
||||
UNREACHABLE();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Emits code to push the input target address to the SSY address stack, incrementing the stack
|
||||
* top.
|
||||
@@ -1083,8 +1233,8 @@ private:
|
||||
return offset + 1;
|
||||
}
|
||||
|
||||
shader.AddLine("// " + std::to_string(offset) + ": " + opcode->GetName() + " (" +
|
||||
std::to_string(instr.value) + ')');
|
||||
shader.AddLine(
|
||||
fmt::format("// {}: {} (0x{:016x})", offset, opcode->GetName(), instr.value));
|
||||
|
||||
using Tegra::Shader::Pred;
|
||||
ASSERT_MSG(instr.pred.full_pred != Pred::NeverExecute,
|
||||
@@ -1806,7 +1956,7 @@ private:
|
||||
const auto LoadNextElement = [&](u32 reg_offset) {
|
||||
regs.SetRegisterToInputAttibute(instr.gpr0.Value() + reg_offset, next_element,
|
||||
static_cast<Attribute::Index>(next_index),
|
||||
input_mode);
|
||||
input_mode, instr.gpr39.Value());
|
||||
|
||||
// Load the next attribute element into the following register. If the element
|
||||
// to load goes beyond the vec4 size, load the first element of the next
|
||||
@@ -1870,8 +2020,8 @@ private:
|
||||
|
||||
const auto StoreNextElement = [&](u32 reg_offset) {
|
||||
regs.SetOutputAttributeToRegister(static_cast<Attribute::Index>(next_index),
|
||||
next_element,
|
||||
instr.gpr0.Value() + reg_offset);
|
||||
next_element, instr.gpr0.Value() + reg_offset,
|
||||
instr.gpr39.Value());
|
||||
|
||||
// Load the next attribute element into the following register. If the element
|
||||
// to load goes beyond the vec4 size, load the first element of the next
|
||||
@@ -1896,24 +2046,35 @@ private:
|
||||
"NODEP is not implemented");
|
||||
ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI),
|
||||
"AOFFI is not implemented");
|
||||
ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC),
|
||||
"DC is not implemented");
|
||||
|
||||
switch (texture_type) {
|
||||
case Tegra::Shader::TextureType::Texture1D: {
|
||||
const bool depth_compare =
|
||||
instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
|
||||
u32 num_coordinates = TextureCoordinates(texture_type);
|
||||
if (depth_compare)
|
||||
num_coordinates += 1;
|
||||
|
||||
switch (num_coordinates) {
|
||||
case 1: {
|
||||
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
coord = "float coords = " + x + ';';
|
||||
break;
|
||||
}
|
||||
case Tegra::Shader::TextureType::Texture2D: {
|
||||
case 2: {
|
||||
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
const std::string z = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled texture type {}",
|
||||
static_cast<u32>(texture_type));
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled coordinates number {}",
|
||||
static_cast<u32>(num_coordinates));
|
||||
UNREACHABLE();
|
||||
|
||||
// Fallback to interpreting as a 2D texture for now
|
||||
@@ -1924,9 +2085,10 @@ private:
|
||||
}
|
||||
// TODO: make sure coordinates are always indexed to gpr8 and gpr20 is always bias
|
||||
// or lod.
|
||||
const std::string op_c = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
std::string op_c;
|
||||
|
||||
const std::string sampler = GetSampler(instr.sampler, texture_type, false);
|
||||
const std::string sampler =
|
||||
GetSampler(instr.sampler, texture_type, false, depth_compare);
|
||||
// Add an extra scope and declare the texture coords inside to prevent
|
||||
// overwriting them in case they are used as outputs of the texs instruction.
|
||||
|
||||
@@ -1935,7 +2097,7 @@ private:
|
||||
shader.AddLine(coord);
|
||||
std::string texture;
|
||||
|
||||
switch (instr.tex.process_mode) {
|
||||
switch (instr.tex.GetTextureProcessMode()) {
|
||||
case Tegra::Shader::TextureProcessMode::None: {
|
||||
texture = "texture(" + sampler + ", coords)";
|
||||
break;
|
||||
@@ -1946,12 +2108,22 @@ private:
|
||||
}
|
||||
case Tegra::Shader::TextureProcessMode::LB:
|
||||
case Tegra::Shader::TextureProcessMode::LBA: {
|
||||
if (num_coordinates <= 2) {
|
||||
op_c = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
} else {
|
||||
op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1);
|
||||
}
|
||||
// TODO: Figure if A suffix changes the equation at all.
|
||||
texture = "texture(" + sampler + ", coords, " + op_c + ')';
|
||||
break;
|
||||
}
|
||||
case Tegra::Shader::TextureProcessMode::LL:
|
||||
case Tegra::Shader::TextureProcessMode::LLA: {
|
||||
if (num_coordinates <= 2) {
|
||||
op_c = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
} else {
|
||||
op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1);
|
||||
}
|
||||
// TODO: Figure if A suffix changes the equation at all.
|
||||
texture = "textureLod(" + sampler + ", coords, " + op_c + ')';
|
||||
break;
|
||||
@@ -1959,18 +2131,22 @@ private:
|
||||
default: {
|
||||
texture = "texture(" + sampler + ", coords)";
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}",
|
||||
static_cast<u32>(instr.tex.process_mode.Value()));
|
||||
static_cast<u32>(instr.tex.GetTextureProcessMode()));
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
std::size_t dest_elem{};
|
||||
for (std::size_t elem = 0; elem < 4; ++elem) {
|
||||
if (!instr.tex.IsComponentEnabled(elem)) {
|
||||
// Skip disabled components
|
||||
continue;
|
||||
if (!depth_compare) {
|
||||
std::size_t dest_elem{};
|
||||
for (std::size_t elem = 0; elem < 4; ++elem) {
|
||||
if (!instr.tex.IsComponentEnabled(elem)) {
|
||||
// Skip disabled components
|
||||
continue;
|
||||
}
|
||||
regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem);
|
||||
++dest_elem;
|
||||
}
|
||||
regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem);
|
||||
++dest_elem;
|
||||
} else {
|
||||
regs.SetRegisterToFloat(instr.gpr0, 0, texture, 1, 1, false);
|
||||
}
|
||||
--shader.scope;
|
||||
shader.AddLine("}");
|
||||
@@ -1983,11 +2159,15 @@ private:
|
||||
|
||||
ASSERT_MSG(!instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP),
|
||||
"NODEP is not implemented");
|
||||
ASSERT_MSG(!instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC),
|
||||
"DC is not implemented");
|
||||
|
||||
switch (texture_type) {
|
||||
case Tegra::Shader::TextureType::Texture2D: {
|
||||
const bool depth_compare =
|
||||
instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
|
||||
u32 num_coordinates = TextureCoordinates(texture_type);
|
||||
if (depth_compare)
|
||||
num_coordinates += 1;
|
||||
|
||||
switch (num_coordinates) {
|
||||
case 2: {
|
||||
if (is_array) {
|
||||
const std::string index = regs.GetRegisterAsInteger(instr.gpr8);
|
||||
const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
@@ -2000,17 +2180,25 @@ private:
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Tegra::Shader::TextureType::TextureCube: {
|
||||
ASSERT_MSG(!is_array, "Unimplemented");
|
||||
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
std::string z = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
|
||||
case 3: {
|
||||
if (is_array) {
|
||||
UNIMPLEMENTED_MSG("3-coordinate arrays not fully implemented");
|
||||
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string y = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
|
||||
texture_type = Tegra::Shader::TextureType::Texture2D;
|
||||
is_array = false;
|
||||
} else {
|
||||
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
const std::string z = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled texture type {}",
|
||||
static_cast<u32>(texture_type));
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled coordinates number {}",
|
||||
static_cast<u32>(num_coordinates));
|
||||
UNREACHABLE();
|
||||
|
||||
// Fallback to interpreting as a 2D texture for now
|
||||
@@ -2020,9 +2208,35 @@ private:
|
||||
texture_type = Tegra::Shader::TextureType::Texture2D;
|
||||
is_array = false;
|
||||
}
|
||||
const std::string sampler = GetSampler(instr.sampler, texture_type, is_array);
|
||||
const std::string texture = "texture(" + sampler + ", coords)";
|
||||
WriteTexsInstruction(instr, coord, texture);
|
||||
const std::string sampler =
|
||||
GetSampler(instr.sampler, texture_type, is_array, depth_compare);
|
||||
std::string texture;
|
||||
switch (instr.texs.GetTextureProcessMode()) {
|
||||
case Tegra::Shader::TextureProcessMode::None: {
|
||||
texture = "texture(" + sampler + ", coords)";
|
||||
break;
|
||||
}
|
||||
case Tegra::Shader::TextureProcessMode::LZ: {
|
||||
texture = "textureLod(" + sampler + ", coords, 0.0)";
|
||||
break;
|
||||
}
|
||||
case Tegra::Shader::TextureProcessMode::LL: {
|
||||
const std::string op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1);
|
||||
texture = "textureLod(" + sampler + ", coords, " + op_c + ')';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
texture = "texture(" + sampler + ", coords)";
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}",
|
||||
static_cast<u32>(instr.texs.GetTextureProcessMode()));
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
if (!depth_compare) {
|
||||
WriteTexsInstruction(instr, coord, texture);
|
||||
} else {
|
||||
WriteTexsInstruction(instr, coord, "vec4(" + texture + ')');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TLDS: {
|
||||
@@ -2062,9 +2276,26 @@ private:
|
||||
static_cast<u32>(texture_type));
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
const std::string sampler = GetSampler(instr.sampler, texture_type, is_array);
|
||||
const std::string texture = "texelFetch(" + sampler + ", coords, 0)";
|
||||
const std::string sampler =
|
||||
GetSampler(instr.sampler, texture_type, is_array, false);
|
||||
std::string texture = "texelFetch(" + sampler + ", coords, 0)";
|
||||
const std::string op_c = regs.GetRegisterAsInteger(instr.gpr20.Value() + 1);
|
||||
switch (instr.tlds.GetTextureProcessMode()) {
|
||||
case Tegra::Shader::TextureProcessMode::LZ: {
|
||||
texture = "texelFetch(" + sampler + ", coords, 0)";
|
||||
break;
|
||||
}
|
||||
case Tegra::Shader::TextureProcessMode::LL: {
|
||||
texture = "texelFetch(" + sampler + ", coords, " + op_c + ')';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
texture = "texelFetch(" + sampler + ", coords, 0)";
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}",
|
||||
static_cast<u32>(instr.tlds.GetTextureProcessMode()));
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
WriteTexsInstruction(instr, coord, texture);
|
||||
break;
|
||||
}
|
||||
@@ -2077,28 +2308,43 @@ private:
|
||||
"NODEP is not implemented");
|
||||
ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI),
|
||||
"AOFFI is not implemented");
|
||||
ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC),
|
||||
"DC is not implemented");
|
||||
ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV),
|
||||
"NDV is not implemented");
|
||||
ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::PTP),
|
||||
"PTP is not implemented");
|
||||
const bool depth_compare =
|
||||
instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
|
||||
auto texture_type = instr.tld4.texture_type.Value();
|
||||
u32 num_coordinates = TextureCoordinates(texture_type);
|
||||
if (depth_compare)
|
||||
num_coordinates += 1;
|
||||
|
||||
switch (instr.tld4.texture_type) {
|
||||
case Tegra::Shader::TextureType::Texture2D: {
|
||||
switch (num_coordinates) {
|
||||
case 2: {
|
||||
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2);
|
||||
coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled texture type {}",
|
||||
static_cast<u32>(instr.tld4.texture_type.Value()));
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled coordinates number {}",
|
||||
static_cast<u32>(num_coordinates));
|
||||
UNREACHABLE();
|
||||
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
|
||||
texture_type = Tegra::Shader::TextureType::Texture2D;
|
||||
}
|
||||
|
||||
const std::string sampler =
|
||||
GetSampler(instr.sampler, instr.tld4.texture_type, false);
|
||||
GetSampler(instr.sampler, texture_type, false, depth_compare);
|
||||
// Add an extra scope and declare the texture coords inside to prevent
|
||||
// overwriting them in case they are used as outputs of the texs instruction.
|
||||
shader.AddLine("{");
|
||||
@@ -2106,15 +2352,18 @@ private:
|
||||
shader.AddLine(coord);
|
||||
const std::string texture = "textureGather(" + sampler + ", coords, " +
|
||||
std::to_string(instr.tld4.component) + ')';
|
||||
|
||||
std::size_t dest_elem{};
|
||||
for (std::size_t elem = 0; elem < 4; ++elem) {
|
||||
if (!instr.tex.IsComponentEnabled(elem)) {
|
||||
// Skip disabled components
|
||||
continue;
|
||||
if (!depth_compare) {
|
||||
std::size_t dest_elem{};
|
||||
for (std::size_t elem = 0; elem < 4; ++elem) {
|
||||
if (!instr.tex.IsComponentEnabled(elem)) {
|
||||
// Skip disabled components
|
||||
continue;
|
||||
}
|
||||
regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem);
|
||||
++dest_elem;
|
||||
}
|
||||
regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem);
|
||||
++dest_elem;
|
||||
} else {
|
||||
regs.SetRegisterToFloat(instr.gpr0, 0, texture, 1, 1, false);
|
||||
}
|
||||
--shader.scope;
|
||||
shader.AddLine("}");
|
||||
@@ -2125,18 +2374,30 @@ private:
|
||||
"NODEP is not implemented");
|
||||
ASSERT_MSG(!instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI),
|
||||
"AOFFI is not implemented");
|
||||
ASSERT_MSG(!instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC),
|
||||
"DC is not implemented");
|
||||
|
||||
const bool depth_compare =
|
||||
instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
|
||||
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
// TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction.
|
||||
const std::string sampler =
|
||||
GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false);
|
||||
const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");";
|
||||
const std::string sampler = GetSampler(
|
||||
instr.sampler, Tegra::Shader::TextureType::Texture2D, false, depth_compare);
|
||||
std::string coord;
|
||||
if (!depth_compare) {
|
||||
coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");";
|
||||
} else {
|
||||
// Note: TLD4S coordinate encoding works just like TEXS's
|
||||
const std::string op_c = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
coord = "vec3 coords = vec3(" + op_a + ", " + op_c + ", " + op_b + ");";
|
||||
}
|
||||
const std::string texture = "textureGather(" + sampler + ", coords, " +
|
||||
std::to_string(instr.tld4s.component) + ')';
|
||||
WriteTexsInstruction(instr, coord, texture);
|
||||
|
||||
if (!depth_compare) {
|
||||
WriteTexsInstruction(instr, coord, texture);
|
||||
} else {
|
||||
WriteTexsInstruction(instr, coord, "vec4(" + texture + ')');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TXQ: {
|
||||
@@ -2147,7 +2408,7 @@ private:
|
||||
// Sadly, not all texture instructions specify the type of texture their sampler
|
||||
// uses. This must be fixed at a later instance.
|
||||
const std::string sampler =
|
||||
GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false);
|
||||
GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false, false);
|
||||
switch (instr.txq.query_type) {
|
||||
case Tegra::Shader::TextureQueryType::Dimension: {
|
||||
const std::string texture = "textureQueryLevels(" + sampler + ')';
|
||||
@@ -2168,24 +2429,22 @@ private:
|
||||
ASSERT_MSG(!instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV),
|
||||
"NDV is not implemented");
|
||||
|
||||
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const bool is_array = instr.tmml.array != 0;
|
||||
auto texture_type = instr.tmml.texture_type.Value();
|
||||
const std::string sampler = GetSampler(instr.sampler, texture_type, is_array);
|
||||
const std::string sampler =
|
||||
GetSampler(instr.sampler, texture_type, is_array, false);
|
||||
|
||||
// TODO: add coordinates for different samplers once other texture types are
|
||||
// implemented.
|
||||
std::string coord;
|
||||
switch (texture_type) {
|
||||
case Tegra::Shader::TextureType::Texture1D: {
|
||||
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
coord = "float coords = " + x + ';';
|
||||
break;
|
||||
}
|
||||
case Tegra::Shader::TextureType::Texture2D: {
|
||||
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
|
||||
break;
|
||||
}
|
||||
@@ -2195,8 +2454,7 @@ private:
|
||||
UNREACHABLE();
|
||||
|
||||
// Fallback to interpreting as a 2D texture for now
|
||||
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
|
||||
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
|
||||
texture_type = Tegra::Shader::TextureType::Texture2D;
|
||||
}
|
||||
@@ -2606,6 +2864,52 @@ private:
|
||||
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::OUT_R: {
|
||||
ASSERT(instr.gpr20.Value() == Register::ZeroIndex);
|
||||
ASSERT_MSG(stage == Maxwell3D::Regs::ShaderStage::Geometry,
|
||||
"OUT is expected to be used in a geometry shader.");
|
||||
|
||||
if (instr.out.emit) {
|
||||
// gpr0 is used to store the next address. Hardware returns a pointer but
|
||||
// we just return the next index with a cyclic cap.
|
||||
const std::string current{regs.GetRegisterAsInteger(instr.gpr8, 0, false)};
|
||||
const std::string next = "((" + current + " + 1" + ") % " +
|
||||
std::to_string(MAX_GEOMETRY_BUFFERS) + ')';
|
||||
shader.AddLine("emit_vertex(" + current + ");");
|
||||
regs.SetRegisterToInteger(instr.gpr0, false, 0, next, 1, 1);
|
||||
}
|
||||
if (instr.out.cut) {
|
||||
shader.AddLine("EndPrimitive();");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::MOV_SYS: {
|
||||
switch (instr.sys20) {
|
||||
case Tegra::Shader::SystemVariable::InvocationInfo: {
|
||||
LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete");
|
||||
regs.SetRegisterToInteger(instr.gpr0, false, 0, "0u", 1, 1);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled system move: {}",
|
||||
static_cast<u32>(instr.sys20.Value()));
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::ISBERD: {
|
||||
ASSERT(instr.isberd.o == 0);
|
||||
ASSERT(instr.isberd.skew == 0);
|
||||
ASSERT(instr.isberd.shift == Tegra::Shader::IsberdShift::None);
|
||||
ASSERT(instr.isberd.mode == Tegra::Shader::IsberdMode::None);
|
||||
ASSERT_MSG(stage == Maxwell3D::Regs::ShaderStage::Geometry,
|
||||
"ISBERD is expected to be used in a geometry shader.");
|
||||
LOG_WARNING(HW_GPU, "ISBERD instruction is incomplete");
|
||||
regs.SetRegisterToFloat(instr.gpr0, 0, regs.GetRegisterAsFloat(instr.gpr8), 1, 1);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::BRA: {
|
||||
ASSERT_MSG(instr.bra.constant_buffer == 0,
|
||||
"BRA with constant buffers are not implemented");
|
||||
@@ -2649,6 +2953,88 @@ private:
|
||||
LOG_WARNING(HW_GPU, "DEPBAR instruction is stubbed");
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::VMAD: {
|
||||
const bool signed_a = instr.vmad.signed_a == 1;
|
||||
const bool signed_b = instr.vmad.signed_b == 1;
|
||||
const bool result_signed = signed_a || signed_b;
|
||||
boost::optional<std::string> forced_result;
|
||||
|
||||
auto Unpack = [&](const std::string& op, bool is_chunk, bool is_signed,
|
||||
Tegra::Shader::VmadType type, u64 byte_height) {
|
||||
const std::string value = [&]() {
|
||||
if (!is_chunk) {
|
||||
const auto offset = static_cast<u32>(byte_height * 8);
|
||||
return "((" + op + " >> " + std::to_string(offset) + ") & 0xff)";
|
||||
}
|
||||
const std::string zero = "0";
|
||||
|
||||
switch (type) {
|
||||
case Tegra::Shader::VmadType::Size16_Low:
|
||||
return '(' + op + " & 0xffff)";
|
||||
case Tegra::Shader::VmadType::Size16_High:
|
||||
return '(' + op + " >> 16)";
|
||||
case Tegra::Shader::VmadType::Size32:
|
||||
// TODO(Rodrigo): From my hardware tests it becomes a bit "mad" when
|
||||
// this type is used (1 * 1 + 0 == 0x5b800000). Until a better
|
||||
// explanation is found: assert.
|
||||
UNREACHABLE_MSG("Unimplemented");
|
||||
return zero;
|
||||
case Tegra::Shader::VmadType::Invalid:
|
||||
// Note(Rodrigo): This flag is invalid according to nvdisasm. From my
|
||||
// testing (even though it's invalid) this makes the whole instruction
|
||||
// assign zero to target register.
|
||||
forced_result = boost::make_optional(zero);
|
||||
return zero;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
return zero;
|
||||
}
|
||||
}();
|
||||
|
||||
if (is_signed) {
|
||||
return "int(" + value + ')';
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const std::string op_a = Unpack(regs.GetRegisterAsInteger(instr.gpr8, 0, false),
|
||||
instr.vmad.is_byte_chunk_a != 0, signed_a,
|
||||
instr.vmad.type_a, instr.vmad.byte_height_a);
|
||||
|
||||
std::string op_b;
|
||||
if (instr.vmad.use_register_b) {
|
||||
op_b = Unpack(regs.GetRegisterAsInteger(instr.gpr20, 0, false),
|
||||
instr.vmad.is_byte_chunk_b != 0, signed_b, instr.vmad.type_b,
|
||||
instr.vmad.byte_height_b);
|
||||
} else {
|
||||
op_b = '(' +
|
||||
std::to_string(signed_b ? static_cast<s16>(instr.alu.GetImm20_16())
|
||||
: instr.alu.GetImm20_16()) +
|
||||
')';
|
||||
}
|
||||
|
||||
const std::string op_c = regs.GetRegisterAsInteger(instr.gpr39, 0, result_signed);
|
||||
|
||||
std::string result;
|
||||
if (forced_result) {
|
||||
result = *forced_result;
|
||||
} else {
|
||||
result = '(' + op_a + " * " + op_b + " + " + op_c + ')';
|
||||
|
||||
switch (instr.vmad.shr) {
|
||||
case Tegra::Shader::VmadShr::Shr7:
|
||||
result = '(' + result + " >> 7)";
|
||||
break;
|
||||
case Tegra::Shader::VmadShr::Shr15:
|
||||
result = '(' + result + " >> 15)";
|
||||
break;
|
||||
}
|
||||
}
|
||||
regs.SetRegisterToInteger(instr.gpr0, result_signed, 1, result, 1, 1,
|
||||
instr.vmad.saturate == 1, 0, Register::Size::Word,
|
||||
instr.vmad.cc);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
LOG_CRITICAL(HW_GPU, "Unhandled instruction: {}", opcode->GetName());
|
||||
UNREACHABLE();
|
||||
@@ -2779,7 +3165,7 @@ private:
|
||||
|
||||
ShaderWriter shader;
|
||||
ShaderWriter declarations;
|
||||
GLSLRegisterManager regs{shader, declarations, stage, suffix};
|
||||
GLSLRegisterManager regs{shader, declarations, stage, suffix, header};
|
||||
|
||||
// Declarations
|
||||
std::set<std::string> declr_predicates;
|
||||
|
||||
@@ -17,7 +17,18 @@ ProgramResult GenerateVertexShader(const ShaderSetup& setup) {
|
||||
std::string out = "#version 430 core\n";
|
||||
out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
|
||||
out += Decompiler::GetCommonDeclarations();
|
||||
out += "bool exec_vertex();\n";
|
||||
|
||||
out += R"(
|
||||
out gl_PerVertex {
|
||||
vec4 gl_Position;
|
||||
};
|
||||
|
||||
layout(std140) uniform vs_config {
|
||||
vec4 viewport_flip;
|
||||
uvec4 instance_id;
|
||||
uvec4 flip_stage;
|
||||
};
|
||||
)";
|
||||
|
||||
if (setup.IsDualProgram()) {
|
||||
out += "bool exec_vertex_b();\n";
|
||||
@@ -28,19 +39,18 @@ ProgramResult GenerateVertexShader(const ShaderSetup& setup) {
|
||||
Maxwell3D::Regs::ShaderStage::Vertex, "vertex")
|
||||
.get_value_or({});
|
||||
|
||||
out += program.first;
|
||||
|
||||
if (setup.IsDualProgram()) {
|
||||
ProgramResult program_b =
|
||||
Decompiler::DecompileProgram(setup.program.code_b, PROGRAM_OFFSET,
|
||||
Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b")
|
||||
.get_value_or({});
|
||||
out += program_b.first;
|
||||
}
|
||||
|
||||
out += R"(
|
||||
|
||||
out gl_PerVertex {
|
||||
vec4 gl_Position;
|
||||
};
|
||||
|
||||
out vec4 position;
|
||||
|
||||
layout (std140) uniform vs_config {
|
||||
vec4 viewport_flip;
|
||||
uvec4 instance_id;
|
||||
};
|
||||
|
||||
void main() {
|
||||
position = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
exec_vertex();
|
||||
@@ -52,27 +62,52 @@ void main() {
|
||||
|
||||
out += R"(
|
||||
|
||||
// Viewport can be flipped, which is unsupported by glViewport
|
||||
position.xy *= viewport_flip.xy;
|
||||
// Check if the flip stage is VertexB
|
||||
if (flip_stage[0] == 1) {
|
||||
// Viewport can be flipped, which is unsupported by glViewport
|
||||
position.xy *= viewport_flip.xy;
|
||||
}
|
||||
gl_Position = position;
|
||||
|
||||
// TODO(bunnei): This is likely a hack, position.w should be interpolated as 1.0
|
||||
// For now, this is here to bring order in lieu of proper emulation
|
||||
position.w = 1.0;
|
||||
if (flip_stage[0] == 1) {
|
||||
position.w = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
)";
|
||||
|
||||
return {out, program.second};
|
||||
}
|
||||
|
||||
ProgramResult GenerateGeometryShader(const ShaderSetup& setup) {
|
||||
std::string out = "#version 430 core\n";
|
||||
out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
|
||||
out += Decompiler::GetCommonDeclarations();
|
||||
out += "bool exec_geometry();\n";
|
||||
|
||||
ProgramResult program =
|
||||
Decompiler::DecompileProgram(setup.program.code, PROGRAM_OFFSET,
|
||||
Maxwell3D::Regs::ShaderStage::Geometry, "geometry")
|
||||
.get_value_or({});
|
||||
out += R"(
|
||||
out gl_PerVertex {
|
||||
vec4 gl_Position;
|
||||
};
|
||||
|
||||
layout (std140) uniform gs_config {
|
||||
vec4 viewport_flip;
|
||||
uvec4 instance_id;
|
||||
uvec4 flip_stage;
|
||||
};
|
||||
|
||||
void main() {
|
||||
exec_geometry();
|
||||
}
|
||||
|
||||
)";
|
||||
out += program.first;
|
||||
|
||||
if (setup.IsDualProgram()) {
|
||||
ProgramResult program_b =
|
||||
Decompiler::DecompileProgram(setup.program.code_b, PROGRAM_OFFSET,
|
||||
Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b")
|
||||
.get_value_or({});
|
||||
out += program_b.first;
|
||||
}
|
||||
|
||||
return {out, program.second};
|
||||
}
|
||||
|
||||
@@ -87,7 +122,6 @@ ProgramResult GenerateFragmentShader(const ShaderSetup& setup) {
|
||||
Maxwell3D::Regs::ShaderStage::Fragment, "fragment")
|
||||
.get_value_or({});
|
||||
out += R"(
|
||||
in vec4 position;
|
||||
layout(location = 0) out vec4 FragColor0;
|
||||
layout(location = 1) out vec4 FragColor1;
|
||||
layout(location = 2) out vec4 FragColor2;
|
||||
@@ -100,6 +134,7 @@ layout(location = 7) out vec4 FragColor7;
|
||||
layout (std140) uniform fs_config {
|
||||
vec4 viewport_flip;
|
||||
uvec4 instance_id;
|
||||
uvec4 flip_stage;
|
||||
};
|
||||
|
||||
void main() {
|
||||
@@ -110,5 +145,4 @@ void main() {
|
||||
out += program.first;
|
||||
return {out, program.second};
|
||||
}
|
||||
|
||||
} // namespace OpenGL::GLShader
|
||||
} // namespace OpenGL::GLShader
|
||||
@@ -75,8 +75,9 @@ class SamplerEntry {
|
||||
|
||||
public:
|
||||
SamplerEntry(Maxwell::ShaderStage stage, std::size_t offset, std::size_t index,
|
||||
Tegra::Shader::TextureType type, bool is_array)
|
||||
: offset(offset), stage(stage), sampler_index(index), type(type), is_array(is_array) {}
|
||||
Tegra::Shader::TextureType type, bool is_array, bool is_shadow)
|
||||
: offset(offset), stage(stage), sampler_index(index), type(type), is_array(is_array),
|
||||
is_shadow(is_shadow) {}
|
||||
|
||||
std::size_t GetOffset() const {
|
||||
return offset;
|
||||
@@ -117,6 +118,8 @@ public:
|
||||
}
|
||||
if (is_array)
|
||||
glsl_type += "Array";
|
||||
if (is_shadow)
|
||||
glsl_type += "Shadow";
|
||||
return glsl_type;
|
||||
}
|
||||
|
||||
@@ -128,6 +131,10 @@ public:
|
||||
return is_array;
|
||||
}
|
||||
|
||||
bool IsShadow() const {
|
||||
return is_shadow;
|
||||
}
|
||||
|
||||
u32 GetHash() const {
|
||||
return (static_cast<u32>(stage) << 16) | static_cast<u32>(sampler_index);
|
||||
}
|
||||
@@ -147,7 +154,8 @@ private:
|
||||
Maxwell::ShaderStage stage; ///< Shader stage where this sampler was used.
|
||||
std::size_t sampler_index; ///< Value used to index into the generated GLSL sampler array.
|
||||
Tegra::Shader::TextureType type; ///< The type used to sample this texture (Texture2D, etc)
|
||||
bool is_array; ///< Whether the texture is being sampled as an array texture or not.
|
||||
bool is_array; ///< Whether the texture is being sampled as an array texture or not.
|
||||
bool is_shadow; ///< Whether the texture is being sampled as a depth texture or not.
|
||||
};
|
||||
|
||||
struct ShaderEntries {
|
||||
@@ -187,6 +195,12 @@ private:
|
||||
*/
|
||||
ProgramResult GenerateVertexShader(const ShaderSetup& setup);
|
||||
|
||||
/**
|
||||
* Generates the GLSL geometry shader program source code for the given GS program
|
||||
* @returns String of the shader source code
|
||||
*/
|
||||
ProgramResult GenerateGeometryShader(const ShaderSetup& setup);
|
||||
|
||||
/**
|
||||
* Generates the GLSL fragment shader program source code for the given FS program
|
||||
* @returns String of the shader source code
|
||||
|
||||
@@ -18,6 +18,14 @@ void MaxwellUniformData::SetFromRegs(const Maxwell3D::State::ShaderStageInfo& sh
|
||||
|
||||
// We only assign the instance to the first component of the vector, the rest is just padding.
|
||||
instance_id[0] = state.current_instance;
|
||||
|
||||
// Assign in which stage the position has to be flipped
|
||||
// (the last stage before the fragment shader).
|
||||
if (gpu.regs.shader_config[static_cast<u32>(Maxwell3D::Regs::ShaderProgram::Geometry)].enable) {
|
||||
flip_stage[0] = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::Geometry);
|
||||
} else {
|
||||
flip_stage[0] = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::VertexB);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace OpenGL::GLShader
|
||||
|
||||
@@ -21,8 +21,9 @@ struct MaxwellUniformData {
|
||||
void SetFromRegs(const Maxwell3D::State::ShaderStageInfo& shader_stage);
|
||||
alignas(16) GLvec4 viewport_flip;
|
||||
alignas(16) GLuvec4 instance_id;
|
||||
alignas(16) GLuvec4 flip_stage;
|
||||
};
|
||||
static_assert(sizeof(MaxwellUniformData) == 32, "MaxwellUniformData structure size is incorrect");
|
||||
static_assert(sizeof(MaxwellUniformData) == 48, "MaxwellUniformData structure size is incorrect");
|
||||
static_assert(sizeof(MaxwellUniformData) < 16384,
|
||||
"MaxwellUniformData structure must be less than 16kb as per the OpenGL spec");
|
||||
|
||||
@@ -36,6 +37,10 @@ public:
|
||||
vs = program;
|
||||
}
|
||||
|
||||
void UseProgrammableGeometryShader(GLuint program) {
|
||||
gs = program;
|
||||
}
|
||||
|
||||
void UseProgrammableFragmentShader(GLuint program) {
|
||||
fs = program;
|
||||
}
|
||||
|
||||
@@ -159,6 +159,31 @@ inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) {
|
||||
return {};
|
||||
}
|
||||
|
||||
inline GLenum DepthCompareFunc(Tegra::Texture::DepthCompareFunc func) {
|
||||
switch (func) {
|
||||
case Tegra::Texture::DepthCompareFunc::Never:
|
||||
return GL_NEVER;
|
||||
case Tegra::Texture::DepthCompareFunc::Less:
|
||||
return GL_LESS;
|
||||
case Tegra::Texture::DepthCompareFunc::LessEqual:
|
||||
return GL_LEQUAL;
|
||||
case Tegra::Texture::DepthCompareFunc::Equal:
|
||||
return GL_EQUAL;
|
||||
case Tegra::Texture::DepthCompareFunc::NotEqual:
|
||||
return GL_NOTEQUAL;
|
||||
case Tegra::Texture::DepthCompareFunc::Greater:
|
||||
return GL_GREATER;
|
||||
case Tegra::Texture::DepthCompareFunc::GreaterEqual:
|
||||
return GL_GEQUAL;
|
||||
case Tegra::Texture::DepthCompareFunc::Always:
|
||||
return GL_ALWAYS;
|
||||
}
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented texture depth compare function ={}",
|
||||
static_cast<u32>(func));
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
inline GLenum BlendEquation(Maxwell::Blend::Equation equation) {
|
||||
switch (equation) {
|
||||
case Maxwell::Blend::Equation::Add:
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/gpu.h"
|
||||
@@ -199,4 +200,19 @@ std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat
|
||||
return rgba_data;
|
||||
}
|
||||
|
||||
std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
|
||||
u32 block_height, u32 block_depth) {
|
||||
if (tiled) {
|
||||
const u32 gobs_in_x = 64 / bytes_per_pixel;
|
||||
const u32 gobs_in_y = 8;
|
||||
const u32 gobs_in_z = 1;
|
||||
const u32 aligned_width = Common::AlignUp(width, gobs_in_x);
|
||||
const u32 aligned_height = Common::AlignUp(height, gobs_in_y * block_height);
|
||||
const u32 aligned_depth = Common::AlignUp(depth, gobs_in_z * block_depth);
|
||||
return aligned_width * aligned_height * aligned_depth * bytes_per_pixel;
|
||||
} else {
|
||||
return width * height * depth * bytes_per_pixel;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Tegra::Texture
|
||||
|
||||
@@ -32,4 +32,10 @@ void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_
|
||||
std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width,
|
||||
u32 height);
|
||||
|
||||
/**
|
||||
* This function calculates the correct size of a texture depending if it's tiled or not.
|
||||
*/
|
||||
std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
|
||||
u32 block_height, u32 block_depth);
|
||||
|
||||
} // namespace Tegra::Texture
|
||||
|
||||
@@ -161,7 +161,9 @@ struct TICEntry {
|
||||
BitField<21, 3, TICHeaderVersion> header_version;
|
||||
};
|
||||
union {
|
||||
BitField<0, 3, u32> block_width;
|
||||
BitField<3, 3, u32> block_height;
|
||||
BitField<6, 3, u32> block_depth;
|
||||
|
||||
// High 16 bits of the pitch value
|
||||
BitField<0, 16, u32> pitch_high;
|
||||
@@ -202,13 +204,24 @@ struct TICEntry {
|
||||
return depth_minus_1 + 1;
|
||||
}
|
||||
|
||||
u32 BlockWidth() const {
|
||||
ASSERT(IsTiled());
|
||||
// The block height is stored in log2 format.
|
||||
return 1 << block_width;
|
||||
}
|
||||
|
||||
u32 BlockHeight() const {
|
||||
ASSERT(header_version == TICHeaderVersion::BlockLinear ||
|
||||
header_version == TICHeaderVersion::BlockLinearColorKey);
|
||||
ASSERT(IsTiled());
|
||||
// The block height is stored in log2 format.
|
||||
return 1 << block_height;
|
||||
}
|
||||
|
||||
u32 BlockDepth() const {
|
||||
ASSERT(IsTiled());
|
||||
// The block height is stored in log2 format.
|
||||
return 1 << block_depth;
|
||||
}
|
||||
|
||||
bool IsTiled() const {
|
||||
return header_version == TICHeaderVersion::BlockLinear ||
|
||||
header_version == TICHeaderVersion::BlockLinearColorKey;
|
||||
@@ -227,6 +240,17 @@ enum class WrapMode : u32 {
|
||||
MirrorOnceClampOGL = 7,
|
||||
};
|
||||
|
||||
enum class DepthCompareFunc : u32 {
|
||||
Never = 0,
|
||||
Less = 1,
|
||||
Equal = 2,
|
||||
LessEqual = 3,
|
||||
Greater = 4,
|
||||
NotEqual = 5,
|
||||
GreaterEqual = 6,
|
||||
Always = 7,
|
||||
};
|
||||
|
||||
enum class TextureFilter : u32 {
|
||||
Nearest = 1,
|
||||
Linear = 2,
|
||||
@@ -244,7 +268,7 @@ struct TSCEntry {
|
||||
BitField<3, 3, WrapMode> wrap_v;
|
||||
BitField<6, 3, WrapMode> wrap_p;
|
||||
BitField<9, 1, u32> depth_compare_enabled;
|
||||
BitField<10, 3, u32> depth_compare_func;
|
||||
BitField<10, 3, DepthCompareFunc> depth_compare_func;
|
||||
};
|
||||
union {
|
||||
BitField<0, 2, TextureFilter> mag_filter;
|
||||
|
||||
@@ -169,16 +169,20 @@ static void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr,
|
||||
const std::string nice_addr = fmt::format("0x{:016x}", addr);
|
||||
std::string object_label;
|
||||
|
||||
switch (identifier) {
|
||||
case GL_TEXTURE:
|
||||
object_label = extra_info + "@" + nice_addr;
|
||||
break;
|
||||
case GL_PROGRAM:
|
||||
object_label = "ShaderProgram@" + nice_addr;
|
||||
break;
|
||||
default:
|
||||
object_label = fmt::format("Object(0x{:x})@{}", identifier, nice_addr);
|
||||
break;
|
||||
if (extra_info.empty()) {
|
||||
switch (identifier) {
|
||||
case GL_TEXTURE:
|
||||
object_label = "Texture@" + nice_addr;
|
||||
break;
|
||||
case GL_PROGRAM:
|
||||
object_label = "Shader@" + nice_addr;
|
||||
break;
|
||||
default:
|
||||
object_label = fmt::format("Object(0x{:x})@{}", identifier, nice_addr);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
object_label = extra_info + '@' + nice_addr;
|
||||
}
|
||||
glObjectLabel(identifier, handle, -1, static_cast<const GLchar*>(object_label.c_str()));
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
|
||||
std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_name,
|
||||
Common::g_scm_branch, Common::g_scm_desc);
|
||||
setWindowTitle(QString::fromStdString(window_title));
|
||||
setAttribute(Qt::WA_AcceptTouchEvents);
|
||||
|
||||
InputCommon::Init();
|
||||
InputCommon::StartJoystickEventHandler();
|
||||
@@ -190,11 +191,17 @@ QByteArray GRenderWindow::saveGeometry() {
|
||||
return geometry;
|
||||
}
|
||||
|
||||
qreal GRenderWindow::windowPixelRatio() {
|
||||
qreal GRenderWindow::windowPixelRatio() const {
|
||||
// windowHandle() might not be accessible until the window is displayed to screen.
|
||||
return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;
|
||||
}
|
||||
|
||||
std::pair<unsigned, unsigned> GRenderWindow::ScaleTouch(const QPointF pos) const {
|
||||
const qreal pixel_ratio = windowPixelRatio();
|
||||
return {static_cast<unsigned>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
|
||||
static_cast<unsigned>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
|
||||
}
|
||||
|
||||
void GRenderWindow::closeEvent(QCloseEvent* event) {
|
||||
emit Closed();
|
||||
QWidget::closeEvent(event);
|
||||
@@ -209,31 +216,81 @@ void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
|
||||
}
|
||||
|
||||
void GRenderWindow::mousePressEvent(QMouseEvent* event) {
|
||||
if (event->source() == Qt::MouseEventSynthesizedBySystem)
|
||||
return; // touch input is handled in TouchBeginEvent
|
||||
|
||||
auto pos = event->pos();
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
qreal pixelRatio = windowPixelRatio();
|
||||
this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio),
|
||||
static_cast<unsigned>(pos.y() * pixelRatio));
|
||||
const auto [x, y] = ScaleTouch(pos);
|
||||
this->TouchPressed(x, y);
|
||||
} else if (event->button() == Qt::RightButton) {
|
||||
InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
|
||||
}
|
||||
}
|
||||
|
||||
void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
|
||||
if (event->source() == Qt::MouseEventSynthesizedBySystem)
|
||||
return; // touch input is handled in TouchUpdateEvent
|
||||
|
||||
auto pos = event->pos();
|
||||
qreal pixelRatio = windowPixelRatio();
|
||||
this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u),
|
||||
std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u));
|
||||
const auto [x, y] = ScaleTouch(pos);
|
||||
this->TouchMoved(x, y);
|
||||
InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
|
||||
}
|
||||
|
||||
void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
|
||||
if (event->source() == Qt::MouseEventSynthesizedBySystem)
|
||||
return; // touch input is handled in TouchEndEvent
|
||||
|
||||
if (event->button() == Qt::LeftButton)
|
||||
this->TouchReleased();
|
||||
else if (event->button() == Qt::RightButton)
|
||||
InputCommon::GetMotionEmu()->EndTilt();
|
||||
}
|
||||
|
||||
void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
|
||||
// TouchBegin always has exactly one touch point, so take the .first()
|
||||
const auto [x, y] = ScaleTouch(event->touchPoints().first().pos());
|
||||
this->TouchPressed(x, y);
|
||||
}
|
||||
|
||||
void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) {
|
||||
QPointF pos;
|
||||
int active_points = 0;
|
||||
|
||||
// average all active touch points
|
||||
for (const auto tp : event->touchPoints()) {
|
||||
if (tp.state() & (Qt::TouchPointPressed | Qt::TouchPointMoved | Qt::TouchPointStationary)) {
|
||||
active_points++;
|
||||
pos += tp.pos();
|
||||
}
|
||||
}
|
||||
|
||||
pos /= active_points;
|
||||
|
||||
const auto [x, y] = ScaleTouch(pos);
|
||||
this->TouchMoved(x, y);
|
||||
}
|
||||
|
||||
void GRenderWindow::TouchEndEvent() {
|
||||
this->TouchReleased();
|
||||
}
|
||||
|
||||
bool GRenderWindow::event(QEvent* event) {
|
||||
if (event->type() == QEvent::TouchBegin) {
|
||||
TouchBeginEvent(static_cast<QTouchEvent*>(event));
|
||||
return true;
|
||||
} else if (event->type() == QEvent::TouchUpdate) {
|
||||
TouchUpdateEvent(static_cast<QTouchEvent*>(event));
|
||||
return true;
|
||||
} else if (event->type() == QEvent::TouchEnd || event->type() == QEvent::TouchCancel) {
|
||||
TouchEndEvent();
|
||||
return true;
|
||||
}
|
||||
|
||||
return QWidget::event(event);
|
||||
}
|
||||
|
||||
void GRenderWindow::focusOutEvent(QFocusEvent* event) {
|
||||
QWidget::focusOutEvent(event);
|
||||
InputCommon::GetKeyboard()->ReleaseAllKeys();
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
class QKeyEvent;
|
||||
class QScreen;
|
||||
class QTouchEvent;
|
||||
|
||||
class GGLWidgetInternal;
|
||||
class GMainWindow;
|
||||
@@ -119,7 +120,7 @@ public:
|
||||
void restoreGeometry(const QByteArray& geometry); // overridden
|
||||
QByteArray saveGeometry(); // overridden
|
||||
|
||||
qreal windowPixelRatio();
|
||||
qreal windowPixelRatio() const;
|
||||
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
|
||||
@@ -130,6 +131,8 @@ public:
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
|
||||
bool event(QEvent* event) override;
|
||||
|
||||
void focusOutEvent(QFocusEvent* event) override;
|
||||
|
||||
void OnClientAreaResized(unsigned width, unsigned height);
|
||||
@@ -148,6 +151,11 @@ signals:
|
||||
void Closed();
|
||||
|
||||
private:
|
||||
std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const;
|
||||
void TouchBeginEvent(const QTouchEvent* event);
|
||||
void TouchUpdateEvent(const QTouchEvent* event);
|
||||
void TouchEndEvent();
|
||||
|
||||
void OnMinimalClientAreaChangeRequest(
|
||||
const std::pair<unsigned, unsigned>& minimal_size) override;
|
||||
|
||||
|
||||
@@ -134,6 +134,7 @@ void Config::ReadValues() {
|
||||
qt_config->beginGroup("Debugging");
|
||||
Settings::values.use_gdbstub = qt_config->value("use_gdbstub", false).toBool();
|
||||
Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt();
|
||||
Settings::values.program_args = qt_config->value("program_args", "").toString().toStdString();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("WebService");
|
||||
@@ -269,6 +270,7 @@ void Config::SaveValues() {
|
||||
qt_config->beginGroup("Debugging");
|
||||
qt_config->setValue("use_gdbstub", Settings::values.use_gdbstub);
|
||||
qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port);
|
||||
qt_config->setValue("program_args", QString::fromStdString(Settings::values.program_args));
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("WebService");
|
||||
|
||||
@@ -33,6 +33,7 @@ void ConfigureDebug::setConfiguration() {
|
||||
ui->toggle_console->setEnabled(!Core::System::GetInstance().IsPoweredOn());
|
||||
ui->toggle_console->setChecked(UISettings::values.show_console);
|
||||
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter));
|
||||
ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args));
|
||||
}
|
||||
|
||||
void ConfigureDebug::applyConfiguration() {
|
||||
@@ -40,6 +41,7 @@ void ConfigureDebug::applyConfiguration() {
|
||||
Settings::values.gdbstub_port = ui->gdbport_spinbox->value();
|
||||
UISettings::values.show_console = ui->toggle_console->isChecked();
|
||||
Settings::values.log_filter = ui->log_filter_edit->text().toStdString();
|
||||
Settings::values.program_args = ui->homebrew_args_edit->text().toStdString();
|
||||
Debugger::ToggleConsole();
|
||||
Log::Filter filter;
|
||||
filter.ParseFilterString(Settings::values.log_filter);
|
||||
|
||||
@@ -106,6 +106,29 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Homebrew</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Arguments String</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="homebrew_args_edit"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QTimer>
|
||||
#include "common/param_package.h"
|
||||
@@ -128,28 +129,63 @@ ConfigureInput::ConfigureInput(QWidget* parent)
|
||||
analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog};
|
||||
|
||||
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
|
||||
if (button_map[button_id])
|
||||
connect(button_map[button_id], &QPushButton::released, [=]() {
|
||||
handleClick(
|
||||
button_map[button_id],
|
||||
[=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
|
||||
InputCommon::Polling::DeviceType::Button);
|
||||
});
|
||||
if (!button_map[button_id])
|
||||
continue;
|
||||
button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(button_map[button_id], &QPushButton::released, [=]() {
|
||||
handleClick(
|
||||
button_map[button_id],
|
||||
[=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
|
||||
InputCommon::Polling::DeviceType::Button);
|
||||
});
|
||||
connect(button_map[button_id], &QPushButton::customContextMenuRequested,
|
||||
[=](const QPoint& menu_location) {
|
||||
QMenu context_menu;
|
||||
context_menu.addAction(tr("Clear"), [&] {
|
||||
buttons_param[button_id].Clear();
|
||||
button_map[button_id]->setText(tr("[not set]"));
|
||||
});
|
||||
context_menu.addAction(tr("Restore Default"), [&] {
|
||||
buttons_param[button_id] = Common::ParamPackage{
|
||||
InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
|
||||
button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
|
||||
});
|
||||
context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
|
||||
});
|
||||
}
|
||||
|
||||
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
|
||||
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
|
||||
if (analog_map_buttons[analog_id][sub_button_id] != nullptr) {
|
||||
connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released,
|
||||
[=]() {
|
||||
handleClick(analog_map_buttons[analog_id][sub_button_id],
|
||||
[=](const Common::ParamPackage& params) {
|
||||
SetAnalogButton(params, analogs_param[analog_id],
|
||||
analog_sub_buttons[sub_button_id]);
|
||||
},
|
||||
InputCommon::Polling::DeviceType::Button);
|
||||
if (!analog_map_buttons[analog_id][sub_button_id])
|
||||
continue;
|
||||
analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy(
|
||||
Qt::CustomContextMenu);
|
||||
connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, [=]() {
|
||||
handleClick(analog_map_buttons[analog_id][sub_button_id],
|
||||
[=](const Common::ParamPackage& params) {
|
||||
SetAnalogButton(params, analogs_param[analog_id],
|
||||
analog_sub_buttons[sub_button_id]);
|
||||
},
|
||||
InputCommon::Polling::DeviceType::Button);
|
||||
});
|
||||
connect(analog_map_buttons[analog_id][sub_button_id],
|
||||
&QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
|
||||
QMenu context_menu;
|
||||
context_menu.addAction(tr("Clear"), [&] {
|
||||
analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
|
||||
analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
|
||||
});
|
||||
}
|
||||
context_menu.addAction(tr("Restore Default"), [&] {
|
||||
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
|
||||
Config::default_analogs[analog_id][sub_button_id])};
|
||||
SetAnalogButton(params, analogs_param[analog_id],
|
||||
analog_sub_buttons[sub_button_id]);
|
||||
analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText(
|
||||
analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
|
||||
});
|
||||
context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(
|
||||
menu_location));
|
||||
});
|
||||
}
|
||||
connect(analog_map_stick[analog_id], &QPushButton::released, [=]() {
|
||||
QMessageBox::information(this, tr("Information"),
|
||||
@@ -162,6 +198,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
|
||||
});
|
||||
}
|
||||
|
||||
connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); });
|
||||
connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });
|
||||
|
||||
timeout_timer->setSingleShot(true);
|
||||
@@ -215,7 +252,21 @@ void ConfigureInput::restoreDefaults() {
|
||||
}
|
||||
}
|
||||
updateButtonLabels();
|
||||
applyConfiguration();
|
||||
}
|
||||
|
||||
void ConfigureInput::ClearAll() {
|
||||
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
|
||||
if (button_map[button_id] && button_map[button_id]->isEnabled())
|
||||
buttons_param[button_id].Clear();
|
||||
}
|
||||
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
|
||||
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
|
||||
if (analog_map_buttons[analog_id][sub_button_id] &&
|
||||
analog_map_buttons[analog_id][sub_button_id]->isEnabled())
|
||||
analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
|
||||
}
|
||||
}
|
||||
updateButtonLabels();
|
||||
}
|
||||
|
||||
void ConfigureInput::updateButtonLabels() {
|
||||
|
||||
@@ -72,6 +72,9 @@ private:
|
||||
void loadConfiguration();
|
||||
/// Restore all buttons to their default values.
|
||||
void restoreDefaults();
|
||||
/// Clear all input configuration
|
||||
void ClearAll();
|
||||
|
||||
/// Update UI to reflect current configuration.
|
||||
void updateButtonLabels();
|
||||
|
||||
|
||||
@@ -694,6 +694,34 @@ Capture:</string>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonClearAll">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="sizeIncrement">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="baseSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Clear All</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonRestoreDefaults">
|
||||
<property name="sizePolicy">
|
||||
|
||||
@@ -27,9 +27,8 @@
|
||||
#include "yuzu/ui_settings.h"
|
||||
|
||||
namespace {
|
||||
void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager,
|
||||
const std::shared_ptr<FileSys::NCA>& nca, std::vector<u8>& icon,
|
||||
std::string& name) {
|
||||
void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const FileSys::NCA& nca,
|
||||
std::vector<u8>& icon, std::string& name) {
|
||||
auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca);
|
||||
if (icon_file != nullptr)
|
||||
icon = icon_file->ReadAllBytes();
|
||||
@@ -57,16 +56,25 @@ QString FormatGameName(const std::string& physical_name) {
|
||||
return physical_name_as_qstring;
|
||||
}
|
||||
|
||||
QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, bool updatable = true) {
|
||||
QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
|
||||
Loader::AppLoader& loader, bool updatable = true) {
|
||||
QString out;
|
||||
for (const auto& kv : patch_manager.GetPatchVersionNames()) {
|
||||
FileSys::VirtualFile update_raw;
|
||||
loader.ReadUpdateRaw(update_raw);
|
||||
for (const auto& kv : patch_manager.GetPatchVersionNames(update_raw)) {
|
||||
if (!updatable && kv.first == "Update")
|
||||
continue;
|
||||
|
||||
if (kv.second.empty()) {
|
||||
out.append(fmt::format("{}\n", kv.first).c_str());
|
||||
} else {
|
||||
out.append(fmt::format("{} ({})\n", kv.first, kv.second).c_str());
|
||||
auto ver = kv.second;
|
||||
|
||||
// Display container name for packed updates
|
||||
if (ver == "PACKED" && kv.first == "Update")
|
||||
ver = Loader::GetFileTypeString(loader.GetFileType());
|
||||
|
||||
out.append(fmt::format("{} ({})\n", kv.first, ver).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +109,7 @@ void GameListWorker::AddInstalledTitlesToGameList() {
|
||||
const FileSys::PatchManager patch{program_id};
|
||||
const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
|
||||
if (control != nullptr)
|
||||
GetMetadataFromControlNCA(patch, control, icon, name);
|
||||
GetMetadataFromControlNCA(patch, *control, icon, name);
|
||||
|
||||
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||
|
||||
@@ -116,7 +124,7 @@ void GameListWorker::AddInstalledTitlesToGameList() {
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
|
||||
program_id),
|
||||
new GameListItemCompat(compatibility),
|
||||
new GameListItem(FormatPatchNameVersions(patch)),
|
||||
new GameListItem(FormatPatchNameVersions(patch, *loader)),
|
||||
new GameListItem(
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
|
||||
new GameListItemSize(file->GetSize()),
|
||||
@@ -188,8 +196,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
||||
res2 == Loader::ResultStatus::Success) {
|
||||
// Use from metadata pool.
|
||||
if (nca_control_map.find(program_id) != nca_control_map.end()) {
|
||||
const auto nca = nca_control_map[program_id];
|
||||
GetMetadataFromControlNCA(patch, nca, icon, name);
|
||||
const auto& nca = nca_control_map[program_id];
|
||||
GetMetadataFromControlNCA(patch, *nca, icon, name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +214,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
|
||||
program_id),
|
||||
new GameListItemCompat(compatibility),
|
||||
new GameListItem(FormatPatchNameVersions(patch, loader->IsRomFSUpdatable())),
|
||||
new GameListItem(
|
||||
FormatPatchNameVersions(patch, *loader, loader->IsRomFSUpdatable())),
|
||||
new GameListItem(
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
|
||||
new GameListItemSize(FileUtil::GetSize(physical_name)),
|
||||
|
||||
@@ -31,6 +31,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QtGui>
|
||||
#include <QtWidgets>
|
||||
#include <fmt/format.h>
|
||||
@@ -171,6 +172,9 @@ GMainWindow::GMainWindow()
|
||||
.arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc));
|
||||
show();
|
||||
|
||||
// Gen keys if necessary
|
||||
OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);
|
||||
|
||||
// Necessary to load titles from nand in gamelist.
|
||||
Service::FileSystem::CreateFactories(vfs);
|
||||
game_list->LoadCompatibilityList();
|
||||
@@ -443,6 +447,8 @@ void GMainWindow::ConnectMenuEvents() {
|
||||
connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen);
|
||||
|
||||
// Help
|
||||
connect(ui.action_Rederive, &QAction::triggered, this,
|
||||
std::bind(&GMainWindow::OnReinitializeKeys, this, ReinitializeKeyBehavior::Warning));
|
||||
connect(ui.action_About, &QAction::triggered, this, &GMainWindow::OnAbout);
|
||||
}
|
||||
|
||||
@@ -485,6 +491,8 @@ QStringList GMainWindow::GetUnsupportedGLExtensions() {
|
||||
unsupported_ext.append("ARB_texture_storage");
|
||||
if (!GLAD_GL_ARB_multi_bind)
|
||||
unsupported_ext.append("ARB_multi_bind");
|
||||
if (!GLAD_GL_ARB_copy_image)
|
||||
unsupported_ext.append("ARB_copy_image");
|
||||
|
||||
// Extensions required to support some texture formats.
|
||||
if (!GLAD_GL_EXT_texture_compression_s3tc)
|
||||
@@ -1373,6 +1381,82 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
|
||||
}
|
||||
}
|
||||
|
||||
void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
|
||||
if (behavior == ReinitializeKeyBehavior::Warning) {
|
||||
const auto res = QMessageBox::information(
|
||||
this, tr("Confirm Key Rederivation"),
|
||||
tr("You are about to force rederive all of your keys. \nIf you do not know what this "
|
||||
"means or what you are doing, \nthis is a potentially destructive action. \nPlease "
|
||||
"make "
|
||||
"sure this is what you want \nand optionally make backups.\n\nThis will delete your "
|
||||
"autogenerated key files and re-run the key derivation module."),
|
||||
QMessageBox::StandardButtons{QMessageBox::Ok, QMessageBox::Cancel});
|
||||
|
||||
if (res == QMessageBox::Cancel)
|
||||
return;
|
||||
|
||||
FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) +
|
||||
"prod.keys_autogenerated");
|
||||
FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) +
|
||||
"console.keys_autogenerated");
|
||||
FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) +
|
||||
"title.keys_autogenerated");
|
||||
}
|
||||
|
||||
Core::Crypto::KeyManager keys{};
|
||||
if (keys.BaseDeriveNecessary()) {
|
||||
Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory(
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), FileSys::Mode::Read)};
|
||||
|
||||
const auto function = [this, &keys, &pdm] {
|
||||
keys.PopulateFromPartitionData(pdm);
|
||||
Service::FileSystem::CreateFactories(vfs);
|
||||
keys.DeriveETicket(pdm);
|
||||
};
|
||||
|
||||
QString errors;
|
||||
|
||||
if (!pdm.HasFuses())
|
||||
errors += tr("- Missing fuses - Cannot derive SBK\n");
|
||||
if (!pdm.HasBoot0())
|
||||
errors += tr("- Missing BOOT0 - Cannot derive master keys\n");
|
||||
if (!pdm.HasPackage2())
|
||||
errors += tr("- Missing BCPKG2-1-Normal-Main - Cannot derive general keys\n");
|
||||
if (!pdm.HasProdInfo())
|
||||
errors += tr("- Missing PRODINFO - Cannot derive title keys\n");
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
|
||||
QMessageBox::warning(
|
||||
this, tr("Warning Missing Derivation Components"),
|
||||
tr("The following are missing from your configuration that may hinder key "
|
||||
"derivation. It will be attempted but may not complete.\n\n") +
|
||||
errors);
|
||||
}
|
||||
|
||||
QProgressDialog prog;
|
||||
prog.setRange(0, 0);
|
||||
prog.setLabelText(tr("Deriving keys...\nThis may take up to a minute depending \non your "
|
||||
"system's performance."));
|
||||
prog.setWindowTitle(tr("Deriving Keys"));
|
||||
|
||||
prog.show();
|
||||
|
||||
auto future = QtConcurrent::run(function);
|
||||
while (!future.isFinished()) {
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
prog.close();
|
||||
}
|
||||
|
||||
Service::FileSystem::CreateFactories(vfs);
|
||||
|
||||
if (behavior == ReinitializeKeyBehavior::Warning) {
|
||||
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
|
||||
}
|
||||
}
|
||||
|
||||
bool GMainWindow::ConfirmClose() {
|
||||
if (emu_thread == nullptr || !UISettings::values.confirm_before_closing)
|
||||
return true;
|
||||
|
||||
@@ -41,6 +41,11 @@ enum class EmulatedDirectoryTarget {
|
||||
SDMC,
|
||||
};
|
||||
|
||||
enum class ReinitializeKeyBehavior {
|
||||
NoWarning,
|
||||
Warning,
|
||||
};
|
||||
|
||||
namespace DiscordRPC {
|
||||
class DiscordInterface;
|
||||
}
|
||||
@@ -167,6 +172,7 @@ private slots:
|
||||
void HideFullscreen();
|
||||
void ToggleWindowMode();
|
||||
void OnCoreError(Core::System::ResultStatus, std::string);
|
||||
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
|
||||
|
||||
private:
|
||||
void UpdateStatusBar();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user