Compare commits
31 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93af663683 | ||
|
|
97648f4841 | ||
|
|
1312cf15d6 | ||
|
|
08d507a196 | ||
|
|
ed9dba89d3 | ||
|
|
f46c3164e7 | ||
|
|
c9f35d96be | ||
|
|
2598433f9c | ||
|
|
61f63bb994 | ||
|
|
353a099481 | ||
|
|
1a3098f11a | ||
|
|
71182643f7 | ||
|
|
bc7e149835 | ||
|
|
088c7c1bb5 | ||
|
|
b81260c65c | ||
|
|
2abf979c35 | ||
|
|
b73e433dff | ||
|
|
9f16833097 | ||
|
|
16adc735a5 | ||
|
|
89c106e31b | ||
|
|
fbf452ab0e | ||
|
|
218ae888f3 | ||
|
|
34c3e2c786 | ||
|
|
b631c09e72 | ||
|
|
2d1f054c61 | ||
|
|
552d5071fa | ||
|
|
41d2565f29 | ||
|
|
60f39060c6 | ||
|
|
45cb41f517 | ||
|
|
a6c7ae6fe8 | ||
|
|
57a4a2ae0f |
@@ -17,6 +17,7 @@
|
||||
#include "core/core_timing.h"
|
||||
#include "core/cpu_core_manager.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/vfs_concat.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
@@ -108,6 +109,8 @@ struct System::Impl {
|
||||
// Create a default fs if one doesn't already exist.
|
||||
if (virtual_filesystem == nullptr)
|
||||
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
|
||||
if (content_provider == nullptr)
|
||||
content_provider = std::make_unique<FileSys::ContentProviderUnion>();
|
||||
|
||||
/// Create default implementations of applets if one is not provided.
|
||||
if (profile_selector == nullptr)
|
||||
@@ -249,6 +252,8 @@ struct System::Impl {
|
||||
Kernel::KernelCore kernel;
|
||||
/// RealVfsFilesystem instance
|
||||
FileSys::VirtualFilesystem virtual_filesystem;
|
||||
/// ContentProviderUnion instance
|
||||
std::unique_ptr<FileSys::ContentProviderUnion> content_provider;
|
||||
/// AppLoader used to load the current executing application
|
||||
std::unique_ptr<Loader::AppLoader> app_loader;
|
||||
std::unique_ptr<VideoCore::RendererBase> renderer;
|
||||
@@ -488,6 +493,27 @@ const Frontend::SoftwareKeyboardApplet& System::GetSoftwareKeyboard() const {
|
||||
return *impl->software_keyboard;
|
||||
}
|
||||
|
||||
void System::SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider) {
|
||||
impl->content_provider = std::move(provider);
|
||||
}
|
||||
|
||||
FileSys::ContentProvider& System::GetContentProvider() {
|
||||
return *impl->content_provider;
|
||||
}
|
||||
|
||||
const FileSys::ContentProvider& System::GetContentProvider() const {
|
||||
return *impl->content_provider;
|
||||
}
|
||||
|
||||
void System::RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
|
||||
FileSys::ContentProvider* provider) {
|
||||
impl->content_provider->SetSlot(slot, provider);
|
||||
}
|
||||
|
||||
void System::ClearContentProvider(FileSys::ContentProviderUnionSlot slot) {
|
||||
impl->content_provider->ClearSlot(slot);
|
||||
}
|
||||
|
||||
void System::SetWebBrowser(std::unique_ptr<Frontend::WebBrowserApplet> applet) {
|
||||
impl->web_browser = std::move(applet);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ class WebBrowserApplet;
|
||||
|
||||
namespace FileSys {
|
||||
class CheatList;
|
||||
class ContentProvider;
|
||||
class ContentProviderUnion;
|
||||
enum class ContentProviderUnionSlot;
|
||||
class VfsFilesystem;
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -270,6 +273,17 @@ public:
|
||||
Frontend::WebBrowserApplet& GetWebBrowser();
|
||||
const Frontend::WebBrowserApplet& GetWebBrowser() const;
|
||||
|
||||
void SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider);
|
||||
|
||||
FileSys::ContentProvider& GetContentProvider();
|
||||
|
||||
const FileSys::ContentProvider& GetContentProvider() const;
|
||||
|
||||
void RegisterContentProvider(FileSys::ContentProviderUnionSlot slot,
|
||||
FileSys::ContentProvider* provider);
|
||||
|
||||
void ClearContentProvider(FileSys::ContentProviderUnionSlot slot);
|
||||
|
||||
private:
|
||||
System();
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "common/file_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/crypto/partition_data_manager.h"
|
||||
@@ -794,7 +795,7 @@ void KeyManager::DeriveBase() {
|
||||
|
||||
void KeyManager::DeriveETicket(PartitionDataManager& data) {
|
||||
// ETicket keys
|
||||
const auto es = Service::FileSystem::GetUnionContents().GetEntry(
|
||||
const auto es = Core::System::GetInstance().GetContentProvider().GetEntry(
|
||||
0x0100000000000033, FileSys::ContentRecordType::Program);
|
||||
|
||||
if (es == nullptr)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "common/file_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/ips_layer.h"
|
||||
@@ -69,7 +70,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
}
|
||||
}
|
||||
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
const auto& installed = Core::System::GetInstance().GetContentProvider();
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
const auto update_disabled =
|
||||
@@ -155,7 +156,7 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
|
||||
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::string& name) const {
|
||||
if (nso.size() < sizeof(Loader::NSOHeader)) {
|
||||
return nso;
|
||||
}
|
||||
@@ -171,18 +172,19 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
|
||||
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
|
||||
|
||||
if (Settings::values.dump_nso) {
|
||||
LOG_INFO(Loader, "Dumping NSO for build_id={}, title_id={:016X}", build_id, title_id);
|
||||
LOG_INFO(Loader, "Dumping NSO for name={}, build_id={}, title_id={:016X}", name, build_id,
|
||||
title_id);
|
||||
const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id);
|
||||
if (dump_dir != nullptr) {
|
||||
const auto nso_dir = GetOrCreateDirectoryRelative(dump_dir, "/nso");
|
||||
const auto file = nso_dir->CreateFile(fmt::format("{}.nso", build_id));
|
||||
const auto file = nso_dir->CreateFile(fmt::format("{}-{}.nso", name, build_id));
|
||||
|
||||
file->Resize(nso.size());
|
||||
file->WriteBytes(nso);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO(Loader, "Patching NSO for build_id={}", build_id);
|
||||
LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id);
|
||||
|
||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
@@ -345,7 +347,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
|
||||
if (romfs == nullptr)
|
||||
return romfs;
|
||||
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
const auto& installed = Core::System::GetInstance().GetContentProvider();
|
||||
|
||||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
@@ -392,7 +394,7 @@ static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
|
||||
std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
|
||||
VirtualFile update_raw) const {
|
||||
std::map<std::string, std::string, std::less<>> out;
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
const auto& installed = Core::System::GetInstance().GetContentProvider();
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
|
||||
// Game Updates
|
||||
@@ -466,10 +468,10 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
||||
|
||||
// DLC
|
||||
const auto dlc_entries = installed.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
|
||||
std::vector<RegisteredCacheEntry> dlc_match;
|
||||
std::vector<ContentProviderEntry> dlc_match;
|
||||
dlc_match.reserve(dlc_entries.size());
|
||||
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
|
||||
[this, &installed](const RegisteredCacheEntry& entry) {
|
||||
[this, &installed](const ContentProviderEntry& entry) {
|
||||
return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == title_id &&
|
||||
installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
|
||||
});
|
||||
@@ -492,7 +494,7 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
||||
}
|
||||
|
||||
std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
|
||||
const auto installed{Service::FileSystem::GetUnionContents()};
|
||||
const auto& installed = Core::System::GetInstance().GetContentProvider();
|
||||
|
||||
const auto base_control_nca = installed.GetEntry(title_id, ContentRecordType::Control);
|
||||
if (base_control_nca == nullptr)
|
||||
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
// Currently tracked NSO patches:
|
||||
// - IPS
|
||||
// - IPSwitch
|
||||
std::vector<u8> PatchNSO(const std::vector<u8>& nso) const;
|
||||
std::vector<u8> PatchNSO(const std::vector<u8>& nso, const std::string& name) const;
|
||||
|
||||
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
|
||||
// Used to prevent expensive copies in NSO loader.
|
||||
|
||||
@@ -23,19 +23,19 @@ namespace FileSys {
|
||||
// The size of blocks to use when vfs raw copying into nand.
|
||||
constexpr size_t VFS_RC_LARGE_COPY_BLOCK = 0x400000;
|
||||
|
||||
std::string RegisteredCacheEntry::DebugInfo() const {
|
||||
std::string ContentProviderEntry::DebugInfo() const {
|
||||
return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type));
|
||||
}
|
||||
|
||||
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
|
||||
bool operator<(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs) {
|
||||
return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type);
|
||||
}
|
||||
|
||||
bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
|
||||
bool operator==(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs) {
|
||||
return std::tie(lhs.title_id, lhs.type) == std::tie(rhs.title_id, rhs.type);
|
||||
}
|
||||
|
||||
bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
|
||||
bool operator!=(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs) {
|
||||
return !operator==(lhs, rhs);
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ static std::string GetCNMTName(TitleType type, u64 title_id) {
|
||||
return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id);
|
||||
}
|
||||
|
||||
static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
|
||||
ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
|
||||
switch (type) {
|
||||
case NCAContentType::Program:
|
||||
// TODO(DarkLordZach): Differentiate between Program and Patch
|
||||
@@ -104,6 +104,28 @@ static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
|
||||
}
|
||||
}
|
||||
|
||||
ContentProvider::~ContentProvider() = default;
|
||||
|
||||
bool ContentProvider::HasEntry(ContentProviderEntry entry) const {
|
||||
return HasEntry(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
VirtualFile ContentProvider::GetEntryUnparsed(ContentProviderEntry entry) const {
|
||||
return GetEntryUnparsed(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
VirtualFile ContentProvider::GetEntryRaw(ContentProviderEntry entry) const {
|
||||
return GetEntryRaw(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
std::unique_ptr<NCA> ContentProvider::GetEntry(ContentProviderEntry entry) const {
|
||||
return GetEntry(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
std::vector<ContentProviderEntry> ContentProvider::ListEntries() const {
|
||||
return ListEntriesFilter(std::nullopt, std::nullopt, std::nullopt);
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
|
||||
std::string_view path) const {
|
||||
const auto file = dir->GetFileRelative(path);
|
||||
@@ -161,8 +183,8 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
|
||||
return file;
|
||||
}
|
||||
|
||||
static std::optional<NcaID> CheckMapForContentRecord(
|
||||
const boost::container::flat_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
|
||||
static std::optional<NcaID> CheckMapForContentRecord(const std::map<u64, CNMT>& map, u64 title_id,
|
||||
ContentRecordType type) {
|
||||
if (map.find(title_id) == map.end())
|
||||
return {};
|
||||
|
||||
@@ -268,7 +290,7 @@ void RegisteredCache::Refresh() {
|
||||
AccumulateYuzuMeta();
|
||||
}
|
||||
|
||||
RegisteredCache::RegisteredCache(VirtualDir dir_, RegisteredCacheParsingFunction parsing_function)
|
||||
RegisteredCache::RegisteredCache(VirtualDir dir_, ContentProviderParsingFunction parsing_function)
|
||||
: dir(std::move(dir_)), parser(std::move(parsing_function)) {
|
||||
Refresh();
|
||||
}
|
||||
@@ -279,19 +301,11 @@ bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const {
|
||||
return GetEntryRaw(title_id, type) != nullptr;
|
||||
}
|
||||
|
||||
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);
|
||||
return id ? GetFileAtID(*id) : nullptr;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::GetEntryUnparsed(RegisteredCacheEntry entry) const {
|
||||
return GetEntryUnparsed(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
std::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
|
||||
const auto meta_iter = meta.find(title_id);
|
||||
if (meta_iter != meta.end())
|
||||
@@ -309,10 +323,6 @@ VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) c
|
||||
return id ? parser(GetFileAtID(*id), *id) : nullptr;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const {
|
||||
return GetEntryRaw(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
const auto raw = GetEntryRaw(title_id, type);
|
||||
if (raw == nullptr)
|
||||
@@ -320,10 +330,6 @@ std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType t
|
||||
return std::make_unique<NCA>(raw, nullptr, 0, keys);
|
||||
}
|
||||
|
||||
std::unique_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
|
||||
return GetEntry(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void RegisteredCache::IterateAllMetadata(
|
||||
std::vector<T>& out, std::function<T(const CNMT&, const ContentRecord&)> proc,
|
||||
@@ -348,25 +354,14 @@ void RegisteredCache::IterateAllMetadata(
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<RegisteredCacheEntry> RegisteredCache::ListEntries() const {
|
||||
std::vector<RegisteredCacheEntry> out;
|
||||
IterateAllMetadata<RegisteredCacheEntry>(
|
||||
out,
|
||||
[](const CNMT& c, const ContentRecord& r) {
|
||||
return RegisteredCacheEntry{c.GetTitleID(), r.type};
|
||||
},
|
||||
[](const CNMT& c, const ContentRecord& r) { return true; });
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
|
||||
std::vector<ContentProviderEntry> RegisteredCache::ListEntriesFilter(
|
||||
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
|
||||
std::optional<u64> title_id) const {
|
||||
std::vector<RegisteredCacheEntry> out;
|
||||
IterateAllMetadata<RegisteredCacheEntry>(
|
||||
std::vector<ContentProviderEntry> out;
|
||||
IterateAllMetadata<ContentProviderEntry>(
|
||||
out,
|
||||
[](const CNMT& c, const ContentRecord& r) {
|
||||
return RegisteredCacheEntry{c.GetTitleID(), r.type};
|
||||
return ContentProviderEntry{c.GetTitleID(), r.type};
|
||||
},
|
||||
[&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
|
||||
if (title_type && *title_type != c.GetType())
|
||||
@@ -521,37 +516,56 @@ bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
|
||||
}) != yuzu_meta.end();
|
||||
}
|
||||
|
||||
RegisteredCacheUnion::RegisteredCacheUnion(std::vector<RegisteredCache*> caches)
|
||||
: caches(std::move(caches)) {}
|
||||
ContentProviderUnion::~ContentProviderUnion() = default;
|
||||
|
||||
void RegisteredCacheUnion::Refresh() {
|
||||
for (const auto& c : caches)
|
||||
c->Refresh();
|
||||
void ContentProviderUnion::SetSlot(ContentProviderUnionSlot slot, ContentProvider* provider) {
|
||||
providers[slot] = provider;
|
||||
}
|
||||
|
||||
bool RegisteredCacheUnion::HasEntry(u64 title_id, ContentRecordType type) const {
|
||||
return std::any_of(caches.begin(), caches.end(), [title_id, type](const auto& cache) {
|
||||
return cache->HasEntry(title_id, type);
|
||||
});
|
||||
void ContentProviderUnion::ClearSlot(ContentProviderUnionSlot slot) {
|
||||
providers[slot] = nullptr;
|
||||
}
|
||||
|
||||
bool RegisteredCacheUnion::HasEntry(RegisteredCacheEntry entry) const {
|
||||
return HasEntry(entry.title_id, entry.type);
|
||||
void ContentProviderUnion::Refresh() {
|
||||
for (auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
|
||||
provider.second->Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<u32> RegisteredCacheUnion::GetEntryVersion(u64 title_id) const {
|
||||
for (const auto& c : caches) {
|
||||
const auto res = c->GetEntryVersion(title_id);
|
||||
if (res)
|
||||
bool ContentProviderUnion::HasEntry(u64 title_id, ContentRecordType type) const {
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
|
||||
if (provider.second->HasEntry(title_id, type))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<u32> ContentProviderUnion::GetEntryVersion(u64 title_id) const {
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
|
||||
const auto res = provider.second->GetEntryVersion(title_id);
|
||||
if (res != std::nullopt)
|
||||
return res;
|
||||
}
|
||||
|
||||
return {};
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCacheUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
|
||||
for (const auto& c : caches) {
|
||||
const auto res = c->GetEntryUnparsed(title_id, type);
|
||||
VirtualFile ContentProviderUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
|
||||
const auto res = provider.second->GetEntryUnparsed(title_id, type);
|
||||
if (res != nullptr)
|
||||
return res;
|
||||
}
|
||||
@@ -559,13 +573,12 @@ VirtualFile RegisteredCacheUnion::GetEntryUnparsed(u64 title_id, ContentRecordTy
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCacheUnion::GetEntryUnparsed(RegisteredCacheEntry entry) const {
|
||||
return GetEntryUnparsed(entry.title_id, entry.type);
|
||||
}
|
||||
VirtualFile ContentProviderUnion::GetEntryRaw(u64 title_id, ContentRecordType type) const {
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
|
||||
VirtualFile RegisteredCacheUnion::GetEntryRaw(u64 title_id, ContentRecordType type) const {
|
||||
for (const auto& c : caches) {
|
||||
const auto res = c->GetEntryRaw(title_id, type);
|
||||
const auto res = provider.second->GetEntryRaw(title_id, type);
|
||||
if (res != nullptr)
|
||||
return res;
|
||||
}
|
||||
@@ -573,30 +586,30 @@ VirtualFile RegisteredCacheUnion::GetEntryRaw(u64 title_id, ContentRecordType ty
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCacheUnion::GetEntryRaw(RegisteredCacheEntry entry) const {
|
||||
return GetEntryRaw(entry.title_id, entry.type);
|
||||
std::unique_ptr<NCA> ContentProviderUnion::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
|
||||
auto res = provider.second->GetEntry(title_id, type);
|
||||
if (res != nullptr)
|
||||
return res;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
const auto raw = GetEntryRaw(title_id, type);
|
||||
if (raw == nullptr)
|
||||
return nullptr;
|
||||
return std::make_unique<NCA>(raw);
|
||||
}
|
||||
std::vector<ContentProviderEntry> ContentProviderUnion::ListEntriesFilter(
|
||||
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
|
||||
std::optional<u64> title_id) const {
|
||||
std::vector<ContentProviderEntry> out;
|
||||
|
||||
std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const {
|
||||
return GetEntry(entry.title_id, entry.type);
|
||||
}
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
|
||||
std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const {
|
||||
std::vector<RegisteredCacheEntry> out;
|
||||
for (const auto& c : caches) {
|
||||
c->IterateAllMetadata<RegisteredCacheEntry>(
|
||||
out,
|
||||
[](const CNMT& c, const ContentRecord& r) {
|
||||
return RegisteredCacheEntry{c.GetTitleID(), r.type};
|
||||
},
|
||||
[](const CNMT& c, const ContentRecord& r) { return true; });
|
||||
const auto vec = provider.second->ListEntriesFilter(title_type, record_type, title_id);
|
||||
std::copy(vec.begin(), vec.end(), std::back_inserter(out));
|
||||
}
|
||||
|
||||
std::sort(out.begin(), out.end());
|
||||
@@ -604,25 +617,87 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const {
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter(
|
||||
std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>>
|
||||
ContentProviderUnion::ListEntriesFilterOrigin(std::optional<ContentProviderUnionSlot> origin,
|
||||
std::optional<TitleType> title_type,
|
||||
std::optional<ContentRecordType> record_type,
|
||||
std::optional<u64> title_id) const {
|
||||
std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> out;
|
||||
|
||||
for (const auto& provider : providers) {
|
||||
if (provider.second == nullptr)
|
||||
continue;
|
||||
|
||||
if (origin.has_value() && *origin != provider.first)
|
||||
continue;
|
||||
|
||||
const auto vec = provider.second->ListEntriesFilter(title_type, record_type, title_id);
|
||||
std::transform(vec.begin(), vec.end(), std::back_inserter(out),
|
||||
[&provider](const ContentProviderEntry& entry) {
|
||||
return std::make_pair(provider.first, entry);
|
||||
});
|
||||
}
|
||||
|
||||
std::sort(out.begin(), out.end());
|
||||
out.erase(std::unique(out.begin(), out.end()), out.end());
|
||||
return out;
|
||||
}
|
||||
|
||||
ManualContentProvider::~ManualContentProvider() = default;
|
||||
|
||||
void ManualContentProvider::AddEntry(TitleType title_type, ContentRecordType content_type,
|
||||
u64 title_id, VirtualFile file) {
|
||||
entries.insert_or_assign({title_type, content_type, title_id}, file);
|
||||
}
|
||||
|
||||
void ManualContentProvider::ClearAllEntries() {
|
||||
entries.clear();
|
||||
}
|
||||
|
||||
void ManualContentProvider::Refresh() {}
|
||||
|
||||
bool ManualContentProvider::HasEntry(u64 title_id, ContentRecordType type) const {
|
||||
return GetEntryRaw(title_id, type) != nullptr;
|
||||
}
|
||||
|
||||
std::optional<u32> ManualContentProvider::GetEntryVersion(u64 title_id) const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
VirtualFile ManualContentProvider::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
|
||||
return GetEntryRaw(title_id, type);
|
||||
}
|
||||
|
||||
VirtualFile ManualContentProvider::GetEntryRaw(u64 title_id, ContentRecordType type) const {
|
||||
const auto iter =
|
||||
std::find_if(entries.begin(), entries.end(), [title_id, type](const auto& entry) {
|
||||
const auto [title_type, content_type, e_title_id] = entry.first;
|
||||
return content_type == type && e_title_id == title_id;
|
||||
});
|
||||
if (iter == entries.end())
|
||||
return nullptr;
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
std::unique_ptr<NCA> ManualContentProvider::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
const auto res = GetEntryRaw(title_id, type);
|
||||
if (res == nullptr)
|
||||
return nullptr;
|
||||
return std::make_unique<NCA>(res, nullptr, 0, keys);
|
||||
}
|
||||
|
||||
std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter(
|
||||
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
|
||||
std::optional<u64> title_id) const {
|
||||
std::vector<RegisteredCacheEntry> out;
|
||||
for (const auto& c : caches) {
|
||||
c->IterateAllMetadata<RegisteredCacheEntry>(
|
||||
out,
|
||||
[](const CNMT& c, const ContentRecord& r) {
|
||||
return RegisteredCacheEntry{c.GetTitleID(), r.type};
|
||||
},
|
||||
[&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
|
||||
if (title_type && *title_type != c.GetType())
|
||||
return false;
|
||||
if (record_type && *record_type != r.type)
|
||||
return false;
|
||||
if (title_id && *title_id != c.GetTitleID())
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
std::vector<ContentProviderEntry> out;
|
||||
|
||||
for (const auto& entry : entries) {
|
||||
const auto [e_title_type, e_content_type, e_title_id] = entry.first;
|
||||
if ((title_type == std::nullopt || e_title_type == *title_type) &&
|
||||
(record_type == std::nullopt || e_content_type == *record_type) &&
|
||||
(title_id == std::nullopt || e_title_id == *title_id)) {
|
||||
out.emplace_back(ContentProviderEntry{e_title_id, e_content_type});
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(out.begin(), out.end());
|
||||
|
||||
@@ -21,12 +21,13 @@ class NSP;
|
||||
class XCI;
|
||||
|
||||
enum class ContentRecordType : u8;
|
||||
enum class NCAContentType : u8;
|
||||
enum class TitleType : u8;
|
||||
|
||||
struct ContentRecord;
|
||||
|
||||
using NcaID = std::array<u8, 0x10>;
|
||||
using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
|
||||
using ContentProviderParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
|
||||
using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile&, size_t)>;
|
||||
|
||||
enum class InstallResult {
|
||||
@@ -36,7 +37,7 @@ enum class InstallResult {
|
||||
ErrorMetaFailed,
|
||||
};
|
||||
|
||||
struct RegisteredCacheEntry {
|
||||
struct ContentProviderEntry {
|
||||
u64 title_id;
|
||||
ContentRecordType type;
|
||||
|
||||
@@ -47,12 +48,46 @@ constexpr u64 GetUpdateTitleID(u64 base_title_id) {
|
||||
return base_title_id | 0x800;
|
||||
}
|
||||
|
||||
ContentRecordType GetCRTypeFromNCAType(NCAContentType type);
|
||||
|
||||
// boost flat_map requires operator< for O(log(n)) lookups.
|
||||
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
|
||||
bool operator<(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs);
|
||||
|
||||
// std unique requires operator== to identify duplicates.
|
||||
bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
|
||||
bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
|
||||
bool operator==(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs);
|
||||
bool operator!=(const ContentProviderEntry& lhs, const ContentProviderEntry& rhs);
|
||||
|
||||
class ContentProvider {
|
||||
public:
|
||||
virtual ~ContentProvider();
|
||||
|
||||
virtual void Refresh() = 0;
|
||||
|
||||
virtual bool HasEntry(u64 title_id, ContentRecordType type) const = 0;
|
||||
virtual bool HasEntry(ContentProviderEntry entry) const;
|
||||
|
||||
virtual std::optional<u32> GetEntryVersion(u64 title_id) const = 0;
|
||||
|
||||
virtual VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const = 0;
|
||||
virtual VirtualFile GetEntryUnparsed(ContentProviderEntry entry) const;
|
||||
|
||||
virtual VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const = 0;
|
||||
virtual VirtualFile GetEntryRaw(ContentProviderEntry entry) const;
|
||||
|
||||
virtual std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const = 0;
|
||||
virtual std::unique_ptr<NCA> GetEntry(ContentProviderEntry entry) const;
|
||||
|
||||
virtual std::vector<ContentProviderEntry> ListEntries() const;
|
||||
|
||||
// If a parameter is not std::nullopt, it will be filtered for from all entries.
|
||||
virtual std::vector<ContentProviderEntry> ListEntriesFilter(
|
||||
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
|
||||
std::optional<u64> title_id = {}) const = 0;
|
||||
|
||||
protected:
|
||||
// A single instance of KeyManager to be used by GetEntry()
|
||||
Core::Crypto::KeyManager keys;
|
||||
};
|
||||
|
||||
/*
|
||||
* A class that catalogues NCAs in the registered directory structure.
|
||||
@@ -67,39 +102,32 @@ bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs
|
||||
* (This impl also supports substituting the nca dir for an nca file, as that's more convenient
|
||||
* when 4GB splitting can be ignored.)
|
||||
*/
|
||||
class RegisteredCache {
|
||||
friend class RegisteredCacheUnion;
|
||||
|
||||
class RegisteredCache : public ContentProvider {
|
||||
public:
|
||||
// Parsing function defines the conversion from raw file to NCA. If there are other steps
|
||||
// besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom
|
||||
// parsing function.
|
||||
explicit RegisteredCache(VirtualDir dir,
|
||||
RegisteredCacheParsingFunction parsing_function =
|
||||
ContentProviderParsingFunction parsing_function =
|
||||
[](const VirtualFile& file, const NcaID& id) { return file; });
|
||||
~RegisteredCache();
|
||||
~RegisteredCache() override;
|
||||
|
||||
void Refresh();
|
||||
void Refresh() override;
|
||||
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const;
|
||||
bool HasEntry(RegisteredCacheEntry entry) const;
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const override;
|
||||
|
||||
std::optional<u32> GetEntryVersion(u64 title_id) const;
|
||||
std::optional<u32> GetEntryVersion(u64 title_id) const override;
|
||||
|
||||
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
|
||||
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const override;
|
||||
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const override;
|
||||
|
||||
std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
|
||||
std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
|
||||
std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const override;
|
||||
|
||||
std::vector<RegisteredCacheEntry> ListEntries() const;
|
||||
// If a parameter is not std::nullopt, it will be filtered for from all entries.
|
||||
std::vector<RegisteredCacheEntry> ListEntriesFilter(
|
||||
std::vector<ContentProviderEntry> ListEntriesFilter(
|
||||
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
|
||||
std::optional<u64> title_id = {}) const;
|
||||
std::optional<u64> title_id = {}) const override;
|
||||
|
||||
// Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure
|
||||
// there is a meta NCA and all of them are accessible.
|
||||
@@ -131,46 +159,70 @@ private:
|
||||
bool RawInstallYuzuMeta(const CNMT& cnmt);
|
||||
|
||||
VirtualDir dir;
|
||||
RegisteredCacheParsingFunction parser;
|
||||
Core::Crypto::KeyManager keys;
|
||||
ContentProviderParsingFunction parser;
|
||||
|
||||
// maps tid -> NcaID of meta
|
||||
boost::container::flat_map<u64, NcaID> meta_id;
|
||||
std::map<u64, NcaID> meta_id;
|
||||
// maps tid -> meta
|
||||
boost::container::flat_map<u64, CNMT> meta;
|
||||
std::map<u64, CNMT> meta;
|
||||
// maps tid -> meta for CNMT in yuzu_meta
|
||||
boost::container::flat_map<u64, CNMT> yuzu_meta;
|
||||
std::map<u64, CNMT> yuzu_meta;
|
||||
};
|
||||
|
||||
// Combines multiple RegisteredCaches (i.e. SysNAND, UserNAND, SDMC) into one interface.
|
||||
class RegisteredCacheUnion {
|
||||
enum class ContentProviderUnionSlot {
|
||||
SysNAND, ///< System NAND
|
||||
UserNAND, ///< User NAND
|
||||
SDMC, ///< SD Card
|
||||
FrontendManual, ///< Frontend-defined game list or similar
|
||||
};
|
||||
|
||||
// Combines multiple ContentProvider(s) (i.e. SysNAND, UserNAND, SDMC) into one interface.
|
||||
class ContentProviderUnion : public ContentProvider {
|
||||
public:
|
||||
explicit RegisteredCacheUnion(std::vector<RegisteredCache*> caches);
|
||||
~ContentProviderUnion() override;
|
||||
|
||||
void Refresh();
|
||||
void SetSlot(ContentProviderUnionSlot slot, ContentProvider* provider);
|
||||
void ClearSlot(ContentProviderUnionSlot slot);
|
||||
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const;
|
||||
bool HasEntry(RegisteredCacheEntry entry) const;
|
||||
void Refresh() override;
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const override;
|
||||
std::optional<u32> GetEntryVersion(u64 title_id) const override;
|
||||
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const override;
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const override;
|
||||
std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const override;
|
||||
std::vector<ContentProviderEntry> ListEntriesFilter(
|
||||
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
|
||||
std::optional<u64> title_id) const override;
|
||||
|
||||
std::optional<u32> GetEntryVersion(u64 title_id) 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;
|
||||
|
||||
std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
|
||||
std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::vector<RegisteredCacheEntry> ListEntries() const;
|
||||
// If a parameter is not std::nullopt, it will be filtered for from all entries.
|
||||
std::vector<RegisteredCacheEntry> ListEntriesFilter(
|
||||
std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> ListEntriesFilterOrigin(
|
||||
std::optional<ContentProviderUnionSlot> origin = {},
|
||||
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
|
||||
std::optional<u64> title_id = {}) const;
|
||||
|
||||
private:
|
||||
std::vector<RegisteredCache*> caches;
|
||||
std::map<ContentProviderUnionSlot, ContentProvider*> providers;
|
||||
};
|
||||
|
||||
class ManualContentProvider : public ContentProvider {
|
||||
public:
|
||||
~ManualContentProvider() override;
|
||||
|
||||
void AddEntry(TitleType title_type, ContentRecordType content_type, u64 title_id,
|
||||
VirtualFile file);
|
||||
void ClearAllEntries();
|
||||
|
||||
void Refresh() override;
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const override;
|
||||
std::optional<u32> GetEntryVersion(u64 title_id) const override;
|
||||
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const override;
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const override;
|
||||
std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const override;
|
||||
std::vector<ContentProviderEntry> ListEntriesFilter(
|
||||
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
|
||||
std::optional<u64> title_id) const override;
|
||||
|
||||
private:
|
||||
std::map<std::tuple<TitleType, ContentRecordType, u64>, VirtualFile> entries;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -48,7 +48,7 @@ ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, Conte
|
||||
|
||||
switch (storage) {
|
||||
case StorageId::None:
|
||||
res = Service::FileSystem::GetUnionContents().GetEntry(title_id, type);
|
||||
res = Core::System::GetInstance().GetContentProvider().GetEntry(title_id, type);
|
||||
break;
|
||||
case StorageId::NandSystem:
|
||||
res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type);
|
||||
|
||||
@@ -143,11 +143,12 @@ std::multimap<u64, std::shared_ptr<NCA>> NSP::GetNCAsByTitleID() const {
|
||||
return out;
|
||||
}
|
||||
|
||||
std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> NSP::GetNCAs() const {
|
||||
std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>>
|
||||
NSP::GetNCAs() const {
|
||||
return ncas;
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type) const {
|
||||
std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type, TitleType title_type) const {
|
||||
if (extracted)
|
||||
LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
|
||||
|
||||
@@ -155,14 +156,14 @@ std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type) const {
|
||||
if (title_id_iter == ncas.end())
|
||||
return nullptr;
|
||||
|
||||
const auto type_iter = title_id_iter->second.find(type);
|
||||
const auto type_iter = title_id_iter->second.find({title_type, type});
|
||||
if (type_iter == title_id_iter->second.end())
|
||||
return nullptr;
|
||||
|
||||
return type_iter->second;
|
||||
}
|
||||
|
||||
VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type) const {
|
||||
VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type, TitleType title_type) const {
|
||||
if (extracted)
|
||||
LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
|
||||
const auto nca = GetNCA(title_id, type);
|
||||
@@ -240,7 +241,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
|
||||
const CNMT cnmt(inner_file);
|
||||
auto& ncas_title = ncas[cnmt.GetTitleID()];
|
||||
|
||||
ncas_title[ContentRecordType::Meta] = nca;
|
||||
ncas_title[{cnmt.GetType(), ContentRecordType::Meta}] = nca;
|
||||
for (const auto& rec : cnmt.GetContentRecords()) {
|
||||
const auto id_string = Common::HexArrayToString(rec.nca_id, false);
|
||||
const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
|
||||
@@ -258,7 +259,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
|
||||
if (next_nca->GetStatus() == Loader::ResultStatus::Success ||
|
||||
(next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
|
||||
(cnmt.GetTitleID() & 0x800) != 0)) {
|
||||
ncas_title[rec.type] = std::move(next_nca);
|
||||
ncas_title[{cnmt.GetType(), rec.type}] = std::move(next_nca);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,9 +42,12 @@ public:
|
||||
// Type 0 Only (Collection of NCAs + Certificate + Ticket + Meta XML)
|
||||
std::vector<std::shared_ptr<NCA>> GetNCAsCollapsed() const;
|
||||
std::multimap<u64, std::shared_ptr<NCA>> GetNCAsByTitleID() const;
|
||||
std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> GetNCAs() const;
|
||||
std::shared_ptr<NCA> GetNCA(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetNCAFile(u64 title_id, ContentRecordType type) const;
|
||||
std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> GetNCAs()
|
||||
const;
|
||||
std::shared_ptr<NCA> GetNCA(u64 title_id, ContentRecordType type,
|
||||
TitleType title_type = TitleType::Application) const;
|
||||
VirtualFile GetNCAFile(u64 title_id, ContentRecordType type,
|
||||
TitleType title_type = TitleType::Application) const;
|
||||
std::vector<Core::Crypto::Key128> GetTitlekey() const;
|
||||
|
||||
std::vector<VirtualFile> GetFiles() const override;
|
||||
@@ -67,7 +70,7 @@ private:
|
||||
|
||||
std::shared_ptr<PartitionFilesystem> pfs;
|
||||
// Map title id -> {map type -> NCA}
|
||||
std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> ncas;
|
||||
std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> ncas;
|
||||
std::vector<VirtualFile> ticket_files;
|
||||
|
||||
Core::Crypto::KeyManager keys;
|
||||
|
||||
@@ -106,6 +106,8 @@ ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
|
||||
is_64bit_process = metadata.Is64BitProgram();
|
||||
|
||||
vm_manager.Reset(metadata.GetAddressSpaceType());
|
||||
// Ensure that the potentially resized page table is seen by CPU backends.
|
||||
Memory::SetCurrentPageTable(&vm_manager.page_table);
|
||||
|
||||
const auto& caps = metadata.GetKernelCapabilities();
|
||||
const auto capability_init_result =
|
||||
|
||||
@@ -28,11 +28,9 @@ ServerSession::~ServerSession() {
|
||||
// the emulated application.
|
||||
|
||||
// Decrease the port's connection count.
|
||||
if (parent->port)
|
||||
if (parent->port) {
|
||||
parent->port->ConnectionClosed();
|
||||
|
||||
// TODO(Subv): Wake up all the ClientSession's waiting threads and set
|
||||
// the SendSyncRequest result to 0xC920181A.
|
||||
}
|
||||
|
||||
parent->server = nullptr;
|
||||
}
|
||||
@@ -74,9 +72,6 @@ void ServerSession::ClientDisconnected() {
|
||||
handler->ClientDisconnected(this);
|
||||
}
|
||||
|
||||
// TODO(Subv): Force a wake up of all the ServerSession's waiting threads and set
|
||||
// their WaitSynchronization result to 0xC920181A.
|
||||
|
||||
// Clean up the list of client threads with pending requests, they are unneeded now that the
|
||||
// client endpoint is closed.
|
||||
pending_requesting_threads.clear();
|
||||
|
||||
@@ -86,7 +86,7 @@ static FileSys::VirtualFile GetManualRomFS() {
|
||||
if (loader.ReadManualRomFS(out) == Loader::ResultStatus::Success)
|
||||
return out;
|
||||
|
||||
const auto& installed{FileSystem::GetUnionContents()};
|
||||
const auto& installed{Core::System::GetInstance().GetContentProvider()};
|
||||
const auto res = installed.GetEntry(Core::System::GetInstance().CurrentProcess()->GetTitleID(),
|
||||
FileSys::ContentRecordType::Manual);
|
||||
|
||||
|
||||
@@ -33,11 +33,11 @@ static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
|
||||
|
||||
static std::vector<u64> AccumulateAOCTitleIDs() {
|
||||
std::vector<u64> add_on_content;
|
||||
const auto rcu = FileSystem::GetUnionContents();
|
||||
const auto& rcu = Core::System::GetInstance().GetContentProvider();
|
||||
const auto list =
|
||||
rcu.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
|
||||
std::transform(list.begin(), list.end(), std::back_inserter(add_on_content),
|
||||
[](const FileSys::RegisteredCacheEntry& rce) { return rce.title_id; });
|
||||
[](const FileSys::ContentProviderEntry& rce) { return rce.title_id; });
|
||||
add_on_content.erase(
|
||||
std::remove_if(
|
||||
add_on_content.begin(), add_on_content.end(),
|
||||
|
||||
@@ -391,11 +391,6 @@ void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
|
||||
save_data_factory->WriteSaveDataSize(type, title_id, user_id, new_value);
|
||||
}
|
||||
|
||||
FileSys::RegisteredCacheUnion GetUnionContents() {
|
||||
return FileSys::RegisteredCacheUnion{
|
||||
{GetSystemNANDContents(), GetUserNANDContents(), GetSDMCContents()}};
|
||||
}
|
||||
|
||||
FileSys::RegisteredCache* GetSystemNANDContents() {
|
||||
LOG_TRACE(Service_FS, "Opening System NAND Contents");
|
||||
|
||||
@@ -460,6 +455,10 @@ void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
|
||||
if (bis_factory == nullptr) {
|
||||
bis_factory =
|
||||
std::make_unique<FileSys::BISFactory>(nand_directory, load_directory, dump_directory);
|
||||
Core::System::GetInstance().RegisterContentProvider(
|
||||
FileSys::ContentProviderUnionSlot::SysNAND, bis_factory->GetSystemNANDContents());
|
||||
Core::System::GetInstance().RegisterContentProvider(
|
||||
FileSys::ContentProviderUnionSlot::UserNAND, bis_factory->GetUserNANDContents());
|
||||
}
|
||||
|
||||
if (save_data_factory == nullptr) {
|
||||
@@ -468,6 +467,8 @@ void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
|
||||
|
||||
if (sdmc_factory == nullptr) {
|
||||
sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
|
||||
Core::System::GetInstance().RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC,
|
||||
sdmc_factory->GetSDMCContents());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,8 +54,6 @@ FileSys::SaveDataSize ReadSaveDataSize(FileSys::SaveDataType type, u64 title_id,
|
||||
void WriteSaveDataSize(FileSys::SaveDataType type, u64 title_id, u128 user_id,
|
||||
FileSys::SaveDataSize new_value);
|
||||
|
||||
FileSys::RegisteredCacheUnion GetUnionContents();
|
||||
|
||||
FileSys::RegisteredCache* GetSystemNANDContents();
|
||||
FileSys::RegisteredCache* GetUserNANDContents();
|
||||
FileSys::RegisteredCache* GetSDMCContents();
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "core/memory.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
#pragma optimize("", off)
|
||||
|
||||
namespace Loader {
|
||||
namespace {
|
||||
struct MODHeader {
|
||||
@@ -136,13 +138,13 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
|
||||
|
||||
// Apply patches if necessary
|
||||
if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) {
|
||||
std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size());
|
||||
std::vector<u8> pi_header;
|
||||
pi_header.insert(pi_header.begin(), reinterpret_cast<u8*>(&nso_header),
|
||||
reinterpret_cast<u8*>(&nso_header) + sizeof(NSOHeader));
|
||||
pi_header.insert(pi_header.begin() + sizeof(NSOHeader), program_image.begin(),
|
||||
program_image.end());
|
||||
|
||||
pi_header = pm->PatchNSO(pi_header);
|
||||
pi_header = pm->PatchNSO(pi_header, file.GetName());
|
||||
|
||||
std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.begin());
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "video_core/dma_pusher.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
|
||||
@@ -1238,13 +1238,16 @@ union Instruction {
|
||||
|
||||
union {
|
||||
BitField<20, 16, u64> imm20_16;
|
||||
BitField<35, 1, u64> high_b_rr; // used on RR
|
||||
BitField<36, 1, u64> product_shift_left;
|
||||
BitField<37, 1, u64> merge_37;
|
||||
BitField<48, 1, u64> sign_a;
|
||||
BitField<49, 1, u64> sign_b;
|
||||
BitField<50, 2, XmadMode> mode_cbf; // used by CR, RC
|
||||
BitField<50, 3, XmadMode> mode;
|
||||
BitField<52, 1, u64> high_b;
|
||||
BitField<53, 1, u64> high_a;
|
||||
BitField<55, 1, u64> product_shift_left_second; // used on CR
|
||||
BitField<56, 1, u64> merge_56;
|
||||
} xmad;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_buffer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_global_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
|
||||
|
||||
@@ -299,6 +299,10 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
|
||||
BaseBindings base_bindings;
|
||||
std::array<bool, Maxwell::NumClipDistances> clip_distances{};
|
||||
|
||||
// Prepare packed bindings
|
||||
bind_ubo_pushbuffer.Setup(base_bindings.cbuf);
|
||||
bind_ssbo_pushbuffer.Setup(base_bindings.gmem);
|
||||
|
||||
for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
|
||||
const auto& shader_config = gpu.regs.shader_config[index];
|
||||
const Maxwell::ShaderProgram program{static_cast<Maxwell::ShaderProgram>(index)};
|
||||
@@ -321,8 +325,8 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
|
||||
&ubo, sizeof(ubo), static_cast<std::size_t>(uniform_buffer_alignment));
|
||||
|
||||
// Bind the emulation info buffer
|
||||
glBindBufferRange(GL_UNIFORM_BUFFER, base_bindings.cbuf, buffer_cache.GetHandle(), offset,
|
||||
static_cast<GLsizeiptr>(sizeof(ubo)));
|
||||
bind_ubo_pushbuffer.Push(buffer_cache.GetHandle(), offset,
|
||||
static_cast<GLsizeiptr>(sizeof(ubo)));
|
||||
|
||||
Shader shader{shader_cache.GetStageProgram(program)};
|
||||
const auto [program_handle, next_bindings] =
|
||||
@@ -366,6 +370,9 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
|
||||
base_bindings = next_bindings;
|
||||
}
|
||||
|
||||
bind_ubo_pushbuffer.Bind();
|
||||
bind_ssbo_pushbuffer.Bind();
|
||||
|
||||
SyncClipEnabled(clip_distances);
|
||||
|
||||
gpu.dirty_flags.shaders = false;
|
||||
@@ -900,23 +907,14 @@ void RasterizerOpenGL::SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::Shader
|
||||
const auto& shader_stage = maxwell3d.state.shader_stages[static_cast<std::size_t>(stage)];
|
||||
const auto& entries = shader->GetShaderEntries().const_buffers;
|
||||
|
||||
constexpr u64 max_binds = Tegra::Engines::Maxwell3D::Regs::MaxConstBuffers;
|
||||
std::array<GLuint, max_binds> bind_buffers;
|
||||
std::array<GLintptr, max_binds> bind_offsets;
|
||||
std::array<GLsizeiptr, max_binds> bind_sizes;
|
||||
|
||||
ASSERT_MSG(entries.size() <= max_binds, "Exceeded expected number of binding points.");
|
||||
|
||||
// Upload only the enabled buffers from the 16 constbuffers of each shader 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()];
|
||||
|
||||
if (!buffer.enabled) {
|
||||
// With disabled buffers set values as zero to unbind them
|
||||
bind_buffers[bindpoint] = 0;
|
||||
bind_offsets[bindpoint] = 0;
|
||||
bind_sizes[bindpoint] = 0;
|
||||
// Set values to zero to unbind buffers
|
||||
bind_ubo_pushbuffer.Push(0, 0, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -944,30 +942,19 @@ void RasterizerOpenGL::SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::Shader
|
||||
const GLintptr const_buffer_offset = buffer_cache.UploadMemory(
|
||||
buffer.address, size, static_cast<std::size_t>(uniform_buffer_alignment));
|
||||
|
||||
// Prepare values for multibind
|
||||
bind_buffers[bindpoint] = buffer_cache.GetHandle();
|
||||
bind_offsets[bindpoint] = const_buffer_offset;
|
||||
bind_sizes[bindpoint] = size;
|
||||
bind_ubo_pushbuffer.Push(buffer_cache.GetHandle(), const_buffer_offset, size);
|
||||
}
|
||||
|
||||
// The first binding is reserved for emulation values
|
||||
const GLuint ubo_base_binding = base_bindings.cbuf + 1;
|
||||
glBindBuffersRange(GL_UNIFORM_BUFFER, ubo_base_binding, static_cast<GLsizei>(entries.size()),
|
||||
bind_buffers.data(), bind_offsets.data(), bind_sizes.data());
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetupGlobalRegions(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage,
|
||||
const Shader& shader, GLenum primitive_mode,
|
||||
BaseBindings base_bindings) {
|
||||
// TODO(Rodrigo): Use ARB_multi_bind here
|
||||
const auto& entries = shader->GetShaderEntries().global_memory_entries;
|
||||
|
||||
for (u32 bindpoint = 0; bindpoint < static_cast<u32>(entries.size()); ++bindpoint) {
|
||||
const auto& entry = entries[bindpoint];
|
||||
const u32 current_bindpoint = base_bindings.gmem + bindpoint;
|
||||
const auto& region = global_cache.GetGlobalRegion(entry, stage);
|
||||
|
||||
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, current_bindpoint, region->GetBufferHandle());
|
||||
for (std::size_t bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
|
||||
const auto& entry{entries[bindpoint]};
|
||||
const auto& region{global_cache.GetGlobalRegion(entry, stage)};
|
||||
bind_ssbo_pushbuffer.Push(region->GetBufferHandle(), 0,
|
||||
static_cast<GLsizeiptr>(region->GetSizeInBytes()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "video_core/renderer_opengl/gl_shader_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
#include "video_core/renderer_opengl/utils.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
@@ -229,6 +230,9 @@ private:
|
||||
PrimitiveAssembler primitive_assembler{buffer_cache};
|
||||
GLint uniform_buffer_alignment;
|
||||
|
||||
BindBuffersRangePushBuffer bind_ubo_pushbuffer{GL_UNIFORM_BUFFER};
|
||||
BindBuffersRangePushBuffer bind_ssbo_pushbuffer{GL_SHADER_STORAGE_BUFFER};
|
||||
|
||||
std::size_t CalculateVertexArraysSize() const;
|
||||
|
||||
std::size_t CalculateIndexBufferSize() const;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/settings.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/morton.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
|
||||
@@ -661,8 +662,8 @@ void CachedSurface::FlushGLBuffer() {
|
||||
gl_buffer[0].resize(GetSizeInBytes());
|
||||
|
||||
const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
|
||||
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
|
||||
ASSERT(params.width * GetBytesPerPixel(params.pixel_format) % 4 == 0);
|
||||
const u32 align = std::clamp(params.RowAlign(0), 1U, 8U);
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, align);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.width));
|
||||
ASSERT(!tuple.compressed);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
@@ -707,8 +708,8 @@ void CachedSurface::UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle,
|
||||
|
||||
const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
|
||||
|
||||
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
|
||||
ASSERT(params.MipWidth(mip_map) * GetBytesPerPixel(params.pixel_format) % 4 == 0);
|
||||
const u32 align = std::clamp(params.RowAlign(mip_map), 1U, 8U);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, align);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.MipWidth(mip_map)));
|
||||
|
||||
const auto image_size = static_cast<GLsizei>(params.GetMipmapSizeGL(mip_map, false));
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/bit_util.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/hash.h"
|
||||
#include "common/math_util.h"
|
||||
@@ -205,6 +206,13 @@ struct SurfaceParams {
|
||||
return bd;
|
||||
}
|
||||
|
||||
u32 RowAlign(u32 mip_level) const {
|
||||
const u32 m_width = MipWidth(mip_level);
|
||||
const u32 bytes_per_pixel = GetBytesPerPixel(pixel_format);
|
||||
const u32 l2 = Common::CountTrailingZeroes32(m_width * bytes_per_pixel);
|
||||
return (1U << l2);
|
||||
}
|
||||
|
||||
/// Creates SurfaceParams from a texture configuration
|
||||
static SurfaceParams CreateForTexture(const Tegra::Texture::FullTextureInfo& config,
|
||||
const GLShader::SamplerEntry& entry);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "common/hash.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
|
||||
|
||||
@@ -552,8 +552,7 @@ private:
|
||||
} else if (std::holds_alternative<OperationNode>(*offset)) {
|
||||
// Indirect access
|
||||
const std::string final_offset = code.GenerateTemporary();
|
||||
code.AddLine("uint " + final_offset + " = (ftou(" + Visit(offset) + ") / 4) & " +
|
||||
std::to_string(MAX_CONSTBUFFER_ELEMENTS - 1) + ';');
|
||||
code.AddLine("uint " + final_offset + " = (ftou(" + Visit(offset) + ") / 4);");
|
||||
return fmt::format("{}[{} / 4][{} % 4]", GetConstBuffer(cbuf->GetIndex()),
|
||||
final_offset, final_offset);
|
||||
|
||||
|
||||
@@ -2,12 +2,44 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||
|
||||
namespace OpenGL::GLShader {
|
||||
|
||||
using Tegra::Engines::Maxwell3D;
|
||||
|
||||
ProgramManager::ProgramManager() {
|
||||
pipeline.Create();
|
||||
}
|
||||
|
||||
ProgramManager::~ProgramManager() = default;
|
||||
|
||||
void ProgramManager::ApplyTo(OpenGLState& state) {
|
||||
UpdatePipeline();
|
||||
state.draw.shader_program = 0;
|
||||
state.draw.program_pipeline = pipeline.handle;
|
||||
}
|
||||
|
||||
void ProgramManager::UpdatePipeline() {
|
||||
// Avoid updating the pipeline when values have no changed
|
||||
if (old_state == current_state) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Workaround for AMD bug
|
||||
constexpr GLenum all_used_stages{GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT |
|
||||
GL_FRAGMENT_SHADER_BIT};
|
||||
glUseProgramStages(pipeline.handle, all_used_stages, 0);
|
||||
|
||||
glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, current_state.vertex_shader);
|
||||
glUseProgramStages(pipeline.handle, GL_GEOMETRY_SHADER_BIT, current_state.geometry_shader);
|
||||
glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, current_state.fragment_shader);
|
||||
|
||||
old_state = current_state;
|
||||
}
|
||||
|
||||
void MaxwellUniformData::SetFromRegs(const Maxwell3D& maxwell, std::size_t shader_stage) {
|
||||
const auto& regs = maxwell.regs;
|
||||
const auto& state = maxwell.state;
|
||||
@@ -16,7 +48,7 @@ void MaxwellUniformData::SetFromRegs(const Maxwell3D& maxwell, std::size_t shade
|
||||
viewport_flip[0] = regs.viewport_transform[0].scale_x < 0.0 ? -1.0f : 1.0f;
|
||||
viewport_flip[1] = regs.viewport_transform[0].scale_y < 0.0 ? -1.0f : 1.0f;
|
||||
|
||||
u32 func = static_cast<u32>(regs.alpha_test_func);
|
||||
auto func{static_cast<u32>(regs.alpha_test_func)};
|
||||
// Normalize the gl variants of opCompare to be the same as the normal variants
|
||||
const u32 op_gl_variant_base = static_cast<u32>(Maxwell3D::Regs::ComparisonOp::Never);
|
||||
if (func >= op_gl_variant_base) {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
@@ -38,55 +40,48 @@ static_assert(sizeof(MaxwellUniformData) < 16384,
|
||||
|
||||
class ProgramManager {
|
||||
public:
|
||||
ProgramManager() {
|
||||
pipeline.Create();
|
||||
}
|
||||
explicit ProgramManager();
|
||||
~ProgramManager();
|
||||
|
||||
void ApplyTo(OpenGLState& state);
|
||||
|
||||
void UseProgrammableVertexShader(GLuint program) {
|
||||
vs = program;
|
||||
current_state.vertex_shader = program;
|
||||
}
|
||||
|
||||
void UseProgrammableGeometryShader(GLuint program) {
|
||||
gs = program;
|
||||
current_state.geometry_shader = program;
|
||||
}
|
||||
|
||||
void UseProgrammableFragmentShader(GLuint program) {
|
||||
fs = program;
|
||||
current_state.fragment_shader = program;
|
||||
}
|
||||
|
||||
void UseTrivialGeometryShader() {
|
||||
gs = 0;
|
||||
}
|
||||
|
||||
void ApplyTo(OpenGLState& state) {
|
||||
UpdatePipeline();
|
||||
state.draw.shader_program = 0;
|
||||
state.draw.program_pipeline = pipeline.handle;
|
||||
current_state.geometry_shader = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdatePipeline() {
|
||||
// Avoid updating the pipeline when values have no changed
|
||||
if (old_vs == vs && old_fs == fs && old_gs == gs)
|
||||
return;
|
||||
// Workaround for AMD bug
|
||||
glUseProgramStages(pipeline.handle,
|
||||
GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT,
|
||||
0);
|
||||
struct PipelineState {
|
||||
bool operator==(const PipelineState& rhs) const {
|
||||
return vertex_shader == rhs.vertex_shader && fragment_shader == rhs.fragment_shader &&
|
||||
geometry_shader == rhs.geometry_shader;
|
||||
}
|
||||
|
||||
glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, vs);
|
||||
glUseProgramStages(pipeline.handle, GL_GEOMETRY_SHADER_BIT, gs);
|
||||
glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, fs);
|
||||
bool operator!=(const PipelineState& rhs) const {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
// Update the old values
|
||||
old_vs = vs;
|
||||
old_fs = fs;
|
||||
old_gs = gs;
|
||||
}
|
||||
GLuint vertex_shader{};
|
||||
GLuint fragment_shader{};
|
||||
GLuint geometry_shader{};
|
||||
};
|
||||
|
||||
void UpdatePipeline();
|
||||
|
||||
OGLPipeline pipeline;
|
||||
GLuint vs{}, fs{}, gs{};
|
||||
GLuint old_vs{}, old_fs{}, old_gs{};
|
||||
PipelineState current_state;
|
||||
PipelineState old_state;
|
||||
};
|
||||
|
||||
} // namespace OpenGL::GLShader
|
||||
|
||||
@@ -5,11 +5,39 @@
|
||||
#include <string>
|
||||
#include <fmt/format.h>
|
||||
#include <glad/glad.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_opengl/utils.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
BindBuffersRangePushBuffer::BindBuffersRangePushBuffer(GLenum target) : target{target} {}
|
||||
|
||||
BindBuffersRangePushBuffer::~BindBuffersRangePushBuffer() = default;
|
||||
|
||||
void BindBuffersRangePushBuffer::Setup(GLuint first_) {
|
||||
first = first_;
|
||||
buffers.clear();
|
||||
offsets.clear();
|
||||
sizes.clear();
|
||||
}
|
||||
|
||||
void BindBuffersRangePushBuffer::Push(GLuint buffer, GLintptr offset, GLsizeiptr size) {
|
||||
buffers.push_back(buffer);
|
||||
offsets.push_back(offset);
|
||||
sizes.push_back(size);
|
||||
}
|
||||
|
||||
void BindBuffersRangePushBuffer::Bind() const {
|
||||
const std::size_t count{buffers.size()};
|
||||
DEBUG_ASSERT(count == offsets.size() && count == sizes.size());
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
glBindBuffersRange(target, first, static_cast<GLsizei>(count), buffers.data(), offsets.data(),
|
||||
sizes.data());
|
||||
}
|
||||
|
||||
void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string extra_info) {
|
||||
if (!GLAD_GL_KHR_debug) {
|
||||
return; // We don't need to throw an error as this is just for debugging
|
||||
|
||||
@@ -5,11 +5,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <glad/glad.h>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class BindBuffersRangePushBuffer {
|
||||
public:
|
||||
BindBuffersRangePushBuffer(GLenum target);
|
||||
~BindBuffersRangePushBuffer();
|
||||
|
||||
void Setup(GLuint first_);
|
||||
|
||||
void Push(GLuint buffer, GLintptr offset, GLsizeiptr size);
|
||||
|
||||
void Bind() const;
|
||||
|
||||
private:
|
||||
GLenum target;
|
||||
GLuint first;
|
||||
std::vector<GLuint> buffers;
|
||||
std::vector<GLintptr> offsets;
|
||||
std::vector<GLsizeiptr> sizes;
|
||||
};
|
||||
|
||||
void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string extra_info = "");
|
||||
|
||||
} // namespace OpenGL
|
||||
@@ -29,39 +29,55 @@ u32 ShaderIR::DecodeXmad(NodeBlock& bb, u32 pc) {
|
||||
const bool is_signed_b = instr.xmad.sign_b == 1;
|
||||
const bool is_signed_c = is_signed_a;
|
||||
|
||||
auto [is_merge, op_b, op_c] = [&]() -> std::tuple<bool, Node, Node> {
|
||||
auto [is_merge, is_psl, is_high_b, mode, op_b,
|
||||
op_c] = [&]() -> std::tuple<bool, bool, bool, Tegra::Shader::XmadMode, Node, Node> {
|
||||
switch (opcode->get().GetId()) {
|
||||
case OpCode::Id::XMAD_CR:
|
||||
return {instr.xmad.merge_56,
|
||||
instr.xmad.product_shift_left_second,
|
||||
instr.xmad.high_b,
|
||||
instr.xmad.mode_cbf,
|
||||
GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset()),
|
||||
GetRegister(instr.gpr39)};
|
||||
case OpCode::Id::XMAD_RR:
|
||||
return {instr.xmad.merge_37, GetRegister(instr.gpr20), GetRegister(instr.gpr39)};
|
||||
return {instr.xmad.merge_37, instr.xmad.product_shift_left, instr.xmad.high_b_rr,
|
||||
instr.xmad.mode, GetRegister(instr.gpr20), GetRegister(instr.gpr39)};
|
||||
case OpCode::Id::XMAD_RC:
|
||||
return {false, GetRegister(instr.gpr39),
|
||||
return {false,
|
||||
false,
|
||||
instr.xmad.high_b,
|
||||
instr.xmad.mode_cbf,
|
||||
GetRegister(instr.gpr39),
|
||||
GetConstBuffer(instr.cbuf34.index, instr.cbuf34.GetOffset())};
|
||||
case OpCode::Id::XMAD_IMM:
|
||||
return {instr.xmad.merge_37, Immediate(static_cast<u32>(instr.xmad.imm20_16)),
|
||||
return {instr.xmad.merge_37,
|
||||
instr.xmad.product_shift_left,
|
||||
false,
|
||||
instr.xmad.mode,
|
||||
Immediate(static_cast<u32>(instr.xmad.imm20_16)),
|
||||
GetRegister(instr.gpr39)};
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unhandled XMAD instruction: {}", opcode->get().GetName());
|
||||
return {false, Immediate(0), Immediate(0)};
|
||||
return {false, false, false, Tegra::Shader::XmadMode::None, Immediate(0), Immediate(0)};
|
||||
}();
|
||||
|
||||
op_a = BitfieldExtract(op_a, instr.xmad.high_a ? 16 : 0, 16);
|
||||
|
||||
const Node original_b = op_b;
|
||||
op_b = BitfieldExtract(op_b, instr.xmad.high_b ? 16 : 0, 16);
|
||||
op_b = BitfieldExtract(op_b, is_high_b ? 16 : 0, 16);
|
||||
|
||||
// TODO(Rodrigo): Use an appropiate sign for this operation
|
||||
Node product = Operation(OperationCode::IMul, NO_PRECISE, op_a, op_b);
|
||||
if (instr.xmad.product_shift_left) {
|
||||
if (is_psl) {
|
||||
product = Operation(OperationCode::ILogicalShiftLeft, NO_PRECISE, product, Immediate(16));
|
||||
}
|
||||
SetTemporal(bb, 0, product);
|
||||
product = GetTemporal(0);
|
||||
|
||||
const Node original_c = op_c;
|
||||
const Tegra::Shader::XmadMode set_mode = mode; // Workaround to clang compile error
|
||||
op_c = [&]() {
|
||||
switch (instr.xmad.mode) {
|
||||
switch (set_mode) {
|
||||
case Tegra::Shader::XmadMode::None:
|
||||
return original_c;
|
||||
case Tegra::Shader::XmadMode::CLo:
|
||||
@@ -80,8 +96,13 @@ u32 ShaderIR::DecodeXmad(NodeBlock& bb, u32 pc) {
|
||||
}
|
||||
}();
|
||||
|
||||
SetTemporal(bb, 1, op_c);
|
||||
op_c = GetTemporal(1);
|
||||
|
||||
// TODO(Rodrigo): Use an appropiate sign for this operation
|
||||
Node sum = Operation(OperationCode::IAdd, product, op_c);
|
||||
SetTemporal(bb, 2, sum);
|
||||
sum = GetTemporal(2);
|
||||
if (is_merge) {
|
||||
const Node a = BitfieldExtract(sum, 0, 16);
|
||||
const Node b =
|
||||
@@ -95,4 +116,4 @@ u32 ShaderIR::DecodeXmad(NodeBlock& bb, u32 pc) {
|
||||
return pc;
|
||||
}
|
||||
|
||||
} // namespace VideoCommon::Shader
|
||||
} // namespace VideoCommon::Shader
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/textures/astc.h"
|
||||
#include "video_core/textures/convert.h"
|
||||
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/surface.h"
|
||||
|
||||
namespace VideoCore::Surface {
|
||||
enum class PixelFormat;
|
||||
}
|
||||
|
||||
namespace Tegra::Texture {
|
||||
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
#include <array>
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
namespace Tegra::Texture {
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ add_executable(yuzu
|
||||
configuration/configure_general.h
|
||||
configuration/configure_graphics.cpp
|
||||
configuration/configure_graphics.h
|
||||
configuration/configure_hotkeys.cpp
|
||||
configuration/configure_hotkeys.h
|
||||
configuration/configure_input.cpp
|
||||
configuration/configure_input.h
|
||||
configuration/configure_input_player.cpp
|
||||
@@ -54,8 +56,6 @@ add_executable(yuzu
|
||||
debugger/graphics/graphics_breakpoints.cpp
|
||||
debugger/graphics/graphics_breakpoints.h
|
||||
debugger/graphics/graphics_breakpoints_p.h
|
||||
debugger/graphics/graphics_surface.cpp
|
||||
debugger/graphics/graphics_surface.h
|
||||
debugger/console.cpp
|
||||
debugger/console.h
|
||||
debugger/profiler.cpp
|
||||
@@ -78,6 +78,8 @@ add_executable(yuzu
|
||||
ui_settings.h
|
||||
util/limitable_input_dialog.cpp
|
||||
util/limitable_input_dialog.h
|
||||
util/sequence_dialog/sequence_dialog.cpp
|
||||
util/sequence_dialog/sequence_dialog.h
|
||||
util/spinbox.cpp
|
||||
util/spinbox.h
|
||||
util/util.cpp
|
||||
@@ -95,6 +97,7 @@ set(UIS
|
||||
configuration/configure_gamelist.ui
|
||||
configuration/configure_general.ui
|
||||
configuration/configure_graphics.ui
|
||||
configuration/configure_hotkeys.ui
|
||||
configuration/configure_input.ui
|
||||
configuration/configure_input_player.ui
|
||||
configuration/configure_input_simple.ui
|
||||
@@ -105,7 +108,6 @@ set(UIS
|
||||
configuration/configure_touchscreen_advanced.ui
|
||||
configuration/configure_web.ui
|
||||
compatdb.ui
|
||||
hotkeys.ui
|
||||
loading_screen.ui
|
||||
main.ui
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <mutex>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QScrollArea>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <vector>
|
||||
#include <QDialog>
|
||||
#include <QList>
|
||||
#include <QTreeView>
|
||||
#include "core/frontend/applets/profile_select.h"
|
||||
|
||||
class GMainWindow;
|
||||
@@ -16,7 +17,6 @@ class QLabel;
|
||||
class QScrollArea;
|
||||
class QStandardItem;
|
||||
class QStandardItemModel;
|
||||
class QTreeView;
|
||||
class QVBoxLayout;
|
||||
|
||||
class QtProfileSelectionDialog final : public QDialog {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <QKeySequence>
|
||||
#include <QSettings>
|
||||
#include "common/file_util.h"
|
||||
#include "configure_input_simple.h"
|
||||
@@ -9,7 +11,6 @@
|
||||
#include "core/hle/service/hid/controllers/npad.h"
|
||||
#include "input_common/main.h"
|
||||
#include "yuzu/configuration/config.h"
|
||||
#include "yuzu/ui_settings.h"
|
||||
|
||||
Config::Config() {
|
||||
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
|
||||
@@ -17,7 +18,6 @@ Config::Config() {
|
||||
FileUtil::CreateFullPath(qt_config_loc);
|
||||
qt_config =
|
||||
std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
|
||||
|
||||
Reload();
|
||||
}
|
||||
|
||||
@@ -205,6 +205,27 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
|
||||
Qt::Key_Control, Qt::Key_Shift, Qt::Key_AltGr, Qt::Key_ApplicationRight,
|
||||
};
|
||||
|
||||
// This shouldn't have anything except static initializers (no functions). So
|
||||
// QKeySequnce(...).toString() is NOT ALLOWED HERE.
|
||||
// This must be in alphabetical order according to action name as it must have the same order as
|
||||
// UISetting::values.shortcuts, which is alphabetically ordered.
|
||||
const std::array<UISettings::Shortcut, 15> Config::default_hotkeys{
|
||||
{{"Capture Screenshot", "Main Window", {"Ctrl+P", Qt::ApplicationShortcut}},
|
||||
{"Continue/Pause Emulation", "Main Window", {"F4", Qt::WindowShortcut}},
|
||||
{"Decrease Speed Limit", "Main Window", {"-", Qt::ApplicationShortcut}},
|
||||
{"Exit yuzu", "Main Window", {"Ctrl+Q", Qt::WindowShortcut}},
|
||||
{"Exit Fullscreen", "Main Window", {"Esc", Qt::WindowShortcut}},
|
||||
{"Fullscreen", "Main Window", {"F11", Qt::WindowShortcut}},
|
||||
{"Increase Speed Limit", "Main Window", {"+", Qt::ApplicationShortcut}},
|
||||
{"Load Amiibo", "Main Window", {"F2", Qt::ApplicationShortcut}},
|
||||
{"Load File", "Main Window", {"Ctrl+O", Qt::WindowShortcut}},
|
||||
{"Restart Emulation", "Main Window", {"F6", Qt::WindowShortcut}},
|
||||
{"Stop Emulation", "Main Window", {"F5", Qt::WindowShortcut}},
|
||||
{"Toggle Filter Bar", "Main Window", {"Ctrl+F", Qt::WindowShortcut}},
|
||||
{"Toggle Speed Limit", "Main Window", {"Ctrl+Z", Qt::ApplicationShortcut}},
|
||||
{"Toggle Status Bar", "Main Window", {"Ctrl+S", Qt::WindowShortcut}},
|
||||
{"Change Docked Mode", "Main Window", {"F10", Qt::ApplicationShortcut}}}};
|
||||
|
||||
void Config::ReadPlayerValues() {
|
||||
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
|
||||
auto& player = Settings::values.players[p];
|
||||
@@ -508,20 +529,15 @@ void Config::ReadValues() {
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Shortcuts");
|
||||
QStringList groups = qt_config->childGroups();
|
||||
for (auto group : groups) {
|
||||
for (auto [name, group, shortcut] : default_hotkeys) {
|
||||
auto [keyseq, context] = shortcut;
|
||||
qt_config->beginGroup(group);
|
||||
|
||||
QStringList hotkeys = qt_config->childGroups();
|
||||
for (auto hotkey : hotkeys) {
|
||||
qt_config->beginGroup(hotkey);
|
||||
UISettings::values.shortcuts.emplace_back(UISettings::Shortcut(
|
||||
group + "/" + hotkey,
|
||||
UISettings::ContextualShortcut(ReadSetting("KeySeq").toString(),
|
||||
ReadSetting("Context").toInt())));
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
qt_config->beginGroup(name);
|
||||
UISettings::values.shortcuts.push_back(
|
||||
{name,
|
||||
group,
|
||||
{ReadSetting("KeySeq", keyseq).toString(), ReadSetting("Context", context).toInt()}});
|
||||
qt_config->endGroup();
|
||||
qt_config->endGroup();
|
||||
}
|
||||
qt_config->endGroup();
|
||||
@@ -758,9 +774,16 @@ void Config::SaveValues() {
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Shortcuts");
|
||||
for (auto shortcut : UISettings::values.shortcuts) {
|
||||
WriteSetting(shortcut.first + "/KeySeq", shortcut.second.first);
|
||||
WriteSetting(shortcut.first + "/Context", shortcut.second.second);
|
||||
// Lengths of UISettings::values.shortcuts & default_hotkeys are same.
|
||||
// However, their ordering must also be the same.
|
||||
for (std::size_t i = 0; i < default_hotkeys.size(); i++) {
|
||||
auto [name, group, shortcut] = UISettings::values.shortcuts[i];
|
||||
qt_config->beginGroup(group);
|
||||
qt_config->beginGroup(name);
|
||||
WriteSetting("KeySeq", shortcut.first, default_hotkeys[i].shortcut.first);
|
||||
WriteSetting("Context", shortcut.second, default_hotkeys[i].shortcut.second);
|
||||
qt_config->endGroup();
|
||||
qt_config->endGroup();
|
||||
}
|
||||
qt_config->endGroup();
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <string>
|
||||
#include <QVariant>
|
||||
#include "core/settings.h"
|
||||
#include "yuzu/ui_settings.h"
|
||||
|
||||
class QSettings;
|
||||
|
||||
@@ -47,6 +48,8 @@ private:
|
||||
void WriteSetting(const QString& name, const QVariant& value);
|
||||
void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value);
|
||||
|
||||
static const std::array<UISettings::Shortcut, 15> default_hotkeys;
|
||||
|
||||
std::unique_ptr<QSettings> qt_config;
|
||||
std::string qt_config_loc;
|
||||
};
|
||||
|
||||
@@ -7,9 +7,15 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>382</width>
|
||||
<height>241</height>
|
||||
<height>650</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>650</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>yuzu Configuration</string>
|
||||
</property>
|
||||
@@ -62,6 +68,11 @@
|
||||
<string>Input</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="ConfigureHotkeys" name="hotkeysTab">
|
||||
<attribute name="title">
|
||||
<string>Hotkeys</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="ConfigureGraphics" name="graphicsTab">
|
||||
<attribute name="title">
|
||||
<string>Graphics</string>
|
||||
@@ -150,6 +161,12 @@
|
||||
<header>configuration/configure_input_simple.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ConfigureHotkeys</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>configuration/configure_hotkeys.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
|
||||
@@ -8,20 +8,22 @@
|
||||
#include "ui_configure.h"
|
||||
#include "yuzu/configuration/config.h"
|
||||
#include "yuzu/configuration/configure_dialog.h"
|
||||
#include "yuzu/configuration/configure_input_player.h"
|
||||
#include "yuzu/hotkeys.h"
|
||||
|
||||
ConfigureDialog::ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry)
|
||||
: QDialog(parent), ui(new Ui::ConfigureDialog) {
|
||||
ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry)
|
||||
: QDialog(parent), registry(registry), ui(new Ui::ConfigureDialog) {
|
||||
ui->setupUi(this);
|
||||
ui->generalTab->PopulateHotkeyList(registry);
|
||||
ui->hotkeysTab->Populate(registry);
|
||||
this->setConfiguration();
|
||||
this->PopulateSelectionList();
|
||||
connect(ui->selectorList, &QListWidget::itemSelectionChanged, this,
|
||||
&ConfigureDialog::UpdateVisibleTabs);
|
||||
|
||||
adjustSize();
|
||||
|
||||
ui->selectorList->setCurrentRow(0);
|
||||
|
||||
// Synchronise lists upon initialisation
|
||||
ui->hotkeysTab->EmitHotkeysChanged();
|
||||
}
|
||||
|
||||
ConfigureDialog::~ConfigureDialog() = default;
|
||||
@@ -34,6 +36,7 @@ void ConfigureDialog::applyConfiguration() {
|
||||
ui->systemTab->applyConfiguration();
|
||||
ui->profileManagerTab->applyConfiguration();
|
||||
ui->inputTab->applyConfiguration();
|
||||
ui->hotkeysTab->applyConfiguration(registry);
|
||||
ui->graphicsTab->applyConfiguration();
|
||||
ui->audioTab->applyConfiguration();
|
||||
ui->debugTab->applyConfiguration();
|
||||
@@ -47,7 +50,7 @@ void ConfigureDialog::PopulateSelectionList() {
|
||||
{{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}},
|
||||
{tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}},
|
||||
{tr("Graphics"), {tr("Graphics")}},
|
||||
{tr("Controls"), {tr("Input")}}}};
|
||||
{tr("Controls"), {tr("Input"), tr("Hotkeys")}}}};
|
||||
|
||||
for (const auto& entry : items) {
|
||||
auto* const item = new QListWidgetItem(entry.first);
|
||||
@@ -66,6 +69,7 @@ void ConfigureDialog::UpdateVisibleTabs() {
|
||||
{tr("System"), ui->systemTab},
|
||||
{tr("Profiles"), ui->profileManagerTab},
|
||||
{tr("Input"), ui->inputTab},
|
||||
{tr("Hotkeys"), ui->hotkeysTab},
|
||||
{tr("Graphics"), ui->graphicsTab},
|
||||
{tr("Audio"), ui->audioTab},
|
||||
{tr("Debug"), ui->debugTab},
|
||||
|
||||
@@ -17,7 +17,7 @@ class ConfigureDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry);
|
||||
explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry);
|
||||
~ConfigureDialog() override;
|
||||
|
||||
void applyConfiguration();
|
||||
@@ -28,4 +28,5 @@ private:
|
||||
void PopulateSelectionList();
|
||||
|
||||
std::unique_ptr<Ui::ConfigureDialog> ui;
|
||||
HotkeyRegistry& registry;
|
||||
};
|
||||
|
||||
@@ -35,10 +35,6 @@ void ConfigureGeneral::setConfiguration() {
|
||||
ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit);
|
||||
}
|
||||
|
||||
void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) {
|
||||
ui->widget->Populate(registry);
|
||||
}
|
||||
|
||||
void ConfigureGeneral::applyConfiguration() {
|
||||
UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
|
||||
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
|
||||
|
||||
@@ -20,7 +20,6 @@ public:
|
||||
explicit ConfigureGeneral(QWidget* parent = nullptr);
|
||||
~ConfigureGeneral() override;
|
||||
|
||||
void PopulateHotkeyList(const HotkeyRegistry& registry);
|
||||
void applyConfiguration();
|
||||
|
||||
private:
|
||||
|
||||
@@ -97,22 +97,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="HotKeysGroupBox">
|
||||
<property name="title">
|
||||
<string>Hotkeys</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="HotKeysHorizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="HotKeysVerticalLayout">
|
||||
<item>
|
||||
<widget class="GHotkeysDialog" name="widget" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
@@ -130,14 +114,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>GHotkeysDialog</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>hotkeys.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
121
src/yuzu/configuration/configure_hotkeys.cpp
Normal file
121
src/yuzu/configuration/configure_hotkeys.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QStandardItemModel>
|
||||
#include "core/settings.h"
|
||||
#include "ui_configure_hotkeys.h"
|
||||
#include "yuzu/configuration/configure_hotkeys.h"
|
||||
#include "yuzu/hotkeys.h"
|
||||
#include "yuzu/util/sequence_dialog/sequence_dialog.h"
|
||||
|
||||
ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
|
||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureHotkeys>()) {
|
||||
ui->setupUi(this);
|
||||
setFocusPolicy(Qt::ClickFocus);
|
||||
|
||||
model = new QStandardItemModel(this);
|
||||
model->setColumnCount(3);
|
||||
model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Context")});
|
||||
|
||||
connect(ui->hotkey_list, &QTreeView::doubleClicked, this, &ConfigureHotkeys::Configure);
|
||||
ui->hotkey_list->setModel(model);
|
||||
|
||||
// TODO(Kloen): Make context configurable as well (hiding the column for now)
|
||||
ui->hotkey_list->hideColumn(2);
|
||||
|
||||
ui->hotkey_list->setColumnWidth(0, 200);
|
||||
ui->hotkey_list->resizeColumnToContents(1);
|
||||
}
|
||||
|
||||
ConfigureHotkeys::~ConfigureHotkeys() = default;
|
||||
|
||||
void ConfigureHotkeys::EmitHotkeysChanged() {
|
||||
emit HotkeysChanged(GetUsedKeyList());
|
||||
}
|
||||
|
||||
QList<QKeySequence> ConfigureHotkeys::GetUsedKeyList() const {
|
||||
QList<QKeySequence> list;
|
||||
for (int r = 0; r < model->rowCount(); r++) {
|
||||
const QStandardItem* parent = model->item(r, 0);
|
||||
for (int r2 = 0; r2 < parent->rowCount(); r2++) {
|
||||
const QStandardItem* keyseq = parent->child(r2, 1);
|
||||
list << QKeySequence::fromString(keyseq->text(), QKeySequence::NativeText);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) {
|
||||
for (const auto& group : registry.hotkey_groups) {
|
||||
auto* parent_item = new QStandardItem(group.first);
|
||||
parent_item->setEditable(false);
|
||||
for (const auto& hotkey : group.second) {
|
||||
auto* action = new QStandardItem(hotkey.first);
|
||||
auto* keyseq =
|
||||
new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText));
|
||||
action->setEditable(false);
|
||||
keyseq->setEditable(false);
|
||||
parent_item->appendRow({action, keyseq});
|
||||
}
|
||||
model->appendRow(parent_item);
|
||||
}
|
||||
|
||||
ui->hotkey_list->expandAll();
|
||||
}
|
||||
|
||||
void ConfigureHotkeys::Configure(QModelIndex index) {
|
||||
if (index.parent() == QModelIndex())
|
||||
return;
|
||||
|
||||
index = index.sibling(index.row(), 1);
|
||||
auto* model = ui->hotkey_list->model();
|
||||
auto previous_key = model->data(index);
|
||||
|
||||
auto* hotkey_dialog = new SequenceDialog;
|
||||
int return_code = hotkey_dialog->exec();
|
||||
|
||||
auto key_sequence = hotkey_dialog->GetSequence();
|
||||
|
||||
if (return_code == QDialog::Rejected || key_sequence.isEmpty())
|
||||
return;
|
||||
|
||||
if (IsUsedKey(key_sequence) && key_sequence != QKeySequence(previous_key.toString())) {
|
||||
QMessageBox::critical(this, tr("Error in inputted key"),
|
||||
tr("You're using a key that's already bound."));
|
||||
} else {
|
||||
model->setData(index, key_sequence.toString(QKeySequence::NativeText));
|
||||
EmitHotkeysChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) {
|
||||
return GetUsedKeyList().contains(key_sequence);
|
||||
}
|
||||
|
||||
void ConfigureHotkeys::applyConfiguration(HotkeyRegistry& registry) {
|
||||
for (int key_id = 0; key_id < model->rowCount(); key_id++) {
|
||||
const QStandardItem* parent = model->item(key_id, 0);
|
||||
for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) {
|
||||
const QStandardItem* action = parent->child(key_column_id, 0);
|
||||
const QStandardItem* keyseq = parent->child(key_column_id, 1);
|
||||
for (auto& [group, sub_actions] : registry.hotkey_groups) {
|
||||
if (group != parent->text())
|
||||
continue;
|
||||
for (auto& [action_name, hotkey] : sub_actions) {
|
||||
if (action_name != action->text())
|
||||
continue;
|
||||
hotkey.keyseq = QKeySequence(keyseq->text());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registry.SaveHotkeys();
|
||||
Settings::Apply();
|
||||
}
|
||||
|
||||
void ConfigureHotkeys::retranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
48
src/yuzu/configuration/configure_hotkeys.h
Normal file
48
src/yuzu/configuration/configure_hotkeys.h
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QWidget>
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Ui {
|
||||
class ConfigureHotkeys;
|
||||
}
|
||||
|
||||
class HotkeyRegistry;
|
||||
class QStandardItemModel;
|
||||
|
||||
class ConfigureHotkeys : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ConfigureHotkeys(QWidget* parent = nullptr);
|
||||
~ConfigureHotkeys() override;
|
||||
|
||||
void applyConfiguration(HotkeyRegistry& registry);
|
||||
void retranslateUi();
|
||||
|
||||
void EmitHotkeysChanged();
|
||||
|
||||
/**
|
||||
* Populates the hotkey list widget using data from the provided registry.
|
||||
* Called everytime the Configure dialog is opened.
|
||||
* @param registry The HotkeyRegistry whose data is used to populate the list.
|
||||
*/
|
||||
void Populate(const HotkeyRegistry& registry);
|
||||
|
||||
signals:
|
||||
void HotkeysChanged(QList<QKeySequence> new_key_list);
|
||||
|
||||
private:
|
||||
void Configure(QModelIndex index);
|
||||
bool IsUsedKey(QKeySequence key_sequence);
|
||||
QList<QKeySequence> GetUsedKeyList() const;
|
||||
|
||||
std::unique_ptr<Ui::ConfigureHotkeys> ui;
|
||||
|
||||
QStandardItemModel* model;
|
||||
};
|
||||
42
src/yuzu/configuration/configure_hotkeys.ui
Normal file
42
src/yuzu/configuration/configure_hotkeys.ui
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ConfigureHotkeys</class>
|
||||
<widget class="QWidget" name="ConfigureHotkeys">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>363</width>
|
||||
<height>388</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Hotkey Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Double-click on a binding to change it.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeView" name="hotkey_list">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -1,516 +0,0 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QBoxLayout>
|
||||
#include <QComboBox>
|
||||
#include <QDebug>
|
||||
#include <QFileDialog>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QMouseEvent>
|
||||
#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 "yuzu/debugger/graphics/graphics_surface.h"
|
||||
#include "yuzu/util/spinbox.h"
|
||||
|
||||
static Tegra::Texture::TextureFormat ConvertToTextureFormat(
|
||||
Tegra::RenderTargetFormat render_target_format) {
|
||||
switch (render_target_format) {
|
||||
case Tegra::RenderTargetFormat::RGBA8_UNORM:
|
||||
return Tegra::Texture::TextureFormat::A8R8G8B8;
|
||||
case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
|
||||
return Tegra::Texture::TextureFormat::A2B10G10R10;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented RT format");
|
||||
return Tegra::Texture::TextureFormat::A8R8G8B8;
|
||||
}
|
||||
}
|
||||
|
||||
SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_)
|
||||
: QLabel(parent), surface_widget(surface_widget_) {}
|
||||
|
||||
SurfacePicture::~SurfacePicture() = default;
|
||||
|
||||
void SurfacePicture::mousePressEvent(QMouseEvent* event) {
|
||||
// Only do something while the left mouse button is held down
|
||||
if (!(event->buttons() & Qt::LeftButton))
|
||||
return;
|
||||
|
||||
if (pixmap() == nullptr)
|
||||
return;
|
||||
|
||||
if (surface_widget)
|
||||
surface_widget->Pick(event->x() * pixmap()->width() / width(),
|
||||
event->y() * pixmap()->height() / height());
|
||||
}
|
||||
|
||||
void SurfacePicture::mouseMoveEvent(QMouseEvent* event) {
|
||||
// We also want to handle the event if the user moves the mouse while holding down the LMB
|
||||
mousePressEvent(event);
|
||||
}
|
||||
|
||||
GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
|
||||
QWidget* parent)
|
||||
: BreakPointObserverDock(debug_context, tr("Maxwell Surface Viewer"), parent),
|
||||
surface_source(Source::RenderTarget0) {
|
||||
setObjectName("MaxwellSurface");
|
||||
|
||||
surface_source_list = new QComboBox;
|
||||
surface_source_list->addItem(tr("Render Target 0"));
|
||||
surface_source_list->addItem(tr("Render Target 1"));
|
||||
surface_source_list->addItem(tr("Render Target 2"));
|
||||
surface_source_list->addItem(tr("Render Target 3"));
|
||||
surface_source_list->addItem(tr("Render Target 4"));
|
||||
surface_source_list->addItem(tr("Render Target 5"));
|
||||
surface_source_list->addItem(tr("Render Target 6"));
|
||||
surface_source_list->addItem(tr("Render Target 7"));
|
||||
surface_source_list->addItem(tr("Z Buffer"));
|
||||
surface_source_list->addItem(tr("Custom"));
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(surface_source));
|
||||
|
||||
surface_address_control = new CSpinBox;
|
||||
surface_address_control->SetBase(16);
|
||||
surface_address_control->SetRange(0, 0x7FFFFFFFFFFFFFFF);
|
||||
surface_address_control->SetPrefix("0x");
|
||||
|
||||
unsigned max_dimension = 16384; // TODO: Find actual maximum
|
||||
|
||||
surface_width_control = new QSpinBox;
|
||||
surface_width_control->setRange(0, max_dimension);
|
||||
|
||||
surface_height_control = new QSpinBox;
|
||||
surface_height_control->setRange(0, max_dimension);
|
||||
|
||||
surface_picker_x_control = new QSpinBox;
|
||||
surface_picker_x_control->setRange(0, max_dimension - 1);
|
||||
|
||||
surface_picker_y_control = new QSpinBox;
|
||||
surface_picker_y_control->setRange(0, max_dimension - 1);
|
||||
|
||||
// clang-format off
|
||||
// Color formats sorted by Maxwell texture format index
|
||||
const QStringList surface_formats{
|
||||
tr("None"),
|
||||
QStringLiteral("R32_G32_B32_A32"),
|
||||
QStringLiteral("R32_G32_B32"),
|
||||
QStringLiteral("R16_G16_B16_A16"),
|
||||
QStringLiteral("R32_G32"),
|
||||
QStringLiteral("R32_B24G8"),
|
||||
QStringLiteral("ETC2_RGB"),
|
||||
QStringLiteral("X8B8G8R8"),
|
||||
QStringLiteral("A8R8G8B8"),
|
||||
QStringLiteral("A2B10G10R10"),
|
||||
QStringLiteral("ETC2_RGB_PTA"),
|
||||
QStringLiteral("ETC2_RGBA"),
|
||||
QStringLiteral("R16_G16"),
|
||||
QStringLiteral("G8R24"),
|
||||
QStringLiteral("G24R8"),
|
||||
QStringLiteral("R32"),
|
||||
QStringLiteral("BC6H_SF16"),
|
||||
QStringLiteral("BC6H_UF16"),
|
||||
QStringLiteral("A4B4G4R4"),
|
||||
QStringLiteral("A5B5G5R1"),
|
||||
QStringLiteral("A1B5G5R5"),
|
||||
QStringLiteral("B5G6R5"),
|
||||
QStringLiteral("B6G5R5"),
|
||||
QStringLiteral("BC7U"),
|
||||
QStringLiteral("G8R8"),
|
||||
QStringLiteral("EAC"),
|
||||
QStringLiteral("EACX2"),
|
||||
QStringLiteral("R16"),
|
||||
QStringLiteral("Y8_VIDEO"),
|
||||
QStringLiteral("R8"),
|
||||
QStringLiteral("G4R4"),
|
||||
QStringLiteral("R1"),
|
||||
QStringLiteral("E5B9G9R9_SHAREDEXP"),
|
||||
QStringLiteral("BF10GF11RF11"),
|
||||
QStringLiteral("G8B8G8R8"),
|
||||
QStringLiteral("B8G8R8G8"),
|
||||
QStringLiteral("DXT1"),
|
||||
QStringLiteral("DXT23"),
|
||||
QStringLiteral("DXT45"),
|
||||
QStringLiteral("DXN1"),
|
||||
QStringLiteral("DXN2"),
|
||||
QStringLiteral("Z24S8"),
|
||||
QStringLiteral("X8Z24"),
|
||||
QStringLiteral("S8Z24"),
|
||||
QStringLiteral("X4V4Z24__COV4R4V"),
|
||||
QStringLiteral("X4V4Z24__COV8R8V"),
|
||||
QStringLiteral("V8Z24__COV4R12V"),
|
||||
QStringLiteral("ZF32"),
|
||||
QStringLiteral("ZF32_X24S8"),
|
||||
QStringLiteral("X8Z24_X20V4S8__COV4R4V"),
|
||||
QStringLiteral("X8Z24_X20V4S8__COV8R8V"),
|
||||
QStringLiteral("ZF32_X20V4X8__COV4R4V"),
|
||||
QStringLiteral("ZF32_X20V4X8__COV8R8V"),
|
||||
QStringLiteral("ZF32_X20V4S8__COV4R4V"),
|
||||
QStringLiteral("ZF32_X20V4S8__COV8R8V"),
|
||||
QStringLiteral("X8Z24_X16V8S8__COV4R12V"),
|
||||
QStringLiteral("ZF32_X16V8X8__COV4R12V"),
|
||||
QStringLiteral("ZF32_X16V8S8__COV4R12V"),
|
||||
QStringLiteral("Z16"),
|
||||
QStringLiteral("V8Z24__COV8R24V"),
|
||||
QStringLiteral("X8Z24_X16V8S8__COV8R24V"),
|
||||
QStringLiteral("ZF32_X16V8X8__COV8R24V"),
|
||||
QStringLiteral("ZF32_X16V8S8__COV8R24V"),
|
||||
QStringLiteral("ASTC_2D_4X4"),
|
||||
QStringLiteral("ASTC_2D_5X5"),
|
||||
QStringLiteral("ASTC_2D_6X6"),
|
||||
QStringLiteral("ASTC_2D_8X8"),
|
||||
QStringLiteral("ASTC_2D_10X10"),
|
||||
QStringLiteral("ASTC_2D_12X12"),
|
||||
QStringLiteral("ASTC_2D_5X4"),
|
||||
QStringLiteral("ASTC_2D_6X5"),
|
||||
QStringLiteral("ASTC_2D_8X6"),
|
||||
QStringLiteral("ASTC_2D_10X8"),
|
||||
QStringLiteral("ASTC_2D_12X10"),
|
||||
QStringLiteral("ASTC_2D_8X5"),
|
||||
QStringLiteral("ASTC_2D_10X5"),
|
||||
QStringLiteral("ASTC_2D_10X6"),
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
surface_format_control = new QComboBox;
|
||||
surface_format_control->addItems(surface_formats);
|
||||
|
||||
surface_info_label = new QLabel();
|
||||
surface_info_label->setWordWrap(true);
|
||||
|
||||
surface_picture_label = new SurfacePicture(0, this);
|
||||
surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
surface_picture_label->setScaledContents(false);
|
||||
|
||||
auto scroll_area = new QScrollArea();
|
||||
scroll_area->setBackgroundRole(QPalette::Dark);
|
||||
scroll_area->setWidgetResizable(false);
|
||||
scroll_area->setWidget(surface_picture_label);
|
||||
|
||||
save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save"));
|
||||
|
||||
// Connections
|
||||
connect(this, &GraphicsSurfaceWidget::Update, this, &GraphicsSurfaceWidget::OnUpdate);
|
||||
connect(surface_source_list, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||
&GraphicsSurfaceWidget::OnSurfaceSourceChanged);
|
||||
connect(surface_address_control, &CSpinBox::ValueChanged, this,
|
||||
&GraphicsSurfaceWidget::OnSurfaceAddressChanged);
|
||||
connect(surface_width_control, qOverload<int>(&QSpinBox::valueChanged), this,
|
||||
&GraphicsSurfaceWidget::OnSurfaceWidthChanged);
|
||||
connect(surface_height_control, qOverload<int>(&QSpinBox::valueChanged), this,
|
||||
&GraphicsSurfaceWidget::OnSurfaceHeightChanged);
|
||||
connect(surface_format_control, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||
&GraphicsSurfaceWidget::OnSurfaceFormatChanged);
|
||||
connect(surface_picker_x_control, qOverload<int>(&QSpinBox::valueChanged), this,
|
||||
&GraphicsSurfaceWidget::OnSurfacePickerXChanged);
|
||||
connect(surface_picker_y_control, qOverload<int>(&QSpinBox::valueChanged), this,
|
||||
&GraphicsSurfaceWidget::OnSurfacePickerYChanged);
|
||||
connect(save_surface, &QPushButton::clicked, this, &GraphicsSurfaceWidget::SaveSurface);
|
||||
|
||||
auto main_widget = new QWidget;
|
||||
auto main_layout = new QVBoxLayout;
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Source:")));
|
||||
sub_layout->addWidget(surface_source_list);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("GPU Address:")));
|
||||
sub_layout->addWidget(surface_address_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Width:")));
|
||||
sub_layout->addWidget(surface_width_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Height:")));
|
||||
sub_layout->addWidget(surface_height_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Format:")));
|
||||
sub_layout->addWidget(surface_format_control);
|
||||
main_layout->addLayout(sub_layout);
|
||||
}
|
||||
main_layout->addWidget(scroll_area);
|
||||
|
||||
auto info_layout = new QHBoxLayout;
|
||||
{
|
||||
auto xy_layout = new QVBoxLayout;
|
||||
{
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("X:")));
|
||||
sub_layout->addWidget(surface_picker_x_control);
|
||||
xy_layout->addLayout(sub_layout);
|
||||
}
|
||||
{
|
||||
auto sub_layout = new QHBoxLayout;
|
||||
sub_layout->addWidget(new QLabel(tr("Y:")));
|
||||
sub_layout->addWidget(surface_picker_y_control);
|
||||
xy_layout->addLayout(sub_layout);
|
||||
}
|
||||
}
|
||||
info_layout->addLayout(xy_layout);
|
||||
surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
info_layout->addWidget(surface_info_label);
|
||||
}
|
||||
main_layout->addLayout(info_layout);
|
||||
|
||||
main_layout->addWidget(save_surface);
|
||||
main_widget->setLayout(main_layout);
|
||||
setWidget(main_widget);
|
||||
|
||||
// Load current data - TODO: Make sure this works when emulation is not running
|
||||
if (debug_context && debug_context->at_breakpoint) {
|
||||
emit Update();
|
||||
widget()->setEnabled(debug_context->at_breakpoint);
|
||||
} else {
|
||||
widget()->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) {
|
||||
emit Update();
|
||||
widget()->setEnabled(true);
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnResumed() {
|
||||
widget()->setEnabled(false);
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) {
|
||||
surface_source = static_cast<Source>(new_value);
|
||||
emit Update();
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) {
|
||||
if (surface_address != new_value) {
|
||||
surface_address = static_cast<GPUVAddr>(new_value);
|
||||
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) {
|
||||
if (surface_width != static_cast<unsigned>(new_value)) {
|
||||
surface_width = static_cast<unsigned>(new_value);
|
||||
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) {
|
||||
if (surface_height != static_cast<unsigned>(new_value)) {
|
||||
surface_height = static_cast<unsigned>(new_value);
|
||||
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) {
|
||||
if (surface_format != static_cast<Tegra::Texture::TextureFormat>(new_value)) {
|
||||
surface_format = static_cast<Tegra::Texture::TextureFormat>(new_value);
|
||||
|
||||
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
|
||||
emit Update();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) {
|
||||
if (surface_picker_x != new_value) {
|
||||
surface_picker_x = new_value;
|
||||
Pick(surface_picker_x, surface_picker_y);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) {
|
||||
if (surface_picker_y != new_value) {
|
||||
surface_picker_y = new_value;
|
||||
Pick(surface_picker_x, surface_picker_y);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::Pick(int x, int y) {
|
||||
surface_picker_x_control->setValue(x);
|
||||
surface_picker_y_control->setValue(y);
|
||||
|
||||
if (x < 0 || x >= static_cast<int>(surface_width) || y < 0 ||
|
||||
y >= static_cast<int>(surface_height)) {
|
||||
surface_info_label->setText(tr("Pixel out of bounds"));
|
||||
surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
return;
|
||||
}
|
||||
|
||||
surface_info_label->setText(QString("Raw: <Unimplemented>\n(%1)").arg("<Unimplemented>"));
|
||||
surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::OnUpdate() {
|
||||
auto& gpu = Core::System::GetInstance().GPU();
|
||||
|
||||
QPixmap pixmap;
|
||||
|
||||
switch (surface_source) {
|
||||
case Source::RenderTarget0:
|
||||
case Source::RenderTarget1:
|
||||
case Source::RenderTarget2:
|
||||
case Source::RenderTarget3:
|
||||
case Source::RenderTarget4:
|
||||
case Source::RenderTarget5:
|
||||
case Source::RenderTarget6:
|
||||
case Source::RenderTarget7: {
|
||||
// TODO: Store a reference to the registers in the debug context instead of accessing them
|
||||
// directly...
|
||||
|
||||
const auto& registers = gpu.Maxwell3D().regs;
|
||||
const auto& rt = registers.rt[static_cast<std::size_t>(surface_source) -
|
||||
static_cast<std::size_t>(Source::RenderTarget0)];
|
||||
|
||||
surface_address = rt.Address();
|
||||
surface_width = rt.width;
|
||||
surface_height = rt.height;
|
||||
if (rt.format != Tegra::RenderTargetFormat::NONE) {
|
||||
surface_format = ConvertToTextureFormat(rt.format);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Source::Custom: {
|
||||
// Keep user-specified values
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
qDebug() << "Unknown surface source " << static_cast<int>(surface_source);
|
||||
break;
|
||||
}
|
||||
|
||||
surface_address_control->SetValue(surface_address);
|
||||
surface_width_control->setValue(surface_width);
|
||||
surface_height_control->setValue(surface_height);
|
||||
surface_format_control->setCurrentIndex(static_cast<int>(surface_format));
|
||||
|
||||
if (surface_address == 0) {
|
||||
surface_picture_label->hide();
|
||||
surface_info_label->setText(tr("(invalid surface address)"));
|
||||
surface_info_label->setAlignment(Qt::AlignCenter);
|
||||
surface_picker_x_control->setEnabled(false);
|
||||
surface_picker_y_control->setEnabled(false);
|
||||
save_surface->setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Implement a good way to visualize alpha components!
|
||||
|
||||
QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32);
|
||||
|
||||
// TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles.
|
||||
// Needs to be fixed if we plan to use this feature more, otherwise we may remove it.
|
||||
auto unswizzled_data = Tegra::Texture::UnswizzleTexture(
|
||||
gpu.MemoryManager().GetPointer(surface_address), 1, 1,
|
||||
Tegra::Texture::BytesPerPixel(surface_format), surface_width, surface_height, 1U);
|
||||
|
||||
auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format,
|
||||
surface_width, surface_height);
|
||||
|
||||
surface_picture_label->show();
|
||||
|
||||
for (unsigned int y = 0; y < surface_height; ++y) {
|
||||
for (unsigned int x = 0; x < surface_width; ++x) {
|
||||
Common::Vec4<u8> color;
|
||||
color[0] = texture_data[x + y * surface_width + 0];
|
||||
color[1] = texture_data[x + y * surface_width + 1];
|
||||
color[2] = texture_data[x + y * surface_width + 2];
|
||||
color[3] = texture_data[x + y * surface_width + 3];
|
||||
decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
|
||||
}
|
||||
}
|
||||
|
||||
pixmap = QPixmap::fromImage(decoded_image);
|
||||
surface_picture_label->setPixmap(pixmap);
|
||||
surface_picture_label->resize(pixmap.size());
|
||||
|
||||
// Update the info with pixel data
|
||||
surface_picker_x_control->setEnabled(true);
|
||||
surface_picker_y_control->setEnabled(true);
|
||||
Pick(surface_picker_x, surface_picker_y);
|
||||
|
||||
// Enable saving the converted pixmap to file
|
||||
save_surface->setEnabled(true);
|
||||
}
|
||||
|
||||
void GraphicsSurfaceWidget::SaveSurface() {
|
||||
const QString png_filter = tr("Portable Network Graphic (*.png)");
|
||||
const QString bin_filter = tr("Binary data (*.bin)");
|
||||
|
||||
QString selected_filter;
|
||||
const QString filename = QFileDialog::getSaveFileName(
|
||||
this, tr("Save Surface"),
|
||||
QStringLiteral("texture-0x%1.png").arg(QString::number(surface_address, 16)),
|
||||
QStringLiteral("%1;;%2").arg(png_filter, bin_filter), &selected_filter);
|
||||
|
||||
if (filename.isEmpty()) {
|
||||
// If the user canceled the dialog, don't save anything.
|
||||
return;
|
||||
}
|
||||
|
||||
if (selected_filter == png_filter) {
|
||||
const QPixmap* const pixmap = surface_picture_label->pixmap();
|
||||
ASSERT_MSG(pixmap != nullptr, "No pixmap set");
|
||||
|
||||
QFile file{filename};
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
QMessageBox::warning(this, tr("Error"), tr("Failed to open file '%1'").arg(filename));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pixmap->save(&file, "PNG")) {
|
||||
QMessageBox::warning(this, tr("Error"),
|
||||
tr("Failed to save surface data to file '%1'").arg(filename));
|
||||
}
|
||||
} else if (selected_filter == bin_filter) {
|
||||
auto& gpu = Core::System::GetInstance().GPU();
|
||||
const std::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);
|
||||
|
||||
const u8* const buffer = Memory::GetPointer(*address);
|
||||
ASSERT_MSG(buffer != nullptr, "Memory not accessible");
|
||||
|
||||
QFile file{filename};
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
QMessageBox::warning(this, tr("Error"), tr("Failed to open file '%1'").arg(filename));
|
||||
return;
|
||||
}
|
||||
|
||||
const int size =
|
||||
surface_width * surface_height * Tegra::Texture::BytesPerPixel(surface_format);
|
||||
const QByteArray data(reinterpret_cast<const char*>(buffer), size);
|
||||
if (file.write(data) != data.size()) {
|
||||
QMessageBox::warning(
|
||||
this, tr("Error"),
|
||||
tr("Failed to completely write surface data to file. The saved data will "
|
||||
"likely be corrupt."));
|
||||
}
|
||||
} else {
|
||||
UNREACHABLE_MSG("Unhandled filter selected");
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h"
|
||||
|
||||
class QComboBox;
|
||||
class QSpinBox;
|
||||
class CSpinBox;
|
||||
|
||||
class GraphicsSurfaceWidget;
|
||||
|
||||
class SurfacePicture : public QLabel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SurfacePicture(QWidget* parent = nullptr,
|
||||
GraphicsSurfaceWidget* surface_widget = nullptr);
|
||||
~SurfacePicture() override;
|
||||
|
||||
protected slots:
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
|
||||
private:
|
||||
GraphicsSurfaceWidget* surface_widget;
|
||||
};
|
||||
|
||||
class GraphicsSurfaceWidget : public BreakPointObserverDock {
|
||||
Q_OBJECT
|
||||
|
||||
using Event = Tegra::DebugContext::Event;
|
||||
|
||||
enum class Source {
|
||||
RenderTarget0 = 0,
|
||||
RenderTarget1 = 1,
|
||||
RenderTarget2 = 2,
|
||||
RenderTarget3 = 3,
|
||||
RenderTarget4 = 4,
|
||||
RenderTarget5 = 5,
|
||||
RenderTarget6 = 6,
|
||||
RenderTarget7 = 7,
|
||||
ZBuffer = 8,
|
||||
Custom = 9,
|
||||
};
|
||||
|
||||
public:
|
||||
explicit GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context,
|
||||
QWidget* parent = nullptr);
|
||||
void Pick(int x, int y);
|
||||
|
||||
public slots:
|
||||
void OnSurfaceSourceChanged(int new_value);
|
||||
void OnSurfaceAddressChanged(qint64 new_value);
|
||||
void OnSurfaceWidthChanged(int new_value);
|
||||
void OnSurfaceHeightChanged(int new_value);
|
||||
void OnSurfaceFormatChanged(int new_value);
|
||||
void OnSurfacePickerXChanged(int new_value);
|
||||
void OnSurfacePickerYChanged(int new_value);
|
||||
void OnUpdate();
|
||||
|
||||
signals:
|
||||
void Update();
|
||||
|
||||
private:
|
||||
void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override;
|
||||
void OnResumed() override;
|
||||
|
||||
void SaveSurface();
|
||||
|
||||
QComboBox* surface_source_list;
|
||||
CSpinBox* surface_address_control;
|
||||
QSpinBox* surface_width_control;
|
||||
QSpinBox* surface_height_control;
|
||||
QComboBox* surface_format_control;
|
||||
|
||||
SurfacePicture* surface_picture_label;
|
||||
QSpinBox* surface_picker_x_control;
|
||||
QSpinBox* surface_picker_y_control;
|
||||
QLabel* surface_info_label;
|
||||
QPushButton* save_surface;
|
||||
|
||||
Source surface_source;
|
||||
GPUVAddr surface_address;
|
||||
unsigned surface_width;
|
||||
unsigned surface_height;
|
||||
Tegra::Texture::TextureFormat surface_format;
|
||||
int surface_picker_x = 0;
|
||||
int surface_picker_y = 0;
|
||||
};
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
#include "yuzu/game_list.h"
|
||||
#include "yuzu/game_list_p.h"
|
||||
@@ -193,8 +194,9 @@ void GameList::onFilterCloseClicked() {
|
||||
main_window->filterBarSetChecked(false);
|
||||
}
|
||||
|
||||
GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent)
|
||||
: QWidget{parent}, vfs(std::move(vfs)) {
|
||||
GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvider* provider,
|
||||
GMainWindow* parent)
|
||||
: QWidget{parent}, vfs(std::move(vfs)), provider(provider) {
|
||||
watcher = new QFileSystemWatcher(this);
|
||||
connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
|
||||
|
||||
@@ -432,7 +434,8 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
|
||||
|
||||
emit ShouldCancelWorker();
|
||||
|
||||
GameListWorker* worker = new GameListWorker(vfs, dir_path, deep_scan, compatibility_list);
|
||||
GameListWorker* worker =
|
||||
new GameListWorker(vfs, provider, dir_path, deep_scan, compatibility_list);
|
||||
|
||||
connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
|
||||
connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating,
|
||||
|
||||
@@ -26,8 +26,9 @@ class GameListSearchField;
|
||||
class GMainWindow;
|
||||
|
||||
namespace FileSys {
|
||||
class ManualContentProvider;
|
||||
class VfsFilesystem;
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
enum class GameListOpenTarget {
|
||||
SaveData,
|
||||
@@ -47,7 +48,8 @@ public:
|
||||
COLUMN_COUNT, // Number of columns
|
||||
};
|
||||
|
||||
explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs, GMainWindow* parent = nullptr);
|
||||
explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs,
|
||||
FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr);
|
||||
~GameList() override;
|
||||
|
||||
void clearFilter();
|
||||
@@ -86,6 +88,7 @@ private:
|
||||
void RefreshGameDirectory();
|
||||
|
||||
std::shared_ptr<FileSys::VfsFilesystem> vfs;
|
||||
FileSys::ManualContentProvider* provider;
|
||||
GameListSearchField* search_field;
|
||||
GMainWindow* main_window = nullptr;
|
||||
QVBoxLayout* layout = nullptr;
|
||||
|
||||
@@ -12,12 +12,15 @@
|
||||
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
@@ -119,20 +122,25 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan,
|
||||
const CompatibilityList& compatibility_list)
|
||||
: vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan),
|
||||
GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs,
|
||||
FileSys::ManualContentProvider* provider, QString dir_path,
|
||||
bool deep_scan, const CompatibilityList& compatibility_list)
|
||||
: vfs(std::move(vfs)), provider(provider), dir_path(std::move(dir_path)), deep_scan(deep_scan),
|
||||
compatibility_list(compatibility_list) {}
|
||||
|
||||
GameListWorker::~GameListWorker() = default;
|
||||
|
||||
void GameListWorker::AddInstalledTitlesToGameList() {
|
||||
const auto cache = Service::FileSystem::GetUnionContents();
|
||||
const auto installed_games = cache.ListEntriesFilter(FileSys::TitleType::Application,
|
||||
FileSys::ContentRecordType::Program);
|
||||
void GameListWorker::AddTitlesToGameList() {
|
||||
const auto& cache = dynamic_cast<FileSys::ContentProviderUnion&>(
|
||||
Core::System::GetInstance().GetContentProvider());
|
||||
const auto installed_games = cache.ListEntriesFilterOrigin(
|
||||
std::nullopt, FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
|
||||
|
||||
for (const auto& game : installed_games) {
|
||||
const auto file = cache.GetEntryUnparsed(game);
|
||||
for (const auto& [slot, game] : installed_games) {
|
||||
if (slot == FileSys::ContentProviderUnionSlot::FrontendManual)
|
||||
continue;
|
||||
|
||||
const auto file = cache.GetEntryUnparsed(game.title_id, game.type);
|
||||
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
|
||||
if (!loader)
|
||||
continue;
|
||||
@@ -150,45 +158,13 @@ void GameListWorker::AddInstalledTitlesToGameList() {
|
||||
emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id,
|
||||
compatibility_list, patch));
|
||||
}
|
||||
|
||||
const auto control_data = cache.ListEntriesFilter(FileSys::TitleType::Application,
|
||||
FileSys::ContentRecordType::Control);
|
||||
|
||||
for (const auto& entry : control_data) {
|
||||
auto nca = cache.GetEntry(entry);
|
||||
if (nca != nullptr) {
|
||||
nca_control_map.insert_or_assign(entry.title_id, std::move(nca));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameListWorker::FillControlMap(const std::string& dir_path) {
|
||||
const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
if (stop_processing) {
|
||||
// Breaks the callback loop
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string physical_name = directory + DIR_SEP + virtual_name;
|
||||
const QFileInfo file_info(QString::fromStdString(physical_name));
|
||||
if (!file_info.isDir() && file_info.suffix() == QStringLiteral("nca")) {
|
||||
auto nca =
|
||||
std::make_unique<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
|
||||
if (nca->GetType() == FileSys::NCAContentType::Control) {
|
||||
const u64 title_id = nca->GetTitleId();
|
||||
nca_control_map.insert_or_assign(title_id, std::move(nca));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
|
||||
}
|
||||
|
||||
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
|
||||
const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path,
|
||||
unsigned int recursion) {
|
||||
const auto callback = [this, target, recursion](u64* num_entries_out,
|
||||
const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
if (stop_processing) {
|
||||
// Breaks the callback loop.
|
||||
return false;
|
||||
@@ -198,7 +174,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
||||
const bool is_dir = FileUtil::IsDirectory(physical_name);
|
||||
if (!is_dir &&
|
||||
(HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) {
|
||||
auto loader = Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
|
||||
const auto file = vfs->OpenFile(physical_name, FileSys::Mode::Read);
|
||||
auto loader = Loader::GetLoader(file);
|
||||
if (!loader) {
|
||||
return true;
|
||||
}
|
||||
@@ -209,31 +186,42 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<u8> icon;
|
||||
const auto res1 = loader->ReadIcon(icon);
|
||||
|
||||
u64 program_id = 0;
|
||||
const auto res2 = loader->ReadProgramId(program_id);
|
||||
|
||||
std::string name = " ";
|
||||
const auto res3 = loader->ReadTitle(name);
|
||||
|
||||
const FileSys::PatchManager patch{program_id};
|
||||
|
||||
if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success &&
|
||||
res2 == Loader::ResultStatus::Success) {
|
||||
// Use from metadata pool.
|
||||
if (nca_control_map.find(program_id) != nca_control_map.end()) {
|
||||
const auto& nca = nca_control_map[program_id];
|
||||
GetMetadataFromControlNCA(patch, *nca, icon, name);
|
||||
if (target == ScanTarget::FillManualContentProvider) {
|
||||
if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
|
||||
provider->AddEntry(FileSys::TitleType::Application,
|
||||
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
|
||||
program_id, file);
|
||||
} else if (res2 == Loader::ResultStatus::Success &&
|
||||
(file_type == Loader::FileType::XCI ||
|
||||
file_type == Loader::FileType::NSP)) {
|
||||
const auto nsp = file_type == Loader::FileType::NSP
|
||||
? std::make_shared<FileSys::NSP>(file)
|
||||
: FileSys::XCI{file}.GetSecurePartitionNSP();
|
||||
for (const auto& title : nsp->GetNCAs()) {
|
||||
for (const auto& entry : title.second) {
|
||||
provider->AddEntry(entry.first.first, entry.first.second, title.first,
|
||||
entry.second->GetBaseFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::vector<u8> icon;
|
||||
const auto res1 = loader->ReadIcon(icon);
|
||||
|
||||
emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
|
||||
compatibility_list, patch));
|
||||
std::string name = " ";
|
||||
const auto res3 = loader->ReadTitle(name);
|
||||
|
||||
const FileSys::PatchManager patch{program_id};
|
||||
|
||||
emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
|
||||
compatibility_list, patch));
|
||||
}
|
||||
} else if (is_dir && recursion > 0) {
|
||||
watch_list.append(QString::fromStdString(physical_name));
|
||||
AddFstEntriesToGameList(physical_name, recursion - 1);
|
||||
ScanFileSystem(target, physical_name, recursion - 1);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -245,10 +233,11 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
||||
void GameListWorker::run() {
|
||||
stop_processing = false;
|
||||
watch_list.append(dir_path);
|
||||
FillControlMap(dir_path.toStdString());
|
||||
AddInstalledTitlesToGameList();
|
||||
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
|
||||
nca_control_map.clear();
|
||||
provider->ClearAllEntries();
|
||||
ScanFileSystem(ScanTarget::FillManualContentProvider, dir_path.toStdString(),
|
||||
deep_scan ? 256 : 0);
|
||||
AddTitlesToGameList();
|
||||
ScanFileSystem(ScanTarget::PopulateGameList, dir_path.toStdString(), deep_scan ? 256 : 0);
|
||||
emit Finished(watch_list);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,8 @@ class GameListWorker : public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan,
|
||||
GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs,
|
||||
FileSys::ManualContentProvider* provider, QString dir_path, bool deep_scan,
|
||||
const CompatibilityList& compatibility_list);
|
||||
~GameListWorker() override;
|
||||
|
||||
@@ -58,12 +59,17 @@ signals:
|
||||
void Finished(QStringList watch_list);
|
||||
|
||||
private:
|
||||
void AddInstalledTitlesToGameList();
|
||||
void FillControlMap(const std::string& dir_path);
|
||||
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
|
||||
void AddTitlesToGameList();
|
||||
|
||||
enum class ScanTarget {
|
||||
FillManualContentProvider,
|
||||
PopulateGameList,
|
||||
};
|
||||
|
||||
void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion = 0);
|
||||
|
||||
std::shared_ptr<FileSys::VfsFilesystem> vfs;
|
||||
std::map<u64, std::unique_ptr<FileSys::NCA>> nca_control_map;
|
||||
FileSys::ManualContentProvider* provider;
|
||||
QStringList watch_list;
|
||||
QString dir_path;
|
||||
bool deep_scan;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <map>
|
||||
#include <QKeySequence>
|
||||
#include <QShortcut>
|
||||
#include <QTreeWidgetItem>
|
||||
@@ -13,47 +12,32 @@
|
||||
HotkeyRegistry::HotkeyRegistry() = default;
|
||||
HotkeyRegistry::~HotkeyRegistry() = default;
|
||||
|
||||
void HotkeyRegistry::LoadHotkeys() {
|
||||
// Make sure NOT to use a reference here because it would become invalid once we call
|
||||
// beginGroup()
|
||||
for (auto shortcut : UISettings::values.shortcuts) {
|
||||
const QStringList cat = shortcut.first.split('/');
|
||||
Q_ASSERT(cat.size() >= 2);
|
||||
|
||||
// RegisterHotkey assigns default keybindings, so use old values as default parameters
|
||||
Hotkey& hk = hotkey_groups[cat[0]][cat[1]];
|
||||
if (!shortcut.second.first.isEmpty()) {
|
||||
hk.keyseq = QKeySequence::fromString(shortcut.second.first);
|
||||
hk.context = static_cast<Qt::ShortcutContext>(shortcut.second.second);
|
||||
}
|
||||
if (hk.shortcut)
|
||||
hk.shortcut->setKey(hk.keyseq);
|
||||
}
|
||||
}
|
||||
|
||||
void HotkeyRegistry::SaveHotkeys() {
|
||||
UISettings::values.shortcuts.clear();
|
||||
for (const auto& group : hotkey_groups) {
|
||||
for (const auto& hotkey : group.second) {
|
||||
UISettings::values.shortcuts.emplace_back(
|
||||
UISettings::Shortcut(group.first + '/' + hotkey.first,
|
||||
UISettings::ContextualShortcut(hotkey.second.keyseq.toString(),
|
||||
hotkey.second.context)));
|
||||
UISettings::values.shortcuts.push_back(
|
||||
{hotkey.first, group.first,
|
||||
UISettings::ContextualShortcut(hotkey.second.keyseq.toString(),
|
||||
hotkey.second.context)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HotkeyRegistry::RegisterHotkey(const QString& group, const QString& action,
|
||||
const QKeySequence& default_keyseq,
|
||||
Qt::ShortcutContext default_context) {
|
||||
auto& hotkey_group = hotkey_groups[group];
|
||||
if (hotkey_group.find(action) != hotkey_group.end()) {
|
||||
return;
|
||||
void HotkeyRegistry::LoadHotkeys() {
|
||||
// Make sure NOT to use a reference here because it would become invalid once we call
|
||||
// beginGroup()
|
||||
for (auto shortcut : UISettings::values.shortcuts) {
|
||||
Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name];
|
||||
if (!shortcut.shortcut.first.isEmpty()) {
|
||||
hk.keyseq = QKeySequence::fromString(shortcut.shortcut.first, QKeySequence::NativeText);
|
||||
hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.second);
|
||||
}
|
||||
if (hk.shortcut) {
|
||||
hk.shortcut->disconnect();
|
||||
hk.shortcut->setKey(hk.keyseq);
|
||||
}
|
||||
}
|
||||
|
||||
auto& hotkey_action = hotkey_groups[group][action];
|
||||
hotkey_action.keyseq = default_keyseq;
|
||||
hotkey_action.context = default_context;
|
||||
}
|
||||
|
||||
QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) {
|
||||
@@ -65,24 +49,11 @@ QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action
|
||||
return hk.shortcut;
|
||||
}
|
||||
|
||||
GHotkeysDialog::GHotkeysDialog(QWidget* parent) : QWidget(parent) {
|
||||
ui.setupUi(this);
|
||||
QKeySequence HotkeyRegistry::GetKeySequence(const QString& group, const QString& action) {
|
||||
return hotkey_groups[group][action].keyseq;
|
||||
}
|
||||
|
||||
void GHotkeysDialog::Populate(const HotkeyRegistry& registry) {
|
||||
for (const auto& group : registry.hotkey_groups) {
|
||||
QTreeWidgetItem* toplevel_item = new QTreeWidgetItem(QStringList(group.first));
|
||||
for (const auto& hotkey : group.second) {
|
||||
QStringList columns;
|
||||
columns << hotkey.first << hotkey.second.keyseq.toString();
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem(columns);
|
||||
toplevel_item->addChild(item);
|
||||
}
|
||||
ui.treeWidget->addTopLevelItem(toplevel_item);
|
||||
}
|
||||
// TODO: Make context configurable as well (hiding the column for now)
|
||||
ui.treeWidget->setColumnCount(2);
|
||||
|
||||
ui.treeWidget->resizeColumnToContents(0);
|
||||
ui.treeWidget->resizeColumnToContents(1);
|
||||
Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const QString& group,
|
||||
const QString& action) {
|
||||
return hotkey_groups[group][action].context;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include "ui_hotkeys.h"
|
||||
|
||||
class QDialog;
|
||||
class QKeySequence;
|
||||
@@ -14,7 +13,7 @@ class QShortcut;
|
||||
|
||||
class HotkeyRegistry final {
|
||||
public:
|
||||
friend class GHotkeysDialog;
|
||||
friend class ConfigureHotkeys;
|
||||
|
||||
explicit HotkeyRegistry();
|
||||
~HotkeyRegistry();
|
||||
@@ -49,22 +48,27 @@ public:
|
||||
QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget);
|
||||
|
||||
/**
|
||||
* Register a hotkey.
|
||||
* Returns a QKeySequence object whose signal can be connected to QAction::setShortcut.
|
||||
*
|
||||
* @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger")
|
||||
* @param action Name of the action (e.g. "Start Emulation", "Load Image")
|
||||
* @param default_keyseq Default key sequence to assign if the hotkey wasn't present in the
|
||||
* settings file before
|
||||
* @param default_context Default context to assign if the hotkey wasn't present in the settings
|
||||
* file before
|
||||
* @warning Both the group and action strings will be displayed in the hotkey settings dialog
|
||||
* @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger").
|
||||
* @param action Name of the action (e.g. "Start Emulation", "Load Image").
|
||||
*/
|
||||
void RegisterHotkey(const QString& group, const QString& action,
|
||||
const QKeySequence& default_keyseq = {},
|
||||
Qt::ShortcutContext default_context = Qt::WindowShortcut);
|
||||
QKeySequence GetKeySequence(const QString& group, const QString& action);
|
||||
|
||||
/**
|
||||
* Returns a Qt::ShortcutContext object who can be connected to other
|
||||
* QAction::setShortcutContext.
|
||||
*
|
||||
* @param group General group this shortcut context belongs to (e.g. "Main Window",
|
||||
* "Debugger").
|
||||
* @param action Name of the action (e.g. "Start Emulation", "Load Image").
|
||||
*/
|
||||
Qt::ShortcutContext GetShortcutContext(const QString& group, const QString& action);
|
||||
|
||||
private:
|
||||
struct Hotkey {
|
||||
Hotkey() : shortcut(nullptr), context(Qt::WindowShortcut) {}
|
||||
|
||||
QKeySequence keyseq;
|
||||
QShortcut* shortcut = nullptr;
|
||||
Qt::ShortcutContext context = Qt::WindowShortcut;
|
||||
@@ -75,15 +79,3 @@ private:
|
||||
|
||||
HotkeyGroupMap hotkey_groups;
|
||||
};
|
||||
|
||||
class GHotkeysDialog : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GHotkeysDialog(QWidget* parent = nullptr);
|
||||
|
||||
void Populate(const HotkeyRegistry& registry);
|
||||
|
||||
private:
|
||||
Ui::hotkeys ui;
|
||||
};
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>hotkeys</class>
|
||||
<widget class="QWidget" name="hotkeys">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>363</width>
|
||||
<height>388</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Hotkey Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="treeWidget">
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectItems</enum>
|
||||
</property>
|
||||
<property name="headerHidden">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Action</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Hotkey</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Context</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -192,7 +192,12 @@ void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size
|
||||
}
|
||||
|
||||
// update labels and progress bar
|
||||
ui->stage->setText(stage_translations[stage].arg(value).arg(total));
|
||||
if (stage == VideoCore::LoadCallbackStage::Decompile ||
|
||||
stage == VideoCore::LoadCallbackStage::Build) {
|
||||
ui->stage->setText(stage_translations[stage].arg(value).arg(total));
|
||||
} else {
|
||||
ui->stage->setText(stage_translations[stage]);
|
||||
}
|
||||
ui->value->setText(estimate);
|
||||
ui->progress_bar->setValue(static_cast<int>(value));
|
||||
previous_time = now;
|
||||
|
||||
@@ -90,7 +90,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
||||
#include "yuzu/configuration/configure_dialog.h"
|
||||
#include "yuzu/debugger/console.h"
|
||||
#include "yuzu/debugger/graphics/graphics_breakpoints.h"
|
||||
#include "yuzu/debugger/graphics/graphics_surface.h"
|
||||
#include "yuzu/debugger/profiler.h"
|
||||
#include "yuzu/debugger/wait_tree.h"
|
||||
#include "yuzu/discord.h"
|
||||
@@ -171,7 +170,8 @@ static void InitializeLogging() {
|
||||
|
||||
GMainWindow::GMainWindow()
|
||||
: config(new Config()), emu_thread(nullptr),
|
||||
vfs(std::make_shared<FileSys::RealVfsFilesystem>()) {
|
||||
vfs(std::make_shared<FileSys::RealVfsFilesystem>()),
|
||||
provider(std::make_unique<FileSys::ManualContentProvider>()) {
|
||||
InitializeLogging();
|
||||
|
||||
debug_context = Tegra::DebugContext::Construct();
|
||||
@@ -203,11 +203,15 @@ GMainWindow::GMainWindow()
|
||||
.arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc));
|
||||
show();
|
||||
|
||||
Core::System::GetInstance().SetContentProvider(
|
||||
std::make_unique<FileSys::ContentProviderUnion>());
|
||||
Core::System::GetInstance().RegisterContentProvider(
|
||||
FileSys::ContentProviderUnionSlot::FrontendManual, provider.get());
|
||||
Service::FileSystem::CreateFactories(*vfs);
|
||||
|
||||
// Gen keys if necessary
|
||||
OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);
|
||||
|
||||
// Necessary to load titles from nand in gamelist.
|
||||
Service::FileSystem::CreateFactories(*vfs);
|
||||
game_list->LoadCompatibilityList();
|
||||
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
|
||||
|
||||
@@ -419,7 +423,7 @@ void GMainWindow::InitializeWidgets() {
|
||||
render_window = new GRenderWindow(this, emu_thread.get());
|
||||
render_window->hide();
|
||||
|
||||
game_list = new GameList(vfs, this);
|
||||
game_list = new GameList(vfs, provider.get(), this);
|
||||
ui.horizontalLayout->addWidget(game_list);
|
||||
|
||||
loading_screen = new LoadingScreen(this);
|
||||
@@ -478,11 +482,6 @@ void GMainWindow::InitializeDebugWidgets() {
|
||||
graphicsBreakpointsWidget->hide();
|
||||
debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction());
|
||||
|
||||
graphicsSurfaceWidget = new GraphicsSurfaceWidget(debug_context, this);
|
||||
addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceWidget);
|
||||
graphicsSurfaceWidget->hide();
|
||||
debug_menu->addAction(graphicsSurfaceWidget->toggleViewAction());
|
||||
|
||||
waitTreeWidget = new WaitTreeWidget(this);
|
||||
addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget);
|
||||
waitTreeWidget->hide();
|
||||
@@ -514,33 +513,34 @@ void GMainWindow::InitializeRecentFileMenuActions() {
|
||||
}
|
||||
|
||||
void GMainWindow::InitializeHotkeys() {
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Load File", QKeySequence::Open);
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Start Emulation");
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Continue/Pause", QKeySequence(Qt::Key_F4));
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Restart", QKeySequence(Qt::Key_F5));
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Fullscreen", QKeySequence::FullScreen);
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence(Qt::Key_Escape),
|
||||
Qt::ApplicationShortcut);
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Toggle Speed Limit", QKeySequence("CTRL+Z"),
|
||||
Qt::ApplicationShortcut);
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Increase Speed Limit", QKeySequence("+"),
|
||||
Qt::ApplicationShortcut);
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Decrease Speed Limit", QKeySequence("-"),
|
||||
Qt::ApplicationShortcut);
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Load Amiibo", QKeySequence(Qt::Key_F2),
|
||||
Qt::ApplicationShortcut);
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Capture Screenshot",
|
||||
QKeySequence(QKeySequence::Print));
|
||||
hotkey_registry.RegisterHotkey("Main Window", "Change Docked Mode", QKeySequence(Qt::Key_F10));
|
||||
|
||||
hotkey_registry.LoadHotkeys();
|
||||
|
||||
ui.action_Load_File->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Load File"));
|
||||
ui.action_Load_File->setShortcutContext(
|
||||
hotkey_registry.GetShortcutContext("Main Window", "Load File"));
|
||||
|
||||
ui.action_Exit->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Exit yuzu"));
|
||||
ui.action_Exit->setShortcutContext(
|
||||
hotkey_registry.GetShortcutContext("Main Window", "Exit yuzu"));
|
||||
|
||||
ui.action_Stop->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Stop Emulation"));
|
||||
ui.action_Stop->setShortcutContext(
|
||||
hotkey_registry.GetShortcutContext("Main Window", "Stop Emulation"));
|
||||
|
||||
ui.action_Show_Filter_Bar->setShortcut(
|
||||
hotkey_registry.GetKeySequence("Main Window", "Toggle Filter Bar"));
|
||||
ui.action_Show_Filter_Bar->setShortcutContext(
|
||||
hotkey_registry.GetShortcutContext("Main Window", "Toggle Filter Bar"));
|
||||
|
||||
ui.action_Show_Status_Bar->setShortcut(
|
||||
hotkey_registry.GetKeySequence("Main Window", "Toggle Status Bar"));
|
||||
ui.action_Show_Status_Bar->setShortcutContext(
|
||||
hotkey_registry.GetShortcutContext("Main Window", "Toggle Status Bar"));
|
||||
|
||||
connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated,
|
||||
this, &GMainWindow::OnMenuLoadFile);
|
||||
connect(hotkey_registry.GetHotkey("Main Window", "Start Emulation", this),
|
||||
&QShortcut::activated, this, &GMainWindow::OnStartGame);
|
||||
connect(hotkey_registry.GetHotkey("Main Window", "Continue/Pause", this), &QShortcut::activated,
|
||||
this, [&] {
|
||||
connect(hotkey_registry.GetHotkey("Main Window", "Continue/Pause Emulation", this),
|
||||
&QShortcut::activated, this, [&] {
|
||||
if (emulation_running) {
|
||||
if (emu_thread->IsRunning()) {
|
||||
OnPauseGame();
|
||||
@@ -549,8 +549,8 @@ void GMainWindow::InitializeHotkeys() {
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(hotkey_registry.GetHotkey("Main Window", "Restart", this), &QShortcut::activated, this,
|
||||
[this] {
|
||||
connect(hotkey_registry.GetHotkey("Main Window", "Restart Emulation", this),
|
||||
&QShortcut::activated, this, [this] {
|
||||
if (!Core::System::GetInstance().IsPoweredOn())
|
||||
return;
|
||||
BootGame(QString(game_path));
|
||||
@@ -697,7 +697,6 @@ void GMainWindow::ConnectMenuEvents() {
|
||||
&GMainWindow::ToggleWindowMode);
|
||||
connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this,
|
||||
&GMainWindow::OnDisplayTitleBars);
|
||||
ui.action_Show_Filter_Bar->setShortcut(tr("CTRL+F"));
|
||||
connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar);
|
||||
connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible);
|
||||
|
||||
@@ -1179,7 +1178,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
|
||||
return;
|
||||
}
|
||||
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
const auto& installed = Core::System::GetInstance().GetContentProvider();
|
||||
const auto romfs_title_id = SelectRomFSDumpTarget(installed, program_id);
|
||||
|
||||
if (!romfs_title_id) {
|
||||
@@ -1662,6 +1661,7 @@ void GMainWindow::OnConfigure() {
|
||||
auto result = configureDialog.exec();
|
||||
if (result == QDialog::Accepted) {
|
||||
configureDialog.applyConfiguration();
|
||||
InitializeHotkeys();
|
||||
if (UISettings::values.theme != old_theme)
|
||||
UpdateUITheme();
|
||||
if (UISettings::values.enable_discord_presence != old_discord_presence)
|
||||
@@ -1924,14 +1924,14 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<u64> GMainWindow::SelectRomFSDumpTarget(
|
||||
const FileSys::RegisteredCacheUnion& installed, u64 program_id) {
|
||||
std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed,
|
||||
u64 program_id) {
|
||||
const auto dlc_entries =
|
||||
installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
|
||||
std::vector<FileSys::RegisteredCacheEntry> dlc_match;
|
||||
std::vector<FileSys::ContentProviderEntry> dlc_match;
|
||||
dlc_match.reserve(dlc_entries.size());
|
||||
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
|
||||
[&program_id, &installed](const FileSys::RegisteredCacheEntry& entry) {
|
||||
[&program_id, &installed](const FileSys::ContentProviderEntry& entry) {
|
||||
return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id &&
|
||||
installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
|
||||
});
|
||||
|
||||
@@ -23,7 +23,6 @@ class EmuThread;
|
||||
class GameList;
|
||||
class GImageInfo;
|
||||
class GraphicsBreakPointsWidget;
|
||||
class GraphicsSurfaceWidget;
|
||||
class GRenderWindow;
|
||||
class LoadingScreen;
|
||||
class MicroProfileDialog;
|
||||
@@ -37,7 +36,8 @@ struct SoftwareKeyboardParameters;
|
||||
} // namespace Core::Frontend
|
||||
|
||||
namespace FileSys {
|
||||
class RegisteredCacheUnion;
|
||||
class ContentProvider;
|
||||
class ManualContentProvider;
|
||||
class VfsFilesystem;
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -120,7 +120,6 @@ private:
|
||||
void InitializeWidgets();
|
||||
void InitializeDebugWidgets();
|
||||
void InitializeRecentFileMenuActions();
|
||||
void InitializeHotkeys();
|
||||
|
||||
void SetDefaultUIGeometry();
|
||||
void RestoreUIState();
|
||||
@@ -196,6 +195,7 @@ private slots:
|
||||
void OnAbout();
|
||||
void OnToggleFilterBar();
|
||||
void OnDisplayTitleBars(bool);
|
||||
void InitializeHotkeys();
|
||||
void ToggleFullscreen();
|
||||
void ShowFullscreen();
|
||||
void HideFullscreen();
|
||||
@@ -205,7 +205,7 @@ private slots:
|
||||
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
|
||||
|
||||
private:
|
||||
std::optional<u64> SelectRomFSDumpTarget(const FileSys::RegisteredCacheUnion&, u64 program_id);
|
||||
std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
|
||||
void UpdateStatusBar();
|
||||
|
||||
Ui::MainWindow ui;
|
||||
@@ -233,12 +233,12 @@ private:
|
||||
|
||||
// FS
|
||||
std::shared_ptr<FileSys::VfsFilesystem> vfs;
|
||||
std::unique_ptr<FileSys::ManualContentProvider> provider;
|
||||
|
||||
// Debugger panes
|
||||
ProfilerWidget* profilerWidget;
|
||||
MicroProfileDialog* microProfileDialog;
|
||||
GraphicsBreakPointsWidget* graphicsBreakpointsWidget;
|
||||
GraphicsSurfaceWidget* graphicsSurfaceWidget;
|
||||
WaitTreeWidget* waitTreeWidget;
|
||||
|
||||
QAction* actions_recent_files[max_recent_files_item];
|
||||
|
||||
@@ -12,5 +12,4 @@ const Themes themes{{
|
||||
}};
|
||||
|
||||
Values values = {};
|
||||
|
||||
} // namespace UISettings
|
||||
|
||||
@@ -15,7 +15,12 @@
|
||||
namespace UISettings {
|
||||
|
||||
using ContextualShortcut = std::pair<QString, int>;
|
||||
using Shortcut = std::pair<QString, ContextualShortcut>;
|
||||
|
||||
struct Shortcut {
|
||||
QString name;
|
||||
QString group;
|
||||
ContextualShortcut shortcut;
|
||||
};
|
||||
|
||||
using Themes = std::array<std::pair<const char*, const char*>, 2>;
|
||||
extern const Themes themes;
|
||||
|
||||
37
src/yuzu/util/sequence_dialog/sequence_dialog.cpp
Normal file
37
src/yuzu/util/sequence_dialog/sequence_dialog.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QKeySequenceEdit>
|
||||
#include <QVBoxLayout>
|
||||
#include "yuzu/util/sequence_dialog/sequence_dialog.h"
|
||||
|
||||
SequenceDialog::SequenceDialog(QWidget* parent) : QDialog(parent) {
|
||||
setWindowTitle(tr("Enter a hotkey"));
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
key_sequence = new QKeySequenceEdit;
|
||||
layout->addWidget(key_sequence);
|
||||
auto* buttons =
|
||||
new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal);
|
||||
buttons->setCenterButtons(true);
|
||||
layout->addWidget(buttons);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
}
|
||||
|
||||
SequenceDialog::~SequenceDialog() = default;
|
||||
|
||||
QKeySequence SequenceDialog::GetSequence() const {
|
||||
// Only the first key is returned. The other 3, if present, are ignored.
|
||||
return QKeySequence(key_sequence->keySequence()[0]);
|
||||
}
|
||||
|
||||
bool SequenceDialog::focusNextPrevChild(bool next) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void SequenceDialog::closeEvent(QCloseEvent*) {
|
||||
reject();
|
||||
}
|
||||
24
src/yuzu/util/sequence_dialog/sequence_dialog.h
Normal file
24
src/yuzu/util/sequence_dialog/sequence_dialog.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class QKeySequenceEdit;
|
||||
|
||||
class SequenceDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SequenceDialog(QWidget* parent = nullptr);
|
||||
~SequenceDialog() override;
|
||||
|
||||
QKeySequence GetSequence() const;
|
||||
void closeEvent(QCloseEvent*) override;
|
||||
|
||||
private:
|
||||
QKeySequenceEdit* key_sequence;
|
||||
bool focusNextPrevChild(bool next) override;
|
||||
};
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
|
||||
|
||||
#include <getopt.h>
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#ifndef _MSC_VER
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
@@ -178,6 +179,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
Core::System& system{Core::System::GetInstance()};
|
||||
system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
|
||||
Service::FileSystem::CreateFactories(*system.GetFilesystem());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user