diff options
Diffstat (limited to 'src')
39 files changed, 950 insertions, 162 deletions
diff --git a/src/core/core.cpp b/src/core/core.cpp index 795fabc65..ce7851538 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -8,6 +8,7 @@ #include <thread> #include <utility> +#include "common/file_util.h" #include "common/logging/log.h" #include "common/string_util.h" #include "core/arm/exclusive_monitor.h" @@ -40,7 +41,6 @@ namespace Core { /*static*/ System System::s_instance; -namespace { FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, const std::string& path) { // To account for split 00+01+etc files. @@ -69,11 +69,13 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName()); } + if (FileUtil::IsDirectory(path)) + return vfs->OpenFile(path + "/" + "main", FileSys::Mode::Read); + return vfs->OpenFile(path, FileSys::Mode::Read); } -} // Anonymous namespace - struct System::Impl { + Cpu& CurrentCpuCore() { return cpu_core_manager.GetCurrentCore(); } diff --git a/src/core/core.h b/src/core/core.h index be71bd437..71031dfcf 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -9,6 +9,7 @@ #include <string> #include "common/common_types.h" +#include "core/file_sys/vfs_types.h" #include "core/hle/kernel/object.h" namespace Core::Frontend { @@ -55,6 +56,9 @@ class TelemetrySession; struct PerfStatsResults; +FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, + const std::string& path); + class System { public: System(const System&) = delete; diff --git a/src/core/file_sys/directory.h b/src/core/file_sys/directory.h index 12bb90ec8..6690aa575 100644 --- a/src/core/file_sys/directory.h +++ b/src/core/file_sys/directory.h @@ -29,8 +29,8 @@ struct Entry { filename[copy_size] = '\0'; } - char filename[0x300]; - INSERT_PADDING_BYTES(4); + char filename[0x301]; + INSERT_PADDING_BYTES(3); EntryType type; INSERT_PADDING_BYTES(3); u64 file_size; diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 6b14e08be..ecdc21c87 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -56,6 +56,10 @@ PatchManager::PatchManager(u64 title_id) : title_id(title_id) {} PatchManager::~PatchManager() = default; +u64 PatchManager::GetTitleID() const { + return title_id; +} + VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id); @@ -73,11 +77,15 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { const auto installed = Service::FileSystem::GetUnionContents(); + const auto& disabled = Settings::values.disabled_addons[title_id]; + const auto update_disabled = + std::find(disabled.begin(), disabled.end(), "Update") != disabled.end(); + // Game Updates const auto update_tid = GetUpdateTitleID(title_id); const auto update = installed.GetEntry(update_tid, ContentRecordType::Program); - if (update != nullptr && update->GetExeFS() != nullptr && + if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr && update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully", FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0))); @@ -95,6 +103,9 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { std::vector<VirtualDir> layers; layers.reserve(patch_dirs.size() + 1); for (const auto& subdir : patch_dirs) { + if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end()) + continue; + auto exefs_dir = subdir->GetSubdirectory("exefs"); if (exefs_dir != nullptr) layers.push_back(std::move(exefs_dir)); @@ -111,11 +122,16 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { return exefs; } -static std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs, - const std::string& build_id) { +std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs, + const std::string& build_id) const { + const auto& disabled = Settings::values.disabled_addons[title_id]; + std::vector<VirtualFile> out; out.reserve(patch_dirs.size()); for (const auto& subdir : patch_dirs) { + if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end()) + continue; + auto exefs_dir = subdir->GetSubdirectory("exefs"); if (exefs_dir != nullptr) { for (const auto& file : exefs_dir->GetFiles()) { @@ -228,6 +244,7 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t return; } + const auto& disabled = Settings::values.disabled_addons[title_id]; auto patch_dirs = load_dir->GetSubdirectories(); std::sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); @@ -237,6 +254,9 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t layers.reserve(patch_dirs.size() + 1); layers_ext.reserve(patch_dirs.size() + 1); for (const auto& subdir : patch_dirs) { + if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end()) + continue; + auto romfs_dir = subdir->GetSubdirectory("romfs"); if (romfs_dir != nullptr) layers.push_back(std::move(romfs_dir)); @@ -282,7 +302,12 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content // Game Updates const auto update_tid = GetUpdateTitleID(title_id); const auto update = installed.GetEntryRaw(update_tid, type); - if (update != nullptr) { + + const auto& disabled = Settings::values.disabled_addons[title_id]; + const auto update_disabled = + std::find(disabled.begin(), disabled.end(), "Update") != disabled.end(); + + if (!update_disabled && update != nullptr) { const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset); if (new_nca->GetStatus() == Loader::ResultStatus::Success && new_nca->GetRomFS() != nullptr) { @@ -290,7 +315,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0))); romfs = new_nca->GetRomFS(); } - } else if (update_raw != nullptr) { + } else if (!update_disabled && update_raw != nullptr) { const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset); if (new_nca->GetStatus() == Loader::ResultStatus::Success && new_nca->GetRomFS() != nullptr) { @@ -320,25 +345,30 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam VirtualFile update_raw) const { std::map<std::string, std::string, std::less<>> out; const auto installed = Service::FileSystem::GetUnionContents(); + const auto& disabled = Settings::values.disabled_addons[title_id]; // Game Updates const auto update_tid = GetUpdateTitleID(title_id); PatchManager update{update_tid}; auto [nacp, discard_icon_file] = update.GetControlMetadata(); + const auto update_disabled = + std::find(disabled.begin(), disabled.end(), "Update") != disabled.end(); + const auto update_label = update_disabled ? "[D] Update" : "Update"; + if (nacp != nullptr) { - out.insert_or_assign("Update", nacp->GetVersionString()); + out.insert_or_assign(update_label, nacp->GetVersionString()); } else { if (installed.HasEntry(update_tid, ContentRecordType::Program)) { const auto meta_ver = installed.GetEntryVersion(update_tid); if (meta_ver.value_or(0) == 0) { - out.insert_or_assign("Update", ""); + out.insert_or_assign(update_label, ""); } else { out.insert_or_assign( - "Update", FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements)); + update_label, FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements)); } } else if (update_raw != nullptr) { - out.insert_or_assign("Update", "PACKED"); + out.insert_or_assign(update_label, "PACKED"); } } @@ -378,7 +408,9 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam if (types.empty()) continue; - out.insert_or_assign(mod->GetName(), types); + const auto mod_disabled = + std::find(disabled.begin(), disabled.end(), mod->GetName()) != disabled.end(); + out.insert_or_assign(mod_disabled ? "[D] " + mod->GetName() : mod->GetName(), types); } } @@ -401,7 +433,9 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam list += fmt::format("{}", dlc_match.back().title_id & 0x7FF); - out.insert_or_assign("DLC", std::move(list)); + const auto dlc_disabled = + std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end(); + out.insert_or_assign(dlc_disabled ? "[D] DLC" : "DLC", std::move(list)); } return out; diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 7d168837f..b8a1652fd 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -30,6 +30,8 @@ public: explicit PatchManager(u64 title_id); ~PatchManager(); + u64 GetTitleID() const; + // Currently tracked ExeFS patches: // - Game Updates VirtualDir PatchExeFS(VirtualDir exefs) const; @@ -63,6 +65,9 @@ public: std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const; private: + std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs, + const std::string& build_id) const; + u64 title_id; }; diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index 5434f2149..bd50fedc7 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -18,7 +18,11 @@ std::string SaveDataDescriptor::DebugInfo() const { static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id); } -SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {} +SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) { + // Delete all temporary storages + // On hardware, it is expected that temporary storage be empty at first use. + dir->DeleteSubdirectoryRecursive("temp"); +} SaveDataFactory::~SaveDataFactory() = default; @@ -120,6 +124,8 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ case SaveDataType::TemporaryStorage: return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0], title_id); + case SaveDataType::CacheStorage: + return fmt::format("{}save/cache/{:016X}", out, title_id); default: ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type)); } diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h index 2a0088040..bd4919610 100644 --- a/src/core/file_sys/savedata_factory.h +++ b/src/core/file_sys/savedata_factory.h @@ -17,8 +17,10 @@ namespace FileSys { enum class SaveDataSpaceId : u8 { NandSystem = 0, NandUser = 1, - SdCard = 2, + SdCardSystem = 2, TemporaryStorage = 3, + SdCardUser = 4, + ProperSystem = 100, }; enum class SaveDataType : u8 { diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 211bf6686..c817fb449 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -66,6 +66,7 @@ ResultCode Process::ClearSignalState() { void Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) { program_id = metadata.GetTitleID(); + ideal_processor = metadata.GetMainThreadCore(); is_64bit_process = metadata.Is64BitProgram(); vm_manager.Reset(metadata.GetAddressSpaceType()); } diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp index 0494581f5..d1ca60125 100644 --- a/src/core/hle/kernel/shared_memory.cpp +++ b/src/core/hle/kernel/shared_memory.cpp @@ -39,15 +39,15 @@ SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, SharedPtr<Proce shared_memory->backing_block.get()); } } else { - auto& vm_manager = shared_memory->owner_process->VMManager(); + const auto& vm_manager = shared_memory->owner_process->VMManager(); // The memory is already available and mapped in the owner process. - auto vma = vm_manager.FindVMA(address); - ASSERT_MSG(vma != vm_manager.vma_map.end(), "Invalid memory address"); + const auto vma = vm_manager.FindVMA(address); + ASSERT_MSG(vm_manager.IsValidHandle(vma), "Invalid memory address"); ASSERT_MSG(vma->second.backing_block, "Backing block doesn't exist for address"); // The returned VMA might be a bigger one encompassing the desired address. - auto vma_offset = address - vma->first; + const auto vma_offset = address - vma->first; ASSERT_MSG(vma_offset + size <= vma->second.size, "Shared memory exceeds bounds of mapped block"); diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 84df2040e..f43c7201c 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -239,7 +239,7 @@ static ResultCode SetMemoryPermission(VAddr addr, u64 size, u32 prot) { } const VMManager::VMAHandle iter = vm_manager.FindVMA(addr); - if (iter == vm_manager.vma_map.end()) { + if (!vm_manager.IsValidHandle(iter)) { LOG_ERROR(Kernel_SVC, "Unable to find VMA for address=0x{:016X}", addr); return ERR_INVALID_ADDRESS_STATE; } @@ -1077,19 +1077,23 @@ static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_i process_handle); return ERR_INVALID_HANDLE; } - auto vma = process->VMManager().FindVMA(addr); + + const auto& vm_manager = process->VMManager(); + const auto vma = vm_manager.FindVMA(addr); + memory_info->attributes = 0; - if (vma == process->VMManager().vma_map.end()) { - memory_info->base_address = 0; - memory_info->permission = static_cast<u32>(VMAPermission::None); - memory_info->size = 0; - memory_info->type = static_cast<u32>(MemoryState::Unmapped); - } else { + if (vm_manager.IsValidHandle(vma)) { memory_info->base_address = vma->second.base; memory_info->permission = static_cast<u32>(vma->second.permissions); memory_info->size = vma->second.size; memory_info->type = static_cast<u32>(vma->second.meminfo_state); + } else { + memory_info->base_address = 0; + memory_info->permission = static_cast<u32>(VMAPermission::None); + memory_info->size = 0; + memory_info->type = static_cast<u32>(MemoryState::Unmapped); } + return RESULT_SUCCESS; } diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 100f8f6bf..6187993ce 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -87,6 +87,10 @@ VMManager::VMAHandle VMManager::FindVMA(VAddr target) const { } } +bool VMManager::IsValidHandle(VMAHandle handle) const { + return handle != vma_map.cend(); +} + ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target, std::shared_ptr<std::vector<u8>> block, std::size_t offset, u64 size, diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index d522404fe..a12419d1e 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -113,16 +113,10 @@ struct VirtualMemoryArea { * - http://duartes.org/gustavo/blog/post/page-cache-the-affair-between-memory-and-files/ */ class VMManager final { + using VMAMap = std::map<VAddr, VirtualMemoryArea>; + public: - /** - * A map covering the entirety of the managed address space, keyed by the `base` field of each - * VMA. It must always be modified by splitting or merging VMAs, so that the invariant - * `elem.base + elem.size == next.base` is preserved, and mergeable regions must always be - * merged when possible so that no two similar and adjacent regions exist that have not been - * merged. - */ - std::map<VAddr, VirtualMemoryArea> vma_map; - using VMAHandle = decltype(vma_map)::const_iterator; + using VMAHandle = VMAMap::const_iterator; VMManager(); ~VMManager(); @@ -133,6 +127,9 @@ public: /// Finds the VMA in which the given address is included in, or `vma_map.end()`. VMAHandle FindVMA(VAddr target) const; + /// Indicates whether or not the given handle is within the VMA map. + bool IsValidHandle(VMAHandle handle) const; + // TODO(yuriks): Should these functions actually return the handle? /** @@ -281,7 +278,7 @@ public: Memory::PageTable page_table; private: - using VMAIter = decltype(vma_map)::iterator; + using VMAIter = VMAMap::iterator; /// Converts a VMAHandle to a mutable VMAIter. VMAIter StripIterConstness(const VMAHandle& iter); @@ -328,6 +325,15 @@ private: /// Clears out the page table void ClearPageTable(); + /** + * A map covering the entirety of the managed address space, keyed by the `base` field of each + * VMA. It must always be modified by splitting or merging VMAs, so that the invariant + * `elem.base + elem.size == next.base` is preserved, and mergeable regions must always be + * merged when possible so that no two similar and adjacent regions exist that have not been + * merged. + */ + VMAMap vma_map; + u32 address_space_width = 0; VAddr address_space_base = 0; VAddr address_space_end = 0; diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index 0417fdb92..b506bc3dd 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp @@ -20,6 +20,7 @@ #include "core/hle/service/aoc/aoc_u.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/loader/loader.h" +#include "core/settings.h" namespace Service::AOC { @@ -76,6 +77,13 @@ void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID(); + + const auto& disabled = Settings::values.disabled_addons[current]; + if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) { + rb.Push<u32>(0); + return; + } + rb.Push<u32>(static_cast<u32>( std::count_if(add_on_content.begin(), add_on_content.end(), [current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); }))); @@ -96,6 +104,10 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) { out.push_back(static_cast<u32>(add_on_content[i] & 0x7FF)); } + const auto& disabled = Settings::values.disabled_addons[current]; + if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) + out = {}; + if (out.size() < offset) { IPC::ResponseBuilder rb{ctx, 2}; // TODO(DarkLordZach): Find the correct error code. diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index d2ffd5776..63fa48133 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -796,9 +796,18 @@ void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext& void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_FS, "(STUBBED) called"); + enum class LogMode : u32 { + Off, + Log, + RedirectToSdCard, + LogToSdCard = Log | RedirectToSdCard, + }; + + // Given we always want to receive logging information, + // we always specify logging as enabled. IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(5); + rb.PushEnum(LogMode::Log); } void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) { diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 5390ab9ee..0838e303b 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -12,6 +12,7 @@ #include <vector> #include "common/common_types.h" +#include "core/file_sys/control_metadata.h" #include "core/file_sys/vfs.h" namespace Kernel { @@ -243,6 +244,15 @@ public: return ResultStatus::ErrorNotImplemented; } + /** + * Get the developer of the application + * @param developer Reference to store the application developer into + * @return ResultStatus result of function + */ + virtual ResultStatus ReadDeveloper(std::string& developer) { + return ResultStatus::ErrorNotImplemented; + } + protected: FileSys::VirtualFile file; bool is_loaded = false; diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index 080d89904..b4ab88ae8 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -151,4 +151,11 @@ ResultStatus AppLoader_NSP::ReadTitle(std::string& title) { title = nacp_file->GetApplicationName(); return ResultStatus::Success; } + +ResultStatus AppLoader_NSP::ReadDeveloper(std::string& developer) { + if (nacp_file == nullptr) + return ResultStatus::ErrorNoControl; + developer = nacp_file->GetDeveloperName(); + return ResultStatus::Success; +} } // namespace Loader diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index 9ed8a59cf..2b1e0719b 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h @@ -43,6 +43,7 @@ public: ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadIcon(std::vector<u8>& buffer) override; ResultStatus ReadTitle(std::string& title) override; + ResultStatus ReadDeveloper(std::string& developer) override; private: std::unique_ptr<FileSys::NSP> nsp; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index 461607c95..bd5a83b49 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -120,4 +120,11 @@ ResultStatus AppLoader_XCI::ReadTitle(std::string& title) { title = nacp_file->GetApplicationName(); return ResultStatus::Success; } + +ResultStatus AppLoader_XCI::ReadDeveloper(std::string& developer) { + if (nacp_file == nullptr) + return ResultStatus::ErrorNoControl; + developer = nacp_file->GetDeveloperName(); + return ResultStatus::Success; +} } // namespace Loader diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index ded5bb88a..15d1b1a23 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -43,6 +43,7 @@ public: ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadIcon(std::vector<u8>& buffer) override; ResultStatus ReadTitle(std::string& title) override; + ResultStatus ReadDeveloper(std::string& developer) override; private: std::unique_ptr<FileSys::XCI> xci; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 41fd2a6a0..643afdee8 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -125,14 +125,13 @@ void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPoin * using a VMA from the current process */ static u8* GetPointerFromVMA(const Kernel::Process& process, VAddr vaddr) { - u8* direct_pointer = nullptr; - - auto& vm_manager = process.VMManager(); + const auto& vm_manager = process.VMManager(); - auto it = vm_manager.FindVMA(vaddr); - ASSERT(it != vm_manager.vma_map.end()); + const auto it = vm_manager.FindVMA(vaddr); + DEBUG_ASSERT(vm_manager.IsValidHandle(it)); - auto& vma = it->second; + u8* direct_pointer = nullptr; + const auto& vma = it->second; switch (vma.type) { case Kernel::VMAType::AllocatedMemoryBlock: direct_pointer = vma.backing_block->data() + vma.offset; diff --git a/src/core/settings.h b/src/core/settings.h index a0c5fd447..de01b05c0 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -6,8 +6,10 @@ #include <array> #include <atomic> +#include <map> #include <optional> #include <string> +#include <vector> #include "common/common_types.h" namespace Settings { @@ -411,6 +413,9 @@ struct Values { std::string web_api_url; std::string yuzu_username; std::string yuzu_token; + + // Add-Ons + std::map<u64, std::vector<std::string>> disabled_addons; } extern values; void Apply(); diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index a3b08c740..09ed74d78 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -103,13 +103,8 @@ bool VerifyLogin(const std::string& username, const std::string& token) { TelemetrySession::TelemetrySession() { #ifdef ENABLE_WEB_SERVICE - if (Settings::values.enable_telemetry) { - backend = std::make_unique<WebService::TelemetryJson>(Settings::values.web_api_url, - Settings::values.yuzu_username, - Settings::values.yuzu_token); - } else { - backend = std::make_unique<Telemetry::NullVisitor>(); - } + backend = std::make_unique<WebService::TelemetryJson>( + Settings::values.web_api_url, Settings::values.yuzu_username, Settings::values.yuzu_token); #else backend = std::make_unique<Telemetry::NullVisitor>(); #endif @@ -180,7 +175,8 @@ TelemetrySession::~TelemetrySession() { // This is just a placeholder to wrap up the session once the core completes and this is // destroyed. This will be moved elsewhere once we are actually doing real I/O with the service. field_collection.Accept(*backend); - backend->Complete(); + if (Settings::values.enable_telemetry) + backend->Complete(); backend = nullptr; } diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 038b25c75..a4265f498 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -2,7 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <boost/functional/hash.hpp> #include "common/assert.h" +#include "common/hash.h" #include "core/core.h" #include "core/memory.h" #include "video_core/engines/maxwell_3d.h" @@ -66,14 +68,17 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type) // stage here. setup.SetProgramB(GetShaderCode(GetShaderAddress(Maxwell::ShaderProgram::VertexB))); case Maxwell::ShaderProgram::VertexB: + CalculateProperties(); program_result = GLShader::GenerateVertexShader(setup); gl_type = GL_VERTEX_SHADER; break; case Maxwell::ShaderProgram::Geometry: + CalculateProperties(); program_result = GLShader::GenerateGeometryShader(setup); gl_type = GL_GEOMETRY_SHADER; break; case Maxwell::ShaderProgram::Fragment: + CalculateProperties(); program_result = GLShader::GenerateFragmentShader(setup); gl_type = GL_FRAGMENT_SHADER; break; @@ -140,6 +145,46 @@ GLuint CachedShader::LazyGeometryProgram(OGLProgram& target_program, return target_program.handle; }; +static bool IsSchedInstruction(u32 offset, u32 main_offset) { + // sched instructions appear once every 4 instructions. + static constexpr std::size_t SchedPeriod = 4; + const std::size_t absolute_offset = offset - main_offset; + return (absolute_offset % SchedPeriod) == 0; +} + +static std::size_t CalculateProgramSize(const GLShader::ProgramCode& program) { + const std::size_t start_offset = 10; + std::size_t offset = start_offset; + std::size_t size = start_offset * sizeof(u64); + while (offset < program.size()) { + const u64 inst = program[offset]; + if (!IsSchedInstruction(offset, start_offset)) { + if (inst == 0 || (inst >> 52) == 0x50b) { + break; + } + } + size += 8; + offset++; + } + return size; +} + +void CachedShader::CalculateProperties() { + setup.program.real_size = CalculateProgramSize(setup.program.code); + setup.program.real_size_b = 0; + setup.program.unique_identifier = Common::CityHash64( + reinterpret_cast<const char*>(setup.program.code.data()), setup.program.real_size); + if (program_type == Maxwell::ShaderProgram::VertexA) { + std::size_t seed = 0; + boost::hash_combine(seed, setup.program.unique_identifier); + setup.program.real_size_b = CalculateProgramSize(setup.program.code_b); + const u64 identifier_b = Common::CityHash64( + reinterpret_cast<const char*>(setup.program.code_b.data()), setup.program.real_size_b); + boost::hash_combine(seed, identifier_b); + setup.program.unique_identifier = static_cast<u64>(seed); + } +} + ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer) : RasterizerCache{rasterizer} {} Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) { diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index 08f470de3..b4ef6030d 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -81,6 +81,8 @@ private: GLuint LazyGeometryProgram(OGLProgram& target_program, const std::string& glsl_topology, u32 max_vertices, const std::string& debug_name); + void CalculateProperties(); + VAddr addr; std::size_t shader_length; Maxwell::ShaderProgram program_type; diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 851012da4..a5cfa0070 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -1681,7 +1681,7 @@ private: for (size_t i = 0; i < coord_count; ++i) { const bool last = (i == (coord_count - 1)) && (coord_count > 1); coord += regs.GetRegisterAsFloat(last ? last_coord_register : coord_register + i); - if (!last) { + if (i < coord_count - 1) { coord += ','; } } @@ -1702,6 +1702,99 @@ private: is_array, (coord_count > 2 ? 1 : 0))); } + std::pair<std::string, std::string> GetTLD4Code(const Instruction& instr, + const Tegra::Shader::TextureType texture_type, + const bool depth_compare, const bool is_array) { + + const size_t coord_count = TextureCoordinates(texture_type); + const size_t total_coord_count = coord_count + (is_array ? 1 : 0); + const size_t total_reg_count = total_coord_count + (depth_compare ? 1 : 0); + + constexpr std::array<const char*, 5> coord_container{ + {"", "", "vec2 coord = vec2(", "vec3 coord = vec3(", "vec4 coord = vec4("}}; + + // If enabled arrays index is always stored in the gpr8 field + const u64 array_register = instr.gpr8.Value(); + // First coordinate index is the gpr8 or gpr8 + 1 when arrays are used + const u64 coord_register = array_register + (is_array ? 1 : 0); + + std::string coord = coord_container[total_coord_count]; + for (size_t i = 0; i < coord_count;) { + coord += regs.GetRegisterAsFloat(coord_register + i); + ++i; + if (i != coord_count) { + coord += ','; + } + } + + if (is_array) { + coord += ',' + regs.GetRegisterAsInteger(array_register); + } + coord += ");"; + + const std::string sampler = + GetSampler(instr.sampler, texture_type, is_array, depth_compare); + + std::string texture = "textureGather(" + sampler + ", coord, "; + if (depth_compare) { + // Depth is always stored in the register signaled by gpr20 + texture += regs.GetRegisterAsFloat(instr.gpr20.Value()) + ')'; + } else { + texture += std::to_string(instr.tld4.component) + ')'; + } + return std::make_pair(coord, texture); + } + + std::pair<std::string, std::string> GetTLDSCode(const Instruction& instr, + const Tegra::Shader::TextureType texture_type, + const bool is_array) { + + const size_t coord_count = TextureCoordinates(texture_type); + const size_t total_coord_count = coord_count + (is_array ? 1 : 0); + const bool lod_enabled = + instr.tlds.GetTextureProcessMode() == Tegra::Shader::TextureProcessMode::LL; + + constexpr std::array<const char*, 4> coord_container{ + {"", "int coord = (", "ivec2 coord = ivec2(", "ivec3 coord = ivec3("}}; + + std::string coord = coord_container[total_coord_count]; + + // If enabled arrays index is always stored in the gpr8 field + const u64 array_register = instr.gpr8.Value(); + + // if is array gpr20 is used + const u64 coord_register = is_array ? instr.gpr20.Value() : instr.gpr8.Value(); + + const u64 last_coord_register = + ((coord_count > 2) || (coord_count == 2 && !lod_enabled)) && !is_array + ? static_cast<u64>(instr.gpr20.Value()) + : coord_register + 1; + + for (size_t i = 0; i < coord_count; ++i) { + const bool last = (i == (coord_count - 1)) && (coord_count > 1); + coord += regs.GetRegisterAsInteger(last ? last_coord_register : coord_register + i); + if (i < coord_count - 1) { + coord += ','; + } + } + if (is_array) { + coord += ',' + regs.GetRegisterAsInteger(array_register); + } + coord += ");"; + + const std::string sampler = GetSampler(instr.sampler, texture_type, is_array, false); + + std::string texture = "texelFetch(" + sampler + ", coords"; + + if (lod_enabled) { + // When lod is used always is in grp20 + texture += ", " + regs.GetRegisterAsInteger(instr.gpr20) + ')'; + } else { + texture += ", 0)"; + } + return std::make_pair(coord, texture); + } + /** * Compiles a single instruction from Tegra to GLSL. * @param offset the offset of the Tegra shader instruction. @@ -2825,9 +2918,6 @@ private: const Tegra::Shader::TextureType texture_type{instr.tlds.GetTextureType()}; const bool is_array{instr.tlds.IsArrayTexture()}; - ASSERT(texture_type == Tegra::Shader::TextureType::Texture2D); - ASSERT(is_array == false); - UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), "NODEP is not implemented"); UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), @@ -2835,54 +2925,16 @@ private: UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::MZ), "MZ is not implemented"); - u32 extra_op_offset = 0; - - ShaderScopedScope scope = shader.Scope(); - - switch (texture_type) { - case Tegra::Shader::TextureType::Texture1D: { - const std::string x = regs.GetRegisterAsInteger(instr.gpr8); - shader.AddLine("float coords = " + x + ';'); - break; - } - case Tegra::Shader::TextureType::Texture2D: { - UNIMPLEMENTED_IF_MSG(is_array, "Unhandled 2d array texture"); - - const std::string x = regs.GetRegisterAsInteger(instr.gpr8); - const std::string y = regs.GetRegisterAsInteger(instr.gpr20); - // shader.AddLine("ivec2 coords = ivec2(" + x + ", " + y + ");"); - shader.AddLine("ivec2 coords = ivec2(" + x + ", " + y + ");"); - extra_op_offset = 1; - break; - } - default: - UNIMPLEMENTED_MSG("Unhandled texture type {}", static_cast<u32>(texture_type)); - } - const std::string sampler = - GetSampler(instr.sampler, texture_type, is_array, false); + const auto [coord, texture] = GetTLDSCode(instr, texture_type, is_array); - const std::string texture = [&]() { - switch (instr.tlds.GetTextureProcessMode()) { - case Tegra::Shader::TextureProcessMode::LZ: - return "texelFetch(" + sampler + ", coords, 0)"; - case Tegra::Shader::TextureProcessMode::LL: - shader.AddLine( - "float lod = " + - regs.GetRegisterAsInteger(instr.gpr20.Value() + extra_op_offset) + ';'); - return "texelFetch(" + sampler + ", coords, lod)"; - default: - UNIMPLEMENTED_MSG("Unhandled texture process mode {}", - static_cast<u32>(instr.tlds.GetTextureProcessMode())); - return "texelFetch(" + sampler + ", coords, 0)"; - } - }(); + const auto scope = shader.Scope(); - WriteTexsInstructionFloat(instr, texture); + shader.AddLine(coord); + shader.AddLine("vec4 texture_tmp = " + texture + ';'); + WriteTexsInstructionFloat(instr, "texture_tmp"); break; } case OpCode::Id::TLD4: { - ASSERT(instr.tld4.texture_type == Tegra::Shader::TextureType::Texture2D); - ASSERT(instr.tld4.array == 0); UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), "NODEP is not implemented"); @@ -2892,56 +2944,29 @@ private: "NDV is not implemented"); UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::PTP), "PTP is not implemented"); + + auto texture_type = instr.tld4.texture_type.Value(); const bool depth_compare = instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC); - auto texture_type = instr.tld4.texture_type.Value(); - u32 num_coordinates = TextureCoordinates(texture_type); - if (depth_compare) - num_coordinates += 1; - - const auto scope = shader.Scope(); + const bool is_array = instr.tld4.array != 0; - switch (num_coordinates) { - case 2: { - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");"); - break; - } - case 3: { - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2); - shader.AddLine("vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"); - break; - } - default: - UNIMPLEMENTED_MSG("Unhandled coordinates number {}", - static_cast<u32>(num_coordinates)); - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");"); - texture_type = Tegra::Shader::TextureType::Texture2D; - } + const auto [coord, texture] = + GetTLD4Code(instr, texture_type, depth_compare, is_array); - const std::string sampler = - GetSampler(instr.sampler, texture_type, false, depth_compare); + const auto scope = shader.Scope(); - const std::string texture = "textureGather(" + sampler + ", coords, " + - std::to_string(instr.tld4.component) + ')'; + shader.AddLine(coord); + std::size_t dest_elem{}; - if (depth_compare) { - regs.SetRegisterToFloat(instr.gpr0, 0, texture, 1, 1, false); - } else { - std::size_t dest_elem{}; - for (std::size_t elem = 0; elem < 4; ++elem) { - if (!instr.tex.IsComponentEnabled(elem)) { - // Skip disabled components - continue; - } - regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem); - ++dest_elem; + shader.AddLine("vec4 texture_tmp = " + texture + ';'); + for (std::size_t elem = 0; elem < 4; ++elem) { + if (!instr.tex.IsComponentEnabled(elem)) { + // Skip disabled components + continue; } + regs.SetRegisterToFloat(instr.gpr0, elem, "texture_tmp", 1, 4, false, + dest_elem); + ++dest_elem; } break; } @@ -2955,28 +2980,31 @@ private: const auto scope = shader.Scope(); + std::string coords; + const bool depth_compare = instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC); - const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); - const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20); - // TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction. + const std::string sampler = GetSampler( instr.sampler, Tegra::Shader::TextureType::Texture2D, false, depth_compare); - if (depth_compare) { - // Note: TLD4S coordinate encoding works just like TEXS's - const std::string op_y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - shader.AddLine("vec3 coords = vec3(" + op_a + ", " + op_y + ", " + op_b + ");"); - } else { - shader.AddLine("vec2 coords = vec2(" + op_a + ", " + op_b + ");"); - } - std::string texture = "textureGather(" + sampler + ", coords, " + - std::to_string(instr.tld4s.component) + ')'; - if (depth_compare) { - texture = "vec4(" + texture + ')'; - } + const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); + coords = "vec2 coords = vec2(" + op_a + ", "; + std::string texture = "textureGather(" + sampler + ", coords, "; - WriteTexsInstructionFloat(instr, texture); + if (!depth_compare) { + const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20); + coords += op_b + ");"; + texture += std::to_string(instr.tld4s.component) + ')'; + } else { + const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string op_c = regs.GetRegisterAsFloat(instr.gpr20); + coords += op_b + ");"; + texture += op_c + ')'; + } + shader.AddLine(coords); + shader.AddLine("vec4 texture_tmp = " + texture + ';'); + WriteTexsInstructionFloat(instr, "texture_tmp"); break; } case OpCode::Id::TXQ: { diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 23ed91e27..5d0819dc5 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <fmt/format.h> #include "common/assert.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/renderer_opengl/gl_shader_decompiler.h" @@ -16,6 +17,8 @@ static constexpr u32 PROGRAM_OFFSET{10}; ProgramResult GenerateVertexShader(const ShaderSetup& setup) { std::string out = "#version 430 core\n"; out += "#extension GL_ARB_separate_shader_objects : enable\n\n"; + const std::string id = fmt::format("{:016x}", setup.program.unique_identifier); + out += "// Shader Unique Id: VS" + id + "\n\n"; out += Decompiler::GetCommonDeclarations(); out += R"( @@ -84,6 +87,8 @@ void main() { ProgramResult GenerateGeometryShader(const ShaderSetup& setup) { // Version is intentionally skipped in shader generation, it's added by the lazy compilation. std::string out = "#extension GL_ARB_separate_shader_objects : enable\n\n"; + const std::string id = fmt::format("{:016x}", setup.program.unique_identifier); + out += "// Shader Unique Id: GS" + id + "\n\n"; out += Decompiler::GetCommonDeclarations(); out += "bool exec_geometry();\n"; @@ -117,6 +122,8 @@ void main() { ProgramResult GenerateFragmentShader(const ShaderSetup& setup) { std::string out = "#version 430 core\n"; out += "#extension GL_ARB_separate_shader_objects : enable\n\n"; + const std::string id = fmt::format("{:016x}", setup.program.unique_identifier); + out += "// Shader Unique Id: FS" + id + "\n\n"; out += Decompiler::GetCommonDeclarations(); out += "bool exec_fragment();\n"; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index 4fa6d7612..fcc20d3b4 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -177,6 +177,9 @@ struct ShaderSetup { struct { ProgramCode code; ProgramCode code_b; // Used for dual vertex shaders + u64 unique_identifier; + std::size_t real_size; + std::size_t real_size_b; } program; /// Used in scenarios where we have a dual vertex shaders diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index cfca8f4a8..3232aa8fb 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -35,6 +35,8 @@ add_executable(yuzu configuration/configure_mouse_advanced.h configuration/configure_system.cpp configuration/configure_system.h + configuration/configure_per_general.cpp + configuration/configure_per_general.h configuration/configure_touchscreen_advanced.cpp configuration/configure_touchscreen_advanced.h configuration/configure_web.cpp @@ -86,6 +88,7 @@ set(UIS configuration/configure_input.ui configuration/configure_input_player.ui configuration/configure_mouse_advanced.ui + configuration/configure_per_general.ui configuration/configure_system.ui configuration/configure_touchscreen_advanced.ui configuration/configure_web.ui diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index c26161169..eb2077b0f 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -441,6 +441,21 @@ void Config::ReadValues() { Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString(); qt_config->endGroup(); + const auto size = qt_config->beginReadArray("DisabledAddOns"); + for (int i = 0; i < size; ++i) { + qt_config->setArrayIndex(i); + const auto title_id = qt_config->value("title_id", 0).toULongLong(); + std::vector<std::string> out; + const auto d_size = qt_config->beginReadArray("disabled"); + for (int j = 0; j < d_size; ++j) { + qt_config->setArrayIndex(j); + out.push_back(qt_config->value("d", "").toString().toStdString()); + } + qt_config->endArray(); + Settings::values.disabled_addons.insert_or_assign(title_id, out); + } + qt_config->endArray(); + qt_config->beginGroup("UI"); UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString(); UISettings::values.enable_discord_presence = @@ -645,6 +660,21 @@ void Config::SaveValues() { qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token)); qt_config->endGroup(); + qt_config->beginWriteArray("DisabledAddOns"); + int i = 0; + for (const auto& elem : Settings::values.disabled_addons) { + qt_config->setArrayIndex(i); + qt_config->setValue("title_id", QVariant::fromValue<u64>(elem.first)); + qt_config->beginWriteArray("disabled"); + for (std::size_t j = 0; j < elem.second.size(); ++j) { + qt_config->setArrayIndex(j); + qt_config->setValue("d", QString::fromStdString(elem.second[j])); + } + qt_config->endArray(); + ++i; + } + qt_config->endArray(); + qt_config->beginGroup("UI"); qt_config->setValue("theme", UISettings::values.theme); qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence); diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_general.cpp new file mode 100644 index 000000000..80109b434 --- /dev/null +++ b/src/yuzu/configuration/configure_per_general.cpp @@ -0,0 +1,170 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <memory> +#include <utility> + +#include <QHeaderView> +#include <QMenu> +#include <QMessageBox> +#include <QStandardItemModel> +#include <QString> +#include <QTimer> +#include <QTreeView> + +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/patch_manager.h" +#include "core/file_sys/xts_archive.h" +#include "core/loader/loader.h" +#include "ui_configure_per_general.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_per_general.h" +#include "yuzu/ui_settings.h" +#include "yuzu/util/util.h" + +ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id) + : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGameGeneral>()), title_id(title_id) { + + ui->setupUi(this); + setFocusPolicy(Qt::ClickFocus); + setWindowTitle(tr("Properties")); + + layout = new QVBoxLayout; + tree_view = new QTreeView; + item_model = new QStandardItemModel(tree_view); + tree_view->setModel(item_model); + tree_view->setAlternatingRowColors(true); + tree_view->setSelectionMode(QHeaderView::SingleSelection); + tree_view->setSelectionBehavior(QHeaderView::SelectRows); + tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setSortingEnabled(true); + tree_view->setEditTriggers(QHeaderView::NoEditTriggers); + tree_view->setUniformRowHeights(true); + tree_view->setContextMenuPolicy(Qt::NoContextMenu); + + item_model->insertColumns(0, 2); + item_model->setHeaderData(0, Qt::Horizontal, "Patch Name"); + item_model->setHeaderData(1, Qt::Horizontal, "Version"); + + // We must register all custom types with the Qt Automoc system so that we are able to use it + // with signals/slots. In this case, QList falls under the umbrells of custom types. + qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); + + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + layout->addWidget(tree_view); + + ui->scrollArea->setLayout(layout); + + scene = new QGraphicsScene; + ui->icon_view->setScene(scene); + + connect(item_model, &QStandardItemModel::itemChanged, + [] { UISettings::values.is_game_list_reload_pending.exchange(true); }); + + this->loadConfiguration(); +} + +ConfigurePerGameGeneral::~ConfigurePerGameGeneral() = default; + +void ConfigurePerGameGeneral::applyConfiguration() { + std::vector<std::string> disabled_addons; + + for (const auto& item : list_items) { + const auto disabled = item.front()->checkState() == Qt::Unchecked; + if (disabled) + disabled_addons.push_back(item.front()->text().toStdString()); + } + + Settings::values.disabled_addons[title_id] = disabled_addons; +} + +void ConfigurePerGameGeneral::loadFromFile(FileSys::VirtualFile file) { + this->file = std::move(file); + this->loadConfiguration(); +} + +void ConfigurePerGameGeneral::loadConfiguration() { + if (file == nullptr) + return; + + const auto loader = Loader::GetLoader(file); + + ui->display_title_id->setText(fmt::format("{:016X}", title_id).c_str()); + + FileSys::PatchManager pm{title_id}; + const auto control = pm.GetControlMetadata(); + + if (control.first != nullptr) { + ui->display_version->setText(QString::fromStdString(control.first->GetVersionString())); + ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName())); + ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName())); + } else { + std::string title; + if (loader->ReadTitle(title) == Loader::ResultStatus::Success) + ui->display_name->setText(QString::fromStdString(title)); + + std::string developer; + if (loader->ReadDeveloper(developer) == Loader::ResultStatus::Success) + ui->display_developer->setText(QString::fromStdString(developer)); + + ui->display_version->setText(QStringLiteral("1.0.0")); + } + + if (control.second != nullptr) { + scene->clear(); + + QPixmap map; + const auto bytes = control.second->ReadAllBytes(); + map.loadFromData(bytes.data(), bytes.size()); + + scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } else { + std::vector<u8> bytes; + if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) { + scene->clear(); + + QPixmap map; + map.loadFromData(bytes.data(), bytes.size()); + + scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } + } + + FileSys::VirtualFile update_raw; + loader->ReadUpdateRaw(update_raw); + + const auto& disabled = Settings::values.disabled_addons[title_id]; + + for (const auto& patch : pm.GetPatchVersionNames(update_raw)) { + QStandardItem* first_item = new QStandardItem; + const auto name = QString::fromStdString(patch.first).replace("[D] ", ""); + first_item->setText(name); + first_item->setCheckable(true); + + const auto patch_disabled = + std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end(); + + first_item->setCheckState(patch_disabled ? Qt::Unchecked : Qt::Checked); + + list_items.push_back(QList<QStandardItem*>{ + first_item, new QStandardItem{QString::fromStdString(patch.second)}}); + item_model->appendRow(list_items.back()); + } + + tree_view->setColumnWidth(0, 5 * tree_view->width() / 16); + + ui->display_filename->setText(QString::fromStdString(file->GetName())); + + ui->display_format->setText( + QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))); + + const auto valueText = ReadableByteSize(file->GetSize()); + ui->display_size->setText(valueText); +} diff --git a/src/yuzu/configuration/configure_per_general.h b/src/yuzu/configuration/configure_per_general.h new file mode 100644 index 000000000..a4494446c --- /dev/null +++ b/src/yuzu/configuration/configure_per_general.h @@ -0,0 +1,50 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <vector> + +#include <QKeyEvent> +#include <QList> +#include <QWidget> + +#include "core/file_sys/vfs_types.h" + +class QTreeView; +class QGraphicsScene; +class QStandardItem; +class QStandardItemModel; + +namespace Ui { +class ConfigurePerGameGeneral; +} + +class ConfigurePerGameGeneral : public QDialog { + Q_OBJECT + +public: + explicit ConfigurePerGameGeneral(QWidget* parent, u64 title_id); + ~ConfigurePerGameGeneral() override; + + /// Save all button configurations to settings file + void applyConfiguration(); + + void loadFromFile(FileSys::VirtualFile file); + +private: + std::unique_ptr<Ui::ConfigurePerGameGeneral> ui; + FileSys::VirtualFile file; + u64 title_id; + + QVBoxLayout* layout; + QTreeView* tree_view; + QStandardItemModel* item_model; + QGraphicsScene* scene; + + std::vector<QList<QStandardItem*>> list_items; + + void loadConfiguration(); +}; diff --git a/src/yuzu/configuration/configure_per_general.ui b/src/yuzu/configuration/configure_per_general.ui new file mode 100644 index 000000000..8fdd96fa4 --- /dev/null +++ b/src/yuzu/configuration/configure_per_general.ui @@ -0,0 +1,276 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigurePerGameGeneral</class> + <widget class="QDialog" name="ConfigurePerGameGeneral"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>520</height> + </rect> + </property> + <property name="windowTitle"> + <string>ConfigurePerGameGeneral</string> + </property> + <layout class="QHBoxLayout" name="HorizontalLayout"> + <item> + <layout class="QVBoxLayout" name="VerticalLayout"> + <item> + <widget class="QGroupBox" name="GeneralGroupBox"> + <property name="title"> + <string>Info</string> + </property> + <layout class="QHBoxLayout" name="GeneralHorizontalLayout"> + <item> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="6" column="1" colspan="2"> + <widget class="QLineEdit" name="display_filename"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="display_name"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Developer</string> + </property> + </widget> + </item> + <item row="5" column="1" colspan="2"> + <widget class="QLineEdit" name="display_size"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Name</string> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Filename</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Version</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Format</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLineEdit" name="display_version"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLineEdit" name="display_format"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Size</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="display_developer"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Title ID</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLineEdit" name="display_title_id"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="2" rowspan="5"> + <widget class="QGraphicsView" name="icon_view"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>128</width> + <height>128</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>128</width> + <height>128</height> + </size> + </property> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="sizeAdjustPolicy"> + <enum>QAbstractScrollArea::AdjustToContents</enum> + </property> + <property name="interactive"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="PerformanceGroupBox"> + <property name="title"> + <string>Add-Ons</string> + </property> + <layout class="QHBoxLayout" name="PerformanceHorizontalLayout"> + <item> + <widget class="QScrollArea" name="scrollArea"> + <property name="widgetResizable"> + <bool>true</bool> + </property> + <widget class="QWidget" name="scrollAreaWidgetContents"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>350</width> + <height>169</height> + </rect> + </property> + </widget> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="PerformanceVerticalLayout"/> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigurePerGameGeneral</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>269</x> + <y>567</y> + </hint> + <hint type="destinationlabel"> + <x>269</x> + <y>294</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigurePerGameGeneral</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>269</x> + <y>567</y> + </hint> + <hint type="destinationlabel"> + <x>269</x> + <y>294</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index b52a50915..8e9524fd6 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -333,6 +333,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS")); QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); + context_menu.addSeparator(); + QAction* properties = context_menu.addAction(tr("Properties")); open_save_location->setEnabled(program_id != 0); auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); @@ -346,6 +348,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); }); connect(navigate_to_gamedb_entry, &QAction::triggered, [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); + connect(properties, &QAction::triggered, [&]() { emit OpenPerGameGeneralRequested(path); }); context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location)); } diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 05e115e19..b317eb2fc 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -70,6 +70,7 @@ signals: void CopyTIDRequested(u64 program_id); void NavigateToGamedbEntryRequested(u64 program_id, const CompatibilityList& compatibility_list); + void OpenPerGameGeneralRequested(const std::string& file); private slots: void onTextChanged(const QString& newText); diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 20f5e8798..b37710f59 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -62,7 +62,7 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, FileSys::VirtualFile update_raw; loader.ReadUpdateRaw(update_raw); for (const auto& kv : patch_manager.GetPatchVersionNames(update_raw)) { - const bool is_update = kv.first == "Update"; + const bool is_update = kv.first == "Update" || kv.first == "[D] Update"; if (!updatable && is_update) { continue; } diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 22c207a3a..90b212ba5 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -9,6 +9,7 @@ // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. #include "applets/software_keyboard.h" +#include "configuration/configure_per_general.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" #include "core/hle/service/acc/profile_manager.h" @@ -441,6 +442,8 @@ void GMainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, &GMainWindow::OnGameListNavigateToGamedbEntry); + connect(game_list, &GameList::OpenPerGameGeneralRequested, this, + &GMainWindow::OnGameListOpenPerGameProperties); connect(this, &GMainWindow::EmulationStarting, render_window, &GRenderWindow::OnEmulationStarting); @@ -988,6 +991,32 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, QDesktopServices::openUrl(QUrl("https://yuzu-emu.org/game/" + directory)); } +void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { + u64 title_id{}; + const auto v_file = Core::GetGameFileFromPath(vfs, file); + const auto loader = Loader::GetLoader(v_file); + if (loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) { + QMessageBox::information(this, tr("Properties"), + tr("The game properties could not be loaded.")); + return; + } + + ConfigurePerGameGeneral dialog(this, title_id); + dialog.loadFromFile(v_file); + auto result = dialog.exec(); + if (result == QDialog::Accepted) { + dialog.applyConfiguration(); + + const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); + if (reload) { + game_list->PopulateAsync(UISettings::values.gamedir, + UISettings::values.gamedir_deepscan); + } + + config->Save(); + } +} + void GMainWindow::OnMenuLoadFile() { const QString extensions = QString("*.").append(GameList::supported_file_extensions.join(" *.")).append(" main"); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 674e73412..ca9c50367 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -168,6 +168,7 @@ private slots: void OnGameListCopyTID(u64 program_id); void OnGameListNavigateToGamedbEntry(u64 program_id, const CompatibilityList& compatibility_list); + void OnGameListOpenPerGameProperties(const std::string& file); void OnMenuLoadFile(); void OnMenuLoadFolder(); void OnMenuInstallToNAND(); diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 097c1fbe3..fe0d1eebf 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <memory> +#include <sstream> #include <SDL.h> #include <inih/cpp/INIReader.h> #include "common/file_util.h" @@ -369,6 +370,23 @@ void Config::ReadValues() { Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false); Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false); + const auto title_list = sdl2_config->Get("AddOns", "title_ids", ""); + std::stringstream ss(title_list); + std::string line; + while (std::getline(ss, line, '|')) { + const auto title_id = std::stoul(line, nullptr, 16); + const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, ""); + + std::stringstream inner_ss(disabled_list); + std::string inner_line; + std::vector<std::string> out; + while (std::getline(inner_ss, inner_line, '|')) { + out.push_back(inner_line); + } + + Settings::values.disabled_addons.insert_or_assign(title_id, out); + } + // Web Service Settings::values.enable_telemetry = sdl2_config->GetBoolean("WebService", "enable_telemetry", true); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index d73669f36..0f3f8da50 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -221,5 +221,12 @@ web_api_url = https://api.yuzu-emu.org # See https://profile.yuzu-emu.org/ for more info yuzu_username = yuzu_token = + +[AddOns] +# Used to disable add-ons +# List of title IDs of games that will have add-ons disabled (separated by '|'): +title_ids = +# For each title ID, have a key/value pair called `disabled_<title_id>` equal to the names of the add-ons to disable (sep. by '|') +# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey )"; } |