Compare commits
114 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7487bf220 | ||
|
|
9683918928 | ||
|
|
4d7e1662c8 | ||
|
|
eb4f2d5596 | ||
|
|
d8ba202070 | ||
|
|
72e4499a9e | ||
|
|
2e7dc4cac9 | ||
|
|
45fb74d262 | ||
|
|
6771a18c6c | ||
|
|
0d2435343a | ||
|
|
f1bc62bb4c | ||
|
|
4a56931703 | ||
|
|
ffe2336136 | ||
|
|
b4ac8218d0 | ||
|
|
9a6bfc55f3 | ||
|
|
a409d49bbd | ||
|
|
b55d8111e6 | ||
|
|
a0e1566dc5 | ||
|
|
382852418b | ||
|
|
2f5ed3877c | ||
|
|
90fd03015a | ||
|
|
2562fe4a16 | ||
|
|
62edc01525 | ||
|
|
5d2043598e | ||
|
|
c6024379a4 | ||
|
|
d3934d7da7 | ||
|
|
887a9c5c29 | ||
|
|
af59d4bff0 | ||
|
|
f96ded9815 | ||
|
|
8c66a5a9a5 | ||
|
|
34a447d24e | ||
|
|
8d86747514 | ||
|
|
43a2598e26 | ||
|
|
d26a46feed | ||
|
|
be2f1eabd7 | ||
|
|
23b86fd3ea | ||
|
|
f708207ae6 | ||
|
|
bfb0c87b7b | ||
|
|
81ca46dd17 | ||
|
|
b8be5524bc | ||
|
|
2fd45093f2 | ||
|
|
f170159fde | ||
|
|
e81354ae38 | ||
|
|
6426b0f551 | ||
|
|
6314a799aa | ||
|
|
43e0d865fa | ||
|
|
c65713832c | ||
|
|
1e6a209649 | ||
|
|
b6425c0511 | ||
|
|
20800f2df7 | ||
|
|
f09da5d1c9 | ||
|
|
8492ec1669 | ||
|
|
c74b7ee204 | ||
|
|
36093a3e4d | ||
|
|
ec59e4a6c5 | ||
|
|
8fd9eb71b4 | ||
|
|
018c25e123 | ||
|
|
f6f5c2e4d8 | ||
|
|
165c23c848 | ||
|
|
d1a6dd61d1 | ||
|
|
4f18c17df7 | ||
|
|
5049ca5d8c | ||
|
|
ba2972bc64 | ||
|
|
06487c2c8d | ||
|
|
67fa51ea2f | ||
|
|
78b109d195 | ||
|
|
0dce6d7008 | ||
|
|
3ed0115e36 | ||
|
|
ccfd176382 | ||
|
|
119ab308b5 | ||
|
|
a7e8d10969 | ||
|
|
42dc856ce1 | ||
|
|
61a5b56abd | ||
|
|
f26fc64cb4 | ||
|
|
cde665c565 | ||
|
|
60b7a3b904 | ||
|
|
ab44192ab0 | ||
|
|
8b52d6682a | ||
|
|
13524578b6 | ||
|
|
4112dd6b4e | ||
|
|
bf33f80fae | ||
|
|
ef3768f323 | ||
|
|
410062031b | ||
|
|
b247e0cab0 | ||
|
|
2164702cf7 | ||
|
|
c4845df3d4 | ||
|
|
10e5356e9a | ||
|
|
6dd369ab88 | ||
|
|
a9dc5a3c10 | ||
|
|
d65f079cc1 | ||
|
|
fee8bdd90c | ||
|
|
fde2017a3f | ||
|
|
ebf5768340 | ||
|
|
a4ac3bed6c | ||
|
|
da3da6be90 | ||
|
|
2a472ff54d | ||
|
|
c4ed0b16b1 | ||
|
|
c7f2fb2151 | ||
|
|
232b0d9d2a | ||
|
|
eccc77a8c8 | ||
|
|
74e08b4800 | ||
|
|
a1bdc597e9 | ||
|
|
c5ea6db02d | ||
|
|
c7c4e6dcba | ||
|
|
c5c0da41b4 | ||
|
|
f835349364 | ||
|
|
12ba80a86c | ||
|
|
1fd979f50a | ||
|
|
b2ca8089ce | ||
|
|
e70a3c5a5d | ||
|
|
d1b1c42c07 | ||
|
|
4877e6c2f6 | ||
|
|
8e8326595f | ||
|
|
8ce02d85e9 |
2
externals/boost
vendored
2
externals/boost
vendored
Submodule externals/boost updated: d80e506e17...0b920df1c9
2
externals/fmt
vendored
2
externals/fmt
vendored
Submodule externals/fmt updated: c2ce7e4f07...62010520ed
@@ -764,7 +764,7 @@ size_t ReadFileToString(bool text_file, const char* filename, std::string& str)
|
||||
IOFile file(filename, text_file ? "r" : "rb");
|
||||
|
||||
if (!file.IsOpen())
|
||||
return false;
|
||||
return 0;
|
||||
|
||||
str.resize(static_cast<u32>(file.GetSize()));
|
||||
return file.ReadArray(&str[0], str.size());
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
@@ -13,18 +14,29 @@ u8 ToHexNibble(char c1) {
|
||||
return c1 - 87;
|
||||
if (c1 >= 48 && c1 <= 57)
|
||||
return c1 - 48;
|
||||
throw std::logic_error("Invalid hex digit");
|
||||
LOG_ERROR(Common, "Invalid hex digit: 0x{:02X}", c1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::array<u8, 16> operator""_array16(const char* str, size_t len) {
|
||||
if (len != 32)
|
||||
throw std::logic_error("Not of correct size.");
|
||||
if (len != 32) {
|
||||
LOG_ERROR(Common,
|
||||
"Attempting to parse string to array that is not of correct size (expected=32, "
|
||||
"actual={}).",
|
||||
len);
|
||||
return {};
|
||||
}
|
||||
return HexStringToArray<16>(str);
|
||||
}
|
||||
|
||||
std::array<u8, 32> operator""_array32(const char* str, size_t len) {
|
||||
if (len != 64)
|
||||
throw std::logic_error("Not of correct size.");
|
||||
if (len != 64) {
|
||||
LOG_ERROR(Common,
|
||||
"Attempting to parse string to array that is not of correct size (expected=64, "
|
||||
"actual={}).",
|
||||
len);
|
||||
return {};
|
||||
}
|
||||
return HexStringToArray<32>(str);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ add_library(core STATIC
|
||||
crypto/key_manager.h
|
||||
crypto/ctr_encryption_layer.cpp
|
||||
crypto/ctr_encryption_layer.h
|
||||
crypto/xts_encryption_layer.cpp
|
||||
crypto/xts_encryption_layer.h
|
||||
file_sys/bis_factory.cpp
|
||||
file_sys/bis_factory.h
|
||||
file_sys/card_image.cpp
|
||||
@@ -57,6 +59,8 @@ add_library(core STATIC
|
||||
file_sys/vfs_real.h
|
||||
file_sys/vfs_vector.cpp
|
||||
file_sys/vfs_vector.h
|
||||
file_sys/xts_archive.cpp
|
||||
file_sys/xts_archive.h
|
||||
frontend/emu_window.cpp
|
||||
frontend/emu_window.h
|
||||
frontend/framebuffer_layout.cpp
|
||||
@@ -347,6 +351,8 @@ add_library(core STATIC
|
||||
loader/linker.h
|
||||
loader/loader.cpp
|
||||
loader/loader.h
|
||||
loader/nax.cpp
|
||||
loader/nax.h
|
||||
loader/nca.cpp
|
||||
loader/nca.h
|
||||
loader/nro.cpp
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
/// Generic ARM11 CPU interface
|
||||
class ARM_Interface : NonCopyable {
|
||||
public:
|
||||
@@ -122,3 +124,5 @@ public:
|
||||
/// Prepare core for thread reschedule (if needed to correctly handle state)
|
||||
virtual void PrepareReschedule() = 0;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include "core/hle/kernel/svc.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
using Vector = Dynarmic::A64::Vector;
|
||||
|
||||
class ARM_Dynarmic_Callbacks : public Dynarmic::A64::UserCallbacks {
|
||||
@@ -300,3 +302,5 @@ bool DynarmicExclusiveMonitor::ExclusiveWrite128(size_t core_index, VAddr vaddr,
|
||||
Memory::Write64(vaddr, value[1]);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/arm/unicorn/arm_unicorn.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ARM_Dynarmic_Callbacks;
|
||||
class DynarmicExclusiveMonitor;
|
||||
|
||||
@@ -81,3 +83,5 @@ private:
|
||||
friend class ARM_Dynarmic;
|
||||
Dynarmic::A64::ExclusiveMonitor monitor;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -4,4 +4,8 @@
|
||||
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
ExclusiveMonitor::~ExclusiveMonitor() = default;
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ExclusiveMonitor {
|
||||
public:
|
||||
virtual ~ExclusiveMonitor();
|
||||
@@ -19,3 +21,5 @@ public:
|
||||
virtual bool ExclusiveWrite64(size_t core_index, VAddr vaddr, u64 value) = 0;
|
||||
virtual bool ExclusiveWrite128(size_t core_index, VAddr vaddr, u128 value) = 0;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
// Load Unicorn DLL once on Windows using RAII
|
||||
#ifdef _MSC_VER
|
||||
#include <unicorn_dynload.h>
|
||||
@@ -211,7 +213,7 @@ void ARM_Unicorn::ExecuteInstructions(int num_instructions) {
|
||||
}
|
||||
}
|
||||
|
||||
void ARM_Unicorn::SaveContext(ARM_Interface::ThreadContext& ctx) {
|
||||
void ARM_Unicorn::SaveContext(ThreadContext& ctx) {
|
||||
int uregs[32];
|
||||
void* tregs[32];
|
||||
|
||||
@@ -238,7 +240,7 @@ void ARM_Unicorn::SaveContext(ARM_Interface::ThreadContext& ctx) {
|
||||
CHECKED(uc_reg_read_batch(uc, uregs, tregs, 32));
|
||||
}
|
||||
|
||||
void ARM_Unicorn::LoadContext(const ARM_Interface::ThreadContext& ctx) {
|
||||
void ARM_Unicorn::LoadContext(const ThreadContext& ctx) {
|
||||
int uregs[32];
|
||||
void* tregs[32];
|
||||
|
||||
@@ -277,3 +279,5 @@ void ARM_Unicorn::RecordBreak(GDBStub::BreakpointAddress bkpt) {
|
||||
last_bkpt = bkpt;
|
||||
last_bkpt_hit = true;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ARM_Unicorn final : public ARM_Interface {
|
||||
public:
|
||||
ARM_Unicorn();
|
||||
@@ -46,3 +48,5 @@ private:
|
||||
GDBStub::BreakpointAddress last_bkpt{};
|
||||
bool last_bkpt_hit;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -135,8 +135,7 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
|
||||
LOG_CRITICAL(Core, "Failed to determine system mode (Error {})!",
|
||||
static_cast<int>(system_mode.second));
|
||||
|
||||
if (system_mode.second != Loader::ResultStatus::Success)
|
||||
return ResultStatus::ErrorSystemMode;
|
||||
return ResultStatus::ErrorSystemMode;
|
||||
}
|
||||
|
||||
ResultStatus init_result{Init(emu_window)};
|
||||
@@ -148,14 +147,12 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
|
||||
}
|
||||
|
||||
const Loader::ResultStatus load_result{app_loader->Load(current_process)};
|
||||
if (Loader::ResultStatus::Success != load_result) {
|
||||
if (load_result != Loader::ResultStatus::Success) {
|
||||
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result));
|
||||
System::Shutdown();
|
||||
|
||||
if (load_result != Loader::ResultStatus::Success) {
|
||||
return static_cast<ResultStatus>(static_cast<u32>(ResultStatus::ErrorLoader) +
|
||||
static_cast<u32>(load_result));
|
||||
}
|
||||
return static_cast<ResultStatus>(static_cast<u32>(ResultStatus::ErrorLoader) +
|
||||
static_cast<u32>(load_result));
|
||||
}
|
||||
status = ResultStatus::Success;
|
||||
return status;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
@@ -22,8 +23,6 @@
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "video_core/gpu.h"
|
||||
|
||||
class ARM_Interface;
|
||||
|
||||
namespace Core::Frontend {
|
||||
class EmuWindow;
|
||||
}
|
||||
@@ -38,6 +37,8 @@ class RendererBase;
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ARM_Interface;
|
||||
|
||||
class System {
|
||||
public:
|
||||
System(const System&) = delete;
|
||||
@@ -187,6 +188,13 @@ public:
|
||||
return current_process;
|
||||
}
|
||||
|
||||
/// Gets the name of the current game
|
||||
Loader::ResultStatus GetGameName(std::string& out) const {
|
||||
if (app_loader == nullptr)
|
||||
return Loader::ResultStatus::ErrorNotInitialized;
|
||||
return app_loader->ReadTitle(out);
|
||||
}
|
||||
|
||||
PerfStats perf_stats;
|
||||
FrameLimiter frame_limiter;
|
||||
|
||||
|
||||
@@ -12,14 +12,14 @@
|
||||
#include "common/common_types.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
|
||||
class ARM_Interface;
|
||||
|
||||
namespace Kernel {
|
||||
class Scheduler;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ARM_Interface;
|
||||
|
||||
constexpr unsigned NUM_CPU_CORES{4};
|
||||
|
||||
class CpuBarrier {
|
||||
|
||||
@@ -99,10 +99,7 @@ void AESCipher<Key, KeySize>::Transcode(const u8* src, size_t size, u8* dest, Op
|
||||
template <typename Key, size_t KeySize>
|
||||
void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id,
|
||||
size_t sector_size, Op op) {
|
||||
if (size % sector_size > 0) {
|
||||
LOG_CRITICAL(Crypto, "Data size must be a multiple of sector size.");
|
||||
return;
|
||||
}
|
||||
ASSERT_MSG(size % sector_size == 0, "XTS decryption size must be a multiple of sector size.");
|
||||
|
||||
for (size_t i = 0; i < size; i += sector_size) {
|
||||
SetIV(CalculateNintendoTweak(sector_id++));
|
||||
@@ -112,4 +109,4 @@ void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, size_t size, u8* dest,
|
||||
|
||||
template class AESCipher<Key128>;
|
||||
template class AESCipher<Key256>;
|
||||
} // namespace Core::Crypto
|
||||
} // namespace Core::Crypto
|
||||
|
||||
@@ -20,10 +20,8 @@ size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const {
|
||||
if (sector_offset == 0) {
|
||||
UpdateIV(base_offset + offset);
|
||||
std::vector<u8> raw = base->ReadBytes(length, offset);
|
||||
if (raw.size() != length)
|
||||
return Read(data, raw.size(), offset);
|
||||
cipher.Transcode(raw.data(), length, data, Op::Decrypt);
|
||||
return length;
|
||||
cipher.Transcode(raw.data(), raw.size(), data, Op::Decrypt);
|
||||
return raw.size();
|
||||
}
|
||||
|
||||
// offset does not fall on block boundary (0x10)
|
||||
@@ -34,7 +32,7 @@ size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const {
|
||||
|
||||
if (length + sector_offset < 0x10) {
|
||||
std::memcpy(data, block.data() + sector_offset, std::min<u64>(length, read));
|
||||
return read;
|
||||
return std::min<u64>(length, read);
|
||||
}
|
||||
std::memcpy(data, block.data() + sector_offset, read);
|
||||
return read + Read(data + read, length - read, offset + read);
|
||||
|
||||
@@ -12,11 +12,112 @@
|
||||
#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/settings.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) {
|
||||
Key128 out{};
|
||||
|
||||
AESCipher<Key128> cipher1(master, Mode::ECB);
|
||||
cipher1.Transcode(kek_seed.data(), kek_seed.size(), out.data(), Op::Decrypt);
|
||||
AESCipher<Key128> cipher2(out, Mode::ECB);
|
||||
cipher2.Transcode(source.data(), source.size(), out.data(), Op::Decrypt);
|
||||
|
||||
if (key_seed != Key128{}) {
|
||||
AESCipher<Key128> cipher3(out, Mode::ECB);
|
||||
cipher3.Transcode(key_seed.data(), key_seed.size(), out.data(), Op::Decrypt);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
boost::optional<Key128> DeriveSDSeed() {
|
||||
const FileUtil::IOFile save_43(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
|
||||
"/system/save/8000000000000043",
|
||||
"rb+");
|
||||
if (!save_43.IsOpen())
|
||||
return boost::none;
|
||||
const FileUtil::IOFile sd_private(
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "/Nintendo/Contents/private", "rb+");
|
||||
if (!sd_private.IsOpen())
|
||||
return boost::none;
|
||||
|
||||
sd_private.Seek(0, SEEK_SET);
|
||||
std::array<u8, 0x10> private_seed{};
|
||||
if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != 0x10)
|
||||
return boost::none;
|
||||
|
||||
std::array<u8, 0x10> buffer{};
|
||||
size_t offset = 0;
|
||||
for (; offset + 0x10 < save_43.GetSize(); ++offset) {
|
||||
save_43.Seek(offset, SEEK_SET);
|
||||
save_43.ReadBytes(buffer.data(), buffer.size());
|
||||
if (buffer == private_seed)
|
||||
break;
|
||||
}
|
||||
|
||||
if (offset + 0x10 >= save_43.GetSize())
|
||||
return boost::none;
|
||||
|
||||
Key128 seed{};
|
||||
save_43.Seek(offset + 0x10, SEEK_SET);
|
||||
save_43.ReadBytes(seed.data(), seed.size());
|
||||
return seed;
|
||||
}
|
||||
|
||||
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const 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)))
|
||||
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));
|
||||
const auto aes_kek_gen =
|
||||
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);
|
||||
|
||||
if (!keys.HasKey(S128KeyType::SDSeed))
|
||||
return Loader::ResultStatus::ErrorMissingSDSeed;
|
||||
const auto sd_seed = keys.GetKey(S128KeyType::SDSeed);
|
||||
|
||||
if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save)))
|
||||
return Loader::ResultStatus::ErrorMissingSDSaveKeySource;
|
||||
if (!keys.HasKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA)))
|
||||
return Loader::ResultStatus::ErrorMissingSDNCAKeySource;
|
||||
|
||||
std::array<Key256, 2> sd_key_sources{
|
||||
keys.GetKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save)),
|
||||
keys.GetKey(S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA)),
|
||||
};
|
||||
|
||||
// Combine sources and seed
|
||||
for (auto& source : sd_key_sources) {
|
||||
for (size_t i = 0; i < source.size(); ++i)
|
||||
source[i] ^= sd_seed[i & 0xF];
|
||||
}
|
||||
|
||||
AESCipher<Key128> cipher(sd_kek, Mode::ECB);
|
||||
// The transform manipulates sd_keys as part of the Transcode, so the return/output is
|
||||
// unnecessary. This does not alter sd_keys_sources.
|
||||
std::transform(sd_key_sources.begin(), sd_key_sources.end(), sd_keys.begin(),
|
||||
sd_key_sources.begin(), [&cipher](const Key256& source, Key256& out) {
|
||||
cipher.Transcode(source.data(), source.size(), out.data(), Op::Decrypt);
|
||||
return source; ///< Return unaltered source to satisfy output requirement.
|
||||
});
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
KeyManager::KeyManager() {
|
||||
// Initialize keys
|
||||
const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
|
||||
@@ -24,12 +125,15 @@ KeyManager::KeyManager() {
|
||||
if (Settings::values.use_dev_keys) {
|
||||
dev_mode = true;
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "dev.keys", false);
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "dev.keys_autogenerated", false);
|
||||
} else {
|
||||
dev_mode = false;
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "prod.keys", false);
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "prod.keys_autogenerated", false);
|
||||
}
|
||||
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true);
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "title.keys_autogenerated", true);
|
||||
}
|
||||
|
||||
void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
|
||||
@@ -56,17 +160,17 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
|
||||
u128 rights_id{};
|
||||
std::memcpy(rights_id.data(), rights_id_raw.data(), rights_id_raw.size());
|
||||
Key128 key = Common::HexStringToArray<16>(out[1]);
|
||||
SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
|
||||
s128_keys[{S128KeyType::Titlekey, rights_id[1], rights_id[0]}] = key;
|
||||
} else {
|
||||
std::transform(out[0].begin(), out[0].end(), out[0].begin(), ::tolower);
|
||||
if (s128_file_id.find(out[0]) != s128_file_id.end()) {
|
||||
const auto index = s128_file_id.at(out[0]);
|
||||
Key128 key = Common::HexStringToArray<16>(out[1]);
|
||||
SetKey(index.type, key, index.field1, index.field2);
|
||||
s128_keys[{index.type, index.field1, index.field2}] = key;
|
||||
} else if (s256_file_id.find(out[0]) != s256_file_id.end()) {
|
||||
const auto index = s256_file_id.at(out[0]);
|
||||
Key256 key = Common::HexStringToArray<32>(out[1]);
|
||||
SetKey(index.type, key, index.field1, index.field2);
|
||||
s256_keys[{index.type, index.field1, index.field2}] = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,11 +204,50 @@ Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const {
|
||||
return s256_keys.at({id, field1, field2});
|
||||
}
|
||||
|
||||
template <size_t Size>
|
||||
void KeyManager::WriteKeyToFile(bool title_key, 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)
|
||||
filename = dev_mode ? "dev.keys_autogenerated" : "prod.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);
|
||||
if (!file.is_open())
|
||||
return;
|
||||
if (add_info_text) {
|
||||
file
|
||||
<< "# This file is autogenerated by Yuzu\n"
|
||||
<< "# It serves to store keys that were automatically generated from the normal keys\n"
|
||||
<< "# If you are experiencing issues involving keys, it may help to delete this file\n";
|
||||
}
|
||||
|
||||
file << fmt::format("\n{} = {}", keyname, Common::HexArrayToString(key));
|
||||
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, title_key);
|
||||
}
|
||||
|
||||
void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
|
||||
const auto iter = std::find_if(
|
||||
s128_file_id.begin(), s128_file_id.end(),
|
||||
[&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) {
|
||||
return std::tie(elem.second.type, elem.second.field1, elem.second.field2) ==
|
||||
std::tie(id, field1, field2);
|
||||
});
|
||||
if (iter != s128_file_id.end())
|
||||
WriteKeyToFile(id == S128KeyType::Titlekey, iter->first, key);
|
||||
s128_keys[{id, field1, field2}] = key;
|
||||
}
|
||||
|
||||
void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
|
||||
const auto iter = std::find_if(
|
||||
s256_file_id.begin(), s256_file_id.end(),
|
||||
[&id, &field1, &field2](const std::pair<std::string, KeyIndex<S256KeyType>> elem) {
|
||||
return std::tie(elem.second.type, elem.second.field1, elem.second.field2) ==
|
||||
std::tie(id, field1, field2);
|
||||
});
|
||||
if (iter != s256_file_id.end())
|
||||
WriteKeyToFile(false, iter->first, key);
|
||||
s256_keys[{id, field1, field2}] = key;
|
||||
}
|
||||
|
||||
@@ -125,7 +268,16 @@ bool KeyManager::KeyFileExists(bool title) {
|
||||
FileUtil::Exists(yuzu_keys_dir + DIR_SEP + "prod.keys");
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
|
||||
void KeyManager::DeriveSDSeedLazy() {
|
||||
if (HasKey(S128KeyType::SDSeed))
|
||||
return;
|
||||
|
||||
const auto res = DeriveSDSeed();
|
||||
if (res != boost::none)
|
||||
SetKey(S128KeyType::SDSeed, res.get());
|
||||
}
|
||||
|
||||
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}},
|
||||
@@ -167,11 +319,17 @@ const std::unordered_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_fi
|
||||
{"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}},
|
||||
{"aes_kek_generation_source",
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration), 0}},
|
||||
{"aes_key_generation_source",
|
||||
{S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration), 0}},
|
||||
{"sd_seed", {S128KeyType::SDSeed, 0, 0}},
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {
|
||||
const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {
|
||||
{"header_key", {S256KeyType::Header, 0, 0}},
|
||||
{"sd_card_save_key", {S256KeyType::SDSave, 0, 0}},
|
||||
{"sd_card_nca_key", {S256KeyType::SDNCA, 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}},
|
||||
};
|
||||
} // namespace Core::Crypto
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <fmt/format.h>
|
||||
#include "common/common_types.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
@@ -22,9 +24,8 @@ static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
|
||||
static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big.");
|
||||
|
||||
enum class S256KeyType : u64 {
|
||||
Header, //
|
||||
SDSave, //
|
||||
SDNCA, //
|
||||
Header, //
|
||||
SDKeySource, // f1=SDKeyType
|
||||
};
|
||||
|
||||
enum class S128KeyType : u64 {
|
||||
@@ -36,6 +37,7 @@ enum class S128KeyType : u64 {
|
||||
KeyArea, // f1=crypto revision f2=type {app, ocean, system}
|
||||
SDSeed, //
|
||||
Titlekey, // f1=rights id LSB f2=rights id MSB
|
||||
Source, // f1=source type, f2= sub id
|
||||
};
|
||||
|
||||
enum class KeyAreaKeyType : u8 {
|
||||
@@ -44,6 +46,17 @@ enum class KeyAreaKeyType : u8 {
|
||||
System,
|
||||
};
|
||||
|
||||
enum class SourceKeyType : u8 {
|
||||
SDKEK,
|
||||
AESKEKGeneration,
|
||||
AESKeyGeneration,
|
||||
};
|
||||
|
||||
enum class SDKeyType : u8 {
|
||||
Save,
|
||||
NCA,
|
||||
};
|
||||
|
||||
template <typename KeyType>
|
||||
struct KeyIndex {
|
||||
KeyType type;
|
||||
@@ -59,34 +72,12 @@ struct KeyIndex {
|
||||
}
|
||||
};
|
||||
|
||||
// The following two (== and hash) are so KeyIndex can be a key in unordered_map
|
||||
|
||||
// boost flat_map requires operator< for O(log(n)) lookups.
|
||||
template <typename KeyType>
|
||||
bool operator==(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) {
|
||||
return std::tie(lhs.type, lhs.field1, lhs.field2) == std::tie(rhs.type, rhs.field1, rhs.field2);
|
||||
bool operator<(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) {
|
||||
return std::tie(lhs.type, lhs.field1, lhs.field2) < std::tie(rhs.type, rhs.field1, rhs.field2);
|
||||
}
|
||||
|
||||
template <typename KeyType>
|
||||
bool operator!=(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) {
|
||||
return !operator==(lhs, rhs);
|
||||
}
|
||||
|
||||
} // namespace Core::Crypto
|
||||
|
||||
namespace std {
|
||||
template <typename KeyType>
|
||||
struct hash<Core::Crypto::KeyIndex<KeyType>> {
|
||||
size_t operator()(const Core::Crypto::KeyIndex<KeyType>& k) const {
|
||||
using std::hash;
|
||||
|
||||
return ((hash<u64>()(static_cast<u64>(k.type)) ^ (hash<u64>()(k.field1) << 1)) >> 1) ^
|
||||
(hash<u64>()(k.field2) << 1);
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
class KeyManager {
|
||||
public:
|
||||
KeyManager();
|
||||
@@ -102,16 +93,27 @@ public:
|
||||
|
||||
static bool KeyFileExists(bool title);
|
||||
|
||||
// Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system save
|
||||
// 8*43 and the private file to exist.
|
||||
void DeriveSDSeedLazy();
|
||||
|
||||
private:
|
||||
std::unordered_map<KeyIndex<S128KeyType>, Key128> s128_keys;
|
||||
std::unordered_map<KeyIndex<S256KeyType>, Key256> s256_keys;
|
||||
boost::container::flat_map<KeyIndex<S128KeyType>, Key128> s128_keys;
|
||||
boost::container::flat_map<KeyIndex<S256KeyType>, Key256> s256_keys;
|
||||
|
||||
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 <size_t Size>
|
||||
void WriteKeyToFile(bool title_key, std::string_view keyname, const std::array<u8, Size>& key);
|
||||
|
||||
static const std::unordered_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
|
||||
static const std::unordered_map<std::string, KeyIndex<S256KeyType>> s256_file_id;
|
||||
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);
|
||||
boost::optional<Key128> DeriveSDSeed();
|
||||
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys);
|
||||
|
||||
} // namespace Core::Crypto
|
||||
|
||||
58
src/core/crypto/xts_encryption_layer.cpp
Normal file
58
src/core/crypto/xts_encryption_layer.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include "common/assert.h"
|
||||
#include "core/crypto/xts_encryption_layer.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
constexpr u64 XTS_SECTOR_SIZE = 0x4000;
|
||||
|
||||
XTSEncryptionLayer::XTSEncryptionLayer(FileSys::VirtualFile base_, Key256 key_)
|
||||
: EncryptionLayer(std::move(base_)), cipher(key_, Mode::XTS) {}
|
||||
|
||||
size_t XTSEncryptionLayer::Read(u8* data, size_t length, size_t offset) const {
|
||||
if (length == 0)
|
||||
return 0;
|
||||
|
||||
const auto sector_offset = offset & 0x3FFF;
|
||||
if (sector_offset == 0) {
|
||||
if (length % XTS_SECTOR_SIZE == 0) {
|
||||
std::vector<u8> raw = base->ReadBytes(length, offset);
|
||||
cipher.XTSTranscode(raw.data(), raw.size(), data, offset / XTS_SECTOR_SIZE,
|
||||
XTS_SECTOR_SIZE, Op::Decrypt);
|
||||
return raw.size();
|
||||
}
|
||||
if (length > XTS_SECTOR_SIZE) {
|
||||
const auto rem = length % XTS_SECTOR_SIZE;
|
||||
const auto read = length - rem;
|
||||
return Read(data, read, offset) + Read(data + read, rem, offset + read);
|
||||
}
|
||||
std::vector<u8> buffer = base->ReadBytes(XTS_SECTOR_SIZE, offset);
|
||||
if (buffer.size() < XTS_SECTOR_SIZE)
|
||||
buffer.resize(XTS_SECTOR_SIZE);
|
||||
cipher.XTSTranscode(buffer.data(), buffer.size(), buffer.data(), offset / XTS_SECTOR_SIZE,
|
||||
XTS_SECTOR_SIZE, Op::Decrypt);
|
||||
std::memcpy(data, buffer.data(), std::min(buffer.size(), length));
|
||||
return std::min(buffer.size(), length);
|
||||
}
|
||||
|
||||
// offset does not fall on block boundary (0x4000)
|
||||
std::vector<u8> block = base->ReadBytes(0x4000, offset - sector_offset);
|
||||
if (block.size() < XTS_SECTOR_SIZE)
|
||||
block.resize(XTS_SECTOR_SIZE);
|
||||
cipher.XTSTranscode(block.data(), block.size(), block.data(),
|
||||
(offset - sector_offset) / XTS_SECTOR_SIZE, XTS_SECTOR_SIZE, Op::Decrypt);
|
||||
const size_t read = XTS_SECTOR_SIZE - sector_offset;
|
||||
|
||||
if (length + sector_offset < XTS_SECTOR_SIZE) {
|
||||
std::memcpy(data, block.data() + sector_offset, std::min<u64>(length, read));
|
||||
return std::min<u64>(length, read);
|
||||
}
|
||||
std::memcpy(data, block.data() + sector_offset, read);
|
||||
return read + Read(data + read, length - read, offset + read);
|
||||
}
|
||||
} // namespace Core::Crypto
|
||||
25
src/core/crypto/xts_encryption_layer.h
Normal file
25
src/core/crypto/xts_encryption_layer.h
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/encryption_layer.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
|
||||
namespace Core::Crypto {
|
||||
|
||||
// Sits on top of a VirtualFile and provides XTS-mode AES decription.
|
||||
class XTSEncryptionLayer : public EncryptionLayer {
|
||||
public:
|
||||
XTSEncryptionLayer(FileSys::VirtualFile base, Key256 key);
|
||||
|
||||
size_t Read(u8* data, size_t length, size_t offset) const override;
|
||||
|
||||
private:
|
||||
// Must be mutable as operations modify cipher contexts.
|
||||
mutable AESCipher<Key256> cipher;
|
||||
};
|
||||
|
||||
} // namespace Core::Crypto
|
||||
@@ -6,19 +6,12 @@
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
static VirtualDir GetOrCreateDirectory(const VirtualDir& dir, std::string_view path) {
|
||||
const auto res = dir->GetDirectoryRelative(path);
|
||||
if (res == nullptr)
|
||||
return dir->CreateDirectoryRelative(path);
|
||||
return res;
|
||||
}
|
||||
|
||||
BISFactory::BISFactory(VirtualDir nand_root_)
|
||||
: nand_root(std::move(nand_root_)),
|
||||
sysnand_cache(std::make_shared<RegisteredCache>(
|
||||
GetOrCreateDirectory(nand_root, "/system/Contents/registered"))),
|
||||
GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))),
|
||||
usrnand_cache(std::make_shared<RegisteredCache>(
|
||||
GetOrCreateDirectory(nand_root, "/user/Contents/registered"))) {}
|
||||
GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))) {}
|
||||
|
||||
std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const {
|
||||
return sysnand_cache;
|
||||
|
||||
@@ -43,6 +43,8 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) {
|
||||
partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw);
|
||||
}
|
||||
|
||||
program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA;
|
||||
|
||||
auto result = AddNCAFromPartition(XCIPartition::Secure);
|
||||
if (result != Loader::ResultStatus::Success) {
|
||||
status = result;
|
||||
@@ -76,6 +78,10 @@ Loader::ResultStatus XCI::GetStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
Loader::ResultStatus XCI::GetProgramNCAStatus() const {
|
||||
return program_nca_status;
|
||||
}
|
||||
|
||||
VirtualDir XCI::GetPartition(XCIPartition partition) const {
|
||||
return partitions[static_cast<size_t>(partition)];
|
||||
}
|
||||
@@ -143,6 +149,12 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
|
||||
if (file->GetExtension() != "nca")
|
||||
continue;
|
||||
auto nca = std::make_shared<NCA>(file);
|
||||
// TODO(DarkLordZach): Add proper Rev1+ Support
|
||||
if (nca->IsUpdate())
|
||||
continue;
|
||||
if (nca->GetType() == NCAContentType::Program) {
|
||||
program_nca_status = nca->GetStatus();
|
||||
}
|
||||
if (nca->GetStatus() == Loader::ResultStatus::Success) {
|
||||
ncas.push_back(std::move(nca));
|
||||
} else {
|
||||
|
||||
@@ -59,6 +59,7 @@ public:
|
||||
explicit XCI(VirtualFile file);
|
||||
|
||||
Loader::ResultStatus GetStatus() const;
|
||||
Loader::ResultStatus GetProgramNCAStatus() const;
|
||||
|
||||
u8 GetFormatVersion() const;
|
||||
|
||||
@@ -90,6 +91,7 @@ private:
|
||||
GamecardHeader header{};
|
||||
|
||||
Loader::ResultStatus status;
|
||||
Loader::ResultStatus program_nca_status;
|
||||
|
||||
std::vector<VirtualDir> partitions;
|
||||
std::vector<std::shared_ptr<NCA>> ncas;
|
||||
|
||||
@@ -178,7 +178,7 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
|
||||
return std::static_pointer_cast<VfsFile>(out);
|
||||
}
|
||||
case NCASectionCryptoType::XTS:
|
||||
// TODO(DarkLordZach): Implement XTSEncryptionLayer.
|
||||
// TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs
|
||||
default:
|
||||
LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}",
|
||||
static_cast<u8>(s_header.raw.header.crypto_type));
|
||||
@@ -258,6 +258,10 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
|
||||
file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
|
||||
}
|
||||
|
||||
is_update = std::find_if(sections.begin(), sections.end(), [](const NCASectionHeader& header) {
|
||||
return header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
|
||||
}) != sections.end();
|
||||
|
||||
for (std::ptrdiff_t i = 0; i < number_sections; ++i) {
|
||||
auto section = sections[i];
|
||||
|
||||
@@ -358,6 +362,10 @@ VirtualFile NCA::GetBaseFile() const {
|
||||
return file;
|
||||
}
|
||||
|
||||
bool NCA::IsUpdate() const {
|
||||
return is_update;
|
||||
}
|
||||
|
||||
bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -93,6 +93,8 @@ public:
|
||||
|
||||
VirtualFile GetBaseFile() const;
|
||||
|
||||
bool IsUpdate() const;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
@@ -111,6 +113,7 @@ private:
|
||||
|
||||
NCAHeader header{};
|
||||
bool has_rights_id{};
|
||||
bool is_update{};
|
||||
|
||||
Loader::ResultStatus status{};
|
||||
|
||||
|
||||
@@ -216,11 +216,11 @@ void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) {
|
||||
|
||||
const auto section0 = nca->GetSubdirectories()[0];
|
||||
|
||||
for (const auto& file : section0->GetFiles()) {
|
||||
if (file->GetExtension() != "cnmt")
|
||||
for (const auto& section0_file : section0->GetFiles()) {
|
||||
if (section0_file->GetExtension() != "cnmt")
|
||||
continue;
|
||||
|
||||
meta.insert_or_assign(nca->GetTitleId(), CNMT(file));
|
||||
meta.insert_or_assign(nca->GetTitleId(), CNMT(section0_file));
|
||||
meta_id.insert_or_assign(nca->GetTitleId(), id);
|
||||
break;
|
||||
}
|
||||
@@ -254,6 +254,8 @@ RegisteredCache::RegisteredCache(VirtualDir dir_, RegisteredCacheParsingFunction
|
||||
Refresh();
|
||||
}
|
||||
|
||||
RegisteredCache::~RegisteredCache() = default;
|
||||
|
||||
bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const {
|
||||
return GetEntryRaw(title_id, type) != nullptr;
|
||||
}
|
||||
@@ -262,6 +264,18 @@ bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const {
|
||||
return GetEntryRaw(entry) != nullptr;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
|
||||
const auto id = GetNcaIDFromMetadata(title_id, type);
|
||||
if (id == boost::none)
|
||||
return nullptr;
|
||||
|
||||
return GetFileAtID(id.get());
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::GetEntryUnparsed(RegisteredCacheEntry entry) const {
|
||||
return GetEntryUnparsed(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const {
|
||||
const auto id = GetNcaIDFromMetadata(title_id, type);
|
||||
if (id == boost::none)
|
||||
|
||||
@@ -63,12 +63,16 @@ public:
|
||||
explicit RegisteredCache(VirtualDir dir,
|
||||
RegisteredCacheParsingFunction parsing_function =
|
||||
[](const VirtualFile& file, const NcaID& id) { return file; });
|
||||
~RegisteredCache();
|
||||
|
||||
void Refresh();
|
||||
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const;
|
||||
bool HasEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
|
||||
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
|
||||
|
||||
|
||||
@@ -3,14 +3,27 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
#include "core/file_sys/xts_archive.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
SDMCFactory::SDMCFactory(VirtualDir dir) : dir(std::move(dir)) {}
|
||||
SDMCFactory::SDMCFactory(VirtualDir dir_)
|
||||
: dir(std::move(dir_)), contents(std::make_shared<RegisteredCache>(
|
||||
GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"),
|
||||
[](const VirtualFile& file, const NcaID& id) {
|
||||
return std::make_shared<NAX>(file, id)->GetDecrypted();
|
||||
})) {}
|
||||
|
||||
SDMCFactory::~SDMCFactory() = default;
|
||||
|
||||
ResultVal<VirtualDir> SDMCFactory::Open() {
|
||||
return MakeResult<VirtualDir>(dir);
|
||||
}
|
||||
|
||||
std::shared_ptr<RegisteredCache> SDMCFactory::GetSDMCContents() const {
|
||||
return contents;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -4,20 +4,27 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class RegisteredCache;
|
||||
|
||||
/// File system interface to the SDCard archive
|
||||
class SDMCFactory {
|
||||
public:
|
||||
explicit SDMCFactory(VirtualDir dir);
|
||||
~SDMCFactory();
|
||||
|
||||
ResultVal<VirtualDir> Open();
|
||||
std::shared_ptr<RegisteredCache> GetSDMCContents() const;
|
||||
|
||||
private:
|
||||
VirtualDir dir;
|
||||
|
||||
std::shared_ptr<RegisteredCache> contents;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -462,4 +462,11 @@ bool VfsRawCopy(VirtualFile src, VirtualFile dest) {
|
||||
std::vector<u8> data = src->ReadAllBytes();
|
||||
return dest->WriteBytes(data, 0) == data.size();
|
||||
}
|
||||
|
||||
VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path) {
|
||||
const auto res = rel->GetDirectoryRelative(path);
|
||||
if (res == nullptr)
|
||||
return rel->CreateDirectoryRelative(path);
|
||||
return res;
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -318,4 +318,8 @@ bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block
|
||||
// directory of src/dest.
|
||||
bool VfsRawCopy(VirtualFile src, VirtualFile dest);
|
||||
|
||||
// Checks if the directory at path relative to rel exists. If it does, returns that. If it does not
|
||||
// it attempts to create it and returns the new dir or nullptr on failure.
|
||||
VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path);
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -341,7 +341,6 @@ std::shared_ptr<VfsFile> RealVfsDirectory::CreateFileRelative(std::string_view p
|
||||
|
||||
std::shared_ptr<VfsDirectory> RealVfsDirectory::CreateDirectoryRelative(std::string_view path) {
|
||||
const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
|
||||
auto parent = std::string(FileUtil::GetParentPath(full_path));
|
||||
return base.CreateDirectory(full_path, perms);
|
||||
}
|
||||
|
||||
|
||||
169
src/core/file_sys/xts_archive.cpp
Normal file
169
src/core/file_sys/xts_archive.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <mbedtls/md.h>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/xts_encryption_layer.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
#include "core/file_sys/xts_archive.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr u64 NAX_HEADER_PADDING_SIZE = 0x4000;
|
||||
|
||||
template <typename SourceData, typename SourceKey, typename Destination>
|
||||
static bool CalculateHMAC256(Destination* out, const SourceKey* key, size_t key_length,
|
||||
const SourceData* data, size_t data_length) {
|
||||
mbedtls_md_context_t context;
|
||||
mbedtls_md_init(&context);
|
||||
|
||||
const auto key_f = reinterpret_cast<const u8*>(key);
|
||||
const std::vector<u8> key_v(key_f, key_f + key_length);
|
||||
|
||||
if (mbedtls_md_setup(&context, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1) ||
|
||||
mbedtls_md_hmac_starts(&context, reinterpret_cast<const u8*>(key), key_length) ||
|
||||
mbedtls_md_hmac_update(&context, reinterpret_cast<const u8*>(data), data_length) ||
|
||||
mbedtls_md_hmac_finish(&context, reinterpret_cast<u8*>(out))) {
|
||||
mbedtls_md_free(&context);
|
||||
return false;
|
||||
}
|
||||
|
||||
mbedtls_md_free(&context);
|
||||
return true;
|
||||
}
|
||||
|
||||
NAX::NAX(VirtualFile file_) : file(std::move(file_)), header(std::make_unique<NAXHeader>()) {
|
||||
std::string path = FileUtil::SanitizePath(file->GetFullPath());
|
||||
static const std::regex nax_path_regex("/registered/(000000[0-9A-F]{2})/([0-9A-F]{32})\\.nca",
|
||||
std::regex_constants::ECMAScript |
|
||||
std::regex_constants::icase);
|
||||
std::smatch match;
|
||||
if (!std::regex_search(path, match, nax_path_regex)) {
|
||||
status = Loader::ResultStatus::ErrorBadNAXFilePath;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string two_dir = match[1];
|
||||
std::string nca_id = match[2];
|
||||
std::transform(two_dir.begin(), two_dir.end(), two_dir.begin(), ::toupper);
|
||||
std::transform(nca_id.begin(), nca_id.end(), nca_id.begin(), ::tolower);
|
||||
|
||||
status = Parse(fmt::format("/registered/{}/{}.nca", two_dir, nca_id));
|
||||
}
|
||||
|
||||
NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id)
|
||||
: file(std::move(file_)), header(std::make_unique<NAXHeader>()) {
|
||||
Core::Crypto::SHA256Hash hash{};
|
||||
mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0);
|
||||
status = Parse(fmt::format("/registered/000000{:02X}/{}.nca", hash[0],
|
||||
Common::HexArrayToString(nca_id, false)));
|
||||
}
|
||||
|
||||
Loader::ResultStatus NAX::Parse(std::string_view path) {
|
||||
if (file->ReadObject(header.get()) != sizeof(NAXHeader))
|
||||
return Loader::ResultStatus::ErrorBadNAXHeader;
|
||||
|
||||
if (header->magic != Common::MakeMagic('N', 'A', 'X', '0'))
|
||||
return Loader::ResultStatus::ErrorBadNAXHeader;
|
||||
|
||||
if (file->GetSize() < NAX_HEADER_PADDING_SIZE + header->file_size)
|
||||
return Loader::ResultStatus::ErrorIncorrectNAXFileSize;
|
||||
|
||||
keys.DeriveSDSeedLazy();
|
||||
std::array<Core::Crypto::Key256, 2> sd_keys{};
|
||||
const auto sd_keys_res = Core::Crypto::DeriveSDKeys(sd_keys, keys);
|
||||
if (sd_keys_res != Loader::ResultStatus::Success) {
|
||||
return sd_keys_res;
|
||||
}
|
||||
|
||||
const auto enc_keys = header->key_area;
|
||||
|
||||
size_t i = 0;
|
||||
for (; i < sd_keys.size(); ++i) {
|
||||
std::array<Core::Crypto::Key128, 2> nax_keys{};
|
||||
if (!CalculateHMAC256(nax_keys.data(), sd_keys[i].data(), 0x10, std::string(path).c_str(),
|
||||
path.size())) {
|
||||
return Loader::ResultStatus::ErrorNAXKeyHMACFailed;
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < nax_keys.size(); ++j) {
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(nax_keys[j],
|
||||
Core::Crypto::Mode::ECB);
|
||||
cipher.Transcode(enc_keys[j].data(), 0x10, header->key_area[j].data(),
|
||||
Core::Crypto::Op::Decrypt);
|
||||
}
|
||||
|
||||
Core::Crypto::SHA256Hash validation{};
|
||||
if (!CalculateHMAC256(validation.data(), &header->magic, 0x60, sd_keys[i].data() + 0x10,
|
||||
0x10)) {
|
||||
return Loader::ResultStatus::ErrorNAXValidationHMACFailed;
|
||||
}
|
||||
if (header->hmac == validation)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == 2) {
|
||||
return Loader::ResultStatus::ErrorNAXKeyDerivationFailed;
|
||||
}
|
||||
|
||||
type = static_cast<NAXContentType>(i);
|
||||
|
||||
Core::Crypto::Key256 final_key{};
|
||||
std::memcpy(final_key.data(), &header->key_area, final_key.size());
|
||||
const auto enc_file =
|
||||
std::make_shared<OffsetVfsFile>(file, header->file_size, NAX_HEADER_PADDING_SIZE);
|
||||
dec_file = std::make_shared<Core::Crypto::XTSEncryptionLayer>(enc_file, final_key);
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
Loader::ResultStatus NAX::GetStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
VirtualFile NAX::GetDecrypted() const {
|
||||
return dec_file;
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> NAX::AsNCA() const {
|
||||
if (type == NAXContentType::NCA)
|
||||
return std::make_shared<NCA>(GetDecrypted());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NAXContentType NAX::GetContentType() const {
|
||||
return type;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> NAX::GetFiles() const {
|
||||
return {dec_file};
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> NAX::GetSubdirectories() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string NAX::GetName() const {
|
||||
return file->GetName();
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> NAX::GetParentDirectory() const {
|
||||
return file->GetContainingDirectory();
|
||||
}
|
||||
|
||||
bool NAX::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
return false;
|
||||
}
|
||||
} // namespace FileSys
|
||||
69
src/core/file_sys/xts_archive.h
Normal file
69
src/core/file_sys/xts_archive.h
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
struct NAXHeader {
|
||||
std::array<u8, 0x20> hmac;
|
||||
u64_le magic;
|
||||
std::array<Core::Crypto::Key128, 2> key_area;
|
||||
u64_le file_size;
|
||||
INSERT_PADDING_BYTES(0x30);
|
||||
};
|
||||
static_assert(sizeof(NAXHeader) == 0x80, "NAXHeader has incorrect size.");
|
||||
|
||||
enum class NAXContentType : u8 {
|
||||
Save = 0,
|
||||
NCA = 1,
|
||||
};
|
||||
|
||||
class NAX : public ReadOnlyVfsDirectory {
|
||||
public:
|
||||
explicit NAX(VirtualFile file);
|
||||
explicit NAX(VirtualFile file, std::array<u8, 0x10> nca_id);
|
||||
|
||||
Loader::ResultStatus GetStatus() const;
|
||||
|
||||
VirtualFile GetDecrypted() const;
|
||||
|
||||
std::shared_ptr<NCA> AsNCA() const;
|
||||
|
||||
NAXContentType GetContentType() const;
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
|
||||
|
||||
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
|
||||
|
||||
std::string GetName() const override;
|
||||
|
||||
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
|
||||
private:
|
||||
Loader::ResultStatus Parse(std::string_view path);
|
||||
|
||||
std::unique_ptr<NAXHeader> header;
|
||||
|
||||
VirtualFile file;
|
||||
Loader::ResultStatus status;
|
||||
NAXContentType type;
|
||||
|
||||
VirtualFile dec_file;
|
||||
|
||||
Core::Crypto::KeyManager keys;
|
||||
};
|
||||
} // namespace FileSys
|
||||
@@ -11,17 +11,16 @@ namespace Kernel {
|
||||
namespace ErrCodes {
|
||||
enum {
|
||||
// TODO(Subv): Remove these 3DS OS error codes.
|
||||
OutOfHandles = 19,
|
||||
SessionClosedByRemote = 26,
|
||||
PortNameTooLong = 30,
|
||||
NoPendingSessions = 35,
|
||||
WrongPermission = 46,
|
||||
InvalidBufferDescriptor = 48,
|
||||
MaxConnectionsReached = 52,
|
||||
|
||||
// Confirmed Switch OS error codes
|
||||
MaxConnectionsReached = 7,
|
||||
InvalidAddress = 102,
|
||||
HandleTableFull = 105,
|
||||
InvalidMemoryState = 106,
|
||||
InvalidMemoryPermissions = 108,
|
||||
InvalidProcessorId = 113,
|
||||
InvalidHandle = 114,
|
||||
InvalidCombination = 116,
|
||||
@@ -30,6 +29,7 @@ enum {
|
||||
TooLarge = 119,
|
||||
InvalidEnumValue = 120,
|
||||
InvalidState = 125,
|
||||
ResourceLimitExceeded = 132,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,18 +37,21 @@ enum {
|
||||
// double check that the code matches before re-using the constant.
|
||||
|
||||
// TODO(bunnei): Replace these with correct errors for Switch OS
|
||||
constexpr ResultCode ERR_OUT_OF_HANDLES(-1);
|
||||
constexpr ResultCode ERR_HANDLE_TABLE_FULL(ErrorModule::Kernel, ErrCodes::HandleTableFull);
|
||||
constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE(-1);
|
||||
constexpr ResultCode ERR_PORT_NAME_TOO_LONG(-1);
|
||||
constexpr ResultCode ERR_WRONG_PERMISSION(-1);
|
||||
constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED(-1);
|
||||
constexpr ResultCode ERR_PORT_NAME_TOO_LONG(ErrorModule::Kernel, ErrCodes::TooLarge);
|
||||
constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED(ErrorModule::Kernel,
|
||||
ErrCodes::MaxConnectionsReached);
|
||||
constexpr ResultCode ERR_INVALID_ENUM_VALUE(ErrorModule::Kernel, ErrCodes::InvalidEnumValue);
|
||||
constexpr ResultCode ERR_INVALID_ENUM_VALUE_FND(-1);
|
||||
constexpr ResultCode ERR_INVALID_COMBINATION(-1);
|
||||
constexpr ResultCode ERR_INVALID_COMBINATION_KERNEL(-1);
|
||||
constexpr ResultCode ERR_INVALID_COMBINATION_KERNEL(ErrorModule::Kernel,
|
||||
ErrCodes::InvalidCombination);
|
||||
constexpr ResultCode ERR_OUT_OF_MEMORY(-1);
|
||||
constexpr ResultCode ERR_INVALID_ADDRESS(ErrorModule::Kernel, ErrCodes::InvalidAddress);
|
||||
constexpr ResultCode ERR_INVALID_ADDRESS_STATE(ErrorModule::Kernel, ErrCodes::InvalidMemoryState);
|
||||
constexpr ResultCode ERR_INVALID_MEMORY_PERMISSIONS(ErrorModule::Kernel,
|
||||
ErrCodes::InvalidMemoryPermissions);
|
||||
constexpr ResultCode ERR_INVALID_HANDLE(ErrorModule::Kernel, ErrCodes::InvalidHandle);
|
||||
constexpr ResultCode ERR_INVALID_STATE(ErrorModule::Kernel, ErrCodes::InvalidState);
|
||||
constexpr ResultCode ERR_INVALID_POINTER(-1);
|
||||
|
||||
@@ -26,7 +26,7 @@ ResultVal<Handle> HandleTable::Create(SharedPtr<Object> obj) {
|
||||
u16 slot = next_free_slot;
|
||||
if (slot >= generations.size()) {
|
||||
LOG_ERROR(Kernel, "Unable to allocate Handle, too many slots in use.");
|
||||
return ERR_OUT_OF_HANDLES;
|
||||
return ERR_HANDLE_TABLE_FULL;
|
||||
}
|
||||
next_free_slot = generations[slot];
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ public:
|
||||
/**
|
||||
* Allocates a handle for the given object.
|
||||
* @return The created Handle or one of the following errors:
|
||||
* - `ERR_OUT_OF_HANDLES`: the maximum number of handles has been exceeded.
|
||||
* - `ERR_HANDLE_TABLE_FULL`: the maximum number of handles has been exceeded.
|
||||
*/
|
||||
ResultVal<Handle> Create(SharedPtr<Object> obj);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Kernel {
|
||||
|
||||
std::mutex Scheduler::scheduler_mutex;
|
||||
|
||||
Scheduler::Scheduler(ARM_Interface* cpu_core) : cpu_core(cpu_core) {}
|
||||
Scheduler::Scheduler(Core::ARM_Interface* cpu_core) : cpu_core(cpu_core) {}
|
||||
|
||||
Scheduler::~Scheduler() {
|
||||
for (auto& thread : thread_list) {
|
||||
|
||||
@@ -11,13 +11,15 @@
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
|
||||
namespace Core {
|
||||
class ARM_Interface;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class Scheduler final {
|
||||
public:
|
||||
explicit Scheduler(ARM_Interface* cpu_core);
|
||||
explicit Scheduler(Core::ARM_Interface* cpu_core);
|
||||
~Scheduler();
|
||||
|
||||
/// Returns whether there are any threads that are ready to run.
|
||||
@@ -70,7 +72,7 @@ private:
|
||||
|
||||
SharedPtr<Thread> current_thread = nullptr;
|
||||
|
||||
ARM_Interface* cpu_core;
|
||||
Core::ARM_Interface* cpu_core;
|
||||
|
||||
static std::mutex scheduler_mutex;
|
||||
};
|
||||
|
||||
@@ -101,7 +101,7 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi
|
||||
static_cast<u32>(this->permissions) & ~static_cast<u32>(other_permissions)) {
|
||||
LOG_ERROR(Kernel, "cannot map id={}, address=0x{:X} name={}, permissions don't match",
|
||||
GetObjectId(), address, name);
|
||||
return ERR_WRONG_PERMISSION;
|
||||
return ERR_INVALID_MEMORY_PERMISSIONS;
|
||||
}
|
||||
|
||||
VAddr target_address = address;
|
||||
|
||||
@@ -319,8 +319,7 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
|
||||
*result = Core::CurrentProcess()->is_virtual_address_memory_enabled;
|
||||
break;
|
||||
case GetInfoType::TitleId:
|
||||
LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query titleid, returned 0");
|
||||
*result = 0;
|
||||
*result = Core::CurrentProcess()->program_id;
|
||||
break;
|
||||
case GetInfoType::PrivilegedProcessId:
|
||||
LOG_WARNING(Kernel_SVC,
|
||||
|
||||
@@ -283,9 +283,9 @@ static std::tuple<std::size_t, std::size_t, bool> GetFreeThreadLocalSlot(
|
||||
* @param entry_point Address of entry point for execution
|
||||
* @param arg User argument for thread
|
||||
*/
|
||||
static void ResetThreadContext(ARM_Interface::ThreadContext& context, VAddr stack_top,
|
||||
static void ResetThreadContext(Core::ARM_Interface::ThreadContext& context, VAddr stack_top,
|
||||
VAddr entry_point, u64 arg) {
|
||||
memset(&context, 0, sizeof(ARM_Interface::ThreadContext));
|
||||
memset(&context, 0, sizeof(Core::ARM_Interface::ThreadContext));
|
||||
|
||||
context.cpu_registers[0] = arg;
|
||||
context.pc = entry_point;
|
||||
|
||||
@@ -204,7 +204,7 @@ public:
|
||||
return status == ThreadStatus::WaitSynchAll;
|
||||
}
|
||||
|
||||
ARM_Interface::ThreadContext context;
|
||||
Core::ARM_Interface::ThreadContext context;
|
||||
|
||||
u32 thread_id;
|
||||
|
||||
|
||||
@@ -227,7 +227,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
ResultVal(ResultVal&& o) : result_code(o.result_code) {
|
||||
ResultVal(ResultVal&& o) noexcept : result_code(o.result_code) {
|
||||
if (!o.empty()) {
|
||||
new (&object) T(std::move(o.object));
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "core/hle/service/apm/apm.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/hle/service/nvflinger/nvflinger.h"
|
||||
#include "core/hle/service/pm/pm.h"
|
||||
#include "core/hle/service/set/set.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
@@ -309,7 +310,7 @@ ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter"
|
||||
{5, &ICommonStateGetter::GetOperationMode, "GetOperationMode"},
|
||||
{6, &ICommonStateGetter::GetPerformanceMode, "GetPerformanceMode"},
|
||||
{7, nullptr, "GetCradleStatus"},
|
||||
{8, nullptr, "GetBootMode"},
|
||||
{8, &ICommonStateGetter::GetBootMode, "GetBootMode"},
|
||||
{9, &ICommonStateGetter::GetCurrentFocusState, "GetCurrentFocusState"},
|
||||
{10, nullptr, "RequestToAcquireSleepLock"},
|
||||
{11, nullptr, "ReleaseSleepLock"},
|
||||
@@ -334,6 +335,15 @@ ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter"
|
||||
event = Kernel::Event::Create(Kernel::ResetType::OneShot, "ICommonStateGetter:Event");
|
||||
}
|
||||
|
||||
void ICommonStateGetter::GetBootMode(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
rb.Push<u8>(static_cast<u8>(Service::PM::SystemBootMode::Normal)); // Normal boot mode
|
||||
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void ICommonStateGetter::GetEventHandle(Kernel::HLERequestContext& ctx) {
|
||||
event->Signal();
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@ private:
|
||||
void GetDefaultDisplayResolutionChangeEvent(Kernel::HLERequestContext& ctx);
|
||||
void GetOperationMode(Kernel::HLERequestContext& ctx);
|
||||
void GetPerformanceMode(Kernel::HLERequestContext& ctx);
|
||||
void GetBootMode(Kernel::HLERequestContext& ctx);
|
||||
|
||||
Kernel::SharedPtr<Kernel::Event> event;
|
||||
};
|
||||
|
||||
@@ -254,7 +254,7 @@ ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) {
|
||||
ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) {
|
||||
ASSERT_MSG(bis_factory == nullptr, "Tried to register a second BIS");
|
||||
bis_factory = std::move(factory);
|
||||
LOG_DEBUG(Service_FS, "Registred BIS");
|
||||
LOG_DEBUG(Service_FS, "Registered BIS");
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -305,17 +305,38 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() {
|
||||
}
|
||||
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() {
|
||||
LOG_TRACE(Service_FS, "Opening System NAND Contents");
|
||||
|
||||
if (bis_factory == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return bis_factory->GetSystemNANDContents();
|
||||
}
|
||||
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() {
|
||||
LOG_TRACE(Service_FS, "Opening User NAND Contents");
|
||||
|
||||
if (bis_factory == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return bis_factory->GetUserNANDContents();
|
||||
}
|
||||
|
||||
void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) {
|
||||
romfs_factory = nullptr;
|
||||
save_data_factory = nullptr;
|
||||
sdmc_factory = nullptr;
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents() {
|
||||
LOG_TRACE(Service_FS, "Opening SDMC Contents");
|
||||
|
||||
if (sdmc_factory == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return sdmc_factory->GetSDMCContents();
|
||||
}
|
||||
|
||||
void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) {
|
||||
if (overwrite) {
|
||||
bis_factory = nullptr;
|
||||
save_data_factory = nullptr;
|
||||
sdmc_factory = nullptr;
|
||||
}
|
||||
|
||||
auto nand_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir),
|
||||
FileSys::Mode::ReadWrite);
|
||||
@@ -324,16 +345,15 @@ void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) {
|
||||
|
||||
if (bis_factory == nullptr)
|
||||
bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory);
|
||||
|
||||
auto savedata = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory));
|
||||
save_data_factory = std::move(savedata);
|
||||
|
||||
auto sdcard = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
|
||||
sdmc_factory = std::move(sdcard);
|
||||
if (save_data_factory == nullptr)
|
||||
save_data_factory = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory));
|
||||
if (sdmc_factory == nullptr)
|
||||
sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
|
||||
}
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs) {
|
||||
RegisterFileSystems(vfs);
|
||||
romfs_factory = nullptr;
|
||||
CreateFactories(vfs, false);
|
||||
std::make_shared<FSP_LDR>()->InstallAsService(service_manager);
|
||||
std::make_shared<FSP_PR>()->InstallAsService(service_manager);
|
||||
std::make_shared<FSP_SRV>()->InstallAsService(service_manager);
|
||||
|
||||
@@ -46,8 +46,12 @@ ResultVal<FileSys::VirtualDir> OpenSDMC();
|
||||
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents();
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents();
|
||||
std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents();
|
||||
|
||||
// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
|
||||
// above is called.
|
||||
void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite = true);
|
||||
|
||||
/// Registers all Filesystem services with the specified service manager.
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs);
|
||||
|
||||
// A class that wraps a VfsDirectory with methods that return ResultVal and ResultCode instead of
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "core/hle/service/hid/irs.h"
|
||||
#include "core/hle/service/hid/xcd.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service::HID {
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service::HID {
|
||||
|
||||
|
||||
@@ -35,6 +35,14 @@ static constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONT
|
||||
std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"),
|
||||
std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf")};
|
||||
|
||||
static constexpr std::array<const char*, 7> SHARED_FONTS_TTF{"FontStandard.ttf",
|
||||
"FontChineseSimplified.ttf",
|
||||
"FontExtendedChineseSimplified.ttf",
|
||||
"FontChineseTraditional.ttf",
|
||||
"FontKorean.ttf",
|
||||
"FontNintendoExtended.ttf",
|
||||
"FontNintendoExtended2.ttf"};
|
||||
|
||||
// The below data is specific to shared font data dumped from Switch on f/w 2.2
|
||||
// Virtual address and offsets/sizes likely will vary by dump
|
||||
static constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL};
|
||||
@@ -76,6 +84,17 @@ void DecryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, s
|
||||
offset += transformed_font.size() * sizeof(u32);
|
||||
}
|
||||
|
||||
static void EncryptSharedFont(const std::vector<u8>& input, std::vector<u8>& output,
|
||||
size_t& offset) {
|
||||
ASSERT_MSG(offset + input.size() + 8 < SHARED_FONT_MEM_SIZE, "Shared fonts exceeds 17mb!");
|
||||
const u32 KEY = EXPECTED_MAGIC ^ EXPECTED_RESULT;
|
||||
std::memcpy(output.data() + offset, &EXPECTED_RESULT, sizeof(u32)); // Magic header
|
||||
const u32 ENC_SIZE = static_cast<u32>(input.size()) ^ KEY;
|
||||
std::memcpy(output.data() + offset + sizeof(u32), &ENC_SIZE, sizeof(u32));
|
||||
std::memcpy(output.data() + offset + (sizeof(u32) * 2), input.data(), input.size());
|
||||
offset += input.size() + (sizeof(u32) * 2);
|
||||
}
|
||||
|
||||
static u32 GetU32Swapped(const u8* data) {
|
||||
u32 value;
|
||||
std::memcpy(&value, data, sizeof(value));
|
||||
@@ -109,10 +128,10 @@ PL_U::PL_U() : ServiceFramework("pl:u") {
|
||||
RegisterHandlers(functions);
|
||||
// Attempt to load shared font data from disk
|
||||
const auto nand = FileSystem::GetSystemNANDContents();
|
||||
size_t offset = 0;
|
||||
// Rebuild shared fonts from data ncas
|
||||
if (nand->HasEntry(static_cast<u64>(FontArchives::Standard),
|
||||
FileSys::ContentRecordType::Data)) {
|
||||
size_t offset = 0;
|
||||
shared_font = std::make_shared<std::vector<u8>>(SHARED_FONT_MEM_SIZE);
|
||||
for (auto font : SHARED_FONTS) {
|
||||
const auto nca =
|
||||
@@ -152,18 +171,45 @@ PL_U::PL_U() : ServiceFramework("pl:u") {
|
||||
DecryptSharedFont(font_data_u32, *shared_font, offset);
|
||||
SHARED_FONT_REGIONS.push_back(region);
|
||||
}
|
||||
|
||||
} else {
|
||||
const std::string filepath{FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) +
|
||||
SHARED_FONT};
|
||||
shared_font = std::make_shared<std::vector<u8>>(
|
||||
SHARED_FONT_MEM_SIZE); // Shared memory needs to always be allocated and a fixed size
|
||||
|
||||
const std::string user_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir);
|
||||
const std::string filepath{user_path + SHARED_FONT};
|
||||
|
||||
// Create path if not already created
|
||||
if (!FileUtil::CreateFullPath(filepath)) {
|
||||
LOG_ERROR(Service_NS, "Failed to create sharedfonts path \"{}\"!", filepath);
|
||||
return;
|
||||
}
|
||||
|
||||
bool using_ttf = false;
|
||||
for (const char* font_ttf : SHARED_FONTS_TTF) {
|
||||
if (FileUtil::Exists(user_path + font_ttf)) {
|
||||
using_ttf = true;
|
||||
FileUtil::IOFile file(user_path + font_ttf, "rb");
|
||||
if (file.IsOpen()) {
|
||||
std::vector<u8> ttf_bytes(file.GetSize());
|
||||
file.ReadBytes<u8>(ttf_bytes.data(), ttf_bytes.size());
|
||||
FontRegion region{
|
||||
static_cast<u32>(offset + 8),
|
||||
static_cast<u32>(ttf_bytes.size())}; // Font offset and size do not account
|
||||
// for the header
|
||||
EncryptSharedFont(ttf_bytes, *shared_font, offset);
|
||||
SHARED_FONT_REGIONS.push_back(region);
|
||||
} else {
|
||||
LOG_WARNING(Service_NS, "Unable to load font: {}", font_ttf);
|
||||
}
|
||||
} else if (using_ttf) {
|
||||
LOG_WARNING(Service_NS, "Unable to find font: {}", font_ttf);
|
||||
}
|
||||
}
|
||||
if (using_ttf)
|
||||
return;
|
||||
FileUtil::IOFile file(filepath, "rb");
|
||||
|
||||
shared_font = std::make_shared<std::vector<u8>>(
|
||||
SHARED_FONT_MEM_SIZE); // Shared memory needs to always be allocated and a fixed size
|
||||
if (file.IsOpen()) {
|
||||
// Read shared font data
|
||||
ASSERT(file.GetSize() == SHARED_FONT_MEM_SIZE);
|
||||
|
||||
@@ -56,9 +56,9 @@ u32 nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>&
|
||||
auto& gpu = Core::System::GetInstance().GPU();
|
||||
const u64 size{static_cast<u64>(params.pages) * static_cast<u64>(params.page_size)};
|
||||
if (params.flags & 1) {
|
||||
params.offset = gpu.memory_manager->AllocateSpace(params.offset, size, 1);
|
||||
params.offset = gpu.MemoryManager().AllocateSpace(params.offset, size, 1);
|
||||
} else {
|
||||
params.offset = gpu.memory_manager->AllocateSpace(size, params.align);
|
||||
params.offset = gpu.MemoryManager().AllocateSpace(size, params.align);
|
||||
}
|
||||
|
||||
std::memcpy(output.data(), ¶ms, output.size());
|
||||
@@ -88,7 +88,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output)
|
||||
u64 size = static_cast<u64>(entry.pages) << 0x10;
|
||||
ASSERT(size <= object->size);
|
||||
|
||||
Tegra::GPUVAddr returned = gpu.memory_manager->MapBufferEx(object->addr, offset, size);
|
||||
Tegra::GPUVAddr returned = gpu.MemoryManager().MapBufferEx(object->addr, offset, size);
|
||||
ASSERT(returned == offset);
|
||||
}
|
||||
std::memcpy(output.data(), entries.data(), output.size());
|
||||
@@ -125,9 +125,9 @@ u32 nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& ou
|
||||
auto& gpu = Core::System::GetInstance().GPU();
|
||||
|
||||
if (params.flags & 1) {
|
||||
params.offset = gpu.memory_manager->MapBufferEx(object->addr, params.offset, object->size);
|
||||
params.offset = gpu.MemoryManager().MapBufferEx(object->addr, params.offset, object->size);
|
||||
} else {
|
||||
params.offset = gpu.memory_manager->MapBufferEx(object->addr, object->size);
|
||||
params.offset = gpu.MemoryManager().MapBufferEx(object->addr, object->size);
|
||||
}
|
||||
|
||||
// Create a new mapping entry for this operation.
|
||||
@@ -161,7 +161,7 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou
|
||||
itr->second.size);
|
||||
|
||||
auto& gpu = system_instance.GPU();
|
||||
params.offset = gpu.memory_manager->UnmapBuffer(params.offset, itr->second.size);
|
||||
params.offset = gpu.MemoryManager().UnmapBuffer(params.offset, itr->second.size);
|
||||
|
||||
buffer_mappings.erase(itr->second.offset);
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/service/pm/pm.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::PM {
|
||||
@@ -10,11 +12,20 @@ class BootMode final : public ServiceFramework<BootMode> {
|
||||
public:
|
||||
explicit BootMode() : ServiceFramework{"pm:bm"} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetBootMode"},
|
||||
{0, &BootMode::GetBootMode, "GetBootMode"},
|
||||
{1, nullptr, "SetMaintenanceBoot"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
void GetBootMode(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(static_cast<u32>(SystemBootMode::Normal)); // Normal boot mode
|
||||
|
||||
LOG_DEBUG(Service_PM, "called");
|
||||
}
|
||||
};
|
||||
|
||||
class DebugMonitor final : public ServiceFramework<DebugMonitor> {
|
||||
|
||||
@@ -9,7 +9,7 @@ class ServiceManager;
|
||||
}
|
||||
|
||||
namespace Service::PM {
|
||||
|
||||
enum class SystemBootMode : u32 { Normal = 0, Maintenance = 1 };
|
||||
/// Registers all PM services with the specified service manager.
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager);
|
||||
|
||||
|
||||
@@ -32,24 +32,59 @@ constexpr std::array<LanguageCode, 17> available_language_codes = {{
|
||||
LanguageCode::ZH_HANT,
|
||||
}};
|
||||
|
||||
constexpr size_t pre4_0_0_max_entries = 0xF;
|
||||
constexpr size_t post4_0_0_max_entries = 0x40;
|
||||
|
||||
LanguageCode GetLanguageCodeFromIndex(size_t index) {
|
||||
return available_language_codes.at(index);
|
||||
}
|
||||
|
||||
void SET::GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx) {
|
||||
ctx.WriteBuffer(available_language_codes);
|
||||
template <size_t size>
|
||||
static std::array<LanguageCode, size> MakeLanguageCodeSubset() {
|
||||
std::array<LanguageCode, size> arr;
|
||||
std::copy_n(available_language_codes.begin(), size, arr.begin());
|
||||
return arr;
|
||||
}
|
||||
|
||||
static void PushResponseLanguageCode(Kernel::HLERequestContext& ctx, size_t max_size) {
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(static_cast<u32>(available_language_codes.size()));
|
||||
if (available_language_codes.size() > max_size)
|
||||
rb.Push(static_cast<u32>(max_size));
|
||||
else
|
||||
rb.Push(static_cast<u32>(available_language_codes.size()));
|
||||
}
|
||||
|
||||
void SET::GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx) {
|
||||
if (available_language_codes.size() > pre4_0_0_max_entries)
|
||||
ctx.WriteBuffer(MakeLanguageCodeSubset<pre4_0_0_max_entries>());
|
||||
else
|
||||
ctx.WriteBuffer(available_language_codes);
|
||||
|
||||
PushResponseLanguageCode(ctx, pre4_0_0_max_entries);
|
||||
|
||||
LOG_DEBUG(Service_SET, "called");
|
||||
}
|
||||
|
||||
void SET::GetAvailableLanguageCodes2(Kernel::HLERequestContext& ctx) {
|
||||
if (available_language_codes.size() > post4_0_0_max_entries)
|
||||
ctx.WriteBuffer(MakeLanguageCodeSubset<post4_0_0_max_entries>());
|
||||
else
|
||||
ctx.WriteBuffer(available_language_codes);
|
||||
|
||||
PushResponseLanguageCode(ctx, post4_0_0_max_entries);
|
||||
|
||||
LOG_DEBUG(Service_SET, "called");
|
||||
}
|
||||
|
||||
void SET::GetAvailableLanguageCodeCount(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(static_cast<u32>(available_language_codes.size()));
|
||||
PushResponseLanguageCode(ctx, pre4_0_0_max_entries);
|
||||
|
||||
LOG_DEBUG(Service_SET, "called");
|
||||
}
|
||||
|
||||
void SET::GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx) {
|
||||
PushResponseLanguageCode(ctx, post4_0_0_max_entries);
|
||||
|
||||
LOG_DEBUG(Service_SET, "called");
|
||||
}
|
||||
@@ -69,8 +104,8 @@ SET::SET() : ServiceFramework("set") {
|
||||
{2, nullptr, "MakeLanguageCode"},
|
||||
{3, &SET::GetAvailableLanguageCodeCount, "GetAvailableLanguageCodeCount"},
|
||||
{4, nullptr, "GetRegionCode"},
|
||||
{5, &SET::GetAvailableLanguageCodes, "GetAvailableLanguageCodes2"},
|
||||
{6, &SET::GetAvailableLanguageCodeCount, "GetAvailableLanguageCodeCount2"},
|
||||
{5, &SET::GetAvailableLanguageCodes2, "GetAvailableLanguageCodes2"},
|
||||
{6, &SET::GetAvailableLanguageCodeCount2, "GetAvailableLanguageCodeCount2"},
|
||||
{7, nullptr, "GetKeyCodeMap"},
|
||||
{8, nullptr, "GetQuestFlag"},
|
||||
};
|
||||
|
||||
@@ -38,7 +38,9 @@ public:
|
||||
private:
|
||||
void GetLanguageCode(Kernel::HLERequestContext& ctx);
|
||||
void GetAvailableLanguageCodes(Kernel::HLERequestContext& ctx);
|
||||
void GetAvailableLanguageCodes2(Kernel::HLERequestContext& ctx);
|
||||
void GetAvailableLanguageCodeCount(Kernel::HLERequestContext& ctx);
|
||||
void GetAvailableLanguageCodeCount2(Kernel::HLERequestContext& ctx);
|
||||
};
|
||||
|
||||
} // namespace Service::Set
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/loader/deconstructed_rom_directory.h"
|
||||
#include "core/loader/elf.h"
|
||||
#include "core/loader/nax.h"
|
||||
#include "core/loader/nca.h"
|
||||
#include "core/loader/nro.h"
|
||||
#include "core/loader/nso.h"
|
||||
@@ -32,6 +33,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
|
||||
CHECK_TYPE(NRO)
|
||||
CHECK_TYPE(NCA)
|
||||
CHECK_TYPE(XCI)
|
||||
CHECK_TYPE(NAX)
|
||||
|
||||
#undef CHECK_TYPE
|
||||
|
||||
@@ -73,6 +75,8 @@ std::string GetFileTypeString(FileType type) {
|
||||
return "NCA";
|
||||
case FileType::XCI:
|
||||
return "XCI";
|
||||
case FileType::NAX:
|
||||
return "NAX";
|
||||
case FileType::DeconstructedRomDirectory:
|
||||
return "Directory";
|
||||
case FileType::Error:
|
||||
@@ -83,7 +87,7 @@ std::string GetFileTypeString(FileType type) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
constexpr std::array<const char*, 36> RESULT_MESSAGES{
|
||||
constexpr std::array<const char*, 49> RESULT_MESSAGES{
|
||||
"The operation completed successfully.",
|
||||
"The loader requested to load is already loaded.",
|
||||
"The operation is not implemented.",
|
||||
@@ -120,6 +124,19 @@ constexpr std::array<const char*, 36> RESULT_MESSAGES{
|
||||
"There was a general error loading the NRO into emulated memory.",
|
||||
"There is no icon available.",
|
||||
"There is no control data available.",
|
||||
"The NAX file has a bad header.",
|
||||
"The NAX file has incorrect size as determined by the header.",
|
||||
"The HMAC to generated the NAX decryption keys failed.",
|
||||
"The HMAC to validate the NAX decryption keys failed.",
|
||||
"The NAX key derivation failed.",
|
||||
"The NAX file cannot be interpreted as an NCA file.",
|
||||
"The NAX file has an incorrect path.",
|
||||
"The SD seed could not be found or derived.",
|
||||
"The SD KEK Source could not be found.",
|
||||
"The AES KEK Generation Source could not be found.",
|
||||
"The AES Key Generation Source could not be found.",
|
||||
"The SD Save Key Source could not be found.",
|
||||
"The SD NCA Key Source could not be found.",
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, ResultStatus status) {
|
||||
@@ -150,13 +167,18 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT
|
||||
case FileType::NRO:
|
||||
return std::make_unique<AppLoader_NRO>(std::move(file));
|
||||
|
||||
// NX NCA file format.
|
||||
// NX NCA (Nintendo Content Archive) file format.
|
||||
case FileType::NCA:
|
||||
return std::make_unique<AppLoader_NCA>(std::move(file));
|
||||
|
||||
// NX XCI (nX Card Image) file format.
|
||||
case FileType::XCI:
|
||||
return std::make_unique<AppLoader_XCI>(std::move(file));
|
||||
|
||||
// NX NAX (NintendoAesXts) file format.
|
||||
case FileType::NAX:
|
||||
return std::make_unique<AppLoader_NAX>(std::move(file));
|
||||
|
||||
// NX deconstructed ROM directory.
|
||||
case FileType::DeconstructedRomDirectory:
|
||||
return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file));
|
||||
@@ -170,7 +192,8 @@ std::unique_ptr<AppLoader> GetLoader(FileSys::VirtualFile file) {
|
||||
FileType type = IdentifyFile(file);
|
||||
FileType filename_type = GuessFromFilename(file->GetName());
|
||||
|
||||
if (type != filename_type) {
|
||||
// Special case: 00 is either a NCA or NAX.
|
||||
if (type != filename_type && !(file->GetName() == "00" && type == FileType::NAX)) {
|
||||
LOG_WARNING(Loader, "File {} has a different type than its extension.", file->GetName());
|
||||
if (FileType::Unknown == type)
|
||||
type = filename_type;
|
||||
|
||||
@@ -32,6 +32,7 @@ enum class FileType {
|
||||
NRO,
|
||||
NCA,
|
||||
XCI,
|
||||
NAX,
|
||||
DeconstructedRomDirectory,
|
||||
};
|
||||
|
||||
@@ -93,6 +94,19 @@ enum class ResultStatus : u16 {
|
||||
ErrorLoadingNRO,
|
||||
ErrorNoIcon,
|
||||
ErrorNoControl,
|
||||
ErrorBadNAXHeader,
|
||||
ErrorIncorrectNAXFileSize,
|
||||
ErrorNAXKeyHMACFailed,
|
||||
ErrorNAXValidationHMACFailed,
|
||||
ErrorNAXKeyDerivationFailed,
|
||||
ErrorNAXInconvertibleToNCA,
|
||||
ErrorBadNAXFilePath,
|
||||
ErrorMissingSDSeed,
|
||||
ErrorMissingSDKEKSource,
|
||||
ErrorMissingAESKEKGenerationSource,
|
||||
ErrorMissingAESKeyGenerationSource,
|
||||
ErrorMissingSDSaveKeySource,
|
||||
ErrorMissingSDNCAKeySource,
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, ResultStatus status);
|
||||
|
||||
66
src/core/loader/nax.cpp
Normal file
66
src/core/loader/nax.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/xts_archive.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/loader/nax.h"
|
||||
#include "core/loader/nca.h"
|
||||
|
||||
namespace Loader {
|
||||
|
||||
AppLoader_NAX::AppLoader_NAX(FileSys::VirtualFile file)
|
||||
: AppLoader(file), nax(std::make_unique<FileSys::NAX>(file)),
|
||||
nca_loader(std::make_unique<AppLoader_NCA>(nax->GetDecrypted())) {}
|
||||
|
||||
AppLoader_NAX::~AppLoader_NAX() = default;
|
||||
|
||||
FileType AppLoader_NAX::IdentifyType(const FileSys::VirtualFile& file) {
|
||||
FileSys::NAX nax(file);
|
||||
|
||||
if (nax.GetStatus() == ResultStatus::Success && nax.AsNCA() != nullptr &&
|
||||
nax.AsNCA()->GetStatus() == ResultStatus::Success) {
|
||||
return FileType::NAX;
|
||||
}
|
||||
|
||||
return FileType::Error;
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_NAX::Load(Kernel::SharedPtr<Kernel::Process>& process) {
|
||||
if (is_loaded) {
|
||||
return ResultStatus::ErrorAlreadyLoaded;
|
||||
}
|
||||
|
||||
if (nax->GetStatus() != ResultStatus::Success)
|
||||
return nax->GetStatus();
|
||||
|
||||
const auto nca = nax->AsNCA();
|
||||
if (nca == nullptr) {
|
||||
if (!Core::Crypto::KeyManager::KeyFileExists(false))
|
||||
return ResultStatus::ErrorMissingProductionKeyFile;
|
||||
return ResultStatus::ErrorNAXInconvertibleToNCA;
|
||||
}
|
||||
|
||||
if (nca->GetStatus() != ResultStatus::Success)
|
||||
return nca->GetStatus();
|
||||
|
||||
const auto result = nca_loader->Load(process);
|
||||
if (result != ResultStatus::Success)
|
||||
return result;
|
||||
|
||||
is_loaded = true;
|
||||
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_NAX::ReadRomFS(FileSys::VirtualFile& dir) {
|
||||
return nca_loader->ReadRomFS(dir);
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) {
|
||||
return nca_loader->ReadProgramId(out_program_id);
|
||||
}
|
||||
} // namespace Loader
|
||||
48
src/core/loader/nax.h
Normal file
48
src/core/loader/nax.h
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class NAX;
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
namespace Loader {
|
||||
|
||||
class AppLoader_NCA;
|
||||
|
||||
/// Loads a NAX file
|
||||
class AppLoader_NAX final : public AppLoader {
|
||||
public:
|
||||
explicit AppLoader_NAX(FileSys::VirtualFile file);
|
||||
~AppLoader_NAX() override;
|
||||
|
||||
/**
|
||||
* Returns the type of the file
|
||||
* @param file std::shared_ptr<VfsFile> open file
|
||||
* @return FileType found, or FileType::Error if this loader doesn't know it
|
||||
*/
|
||||
static FileType IdentifyType(const FileSys::VirtualFile& file);
|
||||
|
||||
FileType GetFileType() override {
|
||||
return IdentifyType(file);
|
||||
}
|
||||
|
||||
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
|
||||
|
||||
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
|
||||
ResultStatus ReadProgramId(u64& out_program_id) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<FileSys::NAX> nax;
|
||||
std::unique_ptr<AppLoader_NCA> nca_loader;
|
||||
};
|
||||
|
||||
} // namespace Loader
|
||||
@@ -61,11 +61,12 @@ ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr<Kernel::Process>& process) {
|
||||
if (xci->GetStatus() != ResultStatus::Success)
|
||||
return xci->GetStatus();
|
||||
|
||||
if (xci->GetNCAFileByType(FileSys::NCAContentType::Program) == nullptr) {
|
||||
if (!Core::Crypto::KeyManager::KeyFileExists(false))
|
||||
return ResultStatus::ErrorMissingProductionKeyFile;
|
||||
return ResultStatus::ErrorXCIMissingProgramNCA;
|
||||
}
|
||||
if (xci->GetProgramNCAStatus() != ResultStatus::Success)
|
||||
return xci->GetProgramNCAStatus();
|
||||
|
||||
const auto nca = xci->GetNCAFileByType(FileSys::NCAContentType::Program);
|
||||
if (nca == nullptr && !Core::Crypto::KeyManager::KeyFileExists(false))
|
||||
return ResultStatus::ErrorMissingProductionKeyFile;
|
||||
|
||||
auto result = nca_loader->Load(process);
|
||||
if (result != ResultStatus::Success)
|
||||
|
||||
@@ -264,7 +264,7 @@ void RasterizerMarkRegionCached(Tegra::GPUVAddr gpu_addr, u64 size, bool cached)
|
||||
u64 num_pages = ((gpu_addr + size - 1) >> PAGE_BITS) - (gpu_addr >> PAGE_BITS) + 1;
|
||||
for (unsigned i = 0; i < num_pages; ++i, gpu_addr += PAGE_SIZE) {
|
||||
boost::optional<VAddr> maybe_vaddr =
|
||||
Core::System::GetInstance().GPU().memory_manager->GpuToCpuAddress(gpu_addr);
|
||||
Core::System::GetInstance().GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
|
||||
// The GPU <-> CPU virtual memory mapping is not 1:1
|
||||
if (!maybe_vaddr) {
|
||||
LOG_ERROR(HW_Memory,
|
||||
@@ -346,7 +346,7 @@ void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode) {
|
||||
const VAddr overlap_end = std::min(end, region_end);
|
||||
|
||||
const std::vector<Tegra::GPUVAddr> gpu_addresses =
|
||||
system_instance.GPU().memory_manager->CpuToGpuAddress(overlap_start);
|
||||
system_instance.GPU().MemoryManager().CpuToGpuAddress(overlap_start);
|
||||
|
||||
if (gpu_addresses.empty()) {
|
||||
return;
|
||||
|
||||
@@ -18,6 +18,7 @@ add_library(video_core STATIC
|
||||
macro_interpreter.h
|
||||
memory_manager.cpp
|
||||
memory_manager.h
|
||||
rasterizer_cache.h
|
||||
rasterizer_interface.h
|
||||
renderer_base.cpp
|
||||
renderer_base.h
|
||||
@@ -26,6 +27,8 @@ add_library(video_core STATIC
|
||||
renderer_opengl/gl_rasterizer_cache.cpp
|
||||
renderer_opengl/gl_rasterizer_cache.h
|
||||
renderer_opengl/gl_resource_manager.h
|
||||
renderer_opengl/gl_shader_cache.cpp
|
||||
renderer_opengl/gl_shader_cache.h
|
||||
renderer_opengl/gl_shader_decompiler.cpp
|
||||
renderer_opengl/gl_shader_decompiler.h
|
||||
renderer_opengl/gl_shader_gen.cpp
|
||||
|
||||
@@ -2,23 +2,8 @@
|
||||
// Licensed under GPLv2
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "common/color.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/math_util.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
@@ -4,19 +4,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <condition_variable>
|
||||
#include <iterator>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/vector_math.h"
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
@@ -46,7 +38,7 @@ public:
|
||||
class BreakPointObserver {
|
||||
public:
|
||||
/// Constructs the object such that it observes events of the given DebugContext.
|
||||
BreakPointObserver(std::shared_ptr<DebugContext> debug_context)
|
||||
explicit BreakPointObserver(std::shared_ptr<DebugContext> debug_context)
|
||||
: context_weak(debug_context) {
|
||||
std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex);
|
||||
debug_context->breakpoint_observers.push_back(this);
|
||||
@@ -141,8 +133,8 @@ public:
|
||||
}
|
||||
|
||||
// TODO: Evaluate if access to these members should be hidden behind a public interface.
|
||||
std::array<BreakPoint, (int)Event::NumEvents> breakpoints;
|
||||
Event active_breakpoint;
|
||||
std::array<BreakPoint, static_cast<int>(Event::NumEvents)> breakpoints;
|
||||
Event active_breakpoint{};
|
||||
bool at_breakpoint = false;
|
||||
|
||||
private:
|
||||
|
||||
@@ -218,10 +218,6 @@ void Maxwell3D::DrawArrays() {
|
||||
debug_context->OnEvent(Tegra::DebugContext::Event::IncomingPrimitiveBatch, nullptr);
|
||||
}
|
||||
|
||||
if (debug_context) {
|
||||
debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr);
|
||||
}
|
||||
|
||||
// Both instance configuration registers can not be set at the same time.
|
||||
ASSERT_MSG(!regs.draw.instance_next || !regs.draw.instance_cont,
|
||||
"Illegal combination of instancing parameters");
|
||||
@@ -237,6 +233,10 @@ void Maxwell3D::DrawArrays() {
|
||||
const bool is_indexed{regs.index_array.count && !regs.vertex_buffer.count};
|
||||
rasterizer.AccelerateDrawBatch(is_indexed);
|
||||
|
||||
if (debug_context) {
|
||||
debug_context->OnEvent(Tegra::DebugContext::Event::FinishedPrimitiveBatch, nullptr);
|
||||
}
|
||||
|
||||
// TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if
|
||||
// the game is trying to draw indexed or direct mode. This needs to be verified on HW still -
|
||||
// it's possible that it is incorrect and that there is some other register used to specify the
|
||||
|
||||
@@ -330,6 +330,17 @@ public:
|
||||
Set = 0x150F,
|
||||
};
|
||||
|
||||
enum class StencilOp : u32 {
|
||||
Keep = 1,
|
||||
Zero = 2,
|
||||
Replace = 3,
|
||||
Incr = 4,
|
||||
Decr = 5,
|
||||
Invert = 6,
|
||||
IncrWrap = 7,
|
||||
DecrWrap = 8,
|
||||
};
|
||||
|
||||
struct Cull {
|
||||
enum class FrontFace : u32 {
|
||||
ClockWise = 0x0900,
|
||||
@@ -508,8 +519,16 @@ public:
|
||||
|
||||
float clear_color[4];
|
||||
float clear_depth;
|
||||
INSERT_PADDING_WORDS(0x3);
|
||||
s32 clear_stencil;
|
||||
|
||||
INSERT_PADDING_WORDS(0x93);
|
||||
INSERT_PADDING_WORDS(0x6C);
|
||||
|
||||
s32 stencil_back_func_ref;
|
||||
u32 stencil_back_mask;
|
||||
u32 stencil_back_func_mask;
|
||||
|
||||
INSERT_PADDING_WORDS(0x20);
|
||||
|
||||
struct {
|
||||
u32 address_high;
|
||||
@@ -573,16 +592,14 @@ public:
|
||||
u32 enable[NumRenderTargets];
|
||||
} blend;
|
||||
|
||||
struct {
|
||||
u32 enable;
|
||||
u32 front_op_fail;
|
||||
u32 front_op_zfail;
|
||||
u32 front_op_zpass;
|
||||
u32 front_func_func;
|
||||
u32 front_func_ref;
|
||||
u32 front_func_mask;
|
||||
u32 front_mask;
|
||||
} stencil;
|
||||
u32 stencil_enable;
|
||||
StencilOp stencil_front_op_fail;
|
||||
StencilOp stencil_front_op_zfail;
|
||||
StencilOp stencil_front_op_zpass;
|
||||
ComparisonOp stencil_front_func_func;
|
||||
s32 stencil_front_func_ref;
|
||||
u32 stencil_front_func_mask;
|
||||
u32 stencil_front_mask;
|
||||
|
||||
INSERT_PADDING_WORDS(0x3);
|
||||
|
||||
@@ -626,13 +643,11 @@ public:
|
||||
|
||||
INSERT_PADDING_WORDS(0x5);
|
||||
|
||||
struct {
|
||||
u32 enable;
|
||||
u32 back_op_fail;
|
||||
u32 back_op_zfail;
|
||||
u32 back_op_zpass;
|
||||
u32 back_func_func;
|
||||
} stencil_two_side;
|
||||
u32 stencil_two_side_enable;
|
||||
StencilOp stencil_back_op_fail;
|
||||
StencilOp stencil_back_op_zfail;
|
||||
StencilOp stencil_back_op_zpass;
|
||||
ComparisonOp stencil_back_func_func;
|
||||
|
||||
INSERT_PADDING_WORDS(0x17);
|
||||
|
||||
@@ -944,6 +959,10 @@ ASSERT_REG_POSITION(viewport, 0x300);
|
||||
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(stencil_back_func_ref, 0x3D5);
|
||||
ASSERT_REG_POSITION(stencil_back_mask, 0x3D6);
|
||||
ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D7);
|
||||
ASSERT_REG_POSITION(zeta, 0x3F8);
|
||||
ASSERT_REG_POSITION(vertex_attrib_format[0], 0x458);
|
||||
ASSERT_REG_POSITION(rt_control, 0x487);
|
||||
@@ -955,13 +974,24 @@ ASSERT_REG_POSITION(depth_write_enabled, 0x4BA);
|
||||
ASSERT_REG_POSITION(d3d_cull_mode, 0x4C2);
|
||||
ASSERT_REG_POSITION(depth_test_func, 0x4C3);
|
||||
ASSERT_REG_POSITION(blend, 0x4CF);
|
||||
ASSERT_REG_POSITION(stencil, 0x4E0);
|
||||
ASSERT_REG_POSITION(stencil_enable, 0x4E0);
|
||||
ASSERT_REG_POSITION(stencil_front_op_fail, 0x4E1);
|
||||
ASSERT_REG_POSITION(stencil_front_op_zfail, 0x4E2);
|
||||
ASSERT_REG_POSITION(stencil_front_op_zpass, 0x4E3);
|
||||
ASSERT_REG_POSITION(stencil_front_func_func, 0x4E4);
|
||||
ASSERT_REG_POSITION(stencil_front_func_ref, 0x4E5);
|
||||
ASSERT_REG_POSITION(stencil_front_func_mask, 0x4E6);
|
||||
ASSERT_REG_POSITION(stencil_front_mask, 0x4E7);
|
||||
ASSERT_REG_POSITION(screen_y_control, 0x4EB);
|
||||
ASSERT_REG_POSITION(vb_element_base, 0x50D);
|
||||
ASSERT_REG_POSITION(zeta_enable, 0x54E);
|
||||
ASSERT_REG_POSITION(tsc, 0x557);
|
||||
ASSERT_REG_POSITION(tic, 0x55D);
|
||||
ASSERT_REG_POSITION(stencil_two_side, 0x565);
|
||||
ASSERT_REG_POSITION(stencil_two_side_enable, 0x565);
|
||||
ASSERT_REG_POSITION(stencil_back_op_fail, 0x566);
|
||||
ASSERT_REG_POSITION(stencil_back_op_zfail, 0x567);
|
||||
ASSERT_REG_POSITION(stencil_back_op_zpass, 0x568);
|
||||
ASSERT_REG_POSITION(stencil_back_func_func, 0x569);
|
||||
ASSERT_REG_POSITION(point_coord_replace, 0x581);
|
||||
ASSERT_REG_POSITION(code_address, 0x582);
|
||||
ASSERT_REG_POSITION(draw, 0x585);
|
||||
|
||||
@@ -280,6 +280,19 @@ union Instruction {
|
||||
BitField<56, 1, u64> invert_b;
|
||||
} lop32i;
|
||||
|
||||
union {
|
||||
BitField<28, 8, u64> imm_lut28;
|
||||
BitField<48, 8, u64> imm_lut48;
|
||||
|
||||
u32 GetImmLut28() const {
|
||||
return static_cast<u32>(imm_lut28);
|
||||
}
|
||||
|
||||
u32 GetImmLut48() const {
|
||||
return static_cast<u32>(imm_lut48);
|
||||
}
|
||||
} lop3;
|
||||
|
||||
u32 GetImm20_19() const {
|
||||
u32 imm{static_cast<u32>(imm20_19)};
|
||||
imm <<= 12;
|
||||
@@ -313,6 +326,10 @@ union Instruction {
|
||||
BitField<49, 1, u64> negate_a;
|
||||
} alu_integer;
|
||||
|
||||
union {
|
||||
BitField<40, 1, u64> invert;
|
||||
} popc;
|
||||
|
||||
union {
|
||||
BitField<39, 3, u64> pred;
|
||||
BitField<42, 1, u64> neg_pred;
|
||||
@@ -623,10 +640,16 @@ public:
|
||||
IADD_C,
|
||||
IADD_R,
|
||||
IADD_IMM,
|
||||
IADD3_C,
|
||||
IADD3_R,
|
||||
IADD3_IMM,
|
||||
IADD32I,
|
||||
ISCADD_C, // Scale and Add
|
||||
ISCADD_R,
|
||||
ISCADD_IMM,
|
||||
POPC_C,
|
||||
POPC_R,
|
||||
POPC_IMM,
|
||||
SEL_C,
|
||||
SEL_R,
|
||||
SEL_IMM,
|
||||
@@ -650,6 +673,9 @@ public:
|
||||
LOP_R,
|
||||
LOP_IMM,
|
||||
LOP32I,
|
||||
LOP3_C,
|
||||
LOP3_R,
|
||||
LOP3_IMM,
|
||||
MOV_C,
|
||||
MOV_R,
|
||||
MOV_IMM,
|
||||
@@ -838,13 +864,19 @@ private:
|
||||
INST("0100110000010---", Id::IADD_C, Type::ArithmeticInteger, "IADD_C"),
|
||||
INST("0101110000010---", Id::IADD_R, Type::ArithmeticInteger, "IADD_R"),
|
||||
INST("0011100-00010---", Id::IADD_IMM, Type::ArithmeticInteger, "IADD_IMM"),
|
||||
INST("010011001100----", Id::IADD3_C, Type::ArithmeticInteger, "IADD3_C"),
|
||||
INST("010111001100----", Id::IADD3_R, Type::ArithmeticInteger, "IADD3_R"),
|
||||
INST("0011100-1100----", Id::IADD3_IMM, Type::ArithmeticInteger, "IADD3_IMM"),
|
||||
INST("0001110---------", Id::IADD32I, Type::ArithmeticIntegerImmediate, "IADD32I"),
|
||||
INST("0100110000011---", Id::ISCADD_C, Type::ArithmeticInteger, "ISCADD_C"),
|
||||
INST("0101110000011---", Id::ISCADD_R, Type::ArithmeticInteger, "ISCADD_R"),
|
||||
INST("0011100-00011---", Id::ISCADD_IMM, Type::ArithmeticInteger, "ISCADD_IMM"),
|
||||
INST("0100110000001---", Id::POPC_C, Type::ArithmeticInteger, "POPC_C"),
|
||||
INST("0101110000001---", Id::POPC_R, Type::ArithmeticInteger, "POPC_R"),
|
||||
INST("0011100-00001---", Id::POPC_IMM, Type::ArithmeticInteger, "POPC_IMM"),
|
||||
INST("0100110010100---", Id::SEL_C, Type::ArithmeticInteger, "SEL_C"),
|
||||
INST("0101110010100---", Id::SEL_R, Type::ArithmeticInteger, "SEL_R"),
|
||||
INST("0011100010100---", Id::SEL_IMM, Type::ArithmeticInteger, "SEL_IMM"),
|
||||
INST("0011100-10100---", Id::SEL_IMM, Type::ArithmeticInteger, "SEL_IMM"),
|
||||
INST("0101000010000---", Id::MUFU, Type::Arithmetic, "MUFU"),
|
||||
INST("0100110010010---", Id::RRO_C, Type::Arithmetic, "RRO_C"),
|
||||
INST("0101110010010---", Id::RRO_R, Type::Arithmetic, "RRO_R"),
|
||||
@@ -872,6 +904,9 @@ private:
|
||||
INST("0101110001000---", Id::LOP_R, Type::ArithmeticInteger, "LOP_R"),
|
||||
INST("0011100001000---", Id::LOP_IMM, Type::ArithmeticInteger, "LOP_IMM"),
|
||||
INST("000001----------", Id::LOP32I, Type::ArithmeticIntegerImmediate, "LOP32I"),
|
||||
INST("0000001---------", Id::LOP3_C, Type::ArithmeticInteger, "LOP3_C"),
|
||||
INST("0101101111100---", Id::LOP3_R, Type::ArithmeticInteger, "LOP3_R"),
|
||||
INST("0011110---------", Id::LOP3_IMM, Type::ArithmeticInteger, "LOP3_IMM"),
|
||||
INST("0100110001001---", Id::SHL_C, Type::Shift, "SHL_C"),
|
||||
INST("0101110001001---", Id::SHL_R, Type::Shift, "SHL_R"),
|
||||
INST("0011100-01001---", Id::SHL_IMM, Type::Shift, "SHL_IMM"),
|
||||
|
||||
@@ -22,7 +22,7 @@ u32 FramebufferConfig::BytesPerPixel(PixelFormat format) {
|
||||
}
|
||||
|
||||
GPU::GPU(VideoCore::RasterizerInterface& rasterizer) {
|
||||
memory_manager = std::make_unique<MemoryManager>();
|
||||
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);
|
||||
maxwell_compute = std::make_unique<Engines::MaxwellCompute>();
|
||||
@@ -31,12 +31,20 @@ GPU::GPU(VideoCore::RasterizerInterface& rasterizer) {
|
||||
|
||||
GPU::~GPU() = default;
|
||||
|
||||
Engines::Maxwell3D& GPU::Maxwell3D() {
|
||||
return *maxwell_3d;
|
||||
}
|
||||
|
||||
const Engines::Maxwell3D& GPU::Maxwell3D() const {
|
||||
return *maxwell_3d;
|
||||
}
|
||||
|
||||
Engines::Maxwell3D& GPU::Maxwell3D() {
|
||||
return *maxwell_3d;
|
||||
MemoryManager& GPU::MemoryManager() {
|
||||
return *memory_manager;
|
||||
}
|
||||
|
||||
const MemoryManager& GPU::MemoryManager() const {
|
||||
return *memory_manager;
|
||||
}
|
||||
|
||||
u32 RenderTargetBytesPerPixel(RenderTargetFormat format) {
|
||||
|
||||
@@ -117,18 +117,24 @@ public:
|
||||
/// Processes a command list stored at the specified address in GPU memory.
|
||||
void ProcessCommandList(GPUVAddr address, u32 size);
|
||||
|
||||
/// Returns a const reference to the Maxwell3D GPU engine.
|
||||
const Engines::Maxwell3D& Maxwell3D() const;
|
||||
|
||||
/// Returns a reference to the Maxwell3D GPU engine.
|
||||
Engines::Maxwell3D& Maxwell3D();
|
||||
|
||||
std::unique_ptr<MemoryManager> memory_manager;
|
||||
/// Returns a const reference to the Maxwell3D GPU engine.
|
||||
const Engines::Maxwell3D& Maxwell3D() const;
|
||||
|
||||
/// Returns a reference to the GPU memory manager.
|
||||
Tegra::MemoryManager& MemoryManager();
|
||||
|
||||
/// Returns a const reference to the GPU memory manager.
|
||||
const Tegra::MemoryManager& MemoryManager() const;
|
||||
|
||||
private:
|
||||
/// Writes a single register in the engine bound to the specified subchannel
|
||||
void WriteReg(u32 method, u32 subchannel, u32 value, u32 remaining_params);
|
||||
|
||||
std::unique_ptr<Tegra::MemoryManager> memory_manager;
|
||||
|
||||
/// Mapping of command subchannels to their bound engine ids.
|
||||
std::unordered_map<u32, EngineID> bound_engines;
|
||||
|
||||
|
||||
116
src/video_core/rasterizer_cache.h
Normal file
116
src/video_core/rasterizer_cache.h
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
template <class T>
|
||||
class RasterizerCache : NonCopyable {
|
||||
public:
|
||||
/// Mark the specified region as being invalidated
|
||||
void InvalidateRegion(Tegra::GPUVAddr region_addr, size_t region_size) {
|
||||
for (auto iter = cached_objects.cbegin(); iter != cached_objects.cend();) {
|
||||
const auto& object{iter->second};
|
||||
|
||||
++iter;
|
||||
|
||||
if (object->GetAddr() <= (region_addr + region_size) &&
|
||||
region_addr <= (object->GetAddr() + object->GetSizeInBytes())) {
|
||||
// Regions overlap, so invalidate
|
||||
Unregister(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
/// Tries to get an object from the cache with the specified address
|
||||
T TryGet(Tegra::GPUVAddr addr) const {
|
||||
const auto& search{cached_objects.find(addr)};
|
||||
if (search != cached_objects.end()) {
|
||||
return search->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// Gets a reference to the cache
|
||||
const std::unordered_map<Tegra::GPUVAddr, T>& GetCache() const {
|
||||
return cached_objects;
|
||||
}
|
||||
|
||||
/// Register an object into the cache
|
||||
void Register(const T& object) {
|
||||
const auto& search{cached_objects.find(object->GetAddr())};
|
||||
if (search != cached_objects.end()) {
|
||||
// Registered already
|
||||
return;
|
||||
}
|
||||
|
||||
cached_objects[object->GetAddr()] = object;
|
||||
UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), 1);
|
||||
}
|
||||
|
||||
/// Unregisters an object from the cache
|
||||
void Unregister(const T& object) {
|
||||
const auto& search{cached_objects.find(object->GetAddr())};
|
||||
if (search == cached_objects.end()) {
|
||||
// Unregistered already
|
||||
return;
|
||||
}
|
||||
|
||||
UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), -1);
|
||||
cached_objects.erase(search);
|
||||
}
|
||||
|
||||
private:
|
||||
using PageMap = boost::icl::interval_map<u64, int>;
|
||||
|
||||
template <typename Map, typename Interval>
|
||||
constexpr auto RangeFromInterval(Map& map, const Interval& interval) {
|
||||
return boost::make_iterator_range(map.equal_range(interval));
|
||||
}
|
||||
|
||||
/// Increase/decrease the number of object in pages touching the specified region
|
||||
void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) {
|
||||
const u64 page_start{addr >> Tegra::MemoryManager::PAGE_BITS};
|
||||
const u64 page_end{(addr + size) >> Tegra::MemoryManager::PAGE_BITS};
|
||||
|
||||
// Interval maps will erase segments if count reaches 0, so if delta is negative we have to
|
||||
// subtract after iterating
|
||||
const auto pages_interval = PageMap::interval_type::right_open(page_start, page_end);
|
||||
if (delta > 0)
|
||||
cached_pages.add({pages_interval, delta});
|
||||
|
||||
for (const auto& pair : RangeFromInterval(cached_pages, pages_interval)) {
|
||||
const auto interval = pair.first & pages_interval;
|
||||
const int count = pair.second;
|
||||
|
||||
const Tegra::GPUVAddr interval_start_addr = boost::icl::first(interval)
|
||||
<< Tegra::MemoryManager::PAGE_BITS;
|
||||
const Tegra::GPUVAddr interval_end_addr = boost::icl::last_next(interval)
|
||||
<< Tegra::MemoryManager::PAGE_BITS;
|
||||
const u64 interval_size = interval_end_addr - interval_start_addr;
|
||||
|
||||
if (delta > 0 && count == delta)
|
||||
Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, true);
|
||||
else if (delta < 0 && count == -delta)
|
||||
Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, false);
|
||||
else
|
||||
ASSERT(count >= 0);
|
||||
}
|
||||
|
||||
if (delta < 0)
|
||||
cached_pages.add({pages_interval, delta});
|
||||
}
|
||||
|
||||
std::unordered_map<Tegra::GPUVAddr, T> cached_objects;
|
||||
PageMap cached_pages;
|
||||
};
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/math_util.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
@@ -177,19 +178,6 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr,
|
||||
return {array_ptr, buffer_offset};
|
||||
}
|
||||
|
||||
static GLShader::ProgramCode GetShaderProgramCode(Maxwell::ShaderProgram program) {
|
||||
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
|
||||
// Fetch program code from memory
|
||||
GLShader::ProgramCode program_code;
|
||||
auto& shader_config = gpu.regs.shader_config[static_cast<size_t>(program)];
|
||||
const u64 gpu_address{gpu.regs.code_address.CodeAddress() + shader_config.offset};
|
||||
const boost::optional<VAddr> cpu_address{gpu.memory_manager.GpuToCpuAddress(gpu_address)};
|
||||
Memory::ReadBlock(*cpu_address, program_code.data(), program_code.size() * sizeof(u64));
|
||||
|
||||
return program_code;
|
||||
}
|
||||
|
||||
std::pair<u8*, GLintptr> RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr buffer_offset) {
|
||||
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
|
||||
@@ -223,31 +211,16 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr
|
||||
buffer_ptr += sizeof(ubo);
|
||||
buffer_offset += sizeof(ubo);
|
||||
|
||||
GLShader::ShaderSetup setup{GetShaderProgramCode(program)};
|
||||
GLShader::ShaderEntries shader_resources;
|
||||
Shader shader{shader_cache.GetStageProgram(program)};
|
||||
|
||||
switch (program) {
|
||||
case Maxwell::ShaderProgram::VertexA: {
|
||||
// VertexB is always enabled, so when VertexA is enabled, we have two vertex shaders.
|
||||
// Conventional HW does not support this, so we combine VertexA and VertexB into one
|
||||
// stage here.
|
||||
setup.SetProgramB(GetShaderProgramCode(Maxwell::ShaderProgram::VertexB));
|
||||
GLShader::MaxwellVSConfig vs_config{setup};
|
||||
shader_resources =
|
||||
shader_program_manager->UseProgrammableVertexShader(vs_config, setup);
|
||||
break;
|
||||
}
|
||||
|
||||
case Maxwell::ShaderProgram::VertexA:
|
||||
case Maxwell::ShaderProgram::VertexB: {
|
||||
GLShader::MaxwellVSConfig vs_config{setup};
|
||||
shader_resources =
|
||||
shader_program_manager->UseProgrammableVertexShader(vs_config, setup);
|
||||
shader_program_manager->UseProgrammableVertexShader(shader->GetProgramHandle());
|
||||
break;
|
||||
}
|
||||
case Maxwell::ShaderProgram::Fragment: {
|
||||
GLShader::MaxwellFSConfig fs_config{setup};
|
||||
shader_resources =
|
||||
shader_program_manager->UseProgrammableFragmentShader(fs_config, setup);
|
||||
shader_program_manager->UseProgrammableFragmentShader(shader->GetProgramHandle());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -256,18 +229,14 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
GLuint gl_stage_program = shader_program_manager->GetCurrentProgramStage(
|
||||
static_cast<Maxwell::ShaderStage>(stage));
|
||||
|
||||
// Configure the const buffers for this shader stage.
|
||||
std::tie(buffer_ptr, buffer_offset, current_constbuffer_bindpoint) = SetupConstBuffers(
|
||||
buffer_ptr, buffer_offset, static_cast<Maxwell::ShaderStage>(stage), gl_stage_program,
|
||||
current_constbuffer_bindpoint, shader_resources.const_buffer_entries);
|
||||
std::tie(buffer_ptr, buffer_offset, current_constbuffer_bindpoint) =
|
||||
SetupConstBuffers(buffer_ptr, buffer_offset, static_cast<Maxwell::ShaderStage>(stage),
|
||||
shader, current_constbuffer_bindpoint);
|
||||
|
||||
// Configure the textures for this shader stage.
|
||||
current_texture_bindpoint =
|
||||
SetupTextures(static_cast<Maxwell::ShaderStage>(stage), gl_stage_program,
|
||||
current_texture_bindpoint, shader_resources.texture_samplers);
|
||||
current_texture_bindpoint = SetupTextures(static_cast<Maxwell::ShaderStage>(stage), shader,
|
||||
current_texture_bindpoint);
|
||||
|
||||
// When VertexA is enabled, we have dual vertex shaders
|
||||
if (program == Maxwell::ShaderProgram::VertexA) {
|
||||
@@ -315,16 +284,14 @@ std::pair<Surface, Surface> RasterizerOpenGL::ConfigureFramebuffers(bool using_c
|
||||
using_color_fb = false;
|
||||
}
|
||||
|
||||
// TODO(bunnei): Implement this
|
||||
const bool has_stencil = false;
|
||||
|
||||
const bool has_stencil = regs.stencil_enable;
|
||||
const bool write_color_fb =
|
||||
state.color_mask.red_enabled == GL_TRUE || state.color_mask.green_enabled == GL_TRUE ||
|
||||
state.color_mask.blue_enabled == GL_TRUE || state.color_mask.alpha_enabled == GL_TRUE;
|
||||
|
||||
const bool write_depth_fb =
|
||||
(state.depth.test_enabled && state.depth.write_mask == GL_TRUE) ||
|
||||
(has_stencil && state.stencil.test_enabled && state.stencil.write_mask != 0);
|
||||
(has_stencil && (state.stencil.front.write_mask || state.stencil.back.write_mask));
|
||||
|
||||
Surface color_surface;
|
||||
Surface depth_surface;
|
||||
@@ -364,41 +331,70 @@ std::pair<Surface, Surface> RasterizerOpenGL::ConfigureFramebuffers(bool using_c
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::Clear() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
const auto prev_state{state};
|
||||
SCOPE_EXIT({ prev_state.Apply(); });
|
||||
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
bool use_color_fb = false;
|
||||
bool use_depth_fb = false;
|
||||
|
||||
GLbitfield clear_mask = 0;
|
||||
if (regs.clear_buffers.R && regs.clear_buffers.G && regs.clear_buffers.B &&
|
||||
OpenGLState clear_state;
|
||||
clear_state.draw.draw_framebuffer = state.draw.draw_framebuffer;
|
||||
clear_state.color_mask.red_enabled = regs.clear_buffers.R ? GL_TRUE : GL_FALSE;
|
||||
clear_state.color_mask.green_enabled = regs.clear_buffers.G ? GL_TRUE : GL_FALSE;
|
||||
clear_state.color_mask.blue_enabled = regs.clear_buffers.B ? GL_TRUE : GL_FALSE;
|
||||
clear_state.color_mask.alpha_enabled = regs.clear_buffers.A ? GL_TRUE : GL_FALSE;
|
||||
|
||||
GLbitfield clear_mask{};
|
||||
if (regs.clear_buffers.R || regs.clear_buffers.G || regs.clear_buffers.B ||
|
||||
regs.clear_buffers.A) {
|
||||
clear_mask |= GL_COLOR_BUFFER_BIT;
|
||||
use_color_fb = true;
|
||||
if (regs.clear_buffers.RT == 0) {
|
||||
// We only support clearing the first color attachment for now
|
||||
clear_mask |= GL_COLOR_BUFFER_BIT;
|
||||
use_color_fb = true;
|
||||
} else {
|
||||
// TODO(subv): Add support for the other color attachments
|
||||
LOG_CRITICAL(HW_GPU, "Clear unimplemented for RT {}", regs.clear_buffers.RT);
|
||||
}
|
||||
}
|
||||
if (regs.clear_buffers.Z) {
|
||||
ASSERT_MSG(regs.zeta_enable != 0, "Tried to clear Z but buffer is not enabled!");
|
||||
use_depth_fb = true;
|
||||
clear_mask |= GL_DEPTH_BUFFER_BIT;
|
||||
use_depth_fb = regs.zeta_enable != 0;
|
||||
|
||||
// Always enable the depth write when clearing the depth buffer. The depth write mask is
|
||||
// ignored when clearing the buffer in the Switch, but OpenGL obeys it so we set it to true.
|
||||
state.depth.test_enabled = true;
|
||||
state.depth.write_mask = GL_TRUE;
|
||||
state.depth.test_func = GL_ALWAYS;
|
||||
state.Apply();
|
||||
clear_state.depth.test_enabled = true;
|
||||
clear_state.depth.test_func = GL_ALWAYS;
|
||||
}
|
||||
if (regs.clear_buffers.S) {
|
||||
ASSERT_MSG(regs.zeta_enable != 0, "Tried to clear stencil but buffer is not enabled!");
|
||||
use_depth_fb = true;
|
||||
clear_mask |= GL_STENCIL_BUFFER_BIT;
|
||||
clear_state.stencil.test_enabled = true;
|
||||
}
|
||||
|
||||
if (clear_mask == 0)
|
||||
if (!use_color_fb && !use_depth_fb) {
|
||||
// No color surface nor depth/stencil surface are enabled
|
||||
return;
|
||||
}
|
||||
|
||||
if (clear_mask == 0) {
|
||||
// No clear mask is enabled
|
||||
return;
|
||||
}
|
||||
|
||||
ScopeAcquireGLContext acquire_context{emu_window};
|
||||
|
||||
auto [dirty_color_surface, dirty_depth_surface] =
|
||||
ConfigureFramebuffers(use_color_fb, use_depth_fb, false);
|
||||
|
||||
// TODO(Subv): Support clearing only partial colors.
|
||||
clear_state.Apply();
|
||||
|
||||
glClearColor(regs.clear_color[0], regs.clear_color[1], regs.clear_color[2],
|
||||
regs.clear_color[3]);
|
||||
glClearDepth(regs.clear_depth);
|
||||
glClearStencil(regs.clear_stencil);
|
||||
|
||||
glClear(clear_mask);
|
||||
|
||||
@@ -428,8 +424,8 @@ std::tuple<u8*, GLintptr, GLintptr> RasterizerOpenGL::UploadMemory(u8* buffer_pt
|
||||
std::tie(buffer_ptr, buffer_offset) = AlignBuffer(buffer_ptr, buffer_offset, alignment);
|
||||
GLintptr uploaded_offset = buffer_offset;
|
||||
|
||||
const auto& memory_manager = Core::System::GetInstance().GPU().memory_manager;
|
||||
const boost::optional<VAddr> cpu_addr{memory_manager->GpuToCpuAddress(gpu_addr)};
|
||||
auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
|
||||
const boost::optional<VAddr> cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
|
||||
Memory::ReadBlock(*cpu_addr, buffer_ptr, size);
|
||||
|
||||
buffer_ptr += size;
|
||||
@@ -451,6 +447,7 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
ConfigureFramebuffers(true, regs.zeta.Address() != 0 && regs.zeta_enable != 0, true);
|
||||
|
||||
SyncDepthTestState();
|
||||
SyncStencilTestState();
|
||||
SyncBlendState();
|
||||
SyncLogicOpState();
|
||||
SyncCullMode();
|
||||
@@ -461,7 +458,6 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
// Draw the vertex batch
|
||||
const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
|
||||
const u64 index_buffer_size{regs.index_array.count * regs.index_array.FormatSizeInBytes()};
|
||||
const unsigned vertex_num{is_indexed ? regs.index_array.count : regs.vertex_buffer.count};
|
||||
|
||||
state.draw.vertex_buffer = stream_buffer.GetHandle();
|
||||
state.Apply();
|
||||
@@ -542,23 +538,21 @@ void RasterizerOpenGL::NotifyMaxwellRegisterChanged(u32 method) {}
|
||||
|
||||
void RasterizerOpenGL::FlushAll() {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
res_cache.FlushRegion(0, Kernel::VMManager::MAX_ADDRESS);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::FlushRegion(Tegra::GPUVAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
res_cache.FlushRegion(addr, size);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::InvalidateRegion(Tegra::GPUVAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
res_cache.InvalidateRegion(addr, size);
|
||||
shader_cache.InvalidateRegion(addr, size);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::FlushAndInvalidateRegion(Tegra::GPUVAddr addr, u64 size) {
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
res_cache.FlushRegion(addr, size);
|
||||
res_cache.InvalidateRegion(addr, size);
|
||||
InvalidateRegion(addr, size);
|
||||
}
|
||||
|
||||
bool RasterizerOpenGL::AccelerateDisplayTransfer(const void* config) {
|
||||
@@ -643,15 +637,17 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<u8*, GLintptr, u32> RasterizerOpenGL::SetupConstBuffers(
|
||||
u8* buffer_ptr, GLintptr buffer_offset, Maxwell::ShaderStage stage, GLuint program,
|
||||
u32 current_bindpoint, const std::vector<GLShader::ConstBufferEntry>& entries) {
|
||||
std::tuple<u8*, GLintptr, u32> RasterizerOpenGL::SetupConstBuffers(u8* buffer_ptr,
|
||||
GLintptr buffer_offset,
|
||||
Maxwell::ShaderStage stage,
|
||||
Shader& shader,
|
||||
u32 current_bindpoint) {
|
||||
const auto& gpu = Core::System::GetInstance().GPU();
|
||||
const auto& maxwell3d = gpu.Maxwell3D();
|
||||
const auto& shader_stage = maxwell3d.state.shader_stages[static_cast<size_t>(stage)];
|
||||
const auto& entries = shader->GetShaderEntries().const_buffer_entries;
|
||||
|
||||
// Upload only the enabled buffers from the 16 constbuffers of each shader stage
|
||||
const auto& shader_stage = maxwell3d.state.shader_stages[static_cast<size_t>(stage)];
|
||||
|
||||
for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
|
||||
const auto& used_buffer = entries[bindpoint];
|
||||
const auto& buffer = shader_stage.const_buffers[used_buffer.GetIndex()];
|
||||
@@ -690,12 +686,9 @@ std::tuple<u8*, GLintptr, u32> RasterizerOpenGL::SetupConstBuffers(
|
||||
stream_buffer.GetHandle(), const_buffer_offset, size);
|
||||
|
||||
// Now configure the bindpoint of the buffer inside the shader
|
||||
const std::string buffer_name = used_buffer.GetName();
|
||||
const GLuint index =
|
||||
glGetProgramResourceIndex(program, GL_UNIFORM_BLOCK, buffer_name.c_str());
|
||||
if (index != GL_INVALID_INDEX) {
|
||||
glUniformBlockBinding(program, index, current_bindpoint + bindpoint);
|
||||
}
|
||||
glUniformBlockBinding(shader->GetProgramHandle(),
|
||||
shader->GetProgramResourceIndex(used_buffer.GetName()),
|
||||
current_bindpoint + bindpoint);
|
||||
}
|
||||
|
||||
state.Apply();
|
||||
@@ -703,10 +696,10 @@ std::tuple<u8*, GLintptr, u32> RasterizerOpenGL::SetupConstBuffers(
|
||||
return {buffer_ptr, buffer_offset, current_bindpoint + static_cast<u32>(entries.size())};
|
||||
}
|
||||
|
||||
u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, GLuint program, u32 current_unit,
|
||||
const std::vector<GLShader::SamplerEntry>& entries) {
|
||||
u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader, u32 current_unit) {
|
||||
const auto& gpu = Core::System::GetInstance().GPU();
|
||||
const auto& maxwell3d = gpu.Maxwell3D();
|
||||
const auto& entries = shader->GetShaderEntries().texture_samplers;
|
||||
|
||||
ASSERT_MSG(current_unit + entries.size() <= std::size(state.texture_units),
|
||||
"Exceeded the number of active textures.");
|
||||
@@ -716,12 +709,9 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, GLuint program,
|
||||
u32 current_bindpoint = current_unit + bindpoint;
|
||||
|
||||
// Bind the uniform to the sampler.
|
||||
GLint uniform = glGetUniformLocation(program, entry.GetName().c_str());
|
||||
if (uniform == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
glProgramUniform1i(program, uniform, current_bindpoint);
|
||||
glProgramUniform1i(shader->GetProgramHandle(), shader->GetUniformLocation(entry.GetName()),
|
||||
current_bindpoint);
|
||||
|
||||
const auto texture = maxwell3d.GetStageTexture(entry.GetStage(), entry.GetOffset());
|
||||
|
||||
@@ -841,6 +831,34 @@ void RasterizerOpenGL::SyncDepthTestState() {
|
||||
state.depth.test_func = MaxwellToGL::ComparisonOp(regs.depth_test_func);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncStencilTestState() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
state.stencil.test_enabled = regs.stencil_enable != 0;
|
||||
|
||||
if (!regs.stencil_enable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(bunnei): Verify behavior when this is not set
|
||||
ASSERT(regs.stencil_two_side_enable);
|
||||
|
||||
state.stencil.front.test_func = MaxwellToGL::ComparisonOp(regs.stencil_front_func_func);
|
||||
state.stencil.front.test_ref = regs.stencil_front_func_ref;
|
||||
state.stencil.front.test_mask = regs.stencil_front_func_mask;
|
||||
state.stencil.front.action_stencil_fail = MaxwellToGL::StencilOp(regs.stencil_front_op_fail);
|
||||
state.stencil.front.action_depth_fail = MaxwellToGL::StencilOp(regs.stencil_front_op_zfail);
|
||||
state.stencil.front.action_depth_pass = MaxwellToGL::StencilOp(regs.stencil_front_op_zpass);
|
||||
state.stencil.front.write_mask = regs.stencil_front_mask;
|
||||
|
||||
state.stencil.back.test_func = MaxwellToGL::ComparisonOp(regs.stencil_back_func_func);
|
||||
state.stencil.back.test_ref = regs.stencil_back_func_ref;
|
||||
state.stencil.back.test_mask = regs.stencil_back_func_mask;
|
||||
state.stencil.back.action_stencil_fail = MaxwellToGL::StencilOp(regs.stencil_back_op_fail);
|
||||
state.stencil.back.action_depth_fail = MaxwellToGL::StencilOp(regs.stencil_back_op_zfail);
|
||||
state.stencil.back.action_depth_pass = MaxwellToGL::StencilOp(regs.stencil_back_op_zpass);
|
||||
state.stencil.back.write_mask = regs.stencil_back_mask;
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncBlendState() {
|
||||
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
|
||||
|
||||
@@ -872,7 +890,8 @@ void RasterizerOpenGL::SyncLogicOpState() {
|
||||
if (!state.logic_op.enabled)
|
||||
return;
|
||||
|
||||
ASSERT_MSG(regs.blend.enable == 0, "Blending and logic op can't be enabled at the same time.");
|
||||
ASSERT_MSG(regs.blend.enable[0] == 0,
|
||||
"Blending and logic op can't be enabled at the same time.");
|
||||
|
||||
state.logic_op.operation = MaxwellToGL::LogicOp(regs.logic_op.operation);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_gen.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
@@ -99,26 +100,23 @@ private:
|
||||
/*
|
||||
* Configures the current constbuffers to use for the draw command.
|
||||
* @param stage The shader stage to configure buffers for.
|
||||
* @param program The OpenGL program object that contains the specified stage.
|
||||
* @param shader The shader object that contains the specified stage.
|
||||
* @param current_bindpoint The offset at which to start counting new buffer bindpoints.
|
||||
* @param entries Vector describing the buffers that are actually used in the guest shader.
|
||||
* @returns The next available bindpoint for use in the next shader stage.
|
||||
*/
|
||||
std::tuple<u8*, GLintptr, u32> SetupConstBuffers(
|
||||
u8* buffer_ptr, GLintptr buffer_offset, Tegra::Engines::Maxwell3D::Regs::ShaderStage stage,
|
||||
GLuint program, u32 current_bindpoint,
|
||||
const std::vector<GLShader::ConstBufferEntry>& entries);
|
||||
Shader& shader, u32 current_bindpoint);
|
||||
|
||||
/*
|
||||
* Configures the current textures to use for the draw command.
|
||||
* @param stage The shader stage to configure textures for.
|
||||
* @param program The OpenGL program object that contains the specified stage.
|
||||
* @param shader The shader object that contains the specified stage.
|
||||
* @param current_unit The offset at which to start counting unused texture units.
|
||||
* @param entries Vector describing the textures that are actually used in the guest shader.
|
||||
* @returns The next available bindpoint for use in the next shader stage.
|
||||
*/
|
||||
u32 SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, GLuint program,
|
||||
u32 current_unit, const std::vector<GLShader::SamplerEntry>& entries);
|
||||
u32 SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, Shader& shader,
|
||||
u32 current_unit);
|
||||
|
||||
/// Syncs the viewport to match the guest state
|
||||
void SyncViewport(const MathUtil::Rectangle<u32>& surfaces_rect);
|
||||
@@ -141,6 +139,9 @@ private:
|
||||
/// Syncs the depth test state to match the guest state
|
||||
void SyncDepthTestState();
|
||||
|
||||
/// Syncs the stencil test state to match the guest state
|
||||
void SyncStencilTestState();
|
||||
|
||||
/// Syncs the blend state to match the guest state
|
||||
void SyncBlendState();
|
||||
|
||||
@@ -154,6 +155,7 @@ private:
|
||||
OpenGLState state;
|
||||
|
||||
RasterizerCacheOpenGL res_cache;
|
||||
ShaderCacheOpenGL shader_cache;
|
||||
|
||||
Core::Frontend::EmuWindow& emu_window;
|
||||
|
||||
|
||||
@@ -168,8 +168,8 @@ static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType
|
||||
}
|
||||
|
||||
VAddr SurfaceParams::GetCpuAddr() const {
|
||||
const auto& gpu = Core::System::GetInstance().GPU();
|
||||
return *gpu.memory_manager->GpuToCpuAddress(addr);
|
||||
auto& gpu = Core::System::GetInstance().GPU();
|
||||
return *gpu.MemoryManager().GpuToCpuAddress(addr);
|
||||
}
|
||||
|
||||
static bool IsPixelFormatASTC(PixelFormat format) {
|
||||
@@ -220,14 +220,14 @@ void MortonCopy(u32 stride, u32 block_height, u32 height, std::vector<u8>& gl_bu
|
||||
Tegra::GPUVAddr addr) {
|
||||
constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / CHAR_BIT;
|
||||
constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format);
|
||||
const auto& gpu = Core::System::GetInstance().GPU();
|
||||
auto& gpu = Core::System::GetInstance().GPU();
|
||||
|
||||
if (morton_to_gl) {
|
||||
// With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
|
||||
// pixel values.
|
||||
const u32 tile_size{IsFormatBCn(format) ? 4U : 1U};
|
||||
const std::vector<u8> data =
|
||||
Tegra::Texture::UnswizzleTexture(*gpu.memory_manager->GpuToCpuAddress(addr), tile_size,
|
||||
Tegra::Texture::UnswizzleTexture(*gpu.MemoryManager().GpuToCpuAddress(addr), tile_size,
|
||||
bytes_per_pixel, stride, height, block_height);
|
||||
const size_t size_to_copy{std::min(gl_buffer.size(), data.size())};
|
||||
gl_buffer.assign(data.begin(), data.begin() + size_to_copy);
|
||||
@@ -237,7 +237,7 @@ void MortonCopy(u32 stride, u32 block_height, u32 height, std::vector<u8>& gl_bu
|
||||
LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!");
|
||||
VideoCore::MortonCopyPixels128(
|
||||
stride, height, bytes_per_pixel, gl_bytes_per_pixel,
|
||||
Memory::GetPointer(*gpu.memory_manager->GpuToCpuAddress(addr)), gl_buffer.data(),
|
||||
Memory::GetPointer(*gpu.MemoryManager().GpuToCpuAddress(addr)), gl_buffer.data(),
|
||||
morton_to_gl);
|
||||
}
|
||||
}
|
||||
@@ -677,12 +677,6 @@ RasterizerCacheOpenGL::RasterizerCacheOpenGL() {
|
||||
draw_framebuffer.Create();
|
||||
}
|
||||
|
||||
RasterizerCacheOpenGL::~RasterizerCacheOpenGL() {
|
||||
while (!surface_cache.empty()) {
|
||||
UnregisterSurface(surface_cache.begin()->second);
|
||||
}
|
||||
}
|
||||
|
||||
Surface RasterizerCacheOpenGL::GetTextureSurface(const Tegra::Texture::FullTextureInfo& config) {
|
||||
return GetSurface(SurfaceParams::CreateForTexture(config));
|
||||
}
|
||||
@@ -760,37 +754,48 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool pres
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto& gpu = Core::System::GetInstance().GPU();
|
||||
auto& gpu = Core::System::GetInstance().GPU();
|
||||
// Don't try to create any entries in the cache if the address of the texture is invalid.
|
||||
if (gpu.memory_manager->GpuToCpuAddress(params.addr) == boost::none)
|
||||
if (gpu.MemoryManager().GpuToCpuAddress(params.addr) == boost::none)
|
||||
return {};
|
||||
|
||||
// Look up surface in the cache based on address
|
||||
const auto& search{surface_cache.find(params.addr)};
|
||||
Surface surface;
|
||||
if (search != surface_cache.end()) {
|
||||
surface = search->second;
|
||||
Surface surface{TryGet(params.addr)};
|
||||
if (surface) {
|
||||
if (Settings::values.use_accurate_framebuffers) {
|
||||
// If use_accurate_framebuffers is enabled, always load from memory
|
||||
FlushSurface(surface);
|
||||
UnregisterSurface(surface);
|
||||
Unregister(surface);
|
||||
} else if (surface->GetSurfaceParams().IsCompatibleSurface(params)) {
|
||||
// Use the cached surface as-is
|
||||
return surface;
|
||||
} else if (preserve_contents) {
|
||||
// If surface parameters changed and we care about keeping the previous data, recreate
|
||||
// the surface from the old one
|
||||
return RecreateSurface(surface, params);
|
||||
Unregister(surface);
|
||||
Surface new_surface{RecreateSurface(surface, params)};
|
||||
Register(new_surface);
|
||||
return new_surface;
|
||||
} else {
|
||||
// Delete the old surface before creating a new one to prevent collisions.
|
||||
UnregisterSurface(surface);
|
||||
Unregister(surface);
|
||||
}
|
||||
}
|
||||
|
||||
// Try to get a previously reserved surface
|
||||
surface = TryGetReservedSurface(params);
|
||||
|
||||
// No surface found - create a new one
|
||||
surface = std::make_shared<CachedSurface>(params);
|
||||
RegisterSurface(surface);
|
||||
LoadSurface(surface);
|
||||
if (!surface) {
|
||||
surface = std::make_shared<CachedSurface>(params);
|
||||
ReserveSurface(surface);
|
||||
Register(surface);
|
||||
}
|
||||
|
||||
// Only load surface from memory if we care about the contents
|
||||
if (preserve_contents) {
|
||||
LoadSurface(surface);
|
||||
}
|
||||
|
||||
return surface;
|
||||
}
|
||||
@@ -799,13 +804,18 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface,
|
||||
const SurfaceParams& new_params) {
|
||||
// Verify surface is compatible for blitting
|
||||
const auto& params{surface->GetSurfaceParams()};
|
||||
ASSERT(params.type == new_params.type);
|
||||
ASSERT_MSG(params.GetCompressionFactor(params.pixel_format) == 1,
|
||||
"Compressed texture reinterpretation is not supported");
|
||||
|
||||
// Create a new surface with the new parameters, and blit the previous surface to it
|
||||
Surface new_surface{std::make_shared<CachedSurface>(new_params)};
|
||||
|
||||
// If format is unchanged, we can do a faster blit without reinterpreting pixel data
|
||||
if (params.pixel_format == new_params.pixel_format) {
|
||||
BlitTextures(surface->Texture().handle, params.GetRect(), new_surface->Texture().handle,
|
||||
new_surface->GetSurfaceParams().GetRect(), params.type,
|
||||
read_framebuffer.handle, draw_framebuffer.handle);
|
||||
return new_surface;
|
||||
}
|
||||
|
||||
auto source_format = GetFormatTuple(params.pixel_format, params.component_type);
|
||||
auto dest_format = GetFormatTuple(new_params.pixel_format, new_params.component_type);
|
||||
|
||||
@@ -818,9 +828,13 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface,
|
||||
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo.handle);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW_ARB);
|
||||
glGetTextureImage(surface->Texture().handle, 0, source_format.format, source_format.type,
|
||||
params.SizeInBytes(), nullptr);
|
||||
|
||||
if (source_format.compressed) {
|
||||
glGetCompressedTextureImage(surface->Texture().handle, 0,
|
||||
static_cast<GLsizei>(params.SizeInBytes()), nullptr);
|
||||
} else {
|
||||
glGetTextureImage(surface->Texture().handle, 0, source_format.format, source_format.type,
|
||||
static_cast<GLsizei>(params.SizeInBytes()), nullptr);
|
||||
}
|
||||
// If the new texture is bigger than the previous one, we need to fill in the rest with data
|
||||
// from the CPU.
|
||||
if (params.SizeInBytes() < new_params.SizeInBytes()) {
|
||||
@@ -834,7 +848,7 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface,
|
||||
"reinterpretation but the texture is tiled.");
|
||||
}
|
||||
size_t remaining_size = new_params.SizeInBytes() - params.SizeInBytes();
|
||||
auto address = Core::System::GetInstance().GPU().memory_manager->GpuToCpuAddress(
|
||||
auto address = Core::System::GetInstance().GPU().MemoryManager().GpuToCpuAddress(
|
||||
new_params.addr + params.SizeInBytes());
|
||||
std::vector<u8> data(remaining_size);
|
||||
Memory::ReadBlock(*address, data.data(), data.size());
|
||||
@@ -846,17 +860,21 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface,
|
||||
const auto& dest_rect{new_params.GetRect()};
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo.handle);
|
||||
glTextureSubImage2D(
|
||||
new_surface->Texture().handle, 0, 0, 0, static_cast<GLsizei>(dest_rect.GetWidth()),
|
||||
static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format, dest_format.type, nullptr);
|
||||
if (dest_format.compressed) {
|
||||
glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
|
||||
static_cast<GLsizei>(dest_rect.GetWidth()),
|
||||
static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format,
|
||||
static_cast<GLsizei>(new_params.SizeInBytes()), nullptr);
|
||||
} else {
|
||||
glTextureSubImage2D(new_surface->Texture().handle, 0, 0, 0,
|
||||
static_cast<GLsizei>(dest_rect.GetWidth()),
|
||||
static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format,
|
||||
dest_format.type, nullptr);
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
pbo.Release();
|
||||
|
||||
// Update cache accordingly
|
||||
UnregisterSurface(surface);
|
||||
RegisterSurface(new_surface);
|
||||
|
||||
return new_surface;
|
||||
}
|
||||
|
||||
@@ -868,7 +886,7 @@ Surface RasterizerCacheOpenGL::TryFindFramebufferSurface(VAddr cpu_addr) const {
|
||||
// framebuffer overlaps surfaces.
|
||||
|
||||
std::vector<Surface> surfaces;
|
||||
for (const auto& surface : surface_cache) {
|
||||
for (const auto& surface : GetCache()) {
|
||||
const auto& params = surface.second->GetSurfaceParams();
|
||||
const VAddr surface_cpu_addr = params.GetCpuAddr();
|
||||
if (cpu_addr >= surface_cpu_addr && cpu_addr < (surface_cpu_addr + params.size_in_bytes)) {
|
||||
@@ -886,88 +904,19 @@ Surface RasterizerCacheOpenGL::TryFindFramebufferSurface(VAddr cpu_addr) const {
|
||||
return surfaces[0];
|
||||
}
|
||||
|
||||
void RasterizerCacheOpenGL::FlushRegion(Tegra::GPUVAddr /*addr*/, size_t /*size*/) {
|
||||
// TODO(bunnei): This is unused in the current implementation of the rasterizer cache. We should
|
||||
// probably implement this in the future, but for now, the `use_accurate_framebufers` setting
|
||||
// can be used to always flush.
|
||||
void RasterizerCacheOpenGL::ReserveSurface(const Surface& surface) {
|
||||
const auto& surface_reserve_key{SurfaceReserveKey::Create(surface->GetSurfaceParams())};
|
||||
surface_reserve[surface_reserve_key] = surface;
|
||||
}
|
||||
|
||||
void RasterizerCacheOpenGL::InvalidateRegion(Tegra::GPUVAddr addr, size_t size) {
|
||||
for (auto iter = surface_cache.cbegin(); iter != surface_cache.cend();) {
|
||||
const auto& surface{iter->second};
|
||||
const auto& params{surface->GetSurfaceParams()};
|
||||
|
||||
++iter;
|
||||
|
||||
if (params.IsOverlappingRegion(addr, size)) {
|
||||
UnregisterSurface(surface);
|
||||
}
|
||||
Surface RasterizerCacheOpenGL::TryGetReservedSurface(const SurfaceParams& params) {
|
||||
const auto& surface_reserve_key{SurfaceReserveKey::Create(params)};
|
||||
auto search{surface_reserve.find(surface_reserve_key)};
|
||||
if (search != surface_reserve.end()) {
|
||||
Register(search->second);
|
||||
return search->second;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerCacheOpenGL::RegisterSurface(const Surface& surface) {
|
||||
const auto& params{surface->GetSurfaceParams()};
|
||||
const auto& search{surface_cache.find(params.addr)};
|
||||
|
||||
if (search != surface_cache.end()) {
|
||||
// Registered already
|
||||
return;
|
||||
}
|
||||
|
||||
surface_cache[params.addr] = surface;
|
||||
UpdatePagesCachedCount(params.addr, params.size_in_bytes, 1);
|
||||
}
|
||||
|
||||
void RasterizerCacheOpenGL::UnregisterSurface(const Surface& surface) {
|
||||
const auto& params{surface->GetSurfaceParams()};
|
||||
const auto& search{surface_cache.find(params.addr)};
|
||||
|
||||
if (search == surface_cache.end()) {
|
||||
// Unregistered already
|
||||
return;
|
||||
}
|
||||
|
||||
UpdatePagesCachedCount(params.addr, params.size_in_bytes, -1);
|
||||
surface_cache.erase(search);
|
||||
}
|
||||
|
||||
template <typename Map, typename Interval>
|
||||
constexpr auto RangeFromInterval(Map& map, const Interval& interval) {
|
||||
return boost::make_iterator_range(map.equal_range(interval));
|
||||
}
|
||||
|
||||
void RasterizerCacheOpenGL::UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) {
|
||||
const u64 num_pages = ((addr + size - 1) >> Tegra::MemoryManager::PAGE_BITS) -
|
||||
(addr >> Tegra::MemoryManager::PAGE_BITS) + 1;
|
||||
const u64 page_start = addr >> Tegra::MemoryManager::PAGE_BITS;
|
||||
const u64 page_end = page_start + num_pages;
|
||||
|
||||
// Interval maps will erase segments if count reaches 0, so if delta is negative we have to
|
||||
// subtract after iterating
|
||||
const auto pages_interval = PageMap::interval_type::right_open(page_start, page_end);
|
||||
if (delta > 0)
|
||||
cached_pages.add({pages_interval, delta});
|
||||
|
||||
for (const auto& pair : RangeFromInterval(cached_pages, pages_interval)) {
|
||||
const auto interval = pair.first & pages_interval;
|
||||
const int count = pair.second;
|
||||
|
||||
const Tegra::GPUVAddr interval_start_addr = boost::icl::first(interval)
|
||||
<< Tegra::MemoryManager::PAGE_BITS;
|
||||
const Tegra::GPUVAddr interval_end_addr = boost::icl::last_next(interval)
|
||||
<< Tegra::MemoryManager::PAGE_BITS;
|
||||
const u64 interval_size = interval_end_addr - interval_start_addr;
|
||||
|
||||
if (delta > 0 && count == delta)
|
||||
Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, true);
|
||||
else if (delta < 0 && count == -delta)
|
||||
Memory::RasterizerMarkRegionCached(interval_start_addr, interval_size, false);
|
||||
else
|
||||
ASSERT(count >= 0);
|
||||
}
|
||||
|
||||
if (delta < 0)
|
||||
cached_pages.add({pages_interval, delta});
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -8,11 +8,12 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/hash.h"
|
||||
#include "common/math_util.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/rasterizer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
|
||||
@@ -21,7 +22,6 @@ namespace OpenGL {
|
||||
class CachedSurface;
|
||||
using Surface = std::shared_ptr<CachedSurface>;
|
||||
using SurfaceSurfaceRect_Tuple = std::tuple<Surface, Surface, MathUtil::Rectangle<u32>>;
|
||||
using PageMap = boost::icl::interval_map<u64, int>;
|
||||
|
||||
struct SurfaceParams {
|
||||
enum class PixelFormat {
|
||||
@@ -631,11 +631,6 @@ struct SurfaceParams {
|
||||
/// Returns the CPU virtual address for this surface
|
||||
VAddr GetCpuAddr() const;
|
||||
|
||||
/// Returns true if the specified region overlaps with this surface's region in Switch memory
|
||||
bool IsOverlappingRegion(Tegra::GPUVAddr region_addr, size_t region_size) const {
|
||||
return addr <= (region_addr + region_size) && region_addr <= (addr + size_in_bytes);
|
||||
}
|
||||
|
||||
/// Creates SurfaceParams from a texture configuration
|
||||
static SurfaceParams CreateForTexture(const Tegra::Texture::FullTextureInfo& config);
|
||||
|
||||
@@ -682,10 +677,39 @@ struct SurfaceParams {
|
||||
u32 cache_height;
|
||||
};
|
||||
|
||||
}; // namespace OpenGL
|
||||
|
||||
/// Hashable variation of SurfaceParams, used for a key in the surface cache
|
||||
struct SurfaceReserveKey : Common::HashableStruct<OpenGL::SurfaceParams> {
|
||||
static SurfaceReserveKey Create(const OpenGL::SurfaceParams& params) {
|
||||
SurfaceReserveKey res;
|
||||
res.state = params;
|
||||
return res;
|
||||
}
|
||||
};
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<SurfaceReserveKey> {
|
||||
size_t operator()(const SurfaceReserveKey& k) const {
|
||||
return k.Hash();
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class CachedSurface final {
|
||||
public:
|
||||
CachedSurface(const SurfaceParams& params);
|
||||
|
||||
Tegra::GPUVAddr GetAddr() const {
|
||||
return params.addr;
|
||||
}
|
||||
|
||||
size_t GetSizeInBytes() const {
|
||||
return params.size_in_bytes;
|
||||
}
|
||||
|
||||
const OGLTexture& Texture() const {
|
||||
return texture;
|
||||
}
|
||||
@@ -715,10 +739,9 @@ private:
|
||||
SurfaceParams params;
|
||||
};
|
||||
|
||||
class RasterizerCacheOpenGL final : NonCopyable {
|
||||
class RasterizerCacheOpenGL final : public RasterizerCache<Surface> {
|
||||
public:
|
||||
RasterizerCacheOpenGL();
|
||||
~RasterizerCacheOpenGL();
|
||||
|
||||
/// Get a surface based on the texture configuration
|
||||
Surface GetTextureSurface(const Tegra::Texture::FullTextureInfo& config);
|
||||
@@ -733,12 +756,6 @@ public:
|
||||
/// Tries to find a framebuffer GPU address based on the provided CPU address
|
||||
Surface TryFindFramebufferSurface(VAddr cpu_addr) const;
|
||||
|
||||
/// Write any cached resources overlapping the region back to memory (if dirty)
|
||||
void FlushRegion(Tegra::GPUVAddr addr, size_t size);
|
||||
|
||||
/// Mark the specified region as being invalidated
|
||||
void InvalidateRegion(Tegra::GPUVAddr addr, size_t size);
|
||||
|
||||
private:
|
||||
void LoadSurface(const Surface& surface);
|
||||
Surface GetSurface(const SurfaceParams& params, bool preserve_contents = true);
|
||||
@@ -746,17 +763,16 @@ private:
|
||||
/// Recreates a surface with new parameters
|
||||
Surface RecreateSurface(const Surface& surface, const SurfaceParams& new_params);
|
||||
|
||||
/// Register surface into the cache
|
||||
void RegisterSurface(const Surface& surface);
|
||||
/// Reserves a unique surface that can be reused later
|
||||
void ReserveSurface(const Surface& surface);
|
||||
|
||||
/// Remove surface from the cache
|
||||
void UnregisterSurface(const Surface& surface);
|
||||
/// Tries to get a reserved surface for the specified parameters
|
||||
Surface TryGetReservedSurface(const SurfaceParams& params);
|
||||
|
||||
/// Increase/decrease the number of surface in pages touching the specified region
|
||||
void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta);
|
||||
|
||||
std::unordered_map<Tegra::GPUVAddr, Surface> surface_cache;
|
||||
PageMap cached_pages;
|
||||
/// The surface reserve is a "backup" cache, this is where we put unique surfaces that have
|
||||
/// previously been used. This is to prevent surfaces from being constantly created and
|
||||
/// destroyed when used with different surface parameters.
|
||||
std::unordered_map<SurfaceReserveKey, Surface> surface_reserve;
|
||||
|
||||
OGLFramebuffer read_framebuffer;
|
||||
OGLFramebuffer draw_framebuffer;
|
||||
|
||||
130
src/video_core/renderer_opengl/gl_shader_cache.cpp
Normal file
130
src/video_core/renderer_opengl/gl_shader_cache.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/core.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
/// Gets the address for the specified shader stage program
|
||||
static Tegra::GPUVAddr GetShaderAddress(Maxwell::ShaderProgram program) {
|
||||
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
auto& shader_config = gpu.regs.shader_config[static_cast<size_t>(program)];
|
||||
|
||||
return gpu.regs.code_address.CodeAddress() + shader_config.offset;
|
||||
}
|
||||
|
||||
/// Gets the shader program code from memory for the specified address
|
||||
static GLShader::ProgramCode GetShaderCode(Tegra::GPUVAddr addr) {
|
||||
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
|
||||
GLShader::ProgramCode program_code(GLShader::MAX_PROGRAM_CODE_LENGTH);
|
||||
const boost::optional<VAddr> cpu_address{gpu.memory_manager.GpuToCpuAddress(addr)};
|
||||
Memory::ReadBlock(*cpu_address, program_code.data(), program_code.size() * sizeof(u64));
|
||||
|
||||
return program_code;
|
||||
}
|
||||
|
||||
/// Helper function to set shader uniform block bindings for a single shader stage
|
||||
static void SetShaderUniformBlockBinding(GLuint shader, const char* name,
|
||||
Maxwell::ShaderStage binding, size_t expected_size) {
|
||||
const GLuint ub_index = glGetUniformBlockIndex(shader, name);
|
||||
if (ub_index == GL_INVALID_INDEX) {
|
||||
return;
|
||||
}
|
||||
|
||||
GLint ub_size = 0;
|
||||
glGetActiveUniformBlockiv(shader, ub_index, GL_UNIFORM_BLOCK_DATA_SIZE, &ub_size);
|
||||
ASSERT_MSG(static_cast<size_t>(ub_size) == expected_size,
|
||||
"Uniform block size did not match! Got {}, expected {}", ub_size, expected_size);
|
||||
glUniformBlockBinding(shader, ub_index, static_cast<GLuint>(binding));
|
||||
}
|
||||
|
||||
/// Sets shader uniform block bindings for an entire shader program
|
||||
static void SetShaderUniformBlockBindings(GLuint shader) {
|
||||
SetShaderUniformBlockBinding(shader, "vs_config", Maxwell::ShaderStage::Vertex,
|
||||
sizeof(GLShader::MaxwellUniformData));
|
||||
SetShaderUniformBlockBinding(shader, "gs_config", Maxwell::ShaderStage::Geometry,
|
||||
sizeof(GLShader::MaxwellUniformData));
|
||||
SetShaderUniformBlockBinding(shader, "fs_config", Maxwell::ShaderStage::Fragment,
|
||||
sizeof(GLShader::MaxwellUniformData));
|
||||
}
|
||||
|
||||
CachedShader::CachedShader(Tegra::GPUVAddr addr, Maxwell::ShaderProgram program_type)
|
||||
: addr{addr}, program_type{program_type}, setup{GetShaderCode(addr)} {
|
||||
|
||||
GLShader::ProgramResult program_result;
|
||||
GLenum gl_type{};
|
||||
|
||||
switch (program_type) {
|
||||
case Maxwell::ShaderProgram::VertexA:
|
||||
// VertexB is always enabled, so when VertexA is enabled, we have two vertex shaders.
|
||||
// Conventional HW does not support this, so we combine VertexA and VertexB into one
|
||||
// stage here.
|
||||
setup.SetProgramB(GetShaderCode(GetShaderAddress(Maxwell::ShaderProgram::VertexB)));
|
||||
case Maxwell::ShaderProgram::VertexB:
|
||||
program_result = GLShader::GenerateVertexShader(setup);
|
||||
gl_type = GL_VERTEX_SHADER;
|
||||
break;
|
||||
case Maxwell::ShaderProgram::Fragment:
|
||||
program_result = GLShader::GenerateFragmentShader(setup);
|
||||
gl_type = GL_FRAGMENT_SHADER;
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unimplemented program_type={}", static_cast<u32>(program_type));
|
||||
UNREACHABLE();
|
||||
return;
|
||||
}
|
||||
|
||||
entries = program_result.second;
|
||||
|
||||
OGLShader shader;
|
||||
shader.Create(program_result.first.c_str(), gl_type);
|
||||
program.Create(true, shader.handle);
|
||||
SetShaderUniformBlockBindings(program.handle);
|
||||
}
|
||||
|
||||
GLuint CachedShader::GetProgramResourceIndex(const std::string& name) {
|
||||
auto search{resource_cache.find(name)};
|
||||
if (search == resource_cache.end()) {
|
||||
const GLuint index{
|
||||
glGetProgramResourceIndex(program.handle, GL_UNIFORM_BLOCK, name.c_str())};
|
||||
resource_cache[name] = index;
|
||||
return index;
|
||||
}
|
||||
|
||||
return search->second;
|
||||
}
|
||||
|
||||
GLint CachedShader::GetUniformLocation(const std::string& name) {
|
||||
auto search{uniform_cache.find(name)};
|
||||
if (search == uniform_cache.end()) {
|
||||
const GLint index{glGetUniformLocation(program.handle, name.c_str())};
|
||||
uniform_cache[name] = index;
|
||||
return index;
|
||||
}
|
||||
|
||||
return search->second;
|
||||
}
|
||||
|
||||
Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
|
||||
const Tegra::GPUVAddr program_addr{GetShaderAddress(program)};
|
||||
|
||||
// Look up shader in the cache based on address
|
||||
Shader shader{TryGet(program_addr)};
|
||||
|
||||
if (!shader) {
|
||||
// No shader found - create a new one
|
||||
shader = std::make_shared<CachedShader>(program_addr, program);
|
||||
Register(shader);
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
||||
69
src/video_core/renderer_opengl/gl_shader_cache.h
Normal file
69
src/video_core/renderer_opengl/gl_shader_cache.h
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/rasterizer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_gen.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class CachedShader;
|
||||
using Shader = std::shared_ptr<CachedShader>;
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
|
||||
class CachedShader final {
|
||||
public:
|
||||
CachedShader(Tegra::GPUVAddr addr, Maxwell::ShaderProgram program_type);
|
||||
|
||||
/// Gets the address of the shader in guest memory, required for cache management
|
||||
Tegra::GPUVAddr GetAddr() const {
|
||||
return addr;
|
||||
}
|
||||
|
||||
/// Gets the size of the shader in guest memory, required for cache management
|
||||
size_t GetSizeInBytes() const {
|
||||
return sizeof(GLShader::ProgramCode);
|
||||
}
|
||||
|
||||
/// Gets the shader entries for the shader
|
||||
const GLShader::ShaderEntries& GetShaderEntries() const {
|
||||
return entries;
|
||||
}
|
||||
|
||||
/// Gets the GL program handle for the shader
|
||||
GLuint GetProgramHandle() const {
|
||||
return program.handle;
|
||||
}
|
||||
|
||||
/// Gets the GL program resource location for the specified resource, caching as needed
|
||||
GLuint GetProgramResourceIndex(const std::string& name);
|
||||
|
||||
/// Gets the GL uniform location for the specified resource, caching as needed
|
||||
GLint GetUniformLocation(const std::string& name);
|
||||
|
||||
private:
|
||||
Tegra::GPUVAddr addr;
|
||||
Maxwell::ShaderProgram program_type;
|
||||
GLShader::ShaderSetup setup;
|
||||
GLShader::ShaderEntries entries;
|
||||
OGLProgram program;
|
||||
|
||||
std::unordered_map<std::string, GLuint> resource_cache;
|
||||
std::unordered_map<std::string, GLint> uniform_cache;
|
||||
};
|
||||
|
||||
class ShaderCacheOpenGL final : public RasterizerCache<Shader> {
|
||||
public:
|
||||
/// Gets the current specified shader stage program
|
||||
Shader GetStageProgram(Maxwell::ShaderProgram program);
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
@@ -849,6 +849,33 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void WriteLop3Instruction(Register dest, const std::string& op_a, const std::string& op_b,
|
||||
const std::string& op_c, const std::string& imm_lut) {
|
||||
if (dest == Tegra::Shader::Register::ZeroIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
static constexpr std::array<const char*, 32> shift_amounts = {
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
|
||||
"11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21",
|
||||
"22", "23", "24", "25", "26", "27", "28", "29", "30", "31"};
|
||||
|
||||
std::string result;
|
||||
result += '(';
|
||||
|
||||
for (size_t i = 0; i < shift_amounts.size(); ++i) {
|
||||
if (i)
|
||||
result += '|';
|
||||
result += "(((" + imm_lut + " >> (((" + op_c + " >> " + shift_amounts[i] +
|
||||
") & 1) | ((" + op_b + " >> " + shift_amounts[i] + ") & 1) << 1 | ((" + op_a +
|
||||
" >> " + shift_amounts[i] + ") & 1) << 2)) & 1) << " + shift_amounts[i] + ")";
|
||||
}
|
||||
|
||||
result += ')';
|
||||
|
||||
regs.SetRegisterToInteger(dest, true, 0, result, 1, 1);
|
||||
}
|
||||
|
||||
void WriteTexsInstruction(const Instruction& instr, const std::string& coord,
|
||||
const std::string& texture) {
|
||||
// Add an extra scope and declare the texture coords inside to prevent
|
||||
@@ -1275,6 +1302,15 @@ private:
|
||||
"((" + op_a + " << " + shift + ") + " + op_b + ')', 1, 1);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::POPC_C:
|
||||
case OpCode::Id::POPC_R:
|
||||
case OpCode::Id::POPC_IMM: {
|
||||
if (instr.popc.invert) {
|
||||
op_b = "~(" + op_b + ')';
|
||||
}
|
||||
regs.SetRegisterToInteger(instr.gpr0, true, 0, "bitCount(" + op_b + ')', 1, 1);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::SEL_C:
|
||||
case OpCode::Id::SEL_R:
|
||||
case OpCode::Id::SEL_IMM: {
|
||||
@@ -1297,6 +1333,20 @@ private:
|
||||
instr.alu.lop.pred_result_mode, instr.alu.lop.pred48);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::LOP3_C:
|
||||
case OpCode::Id::LOP3_R:
|
||||
case OpCode::Id::LOP3_IMM: {
|
||||
std::string op_c = regs.GetRegisterAsInteger(instr.gpr39);
|
||||
std::string lut;
|
||||
if (opcode->GetId() == OpCode::Id::LOP3_R) {
|
||||
lut = '(' + std::to_string(instr.alu.lop3.GetImmLut28()) + ')';
|
||||
} else {
|
||||
lut = '(' + std::to_string(instr.alu.lop3.GetImmLut48()) + ')';
|
||||
}
|
||||
|
||||
WriteLop3Instruction(instr.gpr0, op_a, op_b, op_c, lut);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::IMNMX_C:
|
||||
case OpCode::Id::IMNMX_R:
|
||||
case OpCode::Id::IMNMX_IMM: {
|
||||
|
||||
@@ -13,7 +13,7 @@ using Tegra::Engines::Maxwell3D;
|
||||
|
||||
static constexpr u32 PROGRAM_OFFSET{10};
|
||||
|
||||
ProgramResult GenerateVertexShader(const ShaderSetup& setup, const MaxwellVSConfig& config) {
|
||||
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();
|
||||
@@ -75,7 +75,7 @@ void main() {
|
||||
return {out, program.second};
|
||||
}
|
||||
|
||||
ProgramResult GenerateFragmentShader(const ShaderSetup& setup, const MaxwellFSConfig& config) {
|
||||
ProgramResult GenerateFragmentShader(const ShaderSetup& setup) {
|
||||
std::string out = "#version 430 core\n";
|
||||
out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
|
||||
out += Decompiler::GetCommonDeclarations();
|
||||
|
||||
@@ -6,17 +6,14 @@
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/hash.h"
|
||||
|
||||
namespace OpenGL::GLShader {
|
||||
|
||||
constexpr size_t MAX_PROGRAM_CODE_LENGTH{0x1000};
|
||||
|
||||
using ProgramCode = std::array<u64, MAX_PROGRAM_CODE_LENGTH>;
|
||||
using ProgramCode = std::vector<u64>;
|
||||
|
||||
class ConstBufferEntry {
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
@@ -115,8 +112,8 @@ struct ShaderEntries {
|
||||
using ProgramResult = std::pair<std::string, ShaderEntries>;
|
||||
|
||||
struct ShaderSetup {
|
||||
ShaderSetup(const ProgramCode& program_code) {
|
||||
program.code = program_code;
|
||||
explicit ShaderSetup(ProgramCode program_code) {
|
||||
program.code = std::move(program_code);
|
||||
}
|
||||
|
||||
struct {
|
||||
@@ -124,19 +121,9 @@ struct ShaderSetup {
|
||||
ProgramCode code_b; // Used for dual vertex shaders
|
||||
} program;
|
||||
|
||||
bool program_code_hash_dirty = true;
|
||||
|
||||
u64 GetProgramCodeHash() {
|
||||
if (program_code_hash_dirty) {
|
||||
program_code_hash = GetNewHash();
|
||||
program_code_hash_dirty = false;
|
||||
}
|
||||
return program_code_hash;
|
||||
}
|
||||
|
||||
/// Used in scenarios where we have a dual vertex shaders
|
||||
void SetProgramB(const ProgramCode& program_b) {
|
||||
program.code_b = program_b;
|
||||
void SetProgramB(ProgramCode&& program_b) {
|
||||
program.code_b = std::move(program_b);
|
||||
has_program_b = true;
|
||||
}
|
||||
|
||||
@@ -145,68 +132,19 @@ struct ShaderSetup {
|
||||
}
|
||||
|
||||
private:
|
||||
u64 GetNewHash() const {
|
||||
if (has_program_b) {
|
||||
// Compute hash over dual shader programs
|
||||
return Common::ComputeHash64(&program, sizeof(program));
|
||||
} else {
|
||||
// Compute hash over a single shader program
|
||||
return Common::ComputeHash64(&program.code, program.code.size());
|
||||
}
|
||||
}
|
||||
|
||||
u64 program_code_hash{};
|
||||
bool has_program_b{};
|
||||
};
|
||||
|
||||
struct MaxwellShaderConfigCommon {
|
||||
void Init(ShaderSetup& setup) {
|
||||
program_hash = setup.GetProgramCodeHash();
|
||||
}
|
||||
|
||||
u64 program_hash;
|
||||
};
|
||||
|
||||
struct MaxwellVSConfig : Common::HashableStruct<MaxwellShaderConfigCommon> {
|
||||
explicit MaxwellVSConfig(ShaderSetup& setup) {
|
||||
state.Init(setup);
|
||||
}
|
||||
};
|
||||
|
||||
struct MaxwellFSConfig : Common::HashableStruct<MaxwellShaderConfigCommon> {
|
||||
explicit MaxwellFSConfig(ShaderSetup& setup) {
|
||||
state.Init(setup);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates the GLSL vertex shader program source code for the given VS program
|
||||
* @returns String of the shader source code
|
||||
*/
|
||||
ProgramResult GenerateVertexShader(const ShaderSetup& setup, const MaxwellVSConfig& config);
|
||||
ProgramResult GenerateVertexShader(const ShaderSetup& setup);
|
||||
|
||||
/**
|
||||
* Generates the GLSL fragment shader program source code for the given FS program
|
||||
* @returns String of the shader source code
|
||||
*/
|
||||
ProgramResult GenerateFragmentShader(const ShaderSetup& setup, const MaxwellFSConfig& config);
|
||||
ProgramResult GenerateFragmentShader(const ShaderSetup& setup);
|
||||
|
||||
} // namespace OpenGL::GLShader
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<OpenGL::GLShader::MaxwellVSConfig> {
|
||||
size_t operator()(const OpenGL::GLShader::MaxwellVSConfig& k) const {
|
||||
return k.Hash();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<OpenGL::GLShader::MaxwellFSConfig> {
|
||||
size_t operator()(const OpenGL::GLShader::MaxwellFSConfig& k) const {
|
||||
return k.Hash();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
@@ -3,39 +3,10 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||
|
||||
namespace OpenGL::GLShader {
|
||||
|
||||
namespace Impl {
|
||||
static void SetShaderUniformBlockBinding(GLuint shader, const char* name,
|
||||
Maxwell3D::Regs::ShaderStage binding,
|
||||
size_t expected_size) {
|
||||
const GLuint ub_index = glGetUniformBlockIndex(shader, name);
|
||||
if (ub_index == GL_INVALID_INDEX) {
|
||||
return;
|
||||
}
|
||||
|
||||
GLint ub_size = 0;
|
||||
glGetActiveUniformBlockiv(shader, ub_index, GL_UNIFORM_BLOCK_DATA_SIZE, &ub_size);
|
||||
ASSERT_MSG(static_cast<size_t>(ub_size) == expected_size,
|
||||
"Uniform block size did not match! Got {}, expected {}", ub_size, expected_size);
|
||||
glUniformBlockBinding(shader, ub_index, static_cast<GLuint>(binding));
|
||||
}
|
||||
|
||||
void SetShaderUniformBlockBindings(GLuint shader) {
|
||||
SetShaderUniformBlockBinding(shader, "vs_config", Maxwell3D::Regs::ShaderStage::Vertex,
|
||||
sizeof(MaxwellUniformData));
|
||||
SetShaderUniformBlockBinding(shader, "gs_config", Maxwell3D::Regs::ShaderStage::Geometry,
|
||||
sizeof(MaxwellUniformData));
|
||||
SetShaderUniformBlockBinding(shader, "fs_config", Maxwell3D::Regs::ShaderStage::Fragment,
|
||||
sizeof(MaxwellUniformData));
|
||||
}
|
||||
|
||||
} // namespace Impl
|
||||
|
||||
void MaxwellUniformData::SetFromRegs(const Maxwell3D::State::ShaderStageInfo& shader_stage) {
|
||||
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
|
||||
const auto& regs = gpu.regs;
|
||||
|
||||
@@ -4,12 +4,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_gen.h"
|
||||
#include "video_core/renderer_opengl/maxwell_to_gl.h"
|
||||
|
||||
namespace OpenGL::GLShader {
|
||||
@@ -19,10 +16,6 @@ static constexpr size_t NumTextureSamplers = 32;
|
||||
|
||||
using Tegra::Engines::Maxwell3D;
|
||||
|
||||
namespace Impl {
|
||||
void SetShaderUniformBlockBindings(GLuint shader);
|
||||
} // namespace Impl
|
||||
|
||||
/// Uniform structure for the Uniform Buffer Object, all vectors must be 16-byte aligned
|
||||
// NOTE: Always keep a vec4 at the end. The GL spec is not clear whether the alignment at
|
||||
// the end of a uniform block is included in UNIFORM_BLOCK_DATA_SIZE or not.
|
||||
@@ -36,102 +29,22 @@ static_assert(sizeof(MaxwellUniformData) == 32, "MaxwellUniformData structure si
|
||||
static_assert(sizeof(MaxwellUniformData) < 16384,
|
||||
"MaxwellUniformData structure must be less than 16kb as per the OpenGL spec");
|
||||
|
||||
class OGLShaderStage {
|
||||
public:
|
||||
OGLShaderStage() = default;
|
||||
|
||||
void Create(const ProgramResult& program_result, GLenum type) {
|
||||
OGLShader shader;
|
||||
shader.Create(program_result.first.c_str(), type);
|
||||
program.Create(true, shader.handle);
|
||||
Impl::SetShaderUniformBlockBindings(program.handle);
|
||||
entries = program_result.second;
|
||||
}
|
||||
GLuint GetHandle() const {
|
||||
return program.handle;
|
||||
}
|
||||
|
||||
ShaderEntries GetEntries() const {
|
||||
return entries;
|
||||
}
|
||||
|
||||
private:
|
||||
OGLProgram program;
|
||||
ShaderEntries entries;
|
||||
};
|
||||
|
||||
// TODO(wwylele): beautify this doc
|
||||
// This is a shader cache designed for translating PICA shader to GLSL shader.
|
||||
// The double cache is needed because diffent KeyConfigType, which includes a hash of the code
|
||||
// region (including its leftover unused code) can generate the same GLSL code.
|
||||
template <typename KeyConfigType,
|
||||
ProgramResult (*CodeGenerator)(const ShaderSetup&, const KeyConfigType&),
|
||||
GLenum ShaderType>
|
||||
class ShaderCache {
|
||||
public:
|
||||
ShaderCache() = default;
|
||||
|
||||
using Result = std::pair<GLuint, ShaderEntries>;
|
||||
|
||||
Result Get(const KeyConfigType& key, const ShaderSetup& setup) {
|
||||
auto map_it = shader_map.find(key);
|
||||
if (map_it == shader_map.end()) {
|
||||
ProgramResult program = CodeGenerator(setup, key);
|
||||
|
||||
auto [iter, new_shader] = shader_cache.emplace(program.first, OGLShaderStage{});
|
||||
OGLShaderStage& cached_shader = iter->second;
|
||||
if (new_shader) {
|
||||
cached_shader.Create(program, ShaderType);
|
||||
}
|
||||
shader_map[key] = &cached_shader;
|
||||
return {cached_shader.GetHandle(), program.second};
|
||||
} else {
|
||||
return {map_it->second->GetHandle(), map_it->second->GetEntries()};
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<KeyConfigType, OGLShaderStage*> shader_map;
|
||||
std::unordered_map<std::string, OGLShaderStage> shader_cache;
|
||||
};
|
||||
|
||||
using VertexShaders = ShaderCache<MaxwellVSConfig, &GenerateVertexShader, GL_VERTEX_SHADER>;
|
||||
|
||||
using FragmentShaders = ShaderCache<MaxwellFSConfig, &GenerateFragmentShader, GL_FRAGMENT_SHADER>;
|
||||
|
||||
class ProgramManager {
|
||||
public:
|
||||
ProgramManager() {
|
||||
pipeline.Create();
|
||||
}
|
||||
|
||||
ShaderEntries UseProgrammableVertexShader(const MaxwellVSConfig& config,
|
||||
const ShaderSetup& setup) {
|
||||
ShaderEntries result;
|
||||
std::tie(current.vs, result) = vertex_shaders.Get(config, setup);
|
||||
return result;
|
||||
void UseProgrammableVertexShader(GLuint program) {
|
||||
vs = program;
|
||||
}
|
||||
|
||||
ShaderEntries UseProgrammableFragmentShader(const MaxwellFSConfig& config,
|
||||
const ShaderSetup& setup) {
|
||||
ShaderEntries result;
|
||||
std::tie(current.fs, result) = fragment_shaders.Get(config, setup);
|
||||
return result;
|
||||
}
|
||||
|
||||
GLuint GetCurrentProgramStage(Maxwell3D::Regs::ShaderStage stage) const {
|
||||
switch (stage) {
|
||||
case Maxwell3D::Regs::ShaderStage::Vertex:
|
||||
return current.vs;
|
||||
case Maxwell3D::Regs::ShaderStage::Fragment:
|
||||
return current.fs;
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
void UseProgrammableFragmentShader(GLuint program) {
|
||||
fs = program;
|
||||
}
|
||||
|
||||
void UseTrivialGeometryShader() {
|
||||
current.gs = 0;
|
||||
gs = 0;
|
||||
}
|
||||
|
||||
void ApplyTo(OpenGLState& state) {
|
||||
@@ -140,35 +53,16 @@ public:
|
||||
GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT,
|
||||
0);
|
||||
|
||||
glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, current.vs);
|
||||
glUseProgramStages(pipeline.handle, GL_GEOMETRY_SHADER_BIT, current.gs);
|
||||
glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, current.fs);
|
||||
glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, vs);
|
||||
glUseProgramStages(pipeline.handle, GL_GEOMETRY_SHADER_BIT, gs);
|
||||
glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, fs);
|
||||
state.draw.shader_program = 0;
|
||||
state.draw.program_pipeline = pipeline.handle;
|
||||
}
|
||||
|
||||
private:
|
||||
struct ShaderTuple {
|
||||
GLuint vs = 0, gs = 0, fs = 0;
|
||||
bool operator==(const ShaderTuple& rhs) const {
|
||||
return std::tie(vs, gs, fs) == std::tie(rhs.vs, rhs.gs, rhs.fs);
|
||||
}
|
||||
struct Hash {
|
||||
std::size_t operator()(const ShaderTuple& tuple) const {
|
||||
std::size_t hash = 0;
|
||||
boost::hash_combine(hash, tuple.vs);
|
||||
boost::hash_combine(hash, tuple.gs);
|
||||
boost::hash_combine(hash, tuple.fs);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
};
|
||||
ShaderTuple current;
|
||||
VertexShaders vertex_shaders;
|
||||
FragmentShaders fragment_shaders;
|
||||
|
||||
std::unordered_map<ShaderTuple, OGLProgram, ShaderTuple::Hash> program_cache;
|
||||
OGLPipeline pipeline;
|
||||
GLuint vs{}, fs{}, gs{};
|
||||
};
|
||||
|
||||
} // namespace OpenGL::GLShader
|
||||
|
||||
@@ -27,13 +27,17 @@ OpenGLState::OpenGLState() {
|
||||
color_mask.alpha_enabled = GL_TRUE;
|
||||
|
||||
stencil.test_enabled = false;
|
||||
stencil.test_func = GL_ALWAYS;
|
||||
stencil.test_ref = 0;
|
||||
stencil.test_mask = 0xFF;
|
||||
stencil.write_mask = 0xFF;
|
||||
stencil.action_depth_fail = GL_KEEP;
|
||||
stencil.action_depth_pass = GL_KEEP;
|
||||
stencil.action_stencil_fail = GL_KEEP;
|
||||
auto reset_stencil = [](auto& config) {
|
||||
config.test_func = GL_ALWAYS;
|
||||
config.test_ref = 0;
|
||||
config.test_mask = 0xFFFFFFFF;
|
||||
config.write_mask = 0xFFFFFFFF;
|
||||
config.action_depth_fail = GL_KEEP;
|
||||
config.action_depth_pass = GL_KEEP;
|
||||
config.action_stencil_fail = GL_KEEP;
|
||||
};
|
||||
reset_stencil(stencil.front);
|
||||
reset_stencil(stencil.back);
|
||||
|
||||
blend.enabled = true;
|
||||
blend.rgb_equation = GL_FUNC_ADD;
|
||||
@@ -129,24 +133,23 @@ void OpenGLState::Apply() const {
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
}
|
||||
}
|
||||
|
||||
if (stencil.test_func != cur_state.stencil.test_func ||
|
||||
stencil.test_ref != cur_state.stencil.test_ref ||
|
||||
stencil.test_mask != cur_state.stencil.test_mask) {
|
||||
glStencilFunc(stencil.test_func, stencil.test_ref, stencil.test_mask);
|
||||
}
|
||||
|
||||
if (stencil.action_depth_fail != cur_state.stencil.action_depth_fail ||
|
||||
stencil.action_depth_pass != cur_state.stencil.action_depth_pass ||
|
||||
stencil.action_stencil_fail != cur_state.stencil.action_stencil_fail) {
|
||||
glStencilOp(stencil.action_stencil_fail, stencil.action_depth_fail,
|
||||
stencil.action_depth_pass);
|
||||
}
|
||||
|
||||
// Stencil mask
|
||||
if (stencil.write_mask != cur_state.stencil.write_mask) {
|
||||
glStencilMask(stencil.write_mask);
|
||||
}
|
||||
auto config_stencil = [](GLenum face, const auto& config, const auto& prev_config) {
|
||||
if (config.test_func != prev_config.test_func || config.test_ref != prev_config.test_ref ||
|
||||
config.test_mask != prev_config.test_mask) {
|
||||
glStencilFuncSeparate(face, config.test_func, config.test_ref, config.test_mask);
|
||||
}
|
||||
if (config.action_depth_fail != prev_config.action_depth_fail ||
|
||||
config.action_depth_pass != prev_config.action_depth_pass ||
|
||||
config.action_stencil_fail != prev_config.action_stencil_fail) {
|
||||
glStencilOpSeparate(face, config.action_stencil_fail, config.action_depth_fail,
|
||||
config.action_depth_pass);
|
||||
}
|
||||
if (config.write_mask != prev_config.write_mask) {
|
||||
glStencilMaskSeparate(face, config.write_mask);
|
||||
}
|
||||
};
|
||||
config_stencil(GL_FRONT, stencil.front, cur_state.stencil.front);
|
||||
config_stencil(GL_BACK, stencil.back, cur_state.stencil.back);
|
||||
|
||||
// Blending
|
||||
if (blend.enabled != cur_state.blend.enabled) {
|
||||
|
||||
@@ -58,14 +58,16 @@ public:
|
||||
} color_mask; // GL_COLOR_WRITEMASK
|
||||
|
||||
struct {
|
||||
bool test_enabled; // GL_STENCIL_TEST
|
||||
GLenum test_func; // GL_STENCIL_FUNC
|
||||
GLint test_ref; // GL_STENCIL_REF
|
||||
GLuint test_mask; // GL_STENCIL_VALUE_MASK
|
||||
GLuint write_mask; // GL_STENCIL_WRITEMASK
|
||||
GLenum action_stencil_fail; // GL_STENCIL_FAIL
|
||||
GLenum action_depth_fail; // GL_STENCIL_PASS_DEPTH_FAIL
|
||||
GLenum action_depth_pass; // GL_STENCIL_PASS_DEPTH_PASS
|
||||
bool test_enabled; // GL_STENCIL_TEST
|
||||
struct {
|
||||
GLenum test_func; // GL_STENCIL_FUNC
|
||||
GLint test_ref; // GL_STENCIL_REF
|
||||
GLuint test_mask; // GL_STENCIL_VALUE_MASK
|
||||
GLuint write_mask; // GL_STENCIL_WRITEMASK
|
||||
GLenum action_stencil_fail; // GL_STENCIL_FAIL
|
||||
GLenum action_depth_fail; // GL_STENCIL_PASS_DEPTH_FAIL
|
||||
GLenum action_depth_pass; // GL_STENCIL_PASS_DEPTH_PASS
|
||||
} front, back;
|
||||
} stencil;
|
||||
|
||||
struct {
|
||||
|
||||
@@ -295,6 +295,30 @@ inline GLenum ComparisonOp(Maxwell::ComparisonOp comparison) {
|
||||
return {};
|
||||
}
|
||||
|
||||
inline GLenum StencilOp(Maxwell::StencilOp stencil) {
|
||||
switch (stencil) {
|
||||
case Maxwell::StencilOp::Keep:
|
||||
return GL_KEEP;
|
||||
case Maxwell::StencilOp::Zero:
|
||||
return GL_ZERO;
|
||||
case Maxwell::StencilOp::Replace:
|
||||
return GL_REPLACE;
|
||||
case Maxwell::StencilOp::Incr:
|
||||
return GL_INCR;
|
||||
case Maxwell::StencilOp::Decr:
|
||||
return GL_DECR;
|
||||
case Maxwell::StencilOp::Invert:
|
||||
return GL_INVERT;
|
||||
case Maxwell::StencilOp::IncrWrap:
|
||||
return GL_INCR_WRAP;
|
||||
case Maxwell::StencilOp::DecrWrap:
|
||||
return GL_DECR_WRAP;
|
||||
}
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented stencil op={}", static_cast<u32>(stencil));
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
}
|
||||
|
||||
inline GLenum FrontFace(Maxwell::Cull::FrontFace front_face) {
|
||||
switch (front_face) {
|
||||
case Maxwell::Cull::FrontFace::ClockWise:
|
||||
|
||||
@@ -125,9 +125,9 @@ void Config::ReadValues() {
|
||||
|
||||
qt_config->beginGroup("UIGameList");
|
||||
UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool();
|
||||
UISettings::values.icon_size = qt_config->value("icon_size", 48).toUInt();
|
||||
UISettings::values.row_1_text_id = qt_config->value("row_1_text_id", 0).toUInt();
|
||||
UISettings::values.row_2_text_id = qt_config->value("row_2_text_id", 3).toUInt();
|
||||
UISettings::values.icon_size = qt_config->value("icon_size", 64).toUInt();
|
||||
UISettings::values.row_1_text_id = qt_config->value("row_1_text_id", 3).toUInt();
|
||||
UISettings::values.row_2_text_id = qt_config->value("row_2_text_id", 2).toUInt();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("UILayout");
|
||||
|
||||
@@ -29,24 +29,6 @@
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_6">
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelPlus">
|
||||
<property name="text">
|
||||
<string>Plus:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonPlus">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QVBoxLayout" name="buttonMiscMinusVerticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelMinus">
|
||||
@@ -64,6 +46,24 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelPlus">
|
||||
<property name="text">
|
||||
<string>Plus:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonPlus">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<layout class="QVBoxLayout" name="buttonMiscHomeVerticalLayout">
|
||||
<item>
|
||||
@@ -472,7 +472,7 @@ Capture:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout" name="buttonRStickLeftVerticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelRStickLeft">
|
||||
@@ -490,7 +490,7 @@ Capture:</string>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<item row="1" column="0">
|
||||
<layout class="QVBoxLayout" name="buttonRStickUpVerticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelRStickUp">
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <map>
|
||||
#include <QLabel>
|
||||
#include <QMetaType>
|
||||
#include <QPushButton>
|
||||
|
||||
@@ -11,12 +11,13 @@
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QSpinBox>
|
||||
#include "common/vector_math.h"
|
||||
#include "core/core.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
#include "video_core/utils.h"
|
||||
#include "yuzu/debugger/graphics/graphics_surface.h"
|
||||
#include "yuzu/util/spinbox.h"
|
||||
|
||||
@@ -381,7 +382,7 @@ void GraphicsSurfaceWidget::OnUpdate() {
|
||||
// TODO: Implement a good way to visualize alpha components!
|
||||
|
||||
QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32);
|
||||
boost::optional<VAddr> address = gpu.memory_manager->GpuToCpuAddress(surface_address);
|
||||
boost::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);
|
||||
|
||||
// TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles.
|
||||
// Needs to be fixed if we plan to use this feature more, otherwise we may remove it.
|
||||
@@ -442,7 +443,7 @@ void GraphicsSurfaceWidget::SaveSurface() {
|
||||
pixmap->save(&file, "PNG");
|
||||
} else if (selectedFilter == bin_filter) {
|
||||
auto& gpu = Core::System::GetInstance().GPU();
|
||||
boost::optional<VAddr> address = gpu.memory_manager->GpuToCpuAddress(surface_address);
|
||||
boost::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);
|
||||
|
||||
const u8* buffer = Memory::GetPointer(*address);
|
||||
ASSERT_MSG(buffer != nullptr, "Memory not accessible");
|
||||
|
||||
@@ -426,13 +426,12 @@ static void GetMetadataFromControlNCA(const std::shared_ptr<FileSys::NCA>& nca,
|
||||
}
|
||||
}
|
||||
|
||||
void GameListWorker::AddInstalledTitlesToGameList() {
|
||||
const auto usernand = Service::FileSystem::GetUserNANDContents();
|
||||
const auto installed_games = usernand->ListEntriesFilter(FileSys::TitleType::Application,
|
||||
FileSys::ContentRecordType::Program);
|
||||
void GameListWorker::AddInstalledTitlesToGameList(std::shared_ptr<FileSys::RegisteredCache> cache) {
|
||||
const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application,
|
||||
FileSys::ContentRecordType::Program);
|
||||
|
||||
for (const auto& game : installed_games) {
|
||||
const auto& file = usernand->GetEntryRaw(game);
|
||||
const auto& file = cache->GetEntryUnparsed(game);
|
||||
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
|
||||
if (!loader)
|
||||
continue;
|
||||
@@ -442,8 +441,7 @@ void GameListWorker::AddInstalledTitlesToGameList() {
|
||||
u64 program_id = 0;
|
||||
loader->ReadProgramId(program_id);
|
||||
|
||||
const auto& control =
|
||||
usernand->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
|
||||
const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
|
||||
if (control != nullptr)
|
||||
GetMetadataFromControlNCA(control, icon, name);
|
||||
emit EntryReady({
|
||||
@@ -457,11 +455,11 @@ void GameListWorker::AddInstalledTitlesToGameList() {
|
||||
});
|
||||
}
|
||||
|
||||
const auto control_data = usernand->ListEntriesFilter(FileSys::TitleType::Application,
|
||||
FileSys::ContentRecordType::Control);
|
||||
const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application,
|
||||
FileSys::ContentRecordType::Control);
|
||||
|
||||
for (const auto& entry : control_data) {
|
||||
const auto nca = usernand->GetEntry(entry);
|
||||
const auto nca = cache->GetEntry(entry);
|
||||
if (nca != nullptr)
|
||||
nca_control_map.insert_or_assign(entry.title_id, nca);
|
||||
}
|
||||
@@ -549,7 +547,9 @@ void GameListWorker::run() {
|
||||
stop_processing = false;
|
||||
watch_list.append(dir_path);
|
||||
FillControlMap(dir_path.toStdString());
|
||||
AddInstalledTitlesToGameList();
|
||||
AddInstalledTitlesToGameList(Service::FileSystem::GetUserNANDContents());
|
||||
AddInstalledTitlesToGameList(Service::FileSystem::GetSystemNANDContents());
|
||||
AddInstalledTitlesToGameList(Service::FileSystem::GetSDMCContents());
|
||||
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
|
||||
nca_control_map.clear();
|
||||
emit Finished(watch_list);
|
||||
|
||||
@@ -172,7 +172,7 @@ private:
|
||||
bool deep_scan;
|
||||
std::atomic_bool stop_processing;
|
||||
|
||||
void AddInstalledTitlesToGameList();
|
||||
void AddInstalledTitlesToGameList(std::shared_ptr<FileSys::RegisteredCache> cache);
|
||||
void FillControlMap(const std::string& dir_path);
|
||||
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
|
||||
};
|
||||
|
||||
@@ -91,9 +91,20 @@ void GMainWindow::ShowCallouts() {}
|
||||
|
||||
const int GMainWindow::max_recent_files_item;
|
||||
|
||||
static void InitializeLogging() {
|
||||
Log::Filter log_filter;
|
||||
log_filter.ParseFilterString(Settings::values.log_filter);
|
||||
Log::SetGlobalFilter(log_filter);
|
||||
|
||||
const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
|
||||
FileUtil::CreateFullPath(log_dir);
|
||||
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
|
||||
}
|
||||
|
||||
GMainWindow::GMainWindow()
|
||||
: config(new Config()), emu_thread(nullptr),
|
||||
vfs(std::make_shared<FileSys::RealVfsFilesystem>()) {
|
||||
InitializeLogging();
|
||||
|
||||
debug_context = Tegra::DebugContext::Construct();
|
||||
|
||||
@@ -122,8 +133,7 @@ GMainWindow::GMainWindow()
|
||||
show();
|
||||
|
||||
// Necessary to load titles from nand in gamelist.
|
||||
Service::FileSystem::RegisterBIS(std::make_unique<FileSys::BISFactory>(vfs->OpenDirectory(
|
||||
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), FileSys::Mode::ReadWrite)));
|
||||
Service::FileSystem::CreateFactories(vfs);
|
||||
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
|
||||
|
||||
// Show one-time "callout" messages to the user
|
||||
@@ -545,6 +555,15 @@ void GMainWindow::BootGame(const QString& filename) {
|
||||
}
|
||||
status_bar_update_timer.start(2000);
|
||||
|
||||
std::string title_name;
|
||||
const auto res = Core::System::GetInstance().GetGameName(title_name);
|
||||
if (res != Loader::ResultStatus::Success)
|
||||
title_name = FileUtil::GetFilename(filename.toStdString());
|
||||
|
||||
setWindowTitle(QString("yuzu %1| %4 | %2-%3")
|
||||
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc,
|
||||
QString::fromStdString(title_name)));
|
||||
|
||||
render_window->show();
|
||||
render_window->setFocus();
|
||||
|
||||
@@ -576,6 +595,8 @@ void GMainWindow::ShutdownGame() {
|
||||
render_window->hide();
|
||||
game_list->show();
|
||||
game_list->setFilterFocus();
|
||||
setWindowTitle(QString("yuzu %1| %2-%3")
|
||||
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
|
||||
|
||||
// Disable status bar updates
|
||||
status_bar_update_timer.stop();
|
||||
@@ -872,6 +893,7 @@ void GMainWindow::OnStartGame() {
|
||||
|
||||
ui.action_Pause->setEnabled(true);
|
||||
ui.action_Stop->setEnabled(true);
|
||||
ui.action_Restart->setEnabled(true);
|
||||
}
|
||||
|
||||
void GMainWindow::OnPauseGame() {
|
||||
@@ -880,7 +902,6 @@ void GMainWindow::OnPauseGame() {
|
||||
ui.action_Start->setEnabled(true);
|
||||
ui.action_Pause->setEnabled(false);
|
||||
ui.action_Stop->setEnabled(true);
|
||||
ui.action_Restart->setEnabled(true);
|
||||
}
|
||||
|
||||
void GMainWindow::OnStopGame() {
|
||||
@@ -1173,16 +1194,6 @@ void GMainWindow::UpdateUITheme() {
|
||||
#undef main
|
||||
#endif
|
||||
|
||||
static void InitializeLogging() {
|
||||
Log::Filter log_filter;
|
||||
log_filter.ParseFilterString(Settings::values.log_filter);
|
||||
Log::SetGlobalFilter(log_filter);
|
||||
|
||||
const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
|
||||
FileUtil::CreateFullPath(log_dir);
|
||||
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
MicroProfileOnThreadCreate("Frontend");
|
||||
SCOPE_EXIT({ MicroProfileShutdown(); });
|
||||
@@ -1200,7 +1211,6 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
GMainWindow main_window;
|
||||
// After settings have been loaded by GMainWindow, apply the filter
|
||||
InitializeLogging();
|
||||
main_window.show();
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
<addaction name="action_Start"/>
|
||||
<addaction name="action_Pause"/>
|
||||
<addaction name="action_Stop"/>
|
||||
<addaction name="action_Restart"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_Configure"/>
|
||||
</widget>
|
||||
|
||||
Reference in New Issue
Block a user